首先,我有一个结构,它有一个值和一个默认值
struct S {
int a = 1;
};
当gcc和clang都使用非常量/非常量表达式时,可以默认构造此类型。在这两种情况下,std::is_pod
S s1; // works under both
const S s2{}; // works under both
const S s3; // only works in gcc, clang wants a user-provided constructor
以下任何尝试都不会对clang产生影响:
struct S {
int a = 1;
constexpr S() = default; // defaulted ctor
virtual void f() { } // virtual function, not an aggregate
private:
int b = 2; // private member, really not an aggregate
};
我唯一能做的就是显式地添加constexpr S(){}
。在我看来,const代码>在
常量S{}时失败代码>尤其是当类型不是聚合时。
标准让我觉得叮当声是对的
N4296:8.5/7
如果程序调用const限定类型T的对象的默认初始化,则T应该是具有用户提供的默认构造函数的类类型
那么为什么gcc允许这样做,而且是S{}代码>非默认初始化,即使类型不是POD或聚合?
const S s3;
包含在[dcl.init]/12中:
如果未为对象指定初始化器,则默认初始化该对象。
因此,根据您的报价要求,必须存在用户提供的默认构造函数。加一个这样的
struct S {
int a = 1;
constexpr S(){}
};
然后使声明编译罚款。
[...]特别是当类型不是聚合时。
S
在您的案例中是一个聚合,并且const S{}
的原因是有效的。聚合初始化应用于常量S{}
,一切正常
如果S
不是聚合,
类型T的对象或引用的列表初始化定义如下:
T
是聚合,则执行聚合初始化(8.5.1)李>现在考虑值初始化的定义:
对T
类型的对象进行值初始化意味着:
T
是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或用户提供或删除的默认构造函数,则对象默认初始化李> T
是一个(可能是cv限定的)类类型,没有用户提供或删除的默认构造函数,则该对象为零初始化,并检查默认初始化的语义约束,如果T
具有非平凡的默认构造函数,则该对象为默认初始化对象李>由于成员有一个初始值设定项([class.ctor]/4.9),因此默认的ctor实际上是不重要的,但这与约束的检查方式无关。因此它是默认初始化,并且
const S s{};
同样有效(或无效!)像
const S t;
那么为什么gcc允许这样做呢
好:
>
就目前的标准而言,海湾合作委员会不符合;见上文。
有一个活跃的CWG问题,即15年前创建的253,涵盖了类似的场景。2011年一次会议的最后一条记录是
如果隐式默认构造函数初始化所有子对象,则不需要初始化器。
S
的隐式默认构造函数就是这种情况,这将使您的所有行都有效。
海湾合作委员会的开发者(例如这里)暗示,由于委员会基本上同意上述决议,海湾合作委员会目前的行为是可行的,不应该调整。因此,人们很可能会认为海湾合作委员会是对的,标准被打破了。
所以看起来gcc是基于DR 253的,尽管这个问题还没有解决。我们可以从以下gcc错误报告中看到这一点,报告中说:
这是出于设计,因为正如Dr253所示,规范性标准存在缺陷。
gcc的变化使其生效,它说:
Core 234-如果默认构造函数初始化所有子对象,则允许常量对象不带初始化器或用户提供的默认构造函数。
因此,从技术上讲,clang
是正确的,gcc
是不一致的,但似乎他们相信dr253
将得到有利于他们的解决。这是完全有意义的,如果主要关注的是不确定的初始值,据我所知是。此变更记录在gcc 4.6发行说明中:
在4.6中。0和4.6。1 G不再允许对const限定类型的对象进行默认初始化,除非该类型具有用户声明的默认构造函数。在4.6中。2G实现了DR 253的建议分辨率,因此如果它初始化所有子对象,则允许默认初始化。无法编译的代码可以通过提供初始值设定项来修复,例如。
struct A { A(); };
struct B : A { int i; };
const B b = B();