4
votes

I just stumbled upon the following differences between GCC and Clang concerning an explicitly defaulted constexpr ctor and some inheritance...

template <typename T>
struct A {
  constexpr A() = default;
  T v;
};

struct B : A<int> {
  constexpr B() = default;
};

GCC immediately rejects the code while Clang allows to instantiate non-constexpr versions of both types. My guess is that Clang is probably correct, but I'm not 100% certain...

1
initialize A::v and gcc will be happy.Swordfish

1 Answers

6
votes

The problem boils down to: is a constexpr constructor that default-initializes some non-static data member of builtin type valid, if it is not used?


tl;dr:

  • For a non-template constructor, no, it is invalid to leave any non-static data members uninitialized.

  • For a template constructor, yes, it is valid to have some (but not all, no diagnostic required) instantiated template specializations for which the instantiated constructor does not meet the requirements of a constexpr constructor.

In this case, GCC is right, whereas Clang is wrong.


GCC gives the following error message which is very informative:

prog.cc:8:13: error: explicitly defaulted function 'constexpr B::B()' cannot be declared as 'constexpr' because the implicit declaration is not 'constexpr':
    8 |   constexpr B() = default;
      |             ^
prog.cc:3:13: note: defaulted constructor calls non-'constexpr' 'A<T>::A() [with T = int]'
    3 |   constexpr A() = default;
      |             ^
prog.cc:3:13: note: 'A<T>::A() [with T = int]' is not usable as a 'constexpr' function because:
prog.cc:4:5: note: defaulted default constructor does not initialize 'int A<int>::v'
    4 |   T v;
      |     ^

live demo

Note that the error is raised on the constructor of B, instead of that of A, whose constructor is merely "not usable as a constexpr function because [the] defaulted default constructor does not initialize int A<int>::v."


Per [dcl.constexpr]/4:

The definition of a constexpr constructor shall satisfy the following requirements:

  • the class shall not have any virtual base classes;
  • each of the parameter types shall be a literal type.

In addition, either its function-body shall be = delete, or it shall satisfy the following requirements:

  • [...]
  • every non-variant non-static data member and base class subobject shall be initialized ([class.base.init]);
  • [...]

Here, v is of type int, and is not initialized. Therefore, it seems that the constructor of A cannot be declared constexpr.

However, [dcl.constructor]/6 says:

If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.

Therefore, the constructor of A that is declared constexpr is actually valid, even when instantiated for T = int!


The problem is the constructor of B. B is an ordinary class (as opposed to a class template), and for its constructor to be (merely) declared constexpr, A<int> must have a constexpr constructor, which is not the case.

Therefore, this code should be rejected, as GCC does.


(Note that both compilers reject initialization of such a type, for example:

A a{};
B b{};

The above code is rejected by both compilers.)

As mentioned in a comment, initialize A::v and GCC (and the standard) will be happy.