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

19

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

当我尝试传递 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<test>(1); 不行。模板需要类型或整数常量作为参数。test 既不是类型也不是整数常量,lambda 也一样。 - DeiDei
1
没问题,看这个答案:https://dev59.com/hnM_5IYBdhLWcg3w6HvT#2156899,你也可以试一试。 - gsf
1
@Striker 不是的,为什么会把lambda当作std::function呢?Lambda的类型只是一个带有重载operator()的独特类。 - DeiDei
1
@Striker 是的,这样你就将它放在了一个std::function中,而用auto声明时则不是。 - DeiDei
1
@Striker 如果你这样做,你会在构造函数中使用lambda来构造一个std::function,而std::function有一个相当明显的运行时开销。它在需要运行时类型测量时非常有用。说一个lambda是std::function类型就像说字符串字面量是std::string一样,它们并不是同一种东西。 - Guillaume Racicot
显示剩余4条评论
4个回答

22

一个 lambda 表达式不是函数指针! 它是编译器生成的类的实例!

然而,一个非捕获的 lambda 可以使用其 operator+ 转换为函数指针。

这里有一个例子:

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

    func ptr = +lambda; // this would work

    return 0;
}

很遗憾,operator+在您的情况下甚至无法工作,因为它没有声明为constexpr,所以您不能在模板参数中使用它。
解决方案之一是使用自由函数……直到N4487被接受之前,您不能指望将lambda作为模板参数传递。
另一个解决方案是创建您自己的functor而不是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
是的,我只想内联函数,让我尝试第三个选项。 - gsf

8
这是因为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);
}

6
我不太清楚这是否是我的编译器没有正确实现,还是标准本身的问题,但在使用VS2015时,您无法生成编译时常量lambda表达式。而模板只接受编译时常量,因此不能使用lambda表达式。
但是,如果您想传递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;
}

不是编译器的问题。希望C++17能够支持编译时lambda表达式! - DeiDei
不过,那不是我想要的。我希望这是一个模板参数,这样函数最终可以内联,而不是始终调用运行时指针。 - gsf

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接