10

在 c++ 草案 n4606 [dcl.init] 17.6 中有一段关于保证复制省略:

  • 如果目标类型是(可能是 cv 限定的)类类型:
    • 如果初始化表达式是纯右值并且源类型的 cv 非限定版本与目标类是同一类,则初始化表达式用于初始化目标对象。[示例T x = T(T(T()));调用T默认构造函数进行初始化x。—结束示例]
    • [...]

还有一个问答讨论它是如何工作的。

就我自己的理解而言,我引用的规则保证了当初始化表达式是纯右值并且源类型的 cv 非限定版本与目标类是同一类时,不应该涉及任何 ctor。因此无需检查是否存在复制或移动 ctor,这使得以下代码在 C++17 中是合法的:

struct A {
    A() {}
    A(A const &) = delete;
    A(A &&) = delete;
};
A f() { return A(); } // it's illegal in C++14, and suppose to be legal in C++17

然而,让我抓狂的是我在 c++ 草案 n4606 的列表初始化部分找不到类似的规则。我发现的是 ([dcl.init.list] 3.6)

[...]

  • 否则,如果T是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。如果需要缩小转换(见下文)来转换任何参数,则程序格式错误。[...]

由于列表初始化比我引用的第一条规则具有更高的优先级,因此当初始化程序是初始化程序列表时,我们应该考虑列表初始化部分中的规则。正如我们所见,在列表初始化类类型时会考虑构造函数T。所以,继续前面的例子,将

A ff() { return {A()}; }

在 C++17 中合法吗?有人能找到标准草案在哪里指定保证复制省略在列表初始化中如何工作吗?

4

1 回答 1

7

保证省略通过重新定义纯右值表达式来表示“将初始化一个对象”来工作。他们不再建造临时建筑;临时变量是由纯右值表达式的某些用途构建的。

请注意上面“表达”一词的频繁使用。我指出这一点是因为一个非常重要的事实:braced-init-list不是表达式。标准对此非常明确。它不是表达式,只有表达式可以是纯右值。

事实上,考虑一下标准中关于省略的部分:

这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的:

  • 在具有类返回类型的函数的返回语句中,当表达式是非易失性自动对象的名称时...
  • ...
  • 当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv 非限定类型的类对象时

这些都涉及表达式(临时类对象是表达式)。Braced-init-lists 不是表达式。

因此,如果您发出return {anything};,则返回值的构造 fromanything将不会被忽略,无论是什么anything。当然是按照标准;由于错误,编译器可能会有所不同。

话虽如此,如果您有一个与返回值类型相同的纯右值表达式,那么您不太可能想要键入return {prvalue};而不是return prvalue;. 如果表达式是不同的类型,那么它无论如何都不符合省略的条件。

于 2016-08-06T14:46:33.367 回答