以下代码使用GCC 8.2编译,但不能使用Clang 6.0.1编译:
// A struct named Foo.
struct Foo
{
// Data member of type 'int'.
int val;
// Default constructor (constexpr).
constexpr Foo() noexcept : val(0) {}
};
// A struct named Bar.
struct Bar : Foo
{
// Make use of the constructors declared in Foo.
using Foo::Foo;
// A constructor taking an object of type Foo.
// COMMENTING THIS CONSTRUCTOR SOLVE THE COMPILATION ISSUE.
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
};
// A struct named Test.
struct Test
{
// Data member of type 'Bar'.
Bar bar;
// A defaulted default constructor.
constexpr Test() noexcept = default;
};
// Main function.
int main() { return 0; }
Clang失败并显示以下消息:
错误:默认构造函数的默认定义不是constexpr
constexpr Test()noexcept =默认值;
我想了解Clang为什么拒绝此代码。
答案 0 :(得分:5)
在C ++ 14中,无法继承默认构造函数。
§12.9[class.inhctor] (强调我的)
3对于候选集中的每个非模板构造函数 继承的构造函数,无参数的构造函数除外 或具有单个参数的复制/移动构造函数, 隐式声明具有相同的构造函数特性,除非 在用户声明的构造函数中,具有相同的签名 出现使用声明或构造函数的完整类 将是该类的默认,复制或移动构造函数。 ...
这基本上意味着,对于您的Bar类,ctor将被隐式定义-并且意味着using Foo::Foo
并没有做任何有意义的事情。
但是,由于您有一个单独的Bar构造函数,因此可以防止隐式定义默认构造函数。
当您注释掉单独的constexpr Bar(Foo const obj)
ctor时,此方法起作用的原因是
5 [注意:默认和复制/移动构造函数可能是隐式的 声明为12.1和12.8中指定的值。 —尾注]
§12.1/ 5 [class.ctor]
...如果该用户编写的默认构造函数将满足 constexpr构造函数(7.1.5)的要求, 隐式定义的默认构造函数是constexpr。 ...
因此,隐式声明的构造函数被声明为constexpr,这使您的代码可以按预期工作和编译。
您可以通过如下方式明确地默认默认ctor来解决此问题:
constexpr Bar() noexcept = default;
您还可以查看Constexpr class: Inheritance?
问题有点不同,但与您所看到的非常相似。
可悲的是,我找不到C ++ 17标准中的相关部分。我认为推理是相同的,但找不到100%可靠的参考。
答案 1 :(得分:5)
似乎clang依赖C ++ 14第[class.inhctor]p3节中的C ++ 17之前的措辞:
对于候选继承的构造函数集中的每个非模板构造函数,除了没有参数的构造函数或具有单个参数的复制/移动构造函数之外,构造函数都隐含地声明为相同的构造函数特性,除非完整类中有一个用户声明的构造函数具有相同的签名,该类中出现using-声明,否则该构造函数将是该类的默认,复制或移动构造函数。同样,对于继承的候选构造函数集合中的每个构造函数模板,除非在完整类中存在等效的用户声明的构造函数模板([temp.over.link]),否则将隐式声明具有相同构造函数特征的构造函数模板。出现using-declaration。 [注意:默认参数不会被继承。如[except.spec]中所指定的,暗含了一个异常规范。 —尾注]
所以在C ++ 14中:
using Foo::Foo;
意味着Bar
不继承Foo
的默认构造函数,而Bar
没有默认构造函数,因为它受到以下声明的禁止:
constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
向Bar
添加默认构造函数可解决问题see it live:
constexpr Bar() = default ;
在C ++ 17中,措词p0136r1: Rewording inheriting constructors (core issue 1941 et al)发生了变化,可以看到Changes between C++14 and C++17 DIS的论文被接受了。
以下文件在委员会会议上被移动,但它们的内容过于具体,以至于无法单独列出:N3922,N4089,N4258,N4261,N4268,N4277,N4285,P0017R1,P0031R0, P0033R1,P0074R0, P0136R1 ,P0250R3,P0270R3,P0283R2,P0296R2,P0418R2,P0503R0,P0509R1,P0513R0,P0516R0,P0517R0,P0558R1,P0599R1,P0599R1、00
我们可以看到p0136r1已被删除[class.inhctor]
:
删除12.9 class.inhctor,“继承构造函数”。
我看不到p0136r1
中的任何措辞会进一步限制这种情况。缺陷报告列表并未专门涵盖这种情况,但措词更改似乎是一致的。
所以看来这里的gcc是正确的,而且我们还有一个潜在的clang错误。
我们还在gcc pre 7.x(see it live)中获得了诊断信息。如果我们查看gcc 7 release notes,将会看到:
在P0136之后,继承的构造函数的默认语义在所有模式下均已更改。本质上,重载解析的发生就好像直接调用继承的构造函数一样,并且编译器根据需要填充其他基础和成员的构造。大多数用途不需要任何更改。可以使用-fno-new-inheriting-ctors或-fabi-version小于11来恢复旧的行为。
似乎可以证实最初的结论。如果我们将-fno-new-inheriting-ctors
与对您的程序it no longer compiles进行了稍微修改的版本(用于备份)一起使用,则P0136
进行了更改。