1 2 3
|
int prvalue() { return 42; }
int& lvalue() { static int r; return r; }
int&& xvalue() { static int r; return static_cast<int&&>(r); }
|
The value category rules say the expressions
prvalue(),
lvalue(), and
xvalue(), are prvalue, lvalue, and xvalue expressions respectively.
We can assert the truth of that claim to the compiler:
1 2 3 4
|
#include <concepts>
static_assert(std::same_as<decltype(prvalue()), int>, "prvalue() is a prvalue expression");
static_assert(std::same_as<decltype(lvalue()), int&>, "lvalue() is a lvalue expression");
static_assert(std::same_as<decltype(xvalue()), int&&>, "xvalue() is a xvalue expression");
|
A key point is that
decltype produces reference types that may reveal the value category of the expression passed into it. The
static_asserts above use that property to check the value category of calls to our test functions.
Consider this function:
decltype(auto) a(auto f) { return f(); }
To decide the return type, the compiler puts the initializer in a's return statement into
decltype. For example,
a(prvalue) uses
decltype(prvalue()) as its return type.
1 2 3
|
static_assert(std::same_as<decltype(a(prvalue)), int>, "a(prvalue) is a prvalue expression");
static_assert(std::same_as<decltype(a(lvalue)), int&>, "a(lvalue) is a lvalue expression");
static_assert(std::same_as<decltype(a(xvalue)), int&&>, "a(xvalue) is a xvalue expression");
|
Compare that to
auto b(auto f) { return f(); }
In this case, the compiler uses the type deduced for T as the return type, where T is a parameter of an imaginary function template like this one:
template <typename T> void ftad(T f)
For example,
b(xvalue) returns
int since
ftad(xvalue()) causes the compiler to deduce the type
int for
T.
1 2 3
|
static_assert(std::same_as<decltype(b(prvalue)), int>, "b(prvalue) is an prvalue expression");
static_assert(std::same_as<decltype(b(lvalue)), int>, "b(lvalue) is an prvalue expression");
static_assert(std::same_as<decltype(b(xvalue)), int>, "b(xvalue) is an prvalue expression");
|
decltype(auto) was the right choice for
apply, because if
invoke returned some kind of a reference you'd want
apply to do the same. If
apply used
auto instead, a copy would be incurred.
Test code:
https://coliru.stacked-crooked.com/a/96a6b4c4b69b9549