How to infer a function type parameter in a template function with a lambda passed as argument?

681 views Asked by At

Consider the following:

#include <utility>
#include <string>

template<typename>
class C;

template<typename R, typename T>
class C<R(&)(T)> {
public:
    template<typename F>
    C(F&& fun) {}
};

template<typename T>
C<T> makeC(T&& fun) {
    return C<T>(std::forward<T>(fun));
}

int foo(int a){return a;}

int main() {
    auto p1 = makeC(foo); // OK
    auto p2 = C<int(&)(int)>([](int a){return a;});   // OK
    // auto p3 = makeC([](int a){return a;}); // FAIL
}

Live example

The declaration of p3 fails because the compiler can't infer the type int(&)(int) from the lambda that was passed as parameter. p1 is ok because the type can be easily infered from the function foo, and p2 is ok because the type is explicitly declared.

It fails with:

error: invalid use of incomplete type 'class C<main()::<lambda(int)> >'

Is there any way to make the compiler infer the function type corretly, given a lambda?

P.S.: C++17 answers are also ok, if applicable.

1

There are 1 answers

1
skypjack On BEST ANSWER

The actual problem is that a lambda function has it own type, that cannot be reduced to R(&)(T). Because of that, C<T> is an incomplete type as correctly outlined by the compiler.


As long as you use non-capturing lambdas, you can rely on the fact that they decay to function pointers and do this:

auto p3 = makeC(*(+[](int a){return a;}));

Or this:

template<typename T>
auto makeC(T&& fun) -> C<decltype(*(+std::forward<T>(fun)))> {
    return C<decltype(*(+std::forward<T>(fun)))>(std::forward<T>(fun));
}

Another possible solution that works with capturing lambdas is this:

#include <utility>
#include <string>

template<typename T>
class C: T {
    template<typename F>
    C(F&& fun): T{std::forward<F>(fun)} {}
};

template<typename R, typename T>
class C<R(&)(T)> {
public:
    template<typename F>
    C(F&& fun) {}
};

template<typename T>
C<T> makeC(T&& fun) {
    return C<T>(std::forward<T>(fun));
}

int foo(int a){return a;}

int main() {
    auto p1 = makeC(foo);
    auto p2 = C<int(&)(int)>([](int a){return a;});
    auto p3 = makeC([](int a){return a;});
}

This way, when dealing with a lambda, C actually inherits from it and privately contains its operator().