PODs, non-PODs, rvalue and lvalues

Rvalues are what you get from expressions (a useful simplification taken from the C standard, but not worded in C++ standardese). Lvalues are "locator values". Lvalues can be used as rvalues. References are always lvalues, even if const.

The major difference of which you have to be aware can be condensed to one item: you can't take the address of an rvalue (again, not standardese but a useful generalization of the rules). Or to put it another way, you can't fix a precise location for an rvalue—if you could, then you'd have an lvalue. (You can, however, bind a const& to an rvalue to "fix it in place", and 0x is changing the rules drastically.)

User-defined types (UDTs), however, are slightly special: you can convert any rvalue into an lvalue, if the class's interface allows it:

struct Special {
  Special& get_lvalue() { return *this; }
};
void f() {
  // remember "Special()" is an rvalue
  Special* p = &Special().get_lvalue(); // even though you can't dereference the
  // pointer (because the object is destroyed), you still just took the address
  // of a temporary

  // note that the get_lvalue() method doesn't need to operate on a const
  // object (though that would be fine too, if the return type matched)
}

Something similar is happening for your A() = a, except through the compiler-supplied assignment operator, to turn the rvalue A() into *this. To quote the standard, 12.8/10:

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. The implicitly-declared copy assignment operator for a class X will have the form

X& X::operator=(const X&)

And then it goes on with more qualifications and specs, but that's the important bit here. Since that's a member function, it can be called on rvalues, just like Special::get_lvalue can be, as if you had written A().operator=(a) instead of A() = a.

The int() = 1 is explicitly forbidden as you discovered, because ints don't have operator= implemented in the same way. However, this slight discrepancy between types doesn't matter in practice (at least not that I've found).


POD means Plain Old Data and is a collection of requirements that specify using memcpy is equivalent to copying. Non-POD is any type for which you cannot use memcpy to copy (the natural opposite of POD, nothing hidden here), which tends to be most types you'll write in C++. Being POD or non-POD doesn't change any of the above, and is really a separate issue.


In my understanding both int() and A() should be rvalues, no?

Correct, the epxression T() is always an rvalue for scalar and user-defined types T. As long as no const is involved, the expression T() is a modifiable rvalue, to be more precise.

Assignment involving scalar types requires a modifiable lvalue on the left hand side of the assignment operator. Since int() isn't an lvalue, you can't assign to int().

For user-defined types, assignment is a special member function, and member functions can also be called on rvalues (see §3.10 section 10). That's why A().operator=(a) is well formed.

Tags:

C++

Lvalue

Rvalue