默认构造函数表达式和左值
Default constructor expression and lvalues
我和我的 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++ 标准中找到适当的参考,我将不胜感激。
A{}
始终是每个 [expr.type.conv]
的右值
1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer.
If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression.
Otherwise, if the type is cv void
and the initializer is ()
or {}
(after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization.
Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.
If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
强调我的
这些工作在这里的原因是标准中没有任何东西可以阻止它工作。
对于像 int
这样的内置类型,有 [expr.ass]/1
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.
所以这会阻止你做 int{} = 42;
。不过,本节不适用于 classes。如果我们查看 [class.copy.assign] 没有任何内容表明需要左值,但第一段确实说明了
A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X&, or const volatile X&
这意味着
A{} = A{2};
实际上是
A{}.operator=(A{2})
对右值 class 对象执行此操作是合法的,因为 class 的默认 operator =
没有 ref-qualifier阻止它在右值上被调用。如果您添加
A& operator=(const A& a) & { i = a.i; }
到 A
而不是使用默认的赋值运算符 then
A{} = A{2};
将不再编译,因为 operator=
现在仅适用于左值。
我和我的 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 发现这是
A{}
始终是每个 [expr.type.conv]
1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer. If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cvvoid
and the initializer is()
or{}
(after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
强调我的
这些工作在这里的原因是标准中没有任何东西可以阻止它工作。
对于像 int
这样的内置类型,有 [expr.ass]/1
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.
所以这会阻止你做 int{} = 42;
。不过,本节不适用于 classes。如果我们查看 [class.copy.assign] 没有任何内容表明需要左值,但第一段确实说明了
A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X&, or const volatile X&
这意味着
A{} = A{2};
实际上是
A{}.operator=(A{2})
对右值 class 对象执行此操作是合法的,因为 class 的默认 operator =
没有 ref-qualifier阻止它在右值上被调用。如果您添加
A& operator=(const A& a) & { i = a.i; }
到 A
而不是使用默认的赋值运算符 then
A{} = A{2};
将不再编译,因为 operator=
现在仅适用于左值。