Reputation: 621
Scott meyers in item 3 of effective c++ says
Applying decltype to a name yields the declared type for that name. Names are typically lvalue expressions, but that doesn’t affect decltype’s behavior. For lvalue expressions more complicated than names, however, decltype generally ensures that the type reported is an lvalue reference. That is, if an lvalue expression other than a name has type T, decltype reports that type as T&. This seldom has any impact, because the type of most lvalue expressions inherently includes an lvalue reference qualifier. Functions returning lvalues, for example, always return lvalue references.
What does it mean when he says Functions returning lvalues, for example, always return lvalue references
Upvotes: 8
Views: 1419
Reputation: 97815
Given:
template<typename T> struct foo;
foo<decltype(*(int*)0)> x{};
int v;
foo<decltype(v)> y{};
You get:
error: variable ‘foo<int&> x’ has initializer but incomplete type
foo<decltype(*(int*)0)> x{};
^
error: variable ‘foo<int> y’ has initializer but incomplete type
foo<decltype(v)> y{};
^
As you can see, decltype
gives int&
for the dereferenced pointer, but just int for the int variable v
.
For functions, he means the only way they can return an lvalue is if they return an lvalue reference.
Upvotes: 0
Reputation: 17454
It means what it says!
There is no way to make a function, such that its return type is not T&
, yet calling it results in an lvalue expression.
Every other return type results in the function call being an rvalue expression.
This is intuitive when you consider that the only way to "return" something that already exists, is by reference — the result of a function call is otherwise always a "temporary", whether because it's copying some local variable, or because it's moving from some local variable.
The would-be exception to this rule, returning T&&
, doesn't apply either because these produce rvalue expressions (which is what makes move semantics work, since only rvalue expressions can go on to bind to T&&
parameters).
Scott is reporting a consequence of the rules of the language, and telling us that the same consequence was used as a justification for one of the rules of decltype
.
Arguably, he could have more clearly phrased it thus:
The only functions whose calls evaluate to lvalues, are those that return lvalue references.
Upvotes: 3
Reputation: 73186
First of all, we have no difficulty of coming up with an example of an lvalue expression that is also a name:
int x{42};
x; // 'x' here is a name and an lvalue expression
But what is an lvalue that is not a name?
For example, consider
int f() {}
An invocation of f()
here is not an lvalue expression. There are actually not too many things that are not names but that are lvalue expression, and Scott simply denotes for all those that actually are lvalues expressions but not names, the corresponding type reported by decltype
is of lvalue reference type, by design.
#include <type_traits>
int f() { return 42; };
int& g() { static int g_x; return g_x; }
int main() {
int x;
int arr[3] = {1, 2, 3};
static_assert(
std::is_same_v<decltype(x), int>, "");
// ^ - 'x' is a name, and an lvalue
static_assert(
std::is_same_v<decltype(arr[1]), int&>, "");
// ^^^^^^ - 'arr[1]' is an lvalue expression
static_assert(
std::is_same_v<decltype((x)), int&>, "");
// ^^^ - '(x)' is an lvalue expression
static_assert(
std::is_same_v<decltype(f()), int>, "");
// ^^^ - 'f()' is not an lvalue
static_assert(
std::is_same_v<decltype(g()), int&>, "");
// ^^^ - 'g()' returns an lvalue
}
Upvotes: 0