为什么在特殊成员函数中将右值绑定到常量左值引用是非法的?

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).