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

时间:2015-02-26 15:45:23

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

是否可以将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 const,但这似乎没什么帮助。

9 个答案:

答案 0 :(得分:171)

如果lambda不能从draft C++11 standard部分5.1.2 [expr.prim.lambda] 表示,则只能将其转换为函数指针。 >强调我的):

  

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

请注意,cppreference还会在Lambda functions的部分中介绍这一点。

所以以下替代方案可行:

typedef bool(*DecisionFn)(int);

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

所以这样:

typedef bool(*DecisionFn)();

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

并且5gon12eder指出,您也可以使用std::function,但请注意std::function is heavy weight,因此这不是一种无成本的权衡。

答案 1 :(得分:81)

Shafik Yaghmour's answer正确解释了为什么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表达式。

    由于您的谓词实际上只是一个布尔常量,因此以下内容可以快速解决当前问题。请参阅this answer以获得有关其原因和方式的详细解释。

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

答案 2 :(得分:27)

我知道这有点老..

但我想补充一下:

Lambda表达式(甚至是捕获的表达式)可以作为函数指针处理!

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

当你有创意时,你可以使用它! 想想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”一样开始工作的类,我将做一个简短的例子。首先,你需要一个类/结构,而不是存储对象和函数指针,你还需要一个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 希望它有所帮助:)

电贺!

编辑:删除了针模板FP,删除了函数指针参数,重命名为lambda_expression

更新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;
}

答案 3 :(得分:8)

捕获lambda不能转换为函数指针,正如this answer指出的那样。

但是,向只接受一个API的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给定的相同类型的callable。我们在一定程度上使用int参数N补救了这一点。

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

答案 4 :(得分:1)

不是直接的答案,但是使用“ functor”模板模式进行了一些改动,以隐藏lambda类型的细节,并使代码简洁美观。

我不确定您想如何使用Decision类,因此我不得不使用一个使用该类的函数来扩展该类。在此处查看完整的示例: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

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

答案 5 :(得分:0)

尽管出于各种原因,模板方法很聪明,但是记住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应该工作的原因[运行捕获类型的析构函数])。

答案 6 :(得分:-1)

正如其他人所提到的,你可以用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);

答案 7 :(得分:-1)

使用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);

答案 8 :(得分:-1)

一个模拟答案,但是我做到了,所以您不必指定返回的指针的类型(请注意,通用版本需要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
}