返回具有已删除 move/copy 构造函数的临时类型

Returning temporaries of type with deleted move/copy ctor

考虑以下程序:

#include<iostream>
using namespace std;

struct S
{
    S() = default;
    S(const S& other) = delete;
    S(S&& other) = delete;
    int i;
};

S nakedBrace()
{
     return {}; // no S constructed here?
}

S typedBrace()
{
     return S{};
}

int main()
{
    // produce an observable effect.
    cout << nakedBrace().i << endl; // ok
    cout << typedBrace().i << endl; // error: deleted move ctor
}

示例会话:

$ g++ -Wall -std=c++14 -o no-copy-ctor no-copy-ctor.cpp
no-copy-ctor.cpp: In function 'S typedBrace()':
no-copy-ctor.cpp:19:12: error: use of deleted function 'S::S(S&&)'
   return S{};
            ^
no-copy-ctor.cpp:8:5: note: declared here
     S(S&& other) = delete;

令我惊讶的是 gcc 接受 nakedBrace()。我认为这两个函数在概念上是等价的:构造一个临时 S 并 returned。可能会或可能不会执行复制省略,但移动或复制 ctor(均在此处删除)必须仍可访问,如标准 (12.8/32) 所规定的那样。

这是否意味着 nakedBrace() 永远不会构造 S?或者它确实如此,但直接在带有大括号初始化的 return 值中,因此概念上不需要副本 move/ctor ?

这是标准行为。

N4140 [stmt.return]/2: [...] A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list. [...]

这意味着 nakedBracetypedBrace 执行的初始化等同于这些:

S nakedBrace = {}; //calls default constructor
S typedBrace = S{}; //calls default, then copy constructor (likely elided)

[stmt.return]/2 ...A return statement with an expression of non-void type can be used only in functions returning a value; the value of the expression is returned to the caller of the function. The value of the expression is implicitly converted to the return type of the function in which it appears. A return statement can involve the construction and copy or move of a temporary object (12.2)... A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.

[class.temporary]/1 Temporaries of class type are created in various contexts: ... returning a prvalue (6.6.3) ...

是的,据我所知,存在语义差异。 typedBrace 计算表达式 S{},它产生类型 S 的纯右值,然后尝试从该表达式 copy-construct 其 return 值。 nakedBrace 而是直接从 braced-init-list.

构造其 return 值

这与 S s{};(有效)与 S s = S{};(无效)的情况相同,只是在一定程度上被间接遮蔽了。