我有代码:
class A {
public:
A() = default;
private:
int i = 1;
};
int main() {
const A a;
return 0;
}
它在g ++上编译得很好(参见ideone),但在clang ++上失败并出错:
const类型'const A'的对象的默认初始化需要用户提供的默认构造函数
我在LLVM bug-tracker上报告了此问题并将其视为无效。
我认为试图说服铿锵的开发者绝对毫无意义。另一方面,我没有看到这种限制的原因。
有人可以建议,如果C ++ 11 Standard以某种方式暗示这段代码无效吗?或者我应该向g ++报告错误?或许在语言规则方面有足够的自由来以多种方式处理这些代码?
答案 0 :(得分:23)
N3797§8.5/ 7说:
如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型。
没有进一步的例子或解释。我同意这看起来很奇怪。此外,在C ++ 11中更新规则比在C ++ 03中更严格,当类类型需要用户声明的构造函数时。 (您的构造函数是用户声明的。)
解决方法是使用{}
请求值初始化,或使用Dietmar的聪明的类外inline
定义。
如果你在没有初始化程序的情况下添加另一个成员,GCC确实提供了一个诊断(非常好的,指的是更新的C ++ 11要求)。
private:
int i = 1;
int j;
unmem.cpp:11:11: error: uninitialized const ‘a’ [-fpermissive]
const A a;
^
unmem.cpp:1:7: note: ‘const class A’ has no user-provided default constructor
class A {
^
unmem.cpp:3:5: note: constructor is not user-provided because it is explicitly defaulted in the class body
A() = default;
^
unmem.cpp:7:9: note: and the implicitly-defined constructor does not initialize ‘int A::j’
int j;
GCC source引用DR 253,为什么必须初始化空对象或完全初始化的const对象?这是标准中的一个开放问题,最后一次更新于八月2011年(后C ++ 11),附注:
如果隐式默认构造函数初始化所有子对象,则不需要初始化程序。
因此,虽然Clang符合C ++ 11(并且将遵循C ++ 14的原则),但GCC正在实施标准化委员会的最新思想。
提起GCC bug。我预测你需要-pedantic
才能在错误修复的时候得到诊断。
答案 1 :(得分:22)
请注意,您可以轻松地将您的类转换为具有用户定义的默认构造函数的类:
class A {
public:
A();
private:
int i = 1;
};
inline A::A() = default;
根据8.4.2 [dcl.fct.def.default]第4段:
...如果特殊成员函数是用户声明的而非显式的,则由用户提供 在第一次声明中违约或删除。 ...
这隐含地指出,在第一个声明中未明确默认的函数不是用户提供的。结合8.5 [dcl.init]第6段
...如果程序要求对const限定类型T的对象进行默认初始化,则T应为具有用户提供的默认构造函数的类类型。
很明显,无法使用默认构造函数默认在其第一个声明中初始化const
对象。但是,如果不是上面代码中的第一个声明,则可以使用默认定义。
答案 2 :(得分:4)
编辑:以下内容基于过时的信息。我刚刚通过了N3797,这就是我发现的:
§8.5/ 7 [dcl.init]
如果程序要求默认初始化 对于const限定类型T的对象,T应该是一个类类型 a 用户提供的默认构造函数。
请注意以下链接中的标准引用用户声明的。
以下程序用g ++编译但不用clang ++:
struct A {};
void f()
{
A const a;
}
它可能与此bug report有关,在那里它是“固定的”。除非它们已初始化,否则g ++一旦包含数据成员就无法编译它。请注意,int member = 1
将不再使A
成为POD。相比之下,clang ++拒绝所有排列(空类和数据成员初始化或未初始化。)对于以下段落对标准的含义的解释:
§8.5/ 9 [dcl.init]说:
如果没有为对象指定初始化程序,并且该对象是 (可能是cv限定的)非POD类类型(或其数组),. 对象应默认初始化;如果对象是 const-qualified类型,底层类类型应具有 用户声明的默认构造函数。否则,如果没有初始化器 为对象指定的对象及其子对象(如果有)具有 不确定的初始值;如果对象或其任何子对象 该程序是const-qualified类型,程序结构不合理。
见Why does C++ require a user-provided default constructor to default-construct a const object?。据推测,该程序格式不正确if the object is of const-qualified POD type, and there is no initializer specified (because POD are not default initialized).
请注意g ++如何表现如下:
struct A {int a;};
struct B {int a = 1;};
int main()
{
A a;
B b;
const A c; // A is POD, error
const B d; // B is not POD, contains data member initializer, no error
}
答案 3 :(得分:2)
自C ++ 17起,此代码正确,this question中的类似代码也是如此:
struct MyClass1 { int i{}; };
struct MyClass2 { const MyClass1 m; };
MyClass2 a;
clang 8.0.0即使使用-std=c++17
也拒绝后者的代码,这意味着clang 8.0.0有一个错误。
在C ++ 17中,以下新文本被添加为[dcl.init] / 7(根据P0490R0响应DR 253):
如果
T
的默认初始化会调用用户提供的T
的构造函数(不是继承的),则类类型T
是 const-default-constructible 来自基类)或
M
的每个直接非变量非静态数据成员T
都有一个默认的成员初始化程序,或者如果M
是类类型X
(或其数组) ),X
是const-default-constructible的,- 如果
T
是具有至少一个非静态数据成员的联合,则恰好一个变体成员具有默认的成员初始值设定项,- 如果
T
不是联合,则对于每个至少具有一个非静态数据成员的匿名联合成员,恰好一个非静态数据成员具有默认成员初始化程序,并且- 每个
T
可能构造的基类都是const-default-constructible的。如果程序要求对const限定类型
T
的对象进行默认初始化,则T
应该是const-default可构造的类类型或其数组。
在C ++ 17之前,没有这样的文字;定义为const
的对象必须具有初始化程序或用户提供的构造函数。因此,在C ++ 17之前,clang是正确的,并且g ++被错误地接受了未经诊断的代码。