提问者:小点点

传递捕获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常量,但这似乎没有帮助。


共3个答案

匿名用户

如果lambda没有捕获,则只能将它转换为函数指针,从草案C++11标准章节5.1.2[expr.prim.lambda]中说(强调我的):

没有lambda-capture的lambda-expression的闭包类型有一个公共的,非虚拟的,非显式的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是一种重量级的选择,因此它不是一种成本较低的权衡。

匿名用户

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

>

  • 使用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; } };
    }
    

    使用不捕获任何内容的lambda表达式。

    因为您的谓词实际上只是一个布尔常量,所以下面的方法可以快速解决当前的问题。 请看这个答案,以获得一个很好的解释,为什么和如何工作。

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

  • 匿名用户

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