Consider the following code:
#include <iostream>
#include <type_traits>
#include <functional>
#include <utility>
template <class F>
constexpr decltype(auto) curry(F&& f)
{
if constexpr (std::is_invocable_v<decltype(f)>)
{
return std::invoke(f);
}
else
{
return
[f = std::forward<std::decay_t<F>>(f)]<typename Arg>(Arg&& arg) mutable -> decltype(auto)
{
return curry(
[f = std::forward<std::decay_t<F>>(f), arg = std::forward<Arg>(arg)](auto&& ...args) mutable
-> std::invoke_result_t<decltype(f), decltype(arg), decltype(args)...> // #1
{
return std::invoke(f, arg, args...);
});
};
}
}
constexpr int add(int a, int b, int c)
{
return a + b + c;
}
constexpr int nullary()
{
return 1;
}
void mod(int& a, int& b, int& c)
{
a = 1;
b = 2;
c = 3;
}
int main()
{
constexpr int u = curry(add)(1)(2)(3);
constexpr int v = curry(nullary);
std::cout << u << '\n' << v << std::endl;
int i{}, j{}, k{};
curry(mod)(std::ref(i))(std::ref(j))(std::ref(k));
std::cout << i << ' ' << j << ' ' << k << std::endl;
}
With the trailing return type, the code can compile and outputs: (godbolt)
6
1
1 2 3
If you remove the seemingly redundant trailing return type (line #1
in the above code) and let the compiler to deduce the return type, however, the compiler starts compilaning that
error C2672: 'invoke': no matching overloaded function found
(godbolt), which surprise me.
So, why is the trailing return type necessary in this lambda expression?
If you explicitly give the return type and std::is_invocable_v<decltype(f), decltype(arg), decltype(args)...>
is false
SFINAE applies and a test for whether or not the lambda is callable will simply result in false
.
However without explicit return type, the return type will need to be deduced (in this case because of std::is_invocable_v<decltype(f)>
being applied to the lambda) and in order to deduce the return type the body of the lambda needs to be instantiated.
However, this also instantiates the expression
return std::invoke(f, arg, args...);
which is ill-formed if std::is_invocable_v<decltype(f), decltype(arg), decltype(args)...>
is false
. Since this substitution error appears in the definition rather than the declaration, it is not in the immediate context and therefore a hard error.