Amit Sagar
Amit Sagar

Reputation: 41

Why is a reference type an lvalue when accessed with a temporary object?

Why does assigning a value to a reference variable being accessed using a temporary object work, but not for a non-reference type?

class a
{
    public:
        int m;
        int &n;
        a():m(2),n(m)
        {
            cout<< "A's constructor"<<endl;
        }
};

int main()
{
    // a().m = 6; // this gives an error that a temporary object is being used 
                  // as an lvalue
    a().n = 20;   // But this line works

    return 0;
}

Upvotes: 4

Views: 173

Answers (3)

Oersted
Oersted

Reputation: 2594

I'd like to propose my understanding more than a real answer.

I can present the question in another light with this snippet:

#include <iostream>
#include <type_traits>
struct S {
    int val;
    int& rval = val;
    int* pval = &val;
};

int main() {
    std::cout << "(S{}.val)\tint&&\t" << std::boolalpha
              << std::is_same<decltype((S{}.val)), int&&>::value << '\n';
    std::cout << "(S{}.rval)\tint&\t" << std::boolalpha
              << std::is_same<decltype((S{}.rval)), int&>::value << '\n';
    std::cout << "(S{}.pval)\tint*&&\t" << std::boolalpha
              << std::is_same<decltype((S{}.pval)), int*&&>::value << '\n';
}

Which outputs

(S{}.val)   int&&   true
(S{}.rval)  int&    true
(S{}.pval)  int*&&  true

Live

My understanding from https://en.cppreference.com/w/cpp/language/value_category:
S{}.xxx are glvalue because they are named. Period.

a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity of an object or function;

As S{}.val and S{}.pval are accessed through the materialization of a temporary, their ressources can be reused after the expression usage, making them expiring values: xvalues.

S{}.rval, on the otherhand, is an unmutable "ressource", as a reference can not be changed (only the ressource it references), thus it would disqualify the expression as xvalue, it can then only be a lvalue.
Or maybe we can say that a reference is not a ressource at all. I'm afraid to be unable to give a clearer explanation for this case.
A hint from here: S{}.rval is not movable.

NB this explanation is failing if I add constness: according to my dubious "unmutable/non-movable" argument, I would have expected that all the expressions would become xvalues. It's visibly not the case.

Upvotes: 0

Guillaume Racicot
Guillaume Racicot

Reputation: 41780

But is a().n truely a temporary? Consider this code:

class a
{
    public:
        int m;
        int &n;
        a():m(2),n(m)
        {
            cout<< "A's constructor"<<endl;
        }

        a(int& _n):m(2),n(_n)
        {
            cout<< "A's constructor"<<endl;
        }
};

int main()
{
    a().n = 20;   // (1)

    int n = 0;
    a(n).n        // (2)

    return 0;
}

The line (2) clearly shows that .n is not a temporary. It must not be, since it's a reference to the local n variable.

But then, the compiler cannot know what n will refer. One could even do n(rand_bool() ? m : _n) and it must work.

The compile instead uses the type system to know what should be assigned or not.

For example, the literal 9 is a pr-value of type int. You can't assign to it:

9 = 8; // nope

In your code, a() is a prvalue or type a. All of its value member also are. This is why a().m won't work. m is a prvalue.

But, a().n is an lvalue because n is a lvalue reference. No matter to which variable it points to.

Upvotes: 3

R Sahu
R Sahu

Reputation: 206607

a().n = 20;

works since n is a lvalue reference type. The compiler does not know that n is a reference to m in the implementation. It assumes that n is a valid lvalue reference and hence accepts that line.

In theory, when you assign to a().n, you could be assigning to a variable that lives independent of the life of a(). The compiler has no way of assessing that and will be in the way of the programmer if it didn't accept that line. Imagine the use case below:

// Global variable.
int gv;

class a
{
    public:
        int m;
        int &n;
        a():m(2), n(gv)  // n is a reference to the global variable.
        {
            cout<< "A's constructor"<<endl;
        }
};

int main()
{
    a().n = 20;   // Changes gv. It is a valid operation.
    return 0;
}

Upvotes: 1

Related Questions