可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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.
回答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.
回答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
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.
回答4:
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.
回答5:
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
回答6:
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;
}