【问题标题】:C++ constexpr inheriting constructorC++ constexpr 继承构造函数
【发布时间】:2019-01-21 23:59:32
【问题描述】:

以下代码可以使用 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 = default;

我想了解 Clang 拒绝此代码的原因。

【问题讨论】:

  • 您忘记为Bar 提供默认构造函数(但是,您在Test 中使用了该构造函数)。为Bar int Test() 提供一个参数,或者为Bar 提供一个默认构造函数都应该可以解决问题。但是为什么这与 G++ 编译我不明白。 (顺便说一句,gcc 7.3.0 及以下不支持)
  • @muXXmit2X 可能 C++17 确实消除了不继承默认构造函数的限制(参见 divinias 的回答,“找不到”)?
  • 这很有趣,因为 simplified version of this works 你必须回到 clang 3.8.1 for this to not work ...但是将 constexpr 添加到 mix 中会破坏 simplified version

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


【解决方案1】:

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

对于候选继承构造函数集合中的每个非模板构造函数,除了没有参数的构造函数或具有单个参数的复制/移动构造函数,构造函数被隐式声明为相同的构造函数特征,除非在出现 using 声明的完整类中存在具有相同签名的用户声明构造函数,或者该构造函数将是该类的默认构造函数、复制构造函数或移动构造函数。类似地,对于候选继承构造函数集中的每个构造函数模板,构造函数模板被隐式声明为具有相同的构造函数特征,除非在完整类中存在等效的用户声明的构造函数模板([temp.over.link]),其中使用声明出现。 [注意:默认参数不被继承。 [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, P0599R2P0599R2, P0599R2,

我们可以看到 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

【讨论】:

  • 似乎是一个很大的变化,而且没有具体的缺陷报告让我担心这是不是有意的。
  • 感谢您的回答,我已经填写了错误报告here
【解决方案2】:

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

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

3 对于候选集中的每个非模板构造函数 继承的构造函数除了没有参数的构造函数 或具有单个参数的复制/移动构造函数,构造函数是 隐式声明具有相同的构造函数特征,除非 有一个用户声明的具有相同签名的构造函数 出现 using 声明或构造函数的完整类 将是该类的默认、复制或移动构造函数。 ...

这基本上意味着,对于您的类 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% 确定的参考。

【讨论】:

  • 感谢您的回答。我的期望确实是默认构造函数将通过 using 指令继承。进一步看这个方向,下面的答案 (stackoverflow.com/a/44522990) 表明 C++17 在这个特定点上引入了变化。很高兴能得到证实。
猜你喜欢
  • 2012-11-14
  • 2021-06-24
  • 2015-03-24
  • 2014-08-21
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多