将捕获的 lambda 作为函数指针传递

Passing capturing lambda as function pointer

是否可以将 lambda 函数作为函数指针传递?如果是这样,我一定是做错了什么,因为我遇到了编译错误。

考虑以下示例

using DecisionFn = bool(*)();

class Decide
{
public:
    Decide(DecisionFn dec) : _dec{dec} {}
private:
    DecisionFn _dec;
};

int main()
{
    int x = 5;
    Decide greaterThanThree{ [x](){ return x > 3; } };
    return 0;
}

当我try to compile this时,我得到以下编译错误:

In function 'int main()':
17:31: error: the value of 'x' is not usable in a constant expression
16:9:  note: 'int x' is not const
17:53: error: no matching function for call to 'Decide::Decide(<brace-enclosed initializer list>)'
17:53: note: candidates are:
9:5:   note: Decide::Decide(DecisionFn)
9:5:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'DecisionFn {aka bool (*)()}'
6:7:   note: constexpr Decide::Decide(const Decide&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'const Decide&'
6:7:   note: constexpr Decide::Decide(Decide&&)
6:7:   note: no known conversion for argument 1 from 'main()::<lambda()>' to 'Decide&&'

这是一条需要消化的错误消息,但我想我从中得到的是 lambda 不能被视为 constexpr 因此我不能将它作为函数指针传递?我也试过制作 x constexpr,但这似乎没有帮助。

lambda 只能在不捕获的情况下转换为函数指针,来自 draft C++11 standard 部分 5.1.2 [expr.prim.lambda] 说(强调我的):

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

请注意,cppreference 在 Lambda functions.

的部分中也介绍了这一点

因此以下替代方案可行:

typedef bool(*DecisionFn)(int);

Decide greaterThanThree{ []( int x ){ return x > 3; } };

这也是:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };

points out, you can also use std::function, but note that std::function is heavy weight一样,所以这不是一个没有成本的权衡。

正确解释了为什么 lambda 在有捕获的情况下不能作为函数指针传递。我想展示两个简单的问题修复方法。

  1. 使用 std::function 而不是原始函数指针。

    这是一个非常干净的解决方案。但是请注意,它包括一些额外的类型擦除开销(可能是虚函数调用)。

    #include <functional>
    #include <utility>
    
    struct Decide
    {
      using DecisionFn = std::function<bool()>;
      Decide(DecisionFn dec) : dec_ {std::move(dec)} {}
      DecisionFn dec_;
    };
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree { [x](){ return x > 3; } };
    }
    
  2. 使用不捕获任何内容的 lambda 表达式。

    由于您的谓词实际上只是一个布尔常量,因此以下内容可以快速解决当前问题。请参阅 了解其工作原理和工作原理的详细解释。

    // Your 'Decide' class as in your post.
    
    int
    main()
    {
      int x = 5;
      Decide greaterThanThree {
        (x > 3) ? [](){ return true; } : [](){ return false; }
      };
    }
    

正如其他人所提到的,您可以用 Lambda 函数代替函数指针。我在 F77 ODE 求解器 RKSUITE 的 C++ 接口中使用此方法。

