【问题标题】:Template function that can take lambda or function pointer and deduce arguments for passing to another template模板函数,可以接受 lambda 或函数指针并推断参数以传递给另一个模板
【发布时间】:2016-03-15 03:30:15
【问题描述】:

我维护了一个开源无锁线程库,专为高速并行循环展开而设计,用于几个商业视频游戏。它的开销非常低,大约 8 个时钟用于创建消息,大约 500 个时钟(每个线程,包括延迟)用于整个调度和远程执行开销。我先这么说是为了解释为什么我不简单地使用 use std::function 和 bind。

该库将函数调用和函数调用的参数打包在一条消息中(Functor 类型)。然后使用参数副本远程调用它。

我最近重写了该库,以使用 STL 样式模板元编程而不是它最初使用的过时的 C 样式宏来打包远程调用。令人惊讶的是,这只增加了两个滴答声的开销,但我不知道如何创建一个同时接受 lambda 和函数指针的打包函数。

所需用途:

CreateFunctor([](int a, int b){doSomething(a, b)}, 1, 2);

CreateFunctor(&doSomething, 1, 2);

目前我必须将这些情况分为两个单独的函数(CreateFunctor 和 CreateFunctorLambda)。如果我可以将它们结合起来,我将能够将我的打包和调度阶段合并为一个简洁的函数调用并简化 API。

问题在于推导 lambda 参数的代码似乎无法与推导函数参数的代码共享模板覆盖。我试过使用 enable_if ,它仍然执行 lambda 版本的 ::* 部分,并导致编译器错误与函数指针。

相关片段:

template <typename... Arguments>
inline Functor<Arguments...> CreateFunctor(void(*func)(Arguments...))
{
    return Functor<Arguments...>(func);
};

template <typename... Arguments>
inline Functor<Arguments...> CreateFunctor(void(*func)(Arguments...), Arguments ... arg)
{
    Functor<Arguments...> ret(func);
    ret.Set(arg...);
    return ret;
};

// template to grab function type that lambda can be cast to
// from http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda
template <class T>
struct deduce_lambda_arguments
    : public deduce_lambda_arguments<typename std::enable_if<std::is_class<T>::value, decltype(&T::operator())>::type>
{};

template <class ClassType, typename... Args>
struct deduce_lambda_arguments<void(ClassType::*)(Args...) const>
    // we specialize for pointers to member function
{
    typedef void(*pointer_cast_type)(Args...);
    typedef Functor<Args...> functor_type;
};

template <typename F, typename... Args>
inline auto CreateFunctorLambda(F f, Args... arg) -> typename deduce_lambda_arguments<F>::functor_type
{
    deduce_lambda_arguments<F>::functor_type ret((deduce_lambda_arguments<F>::pointer_cast_type) f);
    ret.Set(arg...);
    return ret;
};

template <typename F>
inline auto CreateFunctorLambda(F f) -> typename deduce_lambda_arguments<F>::functor_type
{
    return deduce_lambda_arguments<F>::functor_type((deduce_lambda_arguments<F>::pointer_cast_type) f);
};

【问题讨论】:

  • 如果我没看错,你只接受无捕获的非泛型 lambda,对吗?
  • 正确。它们都被转换为一个结构,其中包含一个 __cdecl 函数指针、一个参数列表和一个大小。捕获会使这变得更加复杂,并且无论如何对于延迟执行都是不安全的。一旦知道参数,就可以将 lambda 转换为 __cdecl 指针并与一个 mov 一起存储。
  • 你测量过 std::function 的性能吗?它对函数对象“小”时进行了优化。如果你能超越它,我会感到惊讶。
  • Richard,是的,我已经评估了 std::function 的速度(在这种情况下需要绑定,非常不轻量级)。即使对于带有 void 参数的非常小的或空的外壳,使用 std::function 也会将开销时间从每个线程作业开始的大约 150 微秒增加到大约 400 微秒。这包括排队和线程间通信。对于大多数应用程序来说都很好,但对于更大的功能,由于内存管理,它变得昂贵了 2 个数量级。
  • 你能分享你图书馆的链接吗?

