传递捕获lambda作为函数指针

是否可以将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;
}

当我试着编译一下时,我得到以下编译错误:

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,但这似乎没有帮助。

234132 次浏览

如果lambda不能捕获,则只能将其转换为函数指针,从c++ 11标准草案部分5.1.2 (expr.prim.lambda)表示(我特别强调):

lambda表达式没有lambda-capture的闭包类型有一个 公共非虚拟非显式const <强>转换到指针的函数 函数具有与闭包相同的参数和返回类型 类型的函数调用操作符。此转换返回的值 Function应是函数的地址,该函数在调用时具有 与调用闭包类型的函数调用操作符的效果相同

注意,cppreference在Lambda函数的部分中也包含了这个。

因此,以下选择是可行的:

typedef bool(*DecisionFn)(int);


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

这个也一样:

typedef bool(*DecisionFn)();


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

正如5 gon12eder所指出的,你也可以使用std::function,但请注意std::function是重物,所以这不是一个无成本的权衡。

Shafik Yaghmour的回答是正确地解释了为什么lambda不能作为函数指针传递,如果它有捕获。我想介绍两个简单的解决方法。

  1. < p > 使用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. Use a lambda expression that doesn't capture anything.

    Since your predicate is really just a boolean constant, the following would quickly work around the current issue. See this answer for a good explanation why and how this is working.

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

正如其他人提到的,你可以用Lambda函数代替函数指针。我使用这个方法在我的c++接口到F77 ODE求解器RKSUITE。

//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表达式,都可以作为函数指针(指向成员函数的指针)来处理。

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

当你有创造力的时候,你可以使用这个! 考虑std::function风格的“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”一样开始工作的类,首先你需要一个可以存储对象和函数指针的类/结构。你还需要一个操作符()来执行它:

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

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

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

更新04.07.17:

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指针is,并且lambda不是向下延续,那么只应该使用复制[=]lambda。也就是说,即使这样,如果捕获的指针的生命周期(堆栈unwind)比lambda的生命周期短,那么在堆栈上捕获一个指向变量的指针也是不安全的。

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

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

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

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

使用lambda with作为C函数指针的快捷方式是:

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

以Curl为例(Curl调试信息)

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类型的细节,并保持代码的美观和简单。

我不确定你想如何使用决定类,所以我必须用一个使用它的函数扩展类。参见完整的例子:https://godbolt.org/z/jtByqE

类的基本形式可能是这样的:

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

将函数类型作为类类型的一部分传入,如下所示:

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

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

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

有关完整示例,请参见链接

一个类似的答案,但我这样做是为了让你不必指定返回指针的类型(注意,通用版本需要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
}

这是另一种解决方案。c++ 14(可转换为c++ 11)支持返回值、不可复制和可变lambda。如果不需要可变的lambdas,可以通过删除匹配非const版本的专门化并嵌入impl_impl来更短。

对于那些想知道的人来说,它是有效的,因为每个lambda都是唯一的(是不同的类),因此调用to_f会为这个lambda静态和相应的c风格函数生成唯一的,可以访问它。

template <class L, class R, class... Args> static auto impl_impl(L l) {
static_assert(!std::is_same<L, std::function<R(Args...)>>::value,
"Only lambdas are supported, it is unsafe to use "
"std::function or other non-lambda callables");


static L lambda_s = std::move(l);
return +[](Args... args) -> R { return lambda_s(args...); };
}


template <class L>
struct to_f_impl : public to_f_impl<decltype(&L::operator())> {};
template <class ClassType, class R, class... Args>
struct to_f_impl<R (ClassType::*)(Args...) const> {
template <class L> static auto impl(L l) {
return impl_impl<L, R, Args...>(std::move(l));
}
};
template <class ClassType, class R, class... Args>
struct to_f_impl<R (ClassType::*)(Args...)> {
template <class L> static auto impl(L l) {
return impl_impl<L, R, Args...>(std::move(l));
}
};


template <class L> auto to_f(L l) { return to_f_impl<L>::impl(std::move(l)); }

注意,这也适用于其他可调用的对象,如std::function,但如果它不起作用会更好,因为与lambdas不同,std::function这样的对象不会生成唯一类型,因此内部模板和它的内部静态将被所有具有相同签名的函数重用/共享,这很可能不是我们想从它那里得到的。我已经明确禁止std::函数,但还有更多我不知道如何以通用的方式禁止。