Pancake
Pancake

Reputation: 107

Lambda as template function

I have a very strange problem. To keep things simple, lets say I want to have a function which takes 2 functions with the same declaration as arguments

template<typename Func>
void foo(Func a, Func b)
{
    std::cout << "good";
}

To try things out I took putchar from cstdio, and created an identical function to match the putchar.

int myPutcharFunc(int)
{
    return 0;
}

int main()
{
    auto myPutcharLambda = [](int) -> int
    {
        return 0;
    };

    foo(putchar, myPutcharFunc); // okay
    foo(putchar, myPutcharLambda);  //deduced conflicting types for parameter 'Func' ('int (__attribute__((__cdecl__)) *)(int)' and 'main()::<lambda(int)>')
}

Now, the lambda does not want to compile (the key is I want to use lambda capture).

So lets add template specialization, because the programmer is wiser than the machine, right? :)

template<typename Func>
void foo(Func a, Func b)
{
    std::cout << "good";
}

template<>
void foo(int(*)(int), int(*)(int))
{
    std::cout << "good";
}

No luck, the same error - why? But for some reason, when I comment out the template specialization:

//template<>
void foo(int(*)(int), int(*)(int))
{
    std::cout << "good";
}

The code compiles. I obviously do not want to overload foo for EVERY set of function's arguments - thats what templates are for. Every step was tested both with msvc++ and g++. What am I doing wrong?

Upvotes: 4

Views: 305

Answers (3)

xaxxon
xaxxon

Reputation: 19781

Every lambda is a different type, so you'll need to have two different template parameters to get them

template<typename FuncA, typename FuncB>
void foo(FuncA a, FuncB b)

Types don't decay when deducing template types (SEE COMMENT FOR CORRECTION). So a lambda remains a lambda and doesn't decay to a function pointer. Same reason a string literal is deduced as a char[N] instead of a const char *.

With your second example using specialization, it doesn't want to use your specialization, since the lambda is not a function pointer. You can cast the Lambda to a function pointer and make it work: https://godbolt.org/g/ISgPci The trick you can do here is say +my_lambda because + is defined for pointers so it will force the non-capturing lambda to become a function pointer.

Upvotes: 2

T33C
T33C

Reputation: 4429

A lambda has its own type which can decay to a function pointer but not in the case of a template function match, it will for the real function as you found because of the implicit conversion.

In the case of matching to a template you need to disambiguate and explicitly instantiate foo with the type you want or convert the lambda to a function pointer.

foo<decltype(putchar)>(putchar, myPutcharLambda);

or

foo(putchar, +myPutcharLambda);

Upvotes: 1

Aaron McDaid
Aaron McDaid

Reputation: 27183

Two possibilities.

1: Just put + in front of the lambda:

foo(putchar, +myPutcharLambda);

That works because unary + expects an integer-like value, such as a pointer. Therefore, the lambda converts to a function pointer.

Ultimately a (non-capturing) lambda doesn't have the same type as a function pointer, even though it's willing to convert to a function pointer.

How is a compiler supposed to know which conversions are allowed to make two objects of the same type?

2: There is another option, making use of the fact that the ?: is willing to do some conversions, converting one type to another in some circumstances.

template<typename Func1, typename Func2>
void foo2(Func1 a, Func2 b)
{
    using common_type = decltype(true?a:b); // or 'false', it doesn't matter
    foo<common_type>(a,b);
}

Upvotes: 2

Related Questions