3
votes

I want to implement a template function which takes a lambda as argument.

#include <functional>

template<typename ... Result> using Fun = std::function<void(Result ...)>;
template<typename ... Result> void yield(Fun<Result ...>&& body) {};

template <typename T>
struct identity {
    typedef T type;
};

template<typename ... Result> void yield2(typename identity<Fun<Result ...>>::type && body) {};

int main() {
    yield<char>(
        Fun<char>(
            [](char) -> void {} // 1. success
        )
    );

    yield2<char>(
        [](char) -> void {} // 2. success with identify
    );

    yield<char>(
        [](char) -> void {} // 3. fail, seems achievable
    );

    yield(
        [](char) -> void {} // 4. fail, impossible ?
    );

    return 0;
}

Why case 3 fails ? I already giving the template parameters to the template, so it should be able to deduce the function type and implicit convert the lambda to function

EDIT:

Compiler always reduce template parameters from function arguments, can we reverse it ?

    yield<char>(
        [](auto&& c) -> void {} // 5. is it possible ?
    );
1

1 Answers

4
votes

The problem is that you have a variadic template, and it starts trying to see "what else" it can deduce besides your explicit char argument.

If you had a single argument template like so:

template<typename Result> using Fun = std::function<void(Result)>;
template<typename Result> void yield(Fun<Result>&& body) {};

You'll notice that

yield<char>(
    [](char) -> void {} // 3. fail, seems achievable
);

Has no trouble at all because the entirety of std::function is deducible.

But as soon as we make a variadic template our compilers become unhappy:

template<class... Result> using Fun = std::function<void(Result...)>;
template<class... Result> void yield(Fun<Result...>&& body) {};

This is because, like it or not, the compiler is going to try to deduce more template arguments for Fun<Result...> given the value that was passed in ([temp.deduct.type]).

yield2 sidesteps this issue because it places the resulting type into a non-deduced context, but since you explicitly specify the template arguments, it will use only those explicitly specified arguments (char) to deduce the type (#1 works for basically the same reason).

The best workaround I think is your yield2 attempt, however you may also do something like this to prevent the value being passed from participating in type deduction:

auto fn = &yield<char>;
fn(
    [](char) -> void {}
);

Another workaround is to static_cast your call to yield to the proper type: (Really I'm just reading through other possible workarounds under "The non-deduced contexts are:" for [temp.deduct.type])

using fn_type = void(*)(Fun<char>&&);

static_cast<fn_type>(&yield)(
    [](char) -> void {}
);

Edit: Finally, you could write some additional boilerplate templating to make the call look nicer (something approaching your #4). Bear in mind this is an incomplete impl made for example:

The goal of this templating is to detect the lambda's operator() function and extract its return type and arguments so that we can explicitly specify a Fun type when we call yield (extracting the return type is unnecessary since you're only ever using void):

First a helper struct that will allow us to detect an immutable lambda's return type and argument types:

template<class T>
struct Fun_Type;

template<class C, class Ret, class... Args>
struct Fun_Type<Ret(C::*)(Args...) const>
{
    using type = Fun<Args...>;
}; 

Second, our helper function, call_yield, that passes on Fun_Type<...>::type to a call to yield:

template<class ImmutableLambda>
void call_yield(ImmutableLambda&& c)
{
    using Fun_t = typename Fun_Type<decltype(&ImmutableLambda::operator())>::type;
    yield(Fun_t{std::forward<ImmutableLambda>(c)});
}

And now we can call it simply:

int main() {
    call_yield(
        [](char) -> void {}
    );
}

Demo (C++11)