Both from my personal experience and from consulting answers to questions like What are some uses of decltype(auto)? I can find plenty of valuable use cases for decltype(a
Essentially, the case for variables is the same for functions. The idea is that we store the result of an function invocation with a decltype(auto)
variable:
decltype(auto) result = /* function invocation */;
Then, result
is
a non-reference type if the result is a prvalue,
a (possibly cv-qualified) lvalue reference type if the result is a lvalue, or
an rvalue reference type if the result is an xvalue.
Now we need a new version of forward
to differentiate between the prvalue case and the xvalue case: (the name forward
is avoided to prevent ADL problems)
template
T my_forward(std::remove_reference_t& arg)
{
return std::forward(arg);
}
And then use
my_forward(result)
Unlike std::forward
, this function is used to forward decltype(auto)
variables. Therefore, it does not unconditionally return a reference type, and it is supposed to be called with decltype(variable)
, which can be T
, T&
, or T&&
, so that it can differentiate between lvalues, xvalues, and prvalues. Thus, if result
is
a non-reference type, then the second overload is called with a non-reference T
, and a non-reference type is returned, resulting in a prvalue;
an lvalue reference type, then the first overload is called with a T&
, and T&
is returned, resulting in an lvalue;
an rvalue reference type, then the second overload is called with a T&&
, and T&&
is returned, resulting in an xvalue.
Here's an example. Consider that you want to wrap std::invoke
and print something to the log: (the example is for illustration only)
template
decltype(auto) my_invoke(F&& f, Args&&... args)
{
decltype(auto) result = std::invoke(std::forward(f), std::forward(args)...);
my_log("invoke", result); // for illustration only
return my_forward(result);
}
Now, if the invocation expression is
a prvalue, then result
is a non-reference type, and the function returns a non-reference type;
a non-const lvalue, then result
is a non-const lvalue reference, and the function returns a non-const lvalue reference type;
a const lvalue, then result
is a const lvalue reference, and the function returns a const lvalue reference type;
an xvalue, then result
is an rvalue reference type, and the function returns an rvalue reference type.
Given the following functions:
int f();
int& g();
const int& h();
int&& i();
the following assertions hold:
static_assert(std::is_same_v);
static_assert(std::is_same_v);
static_assert(std::is_same_v);
static_assert(std::is_same_v);
(live demo, move only test case)
If auto&&
is used instead, the code will have some trouble differentiating between prvalues and xvalues.