83

Consider this code:

struct foo
{
  int a;
};

foo q() { foo f; f.a =4; return f;}

int main()
{
  foo i;
  i.a = 5;
  q() = i;
}

No compiler complains about it, even Clang. Why q() = ... line is correct?

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
John
  • 2,295
  • 1
  • 20
  • 25
  • 5
    What do you think is wrong with this code? `q()` returns a struct and then you assign a value to it. Whats wrong with that? – Andy Johnson May 24 '11 at 14:32
  • 3
    @Andy: I think it's very error-prone as assigning value to return value usually doesn't do anything (except when operator= is doing some magic which is probably bad design practice). I have expected this to be a warning, like not using local variable. – John May 24 '11 at 14:36
  • 1
    @Andy Johnson: The widespread common misconception is that people assume that everything that you can use on the left-hand side of assignment must be an lvalue. In this case this requirement appears to be violated. But in reality there's no such requirement. Built-in assignment does indeed require and lvalue on its LHS, but overloaded assignment doesn't. In this case we are dealing with the overloaded one, even though it is not obvious. – AnT stands with Russia May 24 '11 at 14:46

4 Answers4

77

No, the return value of a function is an l-value if and only if it is a reference (C++03). (5.2.2 [expr.call] / 10)

If the type returned were a basic type then this would be a compile error. (5.17 [expr.ass] / 1)

The reason that this works is that you are allowed to call member functions (even non-const member functions) on r-values of class type and the assignment of foo is an implementation defined member function: foo& foo::operator=(const foo&). The restrictions for operators in clause 5 only apply to built-in operators, (5 [expr] / 3), if overload resolution selects an overloaded function call for an operator then the restrictions for that function call apply instead.

This is why it is sometimes recommended to return objects of class type as const objects (e.g. const foo q();), however this can have a negative impact in C++0x where it can inhibit move semantics from working as they should.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
  • 19
    For a concrete example, consider `std::cout << "Hello, world" << std::endl`. The first `operator<< ()` returns an lvalue on which you can call the second `operator<< ()`. So this practice of calling methods on return values is more common than many people would think. – MSalters May 24 '11 at 14:34
  • 1
    And my world gets a little bigger... Nice answer. – wheaties May 24 '11 at 14:36
  • 4
    This reminds me of a quote from `The Design and Evolution of C++`: _This is the origin of the notion that over the years grew into a rule of thumb for the design of C++: User-defined and built-in types should behave the same relative to the language rules and receive the same degree of support from the language and its associated tools. When the ideal was formulated built-in types received by far the best support, but C++ has overshot that target so that built-in types now receive slightly inferior support compared to user-defined types._ – Seth Carnegie May 24 '11 at 14:36
  • 11
    @MSalters yeah, but in the case of `cout` it _actually does_ return an lvalue (it returns itself and by reference, `*this`), not something by value. So it's a little different. – Seth Carnegie May 24 '11 at 14:38
  • 5
    @MSalters: ostream::operator<<, like operator= both return reference, which is L-value. – John May 24 '11 at 14:38
  • 4
    @MSalters In `std::cout << "duh" << std::endl`, the `operator<<` all return non-const references (which are lvalues), and because the operators all require non-const references as their first parameter, the original `std::ostream` object cannot be an rvalue. In the other examples (and I presume this is the motivation of the question), non-const functions are being called on a temporary. – James Kanze May 24 '11 at 15:36
  • @all: I intentionally wrote "returns an lvalue", because it's indeed a reference. If it didn't return a reference, I wouldn't have written that it returned an lvalue. My actual point was that even this innocent line works because you can call methods including operators on function call expressions, so `q().operator=(i)` isn't really that exceptional. – MSalters May 26 '11 at 09:29
  • thanks for pointing out the difference of "=" in builtin types and user defined types! – Baiyan Huang May 28 '11 at 02:44
8

Because structs can be assigned to, and your q() returns a copy of struct foo so its assigning the returned struct to the value provided.

This doesn't really do anything in this case thought because the struct falls out of scope afterwards and you don't keep a reference to it in the first place so you couldn't do anything with it anyway (in this specific code).

This makes more sense (though still not really a "best practice")

struct foo
{
  int a;
};

foo* q() { foo *f = new malloc(sizeof(foo)); f->a = 4; return f; }

int main()
{
  foo i;
  i.a = 5;

  //sets the contents of the newly created foo
  //to the contents of your i variable
  (*(q())) = i;
}
Chad
  • 19,219
  • 4
  • 50
  • 73
  • 6
    What's the difference between that and this: `int blah() { int f = 4; return f; } int main() { int a = 99; blah() = a; }` is it something magical about structs? Because that code doesn't compile. – Seth Carnegie May 24 '11 at 14:26
  • @Seth See charles Bailey's answer – Chad May 24 '11 at 14:30
  • 2
    Int's don't have member functions, so they don't have `int::operator=(int)` either. That's the "magical" bit about structs: they have default methods. – MSalters May 24 '11 at 14:32
  • @Seth, because in your example the function returns a int, not an object, so you cannot assign nothing to it. If the function returned a reference to an int ( int& ) you could assign to it. – bruno May 24 '11 at 14:32
  • 1
    @bruno: `s/nothing/anything/` – Lightness Races in Orbit May 24 '11 at 16:59
7

One interesting application of this:

void f(const std::string& x);
std::string g() { return "<tag>"; }

...

f(g() += "</tag>");

Here, g() += modifies the temporary, which may be faster that creating an additional temporary with + because the heap allocated for g()'s return value may already have enough spare capacity to accommodate </tag>.

See it run at ideone.com with GCC / C++11.

Now, which computing novice said something about optimisations and evil...? ;-].

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
0

On top of other good answers, I'd like to point out that std::tie works on top of this mechanism for unpacking data from another function. See here. So it's not error-prone per se, just keep in mind that it could be a useful design pattern

Ruotong Jia
  • 31
  • 1
  • 3