(此答案中的链接指向N4659,即 C++17 最终草案。但是,在撰写本文时,C++20 的情况完全相同。)
是的,这是可能的。有两种情况:
情况1
T
是一个非联合聚合,其零初始化,如果聚合具有非平凡构造函数,则后跟默认初始化,不同于复制初始化{}
。
我们可以使用std::in_place_t
它来构造我们的示例,因为它有一个显式的默认构造函数。最小的可重现示例:
#include <utility>
struct A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(现场演示)
案例 1,变体
T
是一个联合聚合,其第一个元素默认初始化不同于复制初始化{}
。
我们可以在案例 1 中更改struct
为union
以形成一个最小的可重现示例:
#include <utility>
union A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(现场演示)
案例2
T
是形式const U&
或可以从 列表初始化的U&&
地方。U
{}
最小的可重现示例:
int main()
{
using R = const int&;
R{}; // well-formed
R(); // ill-formed
}
(现场演示)
详细解释
T()
根据[dcl.init]/17:
初始化器的语义如下。目标类型
是正在初始化的对象或引用的
类型,源类型是初始化表达式的类型。如果初始化器不是单个(可能是带括号的)表达式,则未定义源类型。
如果初始值设定项是(无括号的)花括号初始化列表或花括号=
初始化列表,则对象或引用是列表初始化的。
如果目标类型是引用类型,请参见 [dcl.init.ref]。
如果目标类型是字符数组、 的数组char16_t
、 的数组char32_t
或 的数组wchar_t
,并且初始值设定项是字符串文字,请参阅 [dcl.init.string]。
如果初始化程序是()
,则对象是值初始化的。
[...]
我们可以得出结论,T()
总是对对象进行值初始化。
T{}
根据[dcl.init]/17:
初始化器的语义如下。目标类型
是正在初始化的对象或引用的
类型,源类型是初始化表达式的类型。如果初始化器不是单个(可能是带括号的)表达式,则未定义源类型。
这足以让我们得出结论,T{}
总是列表初始化对象。
现在让我们来看看[dcl.init.list]/3。我已经强调了可能的情况。其他情况是不可能的,因为它们要求初始化列表不为空。
类型的对象或引用的列表初始化T
定义如下:
(3.1) 如果T
是一个聚合类并且初始化器列表有一个类型为cv U
的元素,其中U
isT
或派生自 的类
T
,则从该元素初始化对象(通过复制列表初始化的复制初始化,或通过直接- 直接列表初始化的初始化)。
(3.2) 否则,如果T
是一个字符数组并且初始化器列表有一个元素是一个适当类型的字符串字面量 ([dcl.init.string]),则按照该节中的描述执行初始化。
(3.3)否则,如果T
是一个聚合,则执行聚合初始化。
(3.4)否则,如果初始化列表没有元素并且T
是具有默认构造函数的类类型,则该对象被值初始化。
(3.5) 否则,如果T
是 的特化
std::initializer_list<E>
,则按如下所述构造对象。
(3.6) 否则,如果T
是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议([over.match]、[over.match.list])选择最佳构造函数。如果需要缩小转换(见下文)来转换任何参数,则程序格式错误。
(3.7) 否则,如果T
是一个具有固定基础类型的枚举 ([dcl.enum]),则初始化器列表只有一个元素v
,并且初始化为直接列表初始化,则使用值T(v)
([ expr.type.conv]); 如果需要缩小转换以转换v
为 的基础类型T
,则程序格式错误。
(3.8) 否则,如果初始化列表有一个类型的元素E
并且T
不是引用类型或其引用类型与 引用相关E
,则从该元素初始化对象或引用(通过复制列表的复制初始化- 初始化,或通过直接列表初始化的直接初始化);如果需要缩小转换(见下文)将元素转换为
T
,则程序格式错误。
(3.9)否则,如果T
是引用类型,T
则生成所引用类型的纯右值。prvalue 通过复制列表初始化或直接列表初始化来初始化其结果对象,具体取决于引用的初始化类型。然后使用纯右值直接初始化引用。
(3.10)否则,如果初始化列表没有元素,则对象被值初始化。
(3.11) 否则,程序是非良构的。
(注意:(3.6)在这种情况下是不可能的,原因如下:(3.4)涵盖了存在默认构造函数的情况。为了考虑(3.6),必须调用非默认构造函数, 这对于空的初始化列表是不可能的。(3.11)是不可能的,因为(3.10)涵盖了所有情况。)
现在让我们分析案例:
(3.3)
对于聚合,值初始化首先执行零初始化,然后,如果元素具有非平凡的默认构造函数,则在聚合上执行默认初始化,每个[dcl.init]/8:
对 T 类型的对象进行值初始化意味着:
非工会聚集体
当从 复制初始化非联合聚合时{}
,未使用默认成员初始化器显式初始化的元素将从[dcl.init.aggr]/8{}
复制初始化:
如果列表中的初始化子句少于非联合聚合中的元素,则每个未显式初始化的元素都按如下方式初始化:
[...]
见案例 1。
联合聚合
如果聚合是联合,并且没有成员具有默认成员初始化程序,则从复制初始化聚合的{}
复制初始化第一个元素{}
:[dcl.init.aggr]/8:
[...]
如果聚合是联合并且初始化列表为空,则
见案例 1,变体。
(3.4)
值初始化,所以没有区别。
(3.9)
T()
T
如果是每个[dcl.init]/9的引用,则不允许:
要求对引用类型的实体进行默认初始化或值初始化的程序是格式错误的。
见案例 2。
(3.10)
同样,值初始化。没有不同。