31

A function call returning a structure is an rvalue expression, but what about its members?
This piece of code works well with my g++ compiler, but gcc gives a error saying "lvalue required as left operand of assignment":

struct A
{
    int v;
};

struct A fun()
{
    struct A tmp;
    return tmp;
}

int main()
{
    fun().v = 1;
}

gcc treats fun().v as rvalue, and I can understand that.
But g++ doesn't think the assignment expression is wrong. Does that mean fun1().v is lvalue in C++?
Now the problem is, I searched the C++98/03 standard, finding nothing telling about whether fun().v is lvalue or rvalue.
So, what is it?

GManNickG
  • 494,350
  • 52
  • 494
  • 543
hpsMouse
  • 2,004
  • 15
  • 20
  • 1
    I hope you don't mind, but I made it so it's easy to copy-paste try. – GManNickG Feb 08 '10 at 08:12
  • Sidenote: In C++, you don't write struct each time, only where the struct is defined. So, instead of _struct A fun()_ do _A fun()_, instead of _struct A tmp;_ do _A tmp;_ – Sebastian Mach Feb 08 '10 at 09:28
  • @phresnel: I was inclined to comment the same, but remember he's testing this in C as well. – GManNickG Feb 08 '10 at 09:32
  • @phresnel: Just as a sidenote, writting the 'struct' keyword is optional in C++ most of the time, but it can be required in some particular cases and it has a slightly different meaning (the name lookup will be different with 'struct' being present or not). Not that in this small test it will have any effect, but 'in c++ you don't write struct each time, unless you need it' – David Rodríguez - dribeas Feb 08 '10 at 10:12
  • @*: IMO, the question really should be titled "Are members of a temporary rvalues themselves?" Comments? Good question, btw. – dirkgently Feb 08 '10 at 10:48
  • @GMan: I remember a mentioning of C somewhere, but fail to re-find that. Now all I see is g++, c++, and C++98/03 :S – Sebastian Mach Feb 08 '10 at 11:09
  • @David Rodríguez - dribeas: I am not sure, I never heard of that the presence of struct would change name-lookup; the "name" of a struct/class/union is optional, and if omitted those anonymous object's members would participate to name lookup of the enclosing scope, but the "struct-keyword-dependent-name-lookup" would be new to me, and I can't find something in the standard. – Sebastian Mach Feb 08 '10 at 11:19
  • @David Rodríguez - dribeas: and: Where would it be needed? I fail to find an example for C++. – Sebastian Mach Feb 08 '10 at 11:22
  • The classical examplem is if you have both a function and a struct named `Foo`. In that case, `Foo` refers to the function, and `struct Foo` to the type. – MSalters Feb 08 '10 at 11:58
  • @phresnel: `struct f {}; void f(); void g() { f(); struct f a; }`. You can read the long explanation here: http://stackoverflow.com/questions/1675351/typedef-struct-vs-struct-definitions – David Rodríguez - dribeas Feb 08 '10 at 12:03
  • @David Rodríguez - dribeas: Thanks, now I think I remember that being mentioned in Josuttis/Vandevoorde :) – Sebastian Mach Feb 08 '10 at 12:49

6 Answers6

18

A member of an rvalue expression is an rvalue.

The standard states in 5.3.5 [expr.ref]:

