C++ constexpr继承构造函数

C++ constexpr inheriting 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 失败并显示以下消息:

error: defaulted definition of default constructor is not constexpr
constexpr Test() noexcept = default;

我想了解为什么 Clang 拒绝此代码。

在C++14中,不能继承默认构造函数。

§12.9 [class.inhctor] (强调我的)

3 For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class. ...

这基本上意味着,对于您的 class 栏,构造函数将被隐式定义 - 并且意味着 using Foo::Foo 没有做任何有意义的事情。

但是,由于 Bar 有一个单独的构造函数,这会阻止默认构造函数的隐式定义。

当您注释掉单独的 constexpr Bar(Foo const obj) ctor 时,这是因为

5 [ Note: Default and copy/move constructors may be implicitly declared as specified in 12.1 and 12.8. —end note ]

§12.1/5 [class.ctor]

... If that user-written default constructor would satisfy the requirements of a constexpr constructor (7.1.5), the implicitly-defined default constructor is constexpr. ...

因此,隐式声明的构造函数被声明为 constexpr,这使您的代码按预期工作和编译。

您可以通过像这样显式默认默认 ctor 来解决此问题:

constexpr Bar() noexcept = default;

你也可以看看Constexpr class: Inheritance?

那里的问题有点不同,但与您所看到的非常相似。

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

看起来 clang 依赖于 C++14 部分中的 C++17 之前的措辞 [class.inhctor]p3:

For each non-template constructor in the candidate set of inherited constructors other than a constructor having no parameters or a copy/move constructor having a single parameter, a constructor is implicitly declared with the same constructor characteristics unless there is a user-declared constructor with the same signature in the complete class where the using-declaration appears or the constructor would be a default, copy, or move constructor for that class. Similarly, for each constructor template in the candidate set of inherited constructors, a constructor template is implicitly declared with the same constructor characteristics unless there is an equivalent user-declared constructor template ([temp.over.link]) in the complete class where the using-declaration appears. [ Note: Default arguments are not inherited. An exception-specification is implied as specified in [except.spec]. — end note ]

所以在 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) which was can see was accepted from Changes between C++14 and C++17 DIS

发生了变化

The following papers were moved at committee meetings, but their contents are too specific to call out as separate features: 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]:

Remove 12.9 class.inhctor, "Inheriting constructors".

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

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

gcc 7 发行说明

我们还在 gcc pre 7.x 中获得了一个诊断(see it live). If we look at the gcc 7 release notes 我们看到:

The default semantics of inherited constructors has changed in all modes, following P0136. Essentially, overload resolution happens as if calling the inherited constructor directly, and the compiler fills in construction of the other bases and members as needed. Most uses should not need any changes. The old behavior can be restored with -fno-new-inheriting-ctors, or -fabi-version less than 11.

这似乎证实了最初的结论。如果我们将 -fno-new-inheriting-ctors 与您的程序 it no longer compiles 稍作修改的版本一起使用,它会用 P0136.

进行更改