为什么在特殊成员函数中将右值绑定到常量左值引用是非法的?
Why is it illegal to bind an r-value to a const l-value reference in special member functions?
对于函数参数,可以将右值绑定到左值常量引用。
但是,这似乎不适用于 C++11 和 C++14 中的复制构造函数和复制赋值运算符等特殊成员函数。这样做有动机吗?
使用 C++17 时,可以从右值复制构造,但不能复制赋值。
为什么这里只更改了复制构造函数的行为?
所有这些都在以下示例中进行了演示:
struct B {
B() = default;
B(B const&) = default;
B(B&&) = delete;
B& operator=(B const&) = default;
B& operator=(B&&) = delete;
};
void bar(B const &) {}
int main() {
bar(B{}); // does work
B(B{}); // only works in C++17
B b{};
b = B{}; // doesn't work
}
B(B{});
从 C++17 开始工作,因为 mandatory copy elision,move-construction 被完全省略;临时对象由默认构造函数直接初始化。
(强调我的)
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
...
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
在 C++17 之前,这是一个优化,B(B{});
是 ill-formed。
This is an optimization: even when it takes place and the copy/move (since C++11)
constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
bar(B{});
之所以有效,是因为 bar
只有一个重叠,其中 lvalue-reference 到 const
,可以绑定右值;自 C++11 以来这没有改变。
b = B{};
不起作用,因为选择了重载的移动赋值运算符;即使它被显式标记为 delete
,它仍然参与重载决议[1]。对于 bar
,如果您添加一个重载,将 rvalue-reference 作为
void bar(B&&)=delete;
它会被选中并导致程序也ill-formed。
[1] 请注意,deleted implicitly declared move constructors 不是这样,它会被重载决议忽略。 (C++14 起)
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).
对于函数参数,可以将右值绑定到左值常量引用。 但是,这似乎不适用于 C++11 和 C++14 中的复制构造函数和复制赋值运算符等特殊成员函数。这样做有动机吗?
使用 C++17 时,可以从右值复制构造,但不能复制赋值。 为什么这里只更改了复制构造函数的行为?
所有这些都在以下示例中进行了演示:
struct B {
B() = default;
B(B const&) = default;
B(B&&) = delete;
B& operator=(B const&) = default;
B& operator=(B&&) = delete;
};
void bar(B const &) {}
int main() {
bar(B{}); // does work
B(B{}); // only works in C++17
B b{};
b = B{}; // doesn't work
}
B(B{});
从 C++17 开始工作,因为 mandatory copy elision,move-construction 被完全省略;临时对象由默认构造函数直接初始化。
(强调我的)
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
...
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Note: the rule above does not specify an optimization: C++17 core language specification of prvalues and temporaries is fundamentally different from that of the earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is "unmaterialized value passing": prvalues are returned and used without ever materializing a temporary.
在 C++17 之前,这是一个优化,B(B{});
是 ill-formed。
This is an optimization: even when it takes place and the copy
/move (since C++11)
constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
bar(B{});
之所以有效,是因为 bar
只有一个重叠,其中 lvalue-reference 到 const
,可以绑定右值;自 C++11 以来这没有改变。
b = B{};
不起作用,因为选择了重载的移动赋值运算符;即使它被显式标记为 delete
,它仍然参与重载决议[1]。对于 bar
,如果您添加一个重载,将 rvalue-reference 作为
void bar(B&&)=delete;
它会被选中并导致程序也ill-formed。
[1] 请注意,deleted implicitly declared move constructors 不是这样,它会被重载决议忽略。 (C++14 起)
The deleted implicitly-declared move constructor is ignored by overload resolution (otherwise it would prevent copy-initialization from rvalue).