views:

520

answers:

5

I believe the expression T() creates an rvalue (by the Standard). However, the following code compiles (at least on gcc4.0):

class T {};

int main()
{
    T() = T();
}

I know technically this is possible because member functions can be invoked on temporaries and the above is just invoking the operator= on the rvalue temporary created from the first T().

But conceptually this is like assigning a new value to an rvalue. Is there a good reason why this is allowed?

Edit: The reason I find this odd is it's strictly forbidden on built-in types yet allowed on user-defined types. For example, int(2) = int(3) won't compile because that is an "invalid lvalue in assignment".

So I guess the real question is, was this somewhat inconsistent behavior built into the language for a reason? Or is it there for some historical reason? (E.g it would be conceptually more sound to allow only const member functions to be invoked on rvalue expressions, but that cannot be done because that might break some existing code.)

+14  A: 

This is allowed purely because of operator overloading, and the possibility that you may overloaded the operator = to do something more fancy, like print to the console, or lock a mutex, or anything really.

Inverse
And not just operator= but the constructors as well. There are plenty of strange constructions in C++ where the compiler just shrugs and hopes you know what you're doing. :-)
Owen S.
+9  A: 

Yes, you are assigning a new value to an rvalue. More precisely, you are calling the operator = member function on a rvalue. Since you are not using the built-in assignment operator, why do you think this should be a problem? operator = is a member function of the class, which in most respects is similar to any other member function of the class, including the fact that it can be called on rvalues.

You should probably also take into account the fact that "being an rvalue" is a property of an expression, not a property of an object. It is true that T() expression evaluates to an rvalue. Nevertheless, the temporary object the T() expression produces is still an object, which can be accessed as an lvalue as well. For example, some other member function can be called on the result of the assignment, and it will see the "new" (freshly assigned) value of the temporary object through *this lvalue

(T() = T()).some_member_function();

You can also extend the lifetime of the temporary by attaching a const-reference to it const T& r = T() = T(); and the value seen through r will be the "new" value of the object. As Johannes correctly noted in his comment, this will not attach it to a temporary.

AndreyT
Yes I think I understand the mechanism of 'how' such operations work. But I am still curious 'why' the language designers allowed the r-values (or the temporaries created from r-value expressions) to be mutated as such?Is that an oversight on their side or was it allowed intentionally (Maybe there were some practical reasons that were compelling enough to justify such a seemingly inconsistent behavior between built-in types and user defined types?)
Rimo
Just to clarify, personally I would find it much less surprising if only const member functions were allowed to be invoked on rvalue expressions.
Rimo
@Rhimo: That defeats reasonable constructs like `Atomic(std::cout) << 1 << " uninterrupted output" << std::endl;`
MSalters
Small oversight here, i think: `const T
Johannes Schaub - litb
+4  A: 

From one POV, it is inconsistent, but you're overlooking how it is consistent: 1) ints and other built-in types still behave as they do in C, 2) operator= on class-types behaves as any other method does without requiring yet another special case.

C compatibility has been highly valued since the beginning of C++, and C++ arguably wouldn't be here today without it. So that part's generally a Good Thing.

The second point is understated. Not special casing operator= allows nonsense code to "work", but why do we care about nonsense code in the first place? Garbage in, garbage out. The current rules give it a defined meaning (UB here would be bad) with negligible cost, as far as I've ever seen.

Given my druthers, things would be simplified even further, so int() = int() would be allowed. C++0x starts to head in that direction with rvalue-references, prvalues, etc.

Roger Pate
+3  A: 

You can restrict operator= to work only on lvalues in C++0x:

class T
{
public:
    T& operator=(const T&) & = default;
};
FredOverflow
+1  A: 

This is why several classes in the Standard library can be implemented. Consider for example std::bitset<>::operator[]

// bit reference:
class reference {
  friend class bitset;
  reference();
public:
  ˜reference();
  reference& operator=(bool x);           // for b[i] = x;
  reference& operator=(const reference&); // for b[i] = b[j];
  bool operator˜() const; // flips the bit
  operator bool() const;  // for x = b[i];
  reference& flip();      // for b[i].flip();
};

reference operator[](size_t pos); // for b[i];

If you do bits[i] = true you exactly assign some value to an rvalue of class type. The proxy that's returned by operator[] can access the bits which are space efficiently packed into integers.

Johannes Schaub - litb
This together with FredOverflow's answer answers my question completely. Thanks!
Rimo