Question

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.)

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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

class T
{
public:
    T& operator=(const T&) & = default;
};

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top