19

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?

gsf
  • 6,612
  • 7
  • 35
  • 64
  • `function(1);` is not `ok`. Templates require a type or an integer constant as parameters. `test` is neither. And neither is `lambda`. – DeiDei Apr 27 '16 at 05:14
  • 1
    it is ok, check this answer http://stackoverflow.com/questions/1174169/function-passed-as-template-argument/2156899#2156899 and you can try it as well – gsf Apr 27 '16 at 05:18
  • lambda is already std::function type. What you are doing looks like function )>(1) which is invalid (because 1 is an int not function). – Striker Apr 27 '16 at 05:34
  • 1
    @Striker No, why would a lambda be `std::function`? The type of a lambda is just a unique class with `operator()` overloaded. – DeiDei Apr 27 '16 at 05:36
  • I meant your variable lambda. Try replace auto with std::function void – Striker Apr 27 '16 at 05:39
  • 1
    @Striker Yes, that way you're **putting** it inside an `std::function`, while declared with `auto` it is **not**. – DeiDei Apr 27 '16 at 05:41
  • 1
    @Striker if you do that you construct a `std::function` with a lambda in it's constructor, and std::function has a quite noticable runtime overhead. It's useful when you need runtime type easure. Saying a lambda is of type `std::function` is like saying that a string literal is a `std::string`. Thay are not the same thing. – Guillaume Racicot Apr 27 '16 at 05:42
  • Just tested it. You are right is becomes a lamda type. – Striker Apr 27 '16 at 05:45
  • In C++20 it's solved like in this [question](https://stackoverflow.com/questions/5849059/lambda-expressions-as-class-template-parameters). – really Jan 06 '23 at 16:24

4 Answers4

22

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.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
7

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);
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
6

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;
}
nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Not the compiler's fault. Compile time lambdas will hopefully become a thing with C++17! – DeiDei Apr 27 '16 at 05:35
  • That is not what I want, though. I would like this to be a template argument so the function to be eventually inlined instead of pointer to be always called runtime. – gsf Apr 27 '16 at 05:35
1

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.

Gran
  • 11
  • 2