How I can pass lambda expression to c++ template a

2020-02-28 05:03发布

问题:

I have a template that accepts a function as an argument.

When I try to pass a lambda expression it does not compile.

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

What I am missing?

回答1:

A lambda is not a function pointer! A lambda is an instance of compiler generated class!

However, a non capturing lambda may be converted to a function pointer using it's operator+

Here's an example:

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

    func ptr = +lambda; // this would work

    return 0;
}

Sadly, the operator+ won't even work in your case because it has not been declared as constexpr, so you can't use it in a template parameter.

A fix to your case would be to use a free function... until N4487 is not accepted, you can't expect to pass lambda as template parameter.

Another fix would be to create your own functor instead of a 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;
}

This solution is not quite appealing, but it might be useful if LambdaType is hidden in a cpp file.

If your goal is only the compiler to be able to inline your code, you can use templates to pass the lambda around:

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

Since the compiler knows the type of T for each instanciation, a good compiler should be able to optimize out the lambda.

With clang, the third option gives the following assembly:

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

I used -std=c++14 -Ofast -march=native as flags.



回答2:

This is because the lambda as its own type.
You have templatize function() on the type of the function passed.

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:

I do not know enough of the standard to say whether this is my compilers fault not implementing it properly or if it's actually the standard, but with VS2015 you cannot generate a compile-time constant lambda expression. And templates only take compile time constants, so no lambdas.

However, you don't need it to be a template if you want to pass a lambda. It's perfectly possible without:

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


回答4:

This is only because "You cannot use the name or address of a local variable as a template argument.". If you want to make a global static lambda, go refer to http://pfultz2.com/blog/2014/09/02/static-lambda/ you may find what you want.