//C interface to Fortran subroutine UT
extern "C"  void UT(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

// C++ wrapper which calls extern "C" void UT routine
static  void   rk_ut(void(*)(double*,double*,double*),double*,double*,double*,
double*,double*,double*,int*);

//  Call of rk_ut with lambda passed instead of function pointer to derivative
//  routine
mathlib::RungeKuttaSolver::rk_ut([](double* T,double* Y,double* YP)->void{YP[0]=Y[1]; YP[1]= -Y[0];}, TWANT,T,Y,YP,YMAX,WORK,UFLAG);

Lambda 表达式,即使是捕获的表达式,也可以作为函数指针(指向成员函数的指针)处理。

这很棘手,因为 lambda 表达式不是一个简单的函数。它实际上是一个带有 operator() 的对象。

当你有创意的时候,你可以用这个! 想想 "function" class 风格的 std::function。 如果保存对象也可以使用函数指针。

要使用函数指针,可以使用以下方法:

int first = 5;
auto lambda = [=](int x, int z) {
    return x + z + first;
};
int(decltype(lambda)::*ptr)(int, int)const = &decltype(lambda)::operator();
std::cout << "test = " << (lambda.*ptr)(2, 3) << std::endl;

要构建一个可以像 "std::function" 一样开始工作的 class,首先您需要一个可以存储对象和函数指针的 class/struct。你还需要一个 operator() 来执行它:

// OT => Object Type
// RT => Return Type
// A ... => Arguments
template<typename OT, typename RT, typename ... A>
struct lambda_expression {
    OT _object;
    RT(OT::*_function)(A...)const;

    lambda_expression(const OT & object)
        : _object(object), _function(&decltype(_object)::operator()) {}

    RT operator() (A ... args) const {
        return (_object.*_function)(args...);
    }
};

有了这个,您现在可以 运行 捕获、非捕获的 lambda,就像您使用原始的一样:

auto capture_lambda() {
    int first = 5;
    auto lambda = [=](int x, int z) {
        return x + z + first;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

auto noncapture_lambda() {
    auto lambda = [](int x, int z) {
        return x + z;
    };
    return lambda_expression<decltype(lambda), int, int, int>(lambda);
}

void refcapture_lambda() {
    int test;
    auto lambda = [&](int x, int z) {
        test = x + z;
    };
    lambda_expression<decltype(lambda), void, int, int>f(lambda);
    f(2, 3);

    std::cout << "test value = " << test << std::endl;
}

int main(int argc, char **argv) {
    auto f_capture = capture_lambda();
    auto f_noncapture = noncapture_lambda();

    std::cout << "main test = " << f_capture(2, 3) << std::endl;
    std::cout << "main test = " << f_noncapture(2, 3) << std::endl;

    refcapture_lambda();

    system("PAUSE");
    return 0;
}

此代码适用于 VS2015

2017 年 7 月 4 日更新:

template <typename CT, typename ... A> struct function
: public function<decltype(&CT::operator())(A...)> {};

template <typename C> struct function<C> {
private:
    C mObject;

public:
    function(const C & obj)
        : mObject(obj) {}

    template<typename... Args> typename 
    std::result_of<C(Args...)>::type operator()(Args... a) {
        return this->mObject.operator()(a...);
    }

    template<typename... Args> typename 
    std::result_of<const C(Args...)>::type operator()(Args... a) const {
        return this->mObject.operator()(a...);
    }
};

namespace make {
    template<typename C> auto function(const C & obj) {
        return ::function<C>(obj);
    }
}

int main(int argc, char ** argv) {
   auto func = make::function([](int y, int x) { return x*y; });
   std::cout << func(2, 4) << std::endl;
   system("PAUSE");
   return 0;
}

无法将捕获的 lambda 转换为函数指针,正如 指出的那样。

然而,向只接受一个的 API 提供一个函数指针通常是一件很痛苦的事情。最常被引用的方法是提供一个函数并用它调用一个静态对象。

static Callable callable;
static bool wrapper()
{
    return callable();
}

这很乏味。我们将这个想法进一步发展并使创建过程自动化 wrapper 并让生活变得更加轻松。

#include<type_traits>
#include<utility>

template<typename Callable>
union storage
{
    storage() {}
    std::decay_t<Callable> callable;
};

template<int, typename Callable, typename Ret, typename... Args>
auto fnptr_(Callable&& c, Ret (*)(Args...))
{
    static bool used = false;
    static storage<Callable> s;
    using type = decltype(s.callable);

    if(used)
        s.callable.~type();
    new (&s.callable) type(std::forward<Callable>(c));
    used = true;

    return [](Args... args) -> Ret {
        return Ret(s.callable(std::forward<Args>(args)...));
    };
}

template<typename Fn, int N = 0, typename Callable>
Fn* fnptr(Callable&& c)
{
    return fnptr_<N>(std::forward<Callable>(c), (Fn*)nullptr);
}

并将其用作

void foo(void (*fn)())
{
    fn();   
}

int main()
{
    int i = 42;
    auto fn = fnptr<void()>([i]{std::cout << i;});
    foo(fn);  // compiles!
}

Live

这实际上是在每次出现 fnptr 时声明一个匿名函数。

请注意,fnptr 的调用会覆盖先前编写的 callable 相同类型的给定可调用对象。我们通过 int 参数 N.

在一定程度上解决了这个问题
std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

虽然出于各种原因模板方法很聪明,但重要的是要记住 lambda 的生命周期和捕获的变量。如果要使用任何形式的 lambda 指针并且 lambda 不是向下延续,则只应使用复制 [=] lambda。也就是说,即便如此,如果那些捕获的指针(堆栈展开)的生命周期短于 lambda 的生命周期,那么捕获指向堆栈上变量的指针也是不安全的。

将 lambda 捕获为指针的更简单的解决方案是:

auto pLamdba = new std::function<...fn-sig...>([=](...fn-sig...){...});

例如,new std::function<void()>([=]() -> void {...}

请记住稍后 delete pLamdba 以确保您不会泄漏 lambda 内存。 这里要意识到的秘密是 lambda 可以捕获 lambda(问问自己它是如何工作的),而且为了 std::function 一般工作,lambda 实现需要包含足够的内部信息以提供对 lambda 大小的访问(和捕获的)数据(这就是为什么 delete 应该工作 [运行 捕获类型的析构函数])。

将 lambda 用作 C 函数指针的快捷方式是:

"auto fun = +[](){}"

以 Curl 为例 (curl debug info)

auto callback = +[](CURL* handle, curl_infotype type, char* data, size_t size, void*){ //add code here :-) };
curl_easy_setopt(curlHande, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curlHande,CURLOPT_DEBUGFUNCTION,callback);

不是直接的答案,而是使用 "functor" 模板模式的细微变化来隐藏 lambda 类型的细节并保持代码简洁明了。

我不确定你想如何使用 decide class,所以我不得不用一个使用它的函数来扩展 class。请在此处查看完整示例:https://godbolt.org/z/jtByqE

您的 class 的基本形式可能如下所示:

template <typename Functor>
class Decide
{
public:
    Decide(Functor dec) : _dec{dec} {}
private:
    Functor _dec;
};

作为 class 类型的一部分传递函数类型的地方,如:

auto decide_fc = [](int x){ return x > 3; };
Decide<decltype(decide_fc)> greaterThanThree{decide_fc};

同样,我不确定你为什么要捕获 x 有一个参数传递给 lambda 更有意义(对我来说))所以你可以像这样使用:

int result = _dec(5); // or whatever value

有关完整示例,请参阅 link

一个类似的答案,但我做到了,所以你不必指定返回指针的类型(请注意,通用版本需要 C++20):

#include <iostream>


template<typename Function>
struct function_traits;

template <typename Ret, typename... Args>
struct function_traits<Ret(Args...)> {
    typedef Ret(*ptr)(Args...);
};

template <typename Ret, typename... Args>
struct function_traits<Ret(*const)(Args...)> : function_traits<Ret(Args...)> {};

template <typename Cls, typename Ret, typename... Args>
struct function_traits<Ret(Cls::*)(Args...) const> : function_traits<Ret(Args...)> {};

using voidfun = void(*)();

template <typename F>
voidfun lambda_to_void_function(F lambda) {
    static auto lambda_copy = lambda;

    return []() {
        lambda_copy();
    };
}

// requires C++20
template <typename F>
auto lambda_to_pointer(F lambda) -> typename function_traits<decltype(&F::operator())>::ptr {
    static auto lambda_copy = lambda;
    
    return []<typename... Args>(Args... args) {
        return lambda_copy(args...);
    };
}



int main() {
    int num;

    void(*foo)() = lambda_to_void_function([&num]() {
        num = 1234;
    });
    foo();
    std::cout << num << std::endl; // 1234

    int(*bar)(int) = lambda_to_pointer([&](int a) -> int {
        num = a;
        return a;
    });
    std::cout << bar(4321) << std::endl; // 4321
    std::cout << num << std::endl; // 4321
}