SCF*_*nch 12 c++ language-lawyer
以下代码在几种不同的编译器上进行编译,包括 g++、clang 和 MSVC,但我不明白为什么:
#include <string>
class C {
std::string m;
};
void Accept(C &&);
extern C c;
int main() {
Accept({{c}});
}
Run Code Online (Sandbox Code Playgroud)
生成的汇编代码似乎显示在调用 Accept() 之前对 std::string 的复制构造函数的调用,我假设这意味着编译器生成了对类 C 的复制构造函数的调用。因此,使用{{c}}
作为右值引用参数的参数似乎会创建一个临时副本。
这是准确的解释吗?如果是这样,哪些 C++ 语言功能组合起来可以{{c}}
创建 的临时副本c
?
use*_*522 12
对象和引用的初始化方式在 [dcl.init] 中指定。具体来说, [dcl.init]/16.1表示从大括号而不是表达式进行初始化是列表初始化。
如何执行列表初始化在[dcl.init.list]/3中指定。如果目标类型是引用,则链中适用的第一项是[dcl.init.list]/3.9,它表示如果我们初始化的花括号初始化列表恰好有一个引用类型的元素- 与目标类型相关,则引用将从该元素初始化。因此Accept({c});
,将通过从 复制初始化来初始化函数参数中的引用c
,使其直接绑定到c
。
如果也有多层大括号,您似乎期望这种行为递归应用。然而,在{{c}}
braced-init-list中只有一个元素,它本身就是一个braced-init-list {c}
,并且braced-init-lists没有类型(它们不是表达式),所以item [dcl.init.list]/清单上的3.9不能申请。然后,以下项[dcl.init.list]/3.10适用于引用目标类型,并且在没有进一步条件的情况下指定将创建引用类型的纯右值并将其用于初始化引用,这意味着将具体化一个临时对象,该临时对象将被实例化。函数参数将被绑定到。
因此,无论如何,嵌套大括号将始终使用临时对象初始化引用。
具体来说,在您的情况下,它将最终作为一个临时对象,通过调用隐式声明的复制构造函数来初始化,C
该构造函数使用 初始化复制构造函数参数中的引用{c}
,这反过来又如上所述意味着它直接绑定到c
,因此不会将创建更多临时文件。相反,它不会以聚合初始化结束,因为您的类成员是聚合初始化,private
因此该类不是聚合。如果它是一个聚合(例如,因为您替换class
为struct
),则临时变量的初始化将是错误的,因为std::string
无法从C
.
然而,似乎(至少在 2017 年)C++ 标准委员会有意使您的代码格式错误,请参阅CWG 问题 2319中的注释。问题描述末尾的建议还会Accept({{c}})
通过禁止将复制/移动构造函数与嵌套大括号一起使用,从而使调用中的临时对象的初始化在非聚合情况下格式不正确。
归档时间: |
|
查看次数: |
442 次 |
最近记录: |