C++ constexpr inheriting constructor

  • A+

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.


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.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: