如何将 lambda 表达式作为参数传递给 c++ 模板

Posted

技术标签:

【中文标题】如何将 lambda 表达式作为参数传递给 c++ 模板【英文标题】:How I can pass lambda expression to c++ template as parameter 【发布时间】:2016-08-21 05:14:43 【问题描述】:

我有一个接受函数作为参数的模板。

当我尝试传递 lambda 表达式时,它不会编译。

typedef int (*func)(int a);
template <func foo>
int function(int a)

    foo(a);


int test(int a)

    return a;


int main()

    function<test>(1);   // ---> this is ok

    auto lambda = [](int a) -> int  return a; ;
    function<lambda>(1); // ---> this is wrong, why?

    return 0;

我错过了什么?

【问题讨论】:

function&lt;test&gt;(1); 不是ok。模板需要类型或整数常量作为参数。 test 两者都不是。 lambda 也不是。 没关系,看看这个答案***.com/questions/1174169/…你也可以试试 @Striker 不,为什么 lambda 会是 std::function? lambda 的类型只是 operator() 重载的唯一类。 @Striker 是的,这样你就它放在std::function 中,而用auto 声明它不是 @Striker 如果你这样做,你会在其构造函数中构造一个带有 lambda 的 std::function,并且 std::function 具有相当显着的运行时开销。当您需要运行时类型时,它很有用。说 lambda 是 std::function 类型就像说字符串文字是 std::string。他们不是一回事。 【参考方案1】:

lambda 不是函数指针! lambda 是编译器生成的类的实例!

但是,非捕获 lambda 可以使用 operator+ 转换为函数指针

这是一个例子:

int main() 
    auto lambda = [](int a)  return a; ;

    func ptr = +lambda; // this would work

    return 0;

遗憾的是,operator+ 在您的情况下甚至都不起作用,因为它尚未声明为 constexpr,因此您不能在模板参数中使用它。

解决您的情况的方法是使用免费函数...在不接受 N4487 之前,您不能期望将 lambda 作为模板参数传递。

另一种解决方法是创建自己的仿函数而不是 lambda:

struct LambdaType 
    constexpr LambdaType() = default;

    int operator()(int a) 
        return run(a);
    

    // this is a non-capturing lambda, the operator can be
    // in a static function
    static int run(int a) 
        return a;
    
;

int main() 
    LambdaType lambda;

    function<&LambdaType::run>(1); // ---> this is working

    return 0;

这个解决方案不是很吸引人,但如果 LambdaType 隐藏在 cpp 文件中,它可能会很有用。

如果您的目标只是编译器能够内联您的代码,您可以使用模板来传递 lambda:

#include <iostream>

template <typename T>
int function(T foo, int a) 
    return foo(a);


int main() 
    int a;
    std::cin >> a;

    int b = function([](int a)  return a; , a);

    return b;

由于编译器知道每个实例化的T 的类型,一个好的编译器应该能够优化出 lambda。

使用 clang,第三个选项给出以下程序集:

main:                               # @main
    pushq   %rax
    leaq    4(%rsp), %rsi
    movl    std::cin, %edi
    callq   std::basic_istream<char, std::char_traits<char> >::operator>>(int&)
    movl    4(%rsp), %eax    # this is the call to the function
    addq    $8, %rsp
    retq

    pushq   %rax
    movl    std::__ioinit, %edi
    callq   std::ios_base::Init::Init()
    movl    std::ios_base::Init::~Init(), %edi
    movl    std::__ioinit, %esi
    movl    $__dso_handle, %edx
    popq    %rax
    jmp     __cxa_atexit            # TAILCALL

我使用-std=c++14 -Ofast -march=native 作为标志。

【讨论】:

是的,内联函数就是我想要的,让我试试第三个选项。【参考方案2】:

这是因为 lambda 作为自己的类型。 您已经对传递的函数类型模板化function()

template<typename F>
int function(F foo, int a) 
    return foo(a);


int test(int a) 
    return a;


int main()

    // function will work out the template types
    // based on the parameters.
    function(test, 1);
    function([](int a) -> int  return a; , 1);

【讨论】:

【参考方案3】:

我对标准的了解不够多,无法说明这是我的编译器错误没有正确实现它还是它实际上是标准,但是使用 VS2015 你无法生成编译时常量 lambda 表达式。模板只需要编译时常量,所以没有 lambdas。

但是,如果您想传递一个 lambda,您不需要 将它作为模板。完全有可能没有:

#include <functional>

int function(std::function<int(int)> const& f, int a)

    f(a);


int test(int a)

    return a;


int main()

    auto lambda = [](int a) -> int  return a; ;

    function(test, 1);
    function(lambda, 1);

    return 0;

【讨论】:

不是编译器的错。编译时 lambda 有望成为 C++17 中的一员! 但这不是我想要的。我希望这是一个模板参数,以便最终内联函数而不是始终调用运行时的指针。【参考方案4】:

这只是因为“您不能使用局部变量的名称或地址作为模板参数。”。 如果要制作全局静态 lambda,请参阅 http://pfultz2.com/blog/2014/09/02/static-lambda/ 你可能会找到你想要的。

【讨论】:

以上是关于如何将 lambda 表达式作为参数传递给 c++ 模板的主要内容,如果未能解决你的问题,请参考以下文章

C++:如何将变量作为参数传递给类构造函数

将 lambda 函数作为第三个参数传递给 QObject::connect 时出错

如何将变量参数传递给 C++ 中的 DBUS 方法调用?

如何使用 QT 和 C++ 将字符串参数传递给函数

如何使用 GET 请求将参数传递给 AWS Lambda 函数?

从 Swift 将文件作为参数传递给 C++ 方法