user3819404
user3819404

Reputation: 621

Functions returning lvalues, always return lvalue references

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

Answers (3)

Artefacto
Artefacto

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

Asteroids With Wings
Asteroids With Wings

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

dfrib
dfrib

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

Related Questions