Reputation: 41
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
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
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
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
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