Ankur deDev
Ankur deDev

Reputation: 505

C++ constexpr inheriting constructor

The following code compiles with GCC 8.2 but not with 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 fails with the following message:

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

I would like to understand why Clang is rejecting this code.

Upvotes: 12

Views: 1249

Answers (2)

Shafik Yaghmour
Shafik Yaghmour

Reputation: 158469

It looks like clang is relying on pre C++17 wording from C++14 section [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 ]

So in C++14:

using Foo::Foo;

means Bar does not inherit Foo's default constructor and Bar does not have a default constructor since it is inhibited by your declaration of:

constexpr Bar(Foo const obj) noexcept : Foo(obj) {}

Adding a default constructor to Bar fixes the problem see it live:

constexpr Bar() = default ;

The wording was changed in C++17 with the paper 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

we can see p0136r1 removed [class.inhctor]:

Remove 12.9 class.inhctor, "Inheriting constructors".

I don't see any wording in p0136r1 that would restrict this case any more. The list of defect reports does not specifically cover this case but the wording changes seem consistent.

So it looks like gcc is correct here and we have a potential clang bug.

gcc 7 release notes

We also obtain a diagnostic in gcc pre 7.x (see it live). If we look at the gcc 7 release notes we see:

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.

Which seems to confirm the initial conclusion. If we use -fno-new-inheriting-ctors with a slightly modified version of your program it no longer compiles which backs up this was changed with P0136.

Upvotes: 5

divinas
divinas

Reputation: 1907

In C++14, default constructors can not be inherited.

§12.9 [class.inhctor] (emphasis mine)

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. ...

This basically means, that for your class Bar, the ctor will be implicitly defined - and means using Foo::Foo is not doing anything meaningful.

However, as you are having a separate constructor for Bar, this prevents the implicit definition of a default constructor.

The reason this works when you comment out your separate constexpr Bar(Foo const obj) ctor is because of

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. ...

So, the implicitly declared constructor is declared as constexpr, which makes your code work and compile as expected.

You can remedy the issue by just explicitly defaulting the default ctor like this:

constexpr Bar() noexcept = default;

You can also take a look at Constexpr class: Inheritance?

The problem there is a bit different, but very similar to what you are seeing.

Sadly, I am unable to find the relevant parts in the C++17 standard. I assume the reasoning is the same, but can't find the reference to be 100% sure.

Upvotes: 5

Related Questions