If E2 is declared to have type “reference to T”, then E1.E2 is an lvalue [...] - If E2 is a non-static data member, and the type of E1 is “cq1 vq1 X”, and the type of E2 is “cq2 vq2 T”, the expression designates the named member of the object designated by the first expression. If E1 is an lvalue, then E1.E2 is an lvalue.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • +1 This is the one precise answer. Expression E1 is `fun()` in the original example, E2 is `v`, without cv qualification. `v` is also not static and not a reference. – MSalters Feb 08 '10 at 14:15
  • I would not have found it if it were not because of you insisting on greater precision on the first answer (that I am deleting now). – David Rodríguez - dribeas Feb 08 '10 at 14:45
  • 4
    The section is 5.2.5. To be anal, that paragraph doesn't specify anything in case E1 is not an lvalue. C++0x, however, appends "otherwise, it is an rvalue". – Potatoswatter Feb 08 '10 at 21:08
  • I have read this part of the standard, too. It doesn't tell anything about what happens when E1 is not an lvalue. That's why I'm so confused... And if C++0x says it's an rvalue, it answers the question. Maybe it's just a little bug for the current standard? :) – hpsMouse Feb 09 '10 at 06:40
  • 2
    It has been amended in the upcoming standard as of DR421, so that there is an extra 'otherwise, it is an rvalue', as Potatoswatter points out. According to the standard committee, while the wording is unfortunate the intent was there: http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/cwg_defects.html#421 – David Rodríguez - dribeas Feb 09 '10 at 08:07
  • 1
    Current C++17 draft is a little bit more explicit: `If E1 is an lvalue, then E1.E2 is an lvalue; otherwise E1.E2 is an xvalue` – Arnaud Nov 20 '16 at 16:34
  • Hey, just for clarification, does that mean that with a member `X && x` or `X x`, in some `decltype(auto) f(void) && {...}`, `return std::move(*this).x` and `return std::forward(x)` are equivalent and return the same `X &&`? I remember someone recommending the first approach, but if anything, it looks to me like going out of your way to make an rvalue out of an lvalue out of an rvalue. If there's no difference I'd rather forward the rvalue I have. – John P Aug 07 '22 at 00:22
3

This is a good time to learn about what xvalues an glvalues are.

Rvalues can be of two types - prvalues and xvalues. According to the new C++17 standard

A prvalue is an expression whose evaluation initializes an object, bit-field, or operand of an operator, as specified by the context in which it appears.

so something like fun() in your example evaluates to an prvalue (which is an rvalue). This also tells us that fun().v is not a prvalue, since it is not a vanilla initialization.

Xvalues which are also rvalues are defined like so

An xvalue (an "eXpiring" value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). Certain kinds of expressions involving rvalue references (8.3.2) yield xvalues. [ Example: The result of calling a function whose return type is an rvalue reference to an object type is an xvalue (5.2.2). - end example ]

In addition to rvalues, another umbrella value category is a glvalue which be of two types xvalues and the traditional lvalues.

We have at this point defined the essential value categories. This can be visualized like so

enter image description here

The category glvalue can broadly be thought to mean what lvalues were supposed to mean before move semantics became a thing - a thing that can be on the left hand side of an expression. glvalue means generalized lvalue.

If we look at the definition of an xvalue, then it says something is an xvalue if it is near the end of its lifetime. In your example, fun().v is near the end of its lifetime. So its resources can be moved. And since its resources can be moved it is not an lvalue, therefore your expression fits in the only leaf value category that remains - an xvalue.

Curious
  • 20,870
  • 8
  • 61
  • 146
2

Edit: Ok, I guess I finally have something from the standard:

Note that v is of type int which has an built-in assignment operator:

13.3.1.2 Operators in expressions

4 For the built-in assignment operators, conversions of the left operand are restricted as follows: — no temporaries are introduced to hold the left operand, and [...]

fun1() should return a reference. A non-reference/pointer return type of a function is a r-value.

3.10 Lvalues and rvalues

5 The result of calling a function that does not return an lvalue reference is an rvalue [...]

Thusly, fun1().v is a rvalue.

8.3.2 References

2 A reference type that is declared using & is called an lvalue reference, and a reference type that is declared using && is called an rvalue reference. Lvalue references and rvalue references are distinct types.

dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • Huh? He should return a reference to a temporary? – GManNickG Feb 08 '10 at 08:16
  • If it's an lvalue, why can you assign to it? And rvalue references are not yet part of official standard C++. – Omnifarious Feb 08 '10 at 08:20
  • There's no doubt that `fun1()` is an rvalue, per 3.10, but please read the question. The expression `fun1().v` is **not** the result of calling a function. It is parsed as `(fun1()) . x` - member access. – MSalters Feb 08 '10 at 09:39
  • @MSalters: Edited post with what I think helps. – dirkgently Feb 08 '10 at 10:44
  • @MSalters: Is there a reason to believe that given an rvalue object of aggregate type, it's members can be lvalues? – dirkgently Feb 08 '10 at 10:52
  • The member access expression `(ObjectExpr).member` obviously is either an rvalue or an lvalue, and intuitively it makes sense that it would be an rvalue iff `(ObjectExpr)` is an rvalue. But where's that rule in the standard? Intuition is not good enough. Note that I'n agreeing with you that `fun1()` is an rvalue expression. – MSalters Feb 08 '10 at 11:39
  • I have added a point in the standard where it states that the result of data member pointer dereferencing will only be a lvalue if the object is an lvalue. This is not exactly the same, but the behavior should be the same. – David Rodríguez - dribeas Feb 08 '10 at 12:07
  • @dribeas: In your answer? Sounds close enough. C&V please, can't find it :( Also, FWIW, note there never is a rvalue-to-lvalue conversion (though lvalue-to-rvalue conversion is legal). – dirkgently Feb 08 '10 at 12:17
  • 5.3.5 [expr.ref], see post below. – MSalters Feb 08 '10 at 14:19
