我可以根据模板参数在堆栈上分配一系列变量吗?

Can I allocate a series of variables on the stack based on template arguments?

在我正在编写的一段代码中,我收到了 uint8_t *std::size_t 组合的数据包。我可以根据从哪个文件描述符接收到数据包来注册使用这两个参数调用的函数。我使用 std::map<int, std::function<void(const uint8_t *, std::size_t)> > handlers 来跟踪调用哪个函数。

我希望能够(间接地)注册具有任意参数的函数。我已经有了这样的函数,可以从 uint8_t *std::size_t 转换为分隔变量:

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;
}

我的问题是:是否可以使用 C++20 自动生成一个函数,该函数从 uint8_t *std::size_t 转换为传递函数所需的参数?

我希望能够做到这一点:

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);

编辑:我意识到我的回答有点太短了,正如一些人提到的(谢谢!)。

我想知道是否可以(如果可以,如何?)实现 function_returning_an_unpacker_function_that_calls_handler 功能。我开始做这样的事情(凭记忆写的):

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;
}

这也是我提供 unpack 函数的原因。我遇到的问题是我在 list_of_variables_based_on_template_params 位上苦苦挣扎。我还没有找到任何方法来生成我可以在两个地方重复相同的变量列表。 我也稍微研究了一下使用 std::tuple::tie 和朋友,但我也没有看到解决方案。

有可能,就是写起来烦

首先你需要一个特征来从函数类型中获取参数:

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)

然后一些模板来分析你得到了什么样的可调用对象(函数、函数指针或仿函数),并相应地应用特征:

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;
};

然后你可以制作一个模板仿函数自动解包参数。由于 Unwrap() 必须按顺序调用,并且函数参数以未指定的顺序求值,我们需要一个接受花括号列表的元组(或类似的东西):

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)};
}

最后,一些测试:

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);
}

我已经更改了 Unpack() 的签名以通过引用获取参数并一次解压一个变量。


差点忘了:var1 = *reinterpret_cast<const T *>(buf); 是严格的别名违规和 UB。更喜欢 memcpy.

这个答案与第一个答案非常相似,但它利用 CTAD 和 std::function 来计算函数签名。

根据函数签名创建一个元组,并将元组中的参数类型和元素传递给 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);
}