保证复制省略在 C++1z 的列表初始化中如何工作?
How does guaranteed copy elision work in list-initialization in C++1z?
c++草案n4606中有一段关于保证复制省略的内容[dcl.init] 17.6:
- If the destination type is a (possibly cv-qualified) class type:
- If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example:
T x = T(T(T()));
calls the T
default constructor to initialize x
. — end example ]
- [...]
还有一个 讨论它是如何工作的。
根据我自己的理解,我引用的规则保证当初始化表达式是纯右值并且源类型的 cv 非限定版本与 class 相同 class 时,不应该涉及任何 ctors ] 的目的地。这样就不需要检查复制或移动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++ draft n4606 的列表初始化部分找不到类似的规则。我发现的是 ([dcl.init.list] 3.6)
[...]
- Otherwise, if
T
is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
[...]
由于list-initialization的优先级高于我引用的第一条规则,当initializer是一个initializer-list时,我们应该考虑list-initialized部分的规则。如我们所见,在列表初始化 class 类型 T
时会考虑构造函数。所以,继续前面的例子,will
A ff() { return {A()}; }
在 C++17 中合法吗?有人能找到标准草案在哪里指定保证复制省略在列表初始化中如何工作吗?
Guaranteed elision 通过重新定义纯右值表达式来表示“将初始化一个对象”。他们不再构建临时对象;临时变量是由某些使用 的纯右值表达式构造的。
请注意上面频繁使用“表达”一词。我指出这一点是因为一个非常重要的事实:花括号初始化列表 不是表达式。 标准对此非常清楚。它不是表达式,只有表达式可以是纯右值。
确实,请考虑标准中关于省略的部分:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances:
- in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object...
- ...
- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type
这些都涉及表达式(临时class对象是表达式)。 Braced-init-lists 不是表达式。
因此,如果您发出 return {anything};
,anything
的 return 值的构造将不会被省略,无论 anything
是什么。当然是按照标准;编译器可能因错误而有所不同。
话虽如此,如果您有一个与 return 值类型相同的纯右值表达式,您极不可能想要键入 return {prvalue};
而不仅仅是 return prvalue;
.如果表达式属于不同类型,则它无论如何都不符合省略条件。
c++草案n4606中有一段关于保证复制省略的内容[dcl.init] 17.6:
- If the destination type is a (possibly cv-qualified) class type:
- If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example:
T x = T(T(T()));
calls theT
default constructor to initializex
. — end example ]- [...]
还有一个
根据我自己的理解,我引用的规则保证当初始化表达式是纯右值并且源类型的 cv 非限定版本与 class 相同 class 时,不应该涉及任何 ctors ] 的目的地。这样就不需要检查复制或移动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++ draft n4606 的列表初始化部分找不到类似的规则。我发现的是 ([dcl.init.list] 3.6)
[...]
- Otherwise, if
T
is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed. [...]
由于list-initialization的优先级高于我引用的第一条规则,当initializer是一个initializer-list时,我们应该考虑list-initialized部分的规则。如我们所见,在列表初始化 class 类型 T
时会考虑构造函数。所以,继续前面的例子,will
A ff() { return {A()}; }
在 C++17 中合法吗?有人能找到标准草案在哪里指定保证复制省略在列表初始化中如何工作吗?
Guaranteed elision 通过重新定义纯右值表达式来表示“将初始化一个对象”。他们不再构建临时对象;临时变量是由某些使用 的纯右值表达式构造的。
请注意上面频繁使用“表达”一词。我指出这一点是因为一个非常重要的事实:花括号初始化列表 不是表达式。 标准对此非常清楚。它不是表达式,只有表达式可以是纯右值。
确实,请考虑标准中关于省略的部分:
This elision of copy/move operations, called copy elision, is permitted in the following circumstances:
- in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object...
- ...
- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type
这些都涉及表达式(临时class对象是表达式)。 Braced-init-lists 不是表达式。
因此,如果您发出 return {anything};
,anything
的 return 值的构造将不会被省略,无论 anything
是什么。当然是按照标准;编译器可能因错误而有所不同。
话虽如此,如果您有一个与 return 值类型相同的纯右值表达式,您极不可能想要键入 return {prvalue};
而不仅仅是 return prvalue;
.如果表达式属于不同类型,则它无论如何都不符合省略条件。