是否可以将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&&'

这是一个要消化的错误消息,但我认为我从中得到的是不能作为constexpr处理,因此我不能将它作为函数指针传递?我也试着让x成为constexpr,但这似乎没有帮助。


当前回答

捕获lambdas不能转换为函数指针,正如这个答案所指出的。

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

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

这太乏味了。我们进一步利用了这个想法,自动化了创建包装器的过程,使工作变得更加简单。

#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的调用将覆盖先前编写的可调用对象。在某种程度上,我们用int参数N来补救这个问题。

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

其他回答

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

Use std::function instead of raw function pointers. This is a very clean solution. Note however that it includes some additional overhead for the type erasure (probably a virtual function call). #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; } }; } 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表达式,甚至是被捕获的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;
}

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

捕获lambdas不能转换为函数指针,正如这个答案所指出的。

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

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

这太乏味了。我们进一步利用了这个想法,自动化了创建包装器的过程,使工作变得更加简单。

#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的调用将覆盖先前编写的可调用对象。在某种程度上,我们用int参数N来补救这个问题。

std::function<void()> func1, func2;
auto fn1 = fnptr<void(), 1>(func1);
auto fn2 = fnptr<void(), 2>(func2);  // different function

如果lambda没有捕获,则只能将其转换为函数指针,摘自c++ 11标准草案5.1.2节[expr.prim. xml]。Lambda]表示(强调我的):

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

注意,cppreference在Lambda函数一节中也介绍了这一点。

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

typedef bool(*DecisionFn)(int);

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

这个也一样:

typedef bool(*DecisionFn)();

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

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