std::forward()
has nothing to do with this question. Nor does constexpr
. So let's start out with the super basic, a function that just returns its argument by value:
template <class T>
auto f(T x) -> decltype(x);
Sure, we could just use T
, but that's too easy. Now the question, how do we lift that into a template parameter while still maintaining SFINAE (granted there's obviously no possible substitution failure here, but bear with me):
template <class T, class R = decltype(x)>
R f(T x);
So that doesn't work because there's no x
yet (or worse, there's some unrelated x
somewhere else that name lookup finds). We can use the argument names in a trailing-return-type because they're in scope, but we cannot use them as part of expression-SFINAE in default template arguments because they're not yet in scope.
But because these are template parameters, we don't care about their values. We just care about their types. This isn't an evaluated expression. So I don't need x
, I need something that has the same type as x
. A first go might be:
template <class T, class R = decltype(T{})>
R f(T x);
This works... as long as T
is default-constructible. But I'm writing a template, I don't want to be making assumptions on types that I don't need to be making. So instead, I can do something like:
template <class T> T make(); // never defined
template <class T, class R = decltype(make<T>())>
R f(T x);
And now we have our arbitrary expression of type T
, that we can use in decltype
, in a default template argument. Although, we're still a little limited with make()
- there are types that you can't return by value from a functon (e.g. arrays), so it turns out to be more useful to add references. And we need a less-likely-to-collide name than make
:
template <class T>
add_rvalue_reference<T> declval();
template <class T, class R = decltype(declval<T>())>
R f(T x);
This is precisely the point of std::declval<T>
- to give you an expression of type T
in an unevaluated context.
Back to your original problem. We use the same thought process of how to get from decltype(x)
to decltype(declval<T>())
, but just apply it to a different expression. Instead of x
, we have myfunction(std::forward<T>(x))
. Which is to say, we're invoking myfunction
with the same type as our argument:
template <class T, class R = decltype(myfunction(std::declval<T&&>()))>
R f(T&& x);
But due to reference collapsing rules, std::declval<T&&>
is actually the same function as std::declval<T>
, so we can just write:
template <class T, class R = decltype(myfunction(std::declval<T>()))>
R f(T&& x);