【问题标题】:Casting capturing lambda to function pointer not using std::function不使用 std::function 将捕获 lambda 转换为函数指针
【发布时间】:2015-12-01 18:17:32
【问题描述】:

考虑以下示例:

using function_pair = std::pair<int, void(*)(void*)>; // This line cannot change
template <class Arg, class F>
function_pair make_function_pair(int i, F&& f)
{
    return function_pair(i, 
    [&](void* x){std::forward<F>(f)(static_cast<Arg*>(x));}); // Not working
}

// Later in code
auto p1 = make_function_pair<char>(1, [](char* x){std::cout<<*x<<std::endl;});
auto p2 = make_function_pair<double>(2, [](double* x){*x = *x*2;});
auto p3 = make_function_pair<float>(3, [](float* x){*x = *x*3;});

由于捕获 lambda,代码无法编译(当 lambda 不必捕获 f 时,它可以工作)。我想知道如何在不使用std::function 的情况下完成这项工作,因为std::function 的计算开销很大,我负担不起。即使没有这个实际原因,我也想知道如何在不使用std::function 的情况下解决这个“学术”问题。

【问题讨论】:

  • 你需要函数对的第二个元素作为函数指针吗?只有非捕获 lambda 可以转换为 1。 OTOH,如果函数指针只是说明您正在尝试做什么的起点,那么您将面临 C++11 的砖墙。对于 C++14,只需使用 auto 作为函数返回类型,就可以避免 std::function 的开销
  • 我第二个 Pradhan -- 由于无法表达返回的 lambda 的类型,您在 C++11 中唯一的选择是在 std::function 或类似的后面擦除它。
  • @Pradhan 是的,我需要一个函数指针,以便 p1、p2 和 p3 都属于同一类型。
  • 你测量std::function的开销了吗?我对此表示怀疑,因为您也是通过引用捕获,当您从当前范围返回 lambda 的副本时,通过引用捕获是一个很大的禁忌。 std::function 的开销比函数指针大,但如果你在小的函数优化大小范围内,它也不会更离谱。
  • @Pradhan 从void(char*) 转换为void(void*) 是未定义的行为。

标签: c++ templates c++11 lambda function-pointers


【解决方案1】:

hack 解决方案是依赖于一个事实,即非捕获 lambda 不会在我见过的每个 C++ 实现中使用其状态。

template<class F>
struct stateless_t {
  constexpr stateless_t() {
    static_assert( std::is_empty<F>::value, "Only works with stateless lambdas" );
  }
  using F_ref = F const&;
  template<class...Ts>
  std::result_of_t<F_ref(Ts...)> operator()(Ts&&...ts)const {
    return (*reinterpret_cast<F const*>(this))(std::forward<Ts>(ts)...);
  }
};

template<class F>
stateless_t<F> stateless() { return {}; }

template <class Arg, class F>
function_pair make_function_pair(int i, F const&)
{
  return function_pair(
    i, 
    [](void* x){return stateless<F>()(static_cast<Arg*>(x));}
  );
}

这是左右中间未定义的行为,但可能适用于您的编译器。

更好的选择是使用std::function 并在拒绝选项之前测量开销。对于小型函数对象,std::function 的开销很小(虚拟调用而不是函数指针调用)。

接下来,编写您自己的std::function 以减少开销或找到一个(例如最快的代表)。例如,您可以创建一个更快的std::function,它将指向调用的指针存储在类本身而不是 vtable 中。

或者,也可以是 UB,但比上面更好——将你的无状态 lambda 转换为 void(T*),然后将其重新解释为 void(void*)。虽然仍然是 UB,但大多数实现使用二进制兼容的指针大小,以及可以在两者之间转换的函数指针。 @Pradhan 在 cmets 中建议了以上内容。

一般情况下,请避免使用 UB,因为从那时起它会增加持续的维护开销。您已经在当前编译器中对其进行了测试,但您必须从现在开始检查它在 每个编译器每个构建 以及 每个使用的编译器设置 中是否有效直到代码生命周期结束,才能长期可靠。

【讨论】:

    【解决方案2】:

    无法将捕获的 lambda 转换为指向函数的指针。如果您考虑一下,就很清楚了-捕获需要以某种方式将它们传递给函数的变量-而函数指针没有该选项。

    学术解决方案是重新实现std::function。没办法。

    【讨论】:

      猜你喜欢
      • 2016-04-18
      • 2021-12-22
      • 1970-01-01
      • 1970-01-01
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多