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 {}
);
}