0

I've noticed that gcc tends to have very few compunctions about using rvalues as lvalues in assignment expressions. This, for example, compiles just fine:

class A {
};

extern A f();

void g()
{
   A myA;
   f() = myA;
}

Why that's legal and this isn't (i.e. it doesn't compile) though really confuses me:

extern int f();

void g()
{
   f() = 5;
}

IMHO, the standard committee has some explaining to do with regards to lvalues, rvalues and where they can be used. It's one of the reasons I'm so interested in this question about rvalues.

Community
  • 1
  • 1
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • This is really interesting... I would say that the compiler is wrong, but I understand how they got there. The second block is explicitly prohibited by the standard: you cannot modify an rvalue, and in 5.17/1 assignment operator requires an lvalue as first argument. Now the standard does allow you to call non-constant methods on rvalues, and I can only assume that after generating the assignment operator, the compiler is translating the first block into: `f().operator=(myA)` and considering the `operator=` as a regular method, regardless of _All [=] require a modifiable lvalue_ in 5.17/1 – David Rodríguez - dribeas Feb 08 '10 at 08:35
  • @David: Do you think that if someone explicitly did `f().operator=(myA)` that it should be considered legal? – Omnifarious Feb 08 '10 at 08:45
  • No, but I can see someone assuming that since `operator=` is a member function (even if it performs modifications) it should be allowed to call it on an lvalue. `class X { void m(); }; X f(); void h() { f().m() }` is a valid piece of code. The problem to me is that even if that is true, allowing `operator=` to be called on an lvalue would prove inconsistent with primitive types (where the standard is clear), so if I was to make the call, I would not allow it. After all, even if `operator=` is a member function, it is _special_ – David Rodríguez - dribeas Feb 08 '10 at 09:06
  • @Omnifarious - I think your code shows why it's in general advisable to return "const A", as that way there is no discrepancy between primitive types and user-defined types. This is usually recommended in the context of operator overloading (e.g., so that operator+(T, T) returns const T) but I think it applies to any function returning a user-defined type. – Manuel Feb 08 '10 at 09:35
  • @Manuael: I used to agree with you but with rvalue references in C++0x, where does that leave things? I want return values to be able to be used as non-const rvalue references. – Omnifarious Feb 08 '10 at 16:40
  • Actually I don't see any problem with this example. For class type, the assignment is performed by the member operator = . If there's no user-defined operator = , a default one is generated. Since a member function call doesn't need a lvalue, it's always OK to do assignment on class type unless explicitly forbidden with a private operator = . And int is a built-in type, so there is no member operator. – hpsMouse Feb 09 '10 at 07:07
  • @DavidRodríguez-dribeas "_The problem to me is that even if that is true, allowing operator= to be called on an lvalue would prove inconsistent with primitive types_" What about +=? Same reasoning? – curiousguy Oct 10 '11 at 23:52
0

It becomes obvious when you consider that the compiler will generate a default constructor, a default copy constructor, and a default copy assignment operator for you, in case your struct/class does not contain reference members. Then, think of that the standard allows you to call member methods on temporaries, that is, you can call non-const members on non-const temporaries.

See this example:

struct Foo {};
Foo foo () {
    return Foo();
}

struct Bar {
private:
    Bar& operator = (Bar const &); // forbid
};
Bar bar () {
    return Bar();
}
int main () {
    foo() = Foo(); // okay, called operator=() on non-const temporarie
    bar() = Bar(); // error, Bar::operator= is private
}

If you write

struct Foo {};
const Foo foo () { // return a const value
    return Foo();
}

int main () {
    foo() = Foo(); // error
}

i.e. if you let function foo() return a const temporary, then a compile error occurs.

To make the example complete, here is how to call a member of a const temporarie:

struct Foo {
    int bar () const { return 0xFEED; }
    int frob ()      { return 0xFEED; }
};
const Foo foo () {
    return Foo();
}

int main () {
    foo().bar(); // okay, called const member method
    foo().frob(); // error, called non-const member of const temporary
}

You could define the lifetime of a temporary to be within the current expression. And then that's why you can also modify member variables; if you couldn't, than the possibility of being able to call non-const member methods would be led ad absurdum.

edit: And here are the required citations:

12.2 Temporary objects:

  • 3) [...] Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created. [...]

