7
votes

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

3
Can you be more specific and describe what part of that doesn't make sense to you? I have no idea how to gauge what you do or do not understand about it. What parts of that do you understand? What parts of it are leaving you asking questions? And what are the specific questions it's leaving you with? - Wyck
@Wyck Imho it's quite clear what OP is asking about. We moreover have three answers and five upvotes (one downvote) on the question, which would imply that others agree. - dfrib
@dfrib I disagree. To be pedantic, it's not even clear from the question that the asker knows what a function is. It's just a quote and then asking what does it mean. I could paste any passage from effective c++ and ask what it means and expect upvotes? I think not. - Wyck
@Wyck "I could paste any passage from effective c++ and ask what it means and expect upvotes?"; I would expect one to expect an answer rather than upvotes whenever answering a question, but upvotes for a particular question tells something about how it is perceived by the community. Looking at the upvote/downvote quota for this one, it's apparently well-perceived, and the particular quoted paragraph is moreover arguably self-contained. A random paragraph from a study book is not necessarily neither self-contained nor material for a valid question. Imho, this is one though. - dfrib
@Wyck It's super-clear to many of us; if you don't get it, even after reading the answers, you can just move on to a more suitable question. :) - Asteroids With Wings

3 Answers

3
votes

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.

0
votes

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
}
0
votes

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.