Haatschii
Haatschii

Reputation: 9319

Function overload using lambda function signature

Consider the following example

void foo(const std::function<int()>& f) {
    std::cout << f() << std::endl;
}

void foo(const std::function<int(int x)>& f) {
std::cout << f(5) << std::endl;
}

int main() {
    foo([](){return 3;});

    foo([](int x){return x;});
}

This does not compile, because the call to foo is said to be ambiguous. As far as I understand this is due to the fact, that the lambda function is not a priori a std::function but has to be casted to it and that there is a std::function constructor that takes an arbitrary argument.

Maybe someone can explain to me why anyone would create an implicit constructor that takes an arbitrary argument. However my acutual question is whether there is a workaround, which allows to use the function signature of lambda functions to overload a the function foo. I have tried function pointers, but that didn't work because capturing lambda functions can't be cast to a normal function pointer.

Any help is most welcome.

Upvotes: 12

Views: 2809

Answers (2)

rparolin
rparolin

Reputation: 508

http://coliru.stacked-crooked.com/a/26bd4c7e9b88bbd0

An alternative to using std::function is to use templates. Templates avoid the memory allocation overhead associated with std::function. The template type deduction machinery will deduce the correct type of the lambda passed so the call site cast goes away. However you still have is disambiguate the overloads for the no-args vs args case.

You can do this using a trick with trailing return types that behave similar to enable_if.

template<typename Callable>
auto baz(Callable c) 
    -> decltype(c(5), void())
{
    std::cout << c(5) << std::endl;
}

The above overload of baz will only be a valid overload candidate when the template parameter Callable can be called with an argument of 5.

You can put more advanced mechanisms on top of this to make it more general (ie. variadic pack expansion of args into callable) but I wanted to show the basic mechanism working.

Upvotes: 8

Brian Bi
Brian Bi

Reputation: 119194

Your compiler is correct according to C++11. In C++14, a rule is added that says that the constructor template shall not participate in overload resolution unless the type of the argument is actually callable with the std::function's argument types. Therefore, this code is supposed to compile in C++14, but not in C++11. Consider this to be an oversight in C++11.

For now, you can work around this by explicit conversion:

foo(std::function<int()>([](){return 3;}));

Upvotes: 16

Related Questions