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

Cor*_*mer 186 c++ lambda function-pointers c++11

是否可以将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;
}
Run Code Online (Sandbox Code Playgroud)

当我尝试编译它时,我得到以下编译错误:

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&&'
Run Code Online (Sandbox Code Playgroud)

这是一个消化错误消息,但我认为我得到的是lambda不能被视为一个constexpr因此我不能将它作为函数指针传递?我也试过制作xconst,但这似乎没有帮助.

Sha*_*our 186

如果没有捕获lambda只能转换为函数指针,从草案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; } };
Run Code Online (Sandbox Code Playgroud)

这样:

typedef bool(*DecisionFn)();

Decide greaterThanThree{ [](){ return true ; } };
Run Code Online (Sandbox Code Playgroud)

正如5gon12eder指出的那样,你也可以使用std::function,但要注意std::function重量很重,所以这不是一个无成本的权衡.

  • 旁注:C语言常用的一种常见解决方案是传递“ void *”作为唯一参数。通常称为“用户指针”。它也是相对轻量级的,但是确实需要您“ malloc”一些空间。 (2认同)

5go*_*der 85

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; } };
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用不捕获任何内容的lambda表达式.

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

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

  • @TC请参阅[问题](http://stackoverflow.com/q/27989031)了解其工作原理 (4认同)
  • 这并不能回答问题。如果可以使用 `std::function` 或 lambda — 为什么不呢?至少它是一种更具可读性的语法。通常需要使用函数指针与 C 库*(实际上,与任何外部库)* 交互,并且确保您不能修改它以接受 std::function 或 lambda。 (3认同)

Nox*_*xer 33

我知道这有点老了..

但我想补充一下:

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;
Run Code Online (Sandbox Code Playgroud)

要构建一个可以像"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...);
    }
};
Run Code Online (Sandbox Code Playgroud)

有了这个,你现在可以运行捕获的,非捕获的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;
}
Run Code Online (Sandbox Code Playgroud)

此代码适用于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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案涉及通过`operator()`实现调用lambda,所以如果我正确读取它,我认为使用**C风格的函数指针**调用lambda是不行的,是吗?这就是原始问题所要求的. (10认同)
  • 你声称lambda可以作为函数指针处理,你没有这样做.你创建了另一个持有lambda的对象,它什么都不做,你可以只用原来的lambda. (8认同)
  • 这不是“将捕获lambda作为函数指针传递”。这是“将lambda捕获为包含函数指针的对象”。有一个不同的世界。 (8认同)
  • 这不是答案,因为您无法将 lambda.*ptr 传递给需要函数指针的 API。你会得到“错误:无效使用类型的非静态成员函数”。 (5认同)
  • 我添加了自己代码的简短版本.这应该使用简单的auto f = make :: function(lambda); 但我很害羞你会发现很多情况我的代码无法正常工作.std :: function的构造比这更好,应该是你工作的时候.这是用于教育和个人使用. (2认同)

Pas*_* By 13

正如这个答案指出的那样,捕获lambda不能转换为函数指针.

但是,向只接受一个API的API提供函数指针通常会非常麻烦.最经常引用的方法是提供一个函数并用它调用一个静态对象.

static Callable callable;
static bool wrapper()
{
    return callable();
}
Run Code Online (Sandbox Code Playgroud)

这很乏味.我们进一步采用这一想法,使创建过程更加自动化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);
}
Run Code Online (Sandbox Code Playgroud)

并用它作为

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

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

生活

这基本上是在每次出现时声明一个匿名函数fnptr.

请注意,调用fnptr覆盖以前写入callable的相同类型的给定callables.我们在一定程度上使用int参数来解决这个问题N.

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