C++ C++;使具有显式构造函数的类的副本初始化工作?

C++ C++;使具有显式构造函数的类的副本初始化工作?,c++,c++14,language-lawyer,c++17,explicit,C++,C++14,Language Lawyer,C++17,Explicit,考虑以下代码: struct X{ explicit X(){} explicit X(const X&){} }; void foo(X a = X()){} int main(){} 使用C++14标准,GCC7.1和Clang4.0都可以编写代码,这正是我所期望的 但是,使用C++17(-std=C++1z),代码。什么规则改变了 对于两个编译器都表现出相同的行为,我怀疑这是一个bug。但据我所知,最新的草案仍然说,default参数使用copy initi

考虑以下代码:

struct X{
    explicit X(){}
    explicit X(const X&){}
};

void foo(X a = X()){}

int main(){}
使用C++14标准,GCC7.1和Clang4.0都可以编写代码,这正是我所期望的

但是,使用C++17(
-std=C++1z
),代码。什么规则改变了


对于两个编译器都表现出相同的行为,我怀疑这是一个bug。但据我所知,最新的草案仍然说,default参数使用copy initialization 1的语义。同样,我们知道
explicit
构造函数只允许直接初始化2

1:; 2:

因为C++17的行为发生了变化;在这种情况下,必须删除副本

强制省略复制/移动操作

在以下情况下,编译器需要省略类对象的复制和移动构造,即使复制/移动构造函数和析构函数有明显的副作用。对象被直接构造到存储中,否则它们将被复制/移动到存储中。复制/移动构造函数不需要存在或可访问:

  • 在对象初始化过程中,当初始化器表达式为 一个与 变量类型:

    T f() {
        return T();
    }
    
    T x = T(T(f())); // only one call to default constructor of T, to initialize x
    

注意:上面的规则没有指定优化:PR值和临时的C++ 17核心语言规范与早期C++修订的根本不同:不再有临时的复制/移动。描述C++17机制的另一种方法是“非物质化的值传递”:返回和使用prvalues,而从不具体化临时值

以及:

复制初始化的效果是:

  • 首先,如果
    T
    是类类型,并且初始值设定项是prvalue 其cv非限定类型与
    T
    的类相同的表达式 初始值设定项表达式本身,而不是临时物化 从中,用于初始化目标对象:请参见(从C++17开始)

  • 如果
    T
    是一个类别类型,则cv不合格版本为 另一个是
    T
    或从
    T
    派生的类,它是 检查
    T
    ,并通过过载分辨率选择最佳匹配。 然后调用构造函数来初始化对象


这意味着对于
xa=X()
,将直接默认构造
a
,复制/移动构造函数及其副作用将被完全忽略。不会选择用于重载解析的非显式构造函数,这在C++14(及之前版本)中是必需的。对于这些有保证的情况,复制/移动构造函数不参与,那么它们是否显式就无关紧要了。

对于问题规则中的示例来说,最重要的是[expr.type.conv]/2。但让我们从以下方面开始:

初始值设定项的语义如下所示。目标类型是要初始化的对象或引用的类型,源类型是初始化器表达式的类型。如果初始值设定项不是单个(可能带括号)表达式,则不定义源类型

-如果目标类型为(可能符合cv条件的)类别类型:

-如果初始值设定项表达式是prvalue,并且源类型的cv非限定版本与目标的类是同一个类,则初始值设定项表达式用于初始化目标对象。[示例:
tx=T(T(T());
调用
T
默认构造函数来初始化
x
。 — [结束示例]

因此,在
xa=X()
中,初始值设定项表达式
X()
用于初始化目标对象。当然,这还不足以回答:为什么选择默认构造函数(即
X()
如何变成
()
)以及为什么
显式
默认构造函数是好的

X()
表达式是函数表示法中的显式类型转换,因此让我们研究一下:

如果初始值设定项是带括号的单个表达式,则类型转换表达式与相应的强制转换表达式等效(定义为,如果定义为含义)。如果类型为cv void且初始值设定项为(),则表达式是指定类型的prvalue,不执行初始化否则,表达式是指定类型的prvalue,其结果对象由初始值设定项直接初始化

相关句子的重点是我的。它说对于
X()

  • 对象是用
    ()
    初始化的(它是[expr.type.conv]/1的“初始值设定项”),这就是选择默认构造函数的原因

  • 对象是直接初始化的,这就是为什么默认构造函数是显式的


更多详细信息:当初始值设定项为
()
时,应用:

如果初始值设定项为
()
,则对象的值已初始化

:

对于类型为T的对象,表示:
-如果
T
是一个(可能是cv限定的)类类型,没有默认构造函数([class.ctor]),或者是用户提供或删除的默认构造函数,则对象默认初始化

:

对于类型为
T
的对象,表示:
-如果
T
是一个(可能是cv限定的)类类型,则考虑构造函数。枚举了适用的构造函数(),并通过选择初始化器
()
的最佳构造函数。调用这样选择的构造函数(参数列表为空)来初始化对象

类类型的对象直接初始化时,从表达式o复制初始化