C ++ constexpr继承构造函数

时间:2018-08-15 04:45:24

标签: c++ language-lawyer constexpr clang++ default-constructor

以下代码使用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为什么拒绝此代码。

2 个答案:

答案 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 7发行说明

我们还在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进行了更改。