6

我和我的 C++ 同事遇到了一个奇怪的结构:

struct A { int i; };
void foo(A const& a);

int main() {
  foo(A() = A{2}); // Legal
}

A() = A{2}表达式完全让我们感到困惑,因为它似乎正在分配A{2}给一个临时的、默认构造的对象。但是在编译器资源管理器(https://gcc.godbolt.org/z/2LsfSk)中看到它。它似乎是一个法律声明(由 GCC 9 和 Clang 9 支持),如下声明:

struct A { int i; };

int main() {
  A() = A{2};
  auto a = A() = A{3};
}

因此,在某些情况下,它似乎A()是一个左值。还是这里发生了其他事情?希望得到一些解释,最好是对 C++17 标准的引用。


更新:@Brian 发现这是分配给右值的副本:为什么会编译?. 但如果有人能在 C++ 标准中找到适当的参考,我将不胜感激。

4

1 回答 1

7

A{}每个[expr.type.conv]始终是一个右值

1 一个简单类型说明符或类型名称说明 后跟一个带括号的可选表达式列表一个花括号初始化列表(初始化器)构造一个给定初始化器的指定类型的值。如果该类型是推导的类类型的占位符,则将其替换为由重载决议选择的函数的返回类型,用于本小节其余部分的类模板推导。
2如果初始值设定项是带括号的单个表达式,则类型转换表达式等价于对应的强制转换表达式。否则,如果类型是cv void并且初始化器是()or {}(在包扩展之后,如果有的话),表达式是指定类型的纯右值,不执行初始化。 否则,表达式是指定类型的纯右值,其结果对象使用初始化程序直接初始化。如果初始值设定项是带括号的可选表达式列表,则指定类型不应是数组类型。

强调我的

这些工作在这里的原因并不是标准中阻止它工作的原因。

对于像[expr.ass]/1int这样的内置类型

赋值运算符 (=) 和复合赋值运算符都从右到左分组。都需要一个可修改的左值作为左操作数;它们的结果是一个引用左操作数的左值。

所以这会阻止你这样做int{} = 42;。不过,本节不适用于课程。如果我们查看[class.copy.assign]并没有说需要左值,但第一段确实说明了

用户声明的复制赋值运算符 X​::​operator= 是类 X 的非静态非模板成员函数,只有一个 X、X&、const X&、volatile X& 或 const volatile X& 类型的参数

意思是

A{} = A{2};

实际上是

A{}.operator=(A{2})

在右值类对象上这样做是合法的,因为operator =您的类的默认值没有引用限定符来阻止它在右值上被调用。如果你添加

A& operator=(const A& a) & { i = a.i; }

toA而不是使用默认的赋值运算符 then

A{} = A{2};

将不再编译,因为operator=现在只适用于左值。

于 2019-12-20T20:09:12.100 回答