返回不可复制常量值的函数的不直观 RVO?

eiv*_*our 11 c++ return-value-optimization copy-elision c++17

考虑以下 C++ >=17 中的示例代码:

struct A{
    A() = default;
    A(const A&) = delete;
};

const A f(){ return A{}; }

int main(){
    const A& a = f(); // OK
    // A& b = f();    // Error: cannot convert 'const A' to 'A&'
    const A c = f();  // OK: Copy elision
    A d = f();        // OK!?
}
Run Code Online (Sandbox Code Playgroud)

该类A是不可复制的,但由于强制复制省略,我们可以将 的结果f()放入变量中。根据cppreference.com 中的这个页面,上述行为是完全合法的,因为它指定const在发生复制省略时忽略返回值上的量词。

然而,这种行为对我来说似乎非常违反直觉。由于A是不可复制的,我觉得应该没有办法const A变成A(除非你有A::A(const A&&)构造函数)。这是一个深思熟虑的决定,还是被认为是语言规范中的缺陷?

(我在尝试实现我自己的类型擦除类时遇到了这个问题。我const在函数的返回值上指定的全部目的f()是为了防止用户获得const对象的非左值引用,但这个规范似乎开个洞。)

编辑:这个例子可能更清楚地展示了反直觉:让我们考虑一个A可移动但不可复制的类。

struct A{
    A() = default;
    A(const A&) = delete;
    A(A&&) = default;
};

int main(){
    // C++14: move / C++17: copy elision
    A a = A{}; 

    // C++14: error (deleted copy constructor) / C++17: copy elision(!!)
    A b = static_cast<const A>(A{}); 
}
Run Code Online (Sandbox Code Playgroud)

这不会在 C++ <=14 中编译,但在 C++ >=17 中编译。在类可移动但不可复制的常见情况下,const这意味着在 C++14 之前无法从中获取非常量对象,但现在不再如此(只要将const其添加到纯右值中) .

Dav*_*ing 2

拒绝使用可移动对象进行此类初始化将是一个重大更改,因为该语言的早期版本会在那里产生移动。让它依赖于变量的cv限定将是非常微妙的。

\n

对于可复制对象,新行为实际上是旧行为的子集const A:从返回值到变量的复制A可能已被省略,在这种情况下,它们与 C++17 中的对象一样。

\n

与此同时,自 C++11 以来,const 返回值就有些不受欢迎了,因为 C++11失去了移入(按值)参数的f(return_const())能力。

\n

C++17 对纯右值的处理(\xe2\x80\x9cm 强制复制省略\xe2\x80\x9d 是一个仅在历史上有意义的名称)还支持其他情况,例如本例中返回不可移动对象:函数被认为指定如何初始化它 \xe2\x80\x9creturns\xe2\x80\x9d 的对象,而不是实际返回完成的对象。在选择这种模型时,通常认为支持更多类型的高效代码比支持现有惯用法更重要,以防止误用。

\n