右值结构的成员是右值还是左值?

发布于 2024-08-20 19:11:12 字数 413 浏览 10 评论 0原文

返回结构的函数调用是右值表达式,但是它的成员呢?
这段代码与我的 g++ 编译器配合得很好,但是 gcc 给出了一个错误,指出“需要左值作为赋值的左操作数”:

struct A
{
    int v;
};

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

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

gcc 将 fun().v 视为右值,我可以理解这一点。
但g++并不认为赋值表达式是错误的。这是否意味着 fun1().v 是 C++ 中的左值?
现在的问题是,我搜索了 C++98/03 标准,没有发现任何关于 fun().v 是左值还是右值的信息。
那么,它是什么?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

迷雾森÷林ヴ 2024-08-27 19:11:12

右值表达式的成员是右值。

该标准在 5.3.5 [expr.ref] 中规定:

如果 E2 被声明为具有类型
“参考 T”,则 E1.E2 是
左值 [...]
- 如果E2是非静态数据成员,并且E1的类型是“cq1 vq1 X”,并且
E2的类型是“cq2 vq2 T”,
表达式指定指定的成员
第一个指定的对象的
表达。如果 E1 是左值,则
E1.E2 是左值。

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.

独夜无伴 2024-08-27 19:11:12

现在是了解什么是xvaluesglvalues 的好时机。

右值可以有两种类型 - 右值x值。根据新的C++17标准

纯右值是一个表达式,其计算会初始化一个对象、位域或运算符的操作数,具体由它出现的上下文指定。

因此,您的示例中类似 fun() 的计算结果为纯右值(即右值)。这也告诉我们 fun().v 不是纯右值,因为它不是普通的初始化。

Xvalues 也是右值,定义如下

xvalue(“eXpiring”值)也指对象,通常接近其生命周期结束(例如,以便可以移动其资源)。涉及右值引用 (8.3.2) 的某些类型的表达式会产生 xvalues。 [ 示例:调用返回类型是对对象类型的右值引用的函数的结果是 xvalue (5.2.2)。 - 结束示例]

除了右值之外,另一个伞值类别是glvalue,它有两种类型xvalues 和传统的lvalues

至此,我们已经定义了基本价值类别。这可以像这样可视化

在此处输入图像描述

类别 glvalue 可以广泛地被认为意味着在移动语义成为事物之前 lvalues 应该意味着什么 -可以位于表达式左侧的事物。 glvalue 表示广义左值。

如果我们查看xvalue的定义,那么如果某个东西接近其生命周期的终点,那么它就是一个xvalue。在您的示例中,fun().v 即将结束其生命周期。所以它的资源是可以移动的。而且由于它的资源可以移动,所以它不是左值,因此您的表达式适合剩下的唯一叶值类别 - xvalue

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.

时光是把杀猪刀 2024-08-27 19:11:12

编辑:好的,我想我终于从标准中得到了一些东西:

请注意, vint 类型,它有一个内置的赋值运算符:

13.3.1.2 表达式中的运算符

4 对于内置赋值运算符,左操作数的转换受到如下限制:
— 没有引入临时变量来保存左操作数,并且 [...]

fun1() 应返回一个引用。函数的非引用/指针返回类型是右值。

3.10 左值和右值

5 调用不返回左值引用的函数的结果是右值[...]

因此,fun1().v 是右值。

8.3.2 参考文献

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.

錯遇了你 2024-08-27 19:11:12

我注意到 gcc 对于在赋值表达式中使用右值作为左值几乎没有什么顾虑。例如,编译得很好:

class A {
};

extern A f();

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

为什么这是合法的,而这是不合法的(即它不能编译),尽管这确实让我困惑:

extern int f();

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

恕我直言,标准委员会对左值、右值以及它们可以在哪里进行一些解释被使用。这是我对这个关于右值的问题如此感兴趣的原因之一< /a>.

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.

樱花细雨 2024-08-27 19:11:12

当您考虑到编译器将为您生成默认构造函数、默认复制构造函数和默认复制赋值运算符(以防您的结构/类不包含引用成员)时,这一点就变得显而易见了。然后,想想标准允许您在临时对象上调用成员方法,也就是说,您可以在非常量临时对象上调用非常量成员。

看这个例子:

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
}

如果你

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

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

这样写,即如果你让函数 foo() 返回一个 const 临时变量,则会发生编译错误。

为了使示例完整,以下是如何调用 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
}

您可以将临时变量的生存期定义在当前表达式内。这就是为什么你也可以修改成员变量;如果你不能,那么调用非常量成员方法的可能性就会变得荒谬。

编辑:这里是所需的引用:

12.2 临时对象:

  • 3) [...] 临时对象在评估完整表达式 (1.9)(词汇上)包含它们被创建的点。 [...]

然后(或者更好,之前)

3.10 左值和右值:

  • 10) 为了修改对象,对象的左值是必要的,但类类型的右值也可以是用于在某些情况下修改其所指对象。 [示例:为对象调用的成员函数(9.3)可以修改该对象。 ]

和一个示例使用: http://en.wikibooks.org/wiki/更多_C%2B%2B_Idioms/Named_Parameter

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

青衫负雪 2024-08-27 19:11:12

你的代码没有场景。返回的结构在堆栈上分配,因此赋值结果将立即丢失。

您的函数应该通过以下方式分配 A 的新实例:

new A()

在这种情况下更好的签名

A* f(){ ...

或返回现有实例,例如:

static A globalInstance;
A& f(){ 
  return globalInstance;
}

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文