and then (or better, before)

3.10 Lvalues and rvalues:

  • 10) An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances. [Example: a member function called for an object (9.3) can modify the object. ]

And an example use: http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Named_Parameter

Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
  • I think you might be having the same confusion I had. See my comment to this answer: http://stackoverflow.com/questions/2220230/is-a-member-of-an-rvalue-structure-an-rvalue-or-lvalue/2220281#2220281 – Manuel Feb 08 '10 at 12:01
  • @Manuel: Tbh, I am confused about why you think that I am confused!? – Sebastian Mach Feb 08 '10 at 13:13
  • @phreshel - Read the comment I linked to and the response by dribeas. Your reasoning is correct but does not apply here because v is of a primitive type. – Manuel Feb 08 '10 at 13:24
  • When I think about this it becomes interesting. A member function call to an rvalue object is allowed, and to call a member function, the object must be binded to "this" pointer, which changes the object into an lvalue during the runtime of the function call. That means an rvalue object can sometimes become an lvalue. But member variable accessing is a little different, and is not mentioned by the standard. And I hate that "under certain circumstances", by the way. :-) – hpsMouse Feb 09 '10 at 06:58
-2

You code has no scene. Returned structure is allocated on stack, so assignment result is immediately will be lost.

Your function should eiter allocate new instance of A by:

new A()

In this case better signature

A* f(){ ...

Or return existing instance, for example:

static A globalInstance;
A& f(){ 
  return globalInstance;
}
Dewfy
  • 23,277
  • 13
  • 73
  • 121
  • 2
    "has no scene"? What does that mean? – nobody Feb 08 '10 at 08:22
  • Your comment does not make sense. – Sebastian Mach Feb 08 '10 at 09:29
  • @Andrew Medico - when function returns stack allocated value then value should be stored somewhere. Otherwise your assignment would be lost. That is why g++ treats this case as sense-less. In my post I've explained why. – Dewfy Feb 08 '10 at 09:47
  • @Dewfy: g++ does not treat this as "sense-less", see my above post about why this behaviour is allowed in C++. Also, we did not know what you mean by "scene" (vs. sense). Further, just because the result of something is unused would not justify validness. Furthermore, in C++, you can very well re-use a statement like _Foo().x=5_ , like in e.g. _(Foo().x=5).member()_ – Sebastian Mach Feb 08 '10 at 10:22
  • The code is obviously simplified, but the fundamental question is still there. If the member would be `volatile`, the assignment is observable and g++ _must_ do the assignment - except for the whole rvalue/lvalue discussion. – MSalters Feb 08 '10 at 10:26
  • @phresnel (Foo().x=5).member() supposes only overridden 'operator =' which returns not int, but reference to 'struct A' in this case g++ wouldn't produce warning. – Dewfy Feb 08 '10 at 10:33
  • You'll laugh, but "(Foo().x = 5).member()" is perfectly valid code, for which g++ will not and should not emit a diagnostic in "struct Foo { Foo & operator = (int) { return *this; } Foo &x; Foo (); static Foo foo; void member() {} }; Foo Foo::foo; Foo::Foo() : x(foo) {} int main () { (Foo().x = 5).member(); } " – Sebastian Mach Feb 08 '10 at 11:07
  • @Dewfy: sorry for the formatting, you'll see better if you paste the code within "". – Sebastian Mach Feb 08 '10 at 11:07
  • @phresnel I've told exactly about what you have just write. You have to declare Foo& operator = (int) to create sense of this assignment - that is why no warnings at compile time. On other hand it is obviously why warning appears for "int x" – Dewfy Feb 08 '10 at 13:15
  • @phresnel: By the way, use ticks: \`code goes here\` to produce `code goes here`. I used backslash to escape the former ticks – GManNickG Feb 08 '10 at 19:38