C++ constexpr继承构造函数

Ank*_*Dev 12 c++ default-constructor language-lawyer constexpr clang++

以下代码使用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; }
Run Code Online (Sandbox Code Playgroud)

Clang失败并显示以下消息:

错误:默认构造函数的默认定义不是constexpr
constexpr Test()noexcept = default;

我想理解为什么Clang拒绝这个代码.

div*_*nas 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;
Run Code Online (Sandbox Code Playgroud)

您还可以查看Constexpr类:继承?

问题有点不同,但与你所看到的非常相似.

遗憾的是,我无法在C++ 17标准中找到相关部分.我假设推理是一样的,但找不到100%肯定的参考.


Sha*_*our 5

看起来clang依赖于来自C++ 14部分[class.inhctor] p3的前C++ 17写法:

对于除了没有参数的构造函数或具有单个参数的复制/移动构造函数之外的候选继承构造函数集中的每个非模板构造函数,构造函数隐式声明具有相同的构造函数特征,除非存在用户声明的构造函数完整类中的相同签名,其中出现using声明或构造函数将是该类的默认,复制或移动构造函数.类似地,对于候选继承构造函数集中的每个构造函数模板,构造函数模板是使用相同的构造函数特性隐式声明的,除非在完整的类中存在等效的用户声明的构造函数模板([temp.over.link])出现使用声明.[注意:默认参数不会被继承.[except.spec]中指定了异常规范. - 结束说明]

所以在C++ 14中:

using Foo::Foo;
Run Code Online (Sandbox Code Playgroud)

means Bar不会继承Foo默认构造函数,Bar也没有默认构造函数,因为它被以下声明禁止:

constexpr Bar(Foo const obj) noexcept : Foo(obj) {}
Run Code Online (Sandbox Code Playgroud)

添加默认构造函数来Bar修复问题,看到它是实时的:

constexpr Bar() = default ;
Run Code Online (Sandbox Code Playgroud)

在C++ 17中用p0136r1改变了措辞:重写了继承构造函数(核心问题1941等),可以看出这是从C++ 14和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,P0607R0,P0612R0

我们可以看到删除了p0136r1 [class.inhctor]:

删除12.9 class.inhctor,"继承构造函数".

我没有看到任何措辞p0136r1会再限制这种情况.缺陷报告列表并未具体涵盖此案例,但措辞变化似乎是一致的.

所以看起来gcc在这里是正确的,我们有一个潜在的clang bug.

gcc 7发行说明

我们还获得了gcc pre 7.x的诊断信息(现场直播).如果我们查看gcc 7发行说明,我们会看到:

在P0136之后,所有模式中的继承构造函数默认语义都已更改.本质上,重载解析就像直接调用继承的构造函数一样,并且编译器根据需要填写其他基础和成员的构造.大多数用途不需要任何更改.可以使用-fno-new-inheriting-ctors或-fabi-version小于11来恢复旧行为.

这似乎证实了初步结论.如果我们使用-fno-new-inheriting-ctors稍微修改过的程序版本,它就不再编译哪个备份更改了P0136.