标签: c++ templates c++11 lambda variadic-templates


【解决方案1】:

诀窍是确保你永远不会评估 &amp;T::operator() 的东西,你想要支持但不是 lambdas。一种方法是添加一个额外的模板参数并专门处理它:

template <class T, bool = std::is_class<T>::value>
struct compute_functor_type
    : public compute_functor_type<decltype(&T::operator())>
{};

template <class ClassType, typename... Args>
struct compute_functor_type<void(ClassType::*)(Args...) const, false>
{
    typedef void(*pointer_cast_type)(Args...);
    typedef Functor<Args...> functor_type;
};

template <class ClassType, typename... Args>
struct compute_functor_type<void(ClassType::*)(Args...), false>
{
    typedef void(*pointer_cast_type)(Args...);
    typedef Functor<Args...> functor_type;
};

template <typename... Args>
struct compute_functor_type<void(*)(Args...), false>
{
    typedef void(*pointer_cast_type)(Args...);
    typedef Functor<Args...> functor_type;
};


template <typename F, typename... Args>
inline auto CreateFunctor(F f, Args... arg) 
         -> typename compute_functor_type<F>::functor_type
{
    typename compute_functor_type<F>::functor_type ret((typename compute_functor_type<F>::pointer_cast_type) f);
    ret.Set(arg...);
    return ret;
};

template <typename F>
inline auto CreateFunctor(F f) -> typename compute_functor_type<F>::functor_type
{
    return typename compute_functor_type<F>::functor_type((typename compute_functor_type<F>::pointer_cast_type) f);
};

在此处为不必使用 MSVC 的人保存(更短的)原始方法:

由于您只关心无捕获的非泛型 lambda 并将它们转换为函数指针,因此这很容易。

从函数指针创建匹配的Functor 类型:

template<class... Args>
Functor<Args...> make_functor(void (*f)(Args...)) { return {f}; }

并通过一元 + 运算符强制转换为函数指针:

template <class F>
inline auto CreateFunctor(F f) -> decltype(make_functor(+f))
{
    return make_functor(+f);
}

template <class F, typename... Arguments>
inline auto CreateFunctor(F f, Arguments ... arg) -> decltype(make_functor(+f))
{
    auto ret = make_functor(+f);
    ret.Set(arg...);
    return ret;
}

【讨论】:

  • 传递 lambda 时编译失败。它不能推断出 lambda 的参数。 1>ParLLTest.cpp(53): 错误 C2672: 'ParLL::CreateFunctor': 找不到匹配的重载函数 1>ParLLTest.cpp(53): 错误 C2893: 无法专门化函数模板'unknown-type ParLL::CreateFunctor( F,Arguments...)' 1> ParLLTest.cpp(53): 注意:使用以下模板参数:1> ParLLTest.cpp(53): note: 'F=main::' 1> ParLLTest. cpp(53):注意:'Arguments={}'
  • @GeorgeDavison Works for me with GCC and clang。我想知道 MSVC 在这里产生了什么幻觉......编辑:很棒的非标准调用约定。 /叹息
  • 您的意思是 MSVC 使用非标准调用约定还是 Clang 和 GCC?
  • @GeorgeDavison MSVC。它的 lambda 有四个独立的函数指针转换函数。
  • 是的,我从早期尝试解决此问题的警告消息中注意到这一点。这是一个先有鸡还是先有蛋的问题,我在不知道参数的情况下无法获得正确的转换运算符。然后我在另一个 SO question 中找到了我用于 deduce_lambda_arguments 的示例。也许如果我知道为什么会这样,我可以想出一种更通用的编写方式。
猜你喜欢
  • 1970-01-01
  • 2015-09-10
  • 2022-06-12
  • 1970-01-01
  • 2018-04-25
  • 2022-08-14
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
相关资源
最近更新 更多