返回带有已删除移动/复制 ctor 的类型临时

Returning temporaries of type with deleted move/copy ctor

本文关键字:ctor 类型 复制 删除 移动 返回      更新时间:2023-10-16

请考虑以下程序:

#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。可以执行也可能不执行复制,但根据标准 (12.8/32) 的规定,移动或复制 ctor(此处删除两者)仍必须可访问。

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

这是

标准行为。

N4140 [stmt.return]/2: [...]带有大括号初始化列表的 return 语句初始化对象 或通过指定初始值设定项的复制列表初始化 (8.5.4) 从函数返回的引用 列表。[...]

这意味着 nakedBracetypedBrace 执行的初始化等效于以下内容:

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

[标准返回]/2 ...具有非 void 类型表达式的 return 语句只能在返回值的函数中使用;表达式的值将返回给函数的调用方。表达式的值隐式转换为它所在的函数的返回类型。返回语句可能涉及临时对象的构造和复制或移动 (12.2)...带有大括号 init-list 的 return 语句通过指定初始值设定项列表中的复制列表初始化 (8.5.4) 来初始化要从函数返回的对象或引用。

[class.temporary]/1 类类型的临时是在各种上下文中创建的:...返回一个 prvalue (6.6.3) ...

所以是的,据我所知,存在语义差异。 typedBrace计算表达式S{},生成类型为 S 的 prvalue,然后尝试从该表达式复制构造其返回值。 相反,nakedBrace直接从大括号初始化列表中构造其返回值。

这与S s{};(有效)与S s = S{};(不起作用)的情况相同,只是被某种程度的间接性所掩盖。