In a piece of code I'm writing, I receive packets as uint8_t * and std::size_t combination. I can register functions to call with these two parameters, based on which file descriptor the packet was received from. I use an std::map<int, std::function<void(const uint8_t *, std::size_t)> > handlers to keep track of which function to call.
I would like to be able to (indirectly) register functions with arbitrary arguments. I already have a function like this to transform from the uint8_t * and std::size_t to separate variables:
int unpack(const uint8_t *buf, std::size_t len) { return 0; }
template <typename T, typename... Types>
int unpack(const uint8_t *buf, std::size_t len, T &var1, Types... var2) {
static_assert(std::is_trivially_copyable<T>::value, "unpack() only works for primitive types");
if (len < sizeof(T)) return -1;
var1 = *reinterpret_cast<const T *>(buf);
const auto sum = unpack(buf sizeof(T), len - sizeof(T), var2...);
const auto ret = (sum == -1) ? -1 : sum sizeof(T);
return ret;
}
My question is: Is it possible with C 20 to auto-generate a function that convers from uint8_t * and std::size_t to the arguments that a passed function needs?
I would like to be able to do this:
void handler(unsigned int i) { ... }
int main(int argc, char ** argv) {
/* some code generating an fd */
handlers[fd] = function_returning_an_unpacker_function_that_calls_handler(handler);
edit: I realize I went a bit too short on my answer, as some mentioned (thanks!).
I am wondering if it is possible (and if so, how?) to implement the function_returning_an_unpacker_function_that_calls_handler function. I started out doing something like this (written from memory):
template<typename... Types>
std::function<void(const uint8_t * buf, std::size_t)>
function_returning_an_unpacker_function_that_calls_handler(std::function<void(Types...)> function_to_call) {
const auto ret = new auto([fun](const uint8_t * buf, std::size_t len) -> void {
const auto unpack_result = unpack(buf, len, list_of_variables_based_on_template_params);
if(unpack_result == -1) return nullptr;
function_to_call(list_of_variables_based_on_template_params);
};
return ret;
}
This is also why I supplied the unpack function. The problem I'm encountering is that I'm struggling with the list_of_variables_based_on_template_params bit. I haven't found any way to generate a list of variables that I can repeat identically in two places.
I also looked a little bit into using std::tuple::tie and friends, but I didn't see a solution there either.
CodePudding user response:
It's possible, just annoying to write.
First you need a trait to get parameters from a function type:
template <typename T>
struct FuncTraits {};
#define GEN_FUNC_TRAITS_A(c, v, ref, noex) \
template <typename R, typename ...P> \
struct FuncTraits<R(P...) c v ref noex> \
{ \
template <template <typename...> typename T> \
using ApplyParams = T<P...>; \
};
#define GEN_FUNC_TRAITS_B(c, v, ref) \
GEN_FUNC_TRAITS_A(c, v, ref,) \
GEN_FUNC_TRAITS_A(c, v, ref, noexcept)
#define GEN_FUNC_TRAITS_C(c, v) \
GEN_FUNC_TRAITS_B(c, v,) \
GEN_FUNC_TRAITS_B(c, v, &) \
GEN_FUNC_TRAITS_B(c, v, &&)
#define GEN_FUNC_TRAITS(c) \
GEN_FUNC_TRAITS_C(c,) \
GEN_FUNC_TRAITS_C(c, volatile)
GEN_FUNC_TRAITS()
GEN_FUNC_TRAITS(const)
Then some templates to analyze what kind of callable (function, function pointer, or a functor) you got, and apply the trait accordingly:
template <typename T> struct RemoveMemPtr {using type = T;};
template <typename T, typename C> struct RemoveMemPtr<T C::*> {using type = T;};
template <typename T>
struct ToFuncType {};
template <typename T>
requires std::is_function_v<std::remove_pointer_t<T>>
struct ToFuncType<T> {using type = std::remove_pointer_t<T>;};
template <typename T>
requires requires {&T::operator();}
struct ToFuncType<T>
{
using type = typename RemoveMemPtr<decltype(&T::operator())>::type;
};
Then you can make a templated functor that automatically unwraps the arguments. Since Unwrap() must be called in order, and function arguments are evaluated in unspecified order, we need a tuple (or something similar) that accepts a braced list:
template <typename T>
T Unpack(char *&, std::size_t &)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
return {};
}
template <typename F>
struct WrapFunctor
{
template <typename ...P>
struct Impl
{
std::decay_t<F> func;
void operator()(char *p, std::size_t n)
{
std::apply(func, std::tuple{Unpack<P>(p, n)...});
}
};
};
template <typename F>
auto Wrap(F &&func)
{
using Functor = typename FuncTraits<typename ToFuncType<std::remove_cvref_t<F>>::type>::template ApplyParams<WrapFunctor<F>::template Impl>;
return Functor{std::forward<F>(func)};
}
Finally, some tests:
void foo(int, float, char)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}
int main()
{
Wrap(foo)(nullptr, 42);
Wrap(&foo)(nullptr, 42);
Wrap([](int, float, char){std::cout << __PRETTY_FUNCTION__ << '\n';})(nullptr, 42);
}
I've changed the signature of Unpack() to take the parameters by reference and unpack one variable at a time.
Almost forgot: var1 = *reinterpret_cast<const T *>(buf); is a strict aliasing violation and UB. Prefer memcpy.
CodePudding user response:
This answer is very similar to the first one, but it leverages the use of CTAD and std::function to figure out the function signature.
Creates a tuple based on the function signature, and passes both the argument types and the elements from the tuple on to unpack.
#include <iostream>
#include <tuple>
#include <type_traits>
#include <cstring>
#include <functional>
int unpack(const uint8_t *buf, std::size_t len) { return 0; }
template <typename T, typename... Types>
int unpack(const uint8_t *buf, std::size_t len, T &var1, Types&... var2) {
static_assert(std::is_trivially_copyable<T>::value, "unpack() only works for primitive types");
if (len < sizeof(T)) return -1;
var1 = *reinterpret_cast<const T *>(buf);
std::cout << "In unpack " << var1 << "\n";
const auto sum = unpack(buf sizeof(T), len - sizeof(T), var2...);
const auto ret = (sum == -1) ? -1 : sum sizeof(T);
return ret;
}
template<typename T, typename R, typename... Args>
std::function<void(const uint8_t * buf, std::size_t)>
unpack_wrapper_impl(T function_to_call, std::function<R(Args...)>) {
return [function_to_call](const uint8_t *buf, std::size_t len) -> void {
std::tuple<std::decay_t<Args>...> tup;
std::apply([&](auto&... args) {
unpack(buf, len, args...);
}, tup);
std::apply(function_to_call, tup);
};
}
template<typename T>
std::function<void(const uint8_t * buf, std::size_t)>
unpack_wrapper(T&& function_to_call) {
return unpack_wrapper_impl(std::forward<T>(function_to_call), std::function{function_to_call});
}
void test(int a, int b) {
std::cout << a << " " << b << "\n";
}
int main() {
int a= 5, b = 9;
uint8_t* buf = new uint8_t[8];
std::memcpy(buf, &a, 4);
std::memcpy(buf 4, &b, 4);
auto f = unpack_wrapper(test);
f(buf, 8);
}
