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


210

是否可以将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视为a constexpr,因此无法将其作为函数指针传递?我也尝试过使xconst,但这似乎没有帮助。


34
仅当lambda不捕获任何内容时,它们才能衰减到函数指针。
Jarod42


为了后代,上面链接的博客文章现在位于devblogs.microsoft.com/oldnewthing/20150220-00/?p=44623
Warrenm

Answers:


205

如果它不捕获,从A拉姆达只能被转换为一个函数指针草案C ++ 11标准5.1.2 [expr.prim.lambda]表示(重点矿山):

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

注意,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它很重,因此这不是一项不需付出代价的折衷。


2
旁注:C东西使用的一种常见解决方案是传递a void*作为唯一参数。通常称为“用户指针”。它也相对较轻,但是确实需要您malloc占用一些空间。
基金莫妮卡的官司

94

Shafik Yaghmour的答案正确地解释了为什么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; }
      };
    }

4
@TC有关其工作原理的详细信息,请参阅此问题
Shafik Yaghmour 2015年

请注意,通常,如果您在编译时知道捕获数据,则可以将其转换为类型数据,然后返回没有捕获的lambda-请参阅我刚刚写给另一个问题的答案(感谢@ 5gon12eder的答案在这里)。
dan-man

那么,对象的寿命不应该比指针函数更长吗?我想用它glutReshapeFunc
ar2015

我不建议使用此建议,因为有些事情会神奇地起作用,但会引入新的错误。以及伴随这些错误的做法。如果您想使用std :: function,则应该看到可以使用std :: function的各种方式。因为某些方式也许是您不想要的。
TheNegative

1
这不能回答问题。如果可以使用std::function或使用lambda,为什么不呢?至少它是一种更具可读性的语法。通常,需要使用一个函数指针与C库(实际上,与任何外部库)进行交互,并确保您不能修改它以接受std :: function或lambda。
Hi-Angel

40

Lambda表达式,甚至是捕获的表达式,都可以作为函数指针(指向成员函数的指针)进行处理。

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

当您富有创造力时,就可以使用它!考虑一下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”一样开始工作的类,首先需要一个可以存储对象和函数指针的类/结构。您还需要一个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一起使用

更新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的类内部指针(指向成员函数operator())来调用包装器类中存储的lambda!惊人!!那么为什么我们需要std :: function呢?是否可以使lambda_expression <decltype(lambda),int,int,int>直接从传递的lambda本身自动推断/这些“ int”参数?
barney

2
我添加了自己代码的简短版本。这应该使用简单的自动f = make :: function(lambda); 但是我很确定您会发现很多情况我的代码无法正常工作。std :: function的构造方式比这更好,并且应该在工作时使用。这是供教育和个人使用。
Noxxer

14
此解决方案涉及通过operator()实现调用lambda ,因此,如果我没看错,我认为使用C样式函数指针调用lambda不会起作用,是吗?这就是原始问题的要求。
雷米·勒博

13
您声称可以将lambda当作函数指针来处理,而您并未这​​样做。您创建了另一个对象来保存lambda,该对象什么也不做,您可以只使用原始的lambda。
传球客

9
这不是“将捕获lambda作为函数指针传递”。这是“将lambda捕获为包含函数指针的对象”。有一个不同的世界。
n。代词

15

正如该答案指出的那样,捕获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!
}

生活

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

注意,调用将fnptr覆盖先前编写callable的相同类型的给定可调用对象。我们在一定程度上使用int参数对此进行了补救N

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

强制声明N整数将是一种记住客户端以避免在编译时覆盖函数指针的好方法。
fiorentinoing

2

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

3
该lambda没有捕获。OP的问题是捕获,而不必推断函数指针类型(这就是+窍门)。
Sneftel

2

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


为什么要麻烦new-std :: function已经将lambda存储在堆上,并且避免需要记住调用delete。
克里斯·多德

0

这不是一个直接的答案,而是使用“ 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

请参阅链接以获取完整示例


-2

正如其他人所提到的,您可以用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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.