I think the correct approach to deal with this issue is similar to what is details in this pre-C++17 answer to a question about the order of initialization of static constexpr templated data members.
TL;DR - yes GCC, got it right, Clang tries to resolve the copy c'tor even though it is not allowed.
To summarize:
C++14
In p9.4.2.3 - Static data members, we have:
[...] A static
data member of literal type can be declared in the
class definition with the constexpr
specifier; if so, its declaration shall specify a brace-or-equal-initializer
in which every initializer-clause that is an assignment-expression is a constant expression. [...] The member shall still be defined
in a namespace scope if it is odr-used in the program and the namespace scope definition shall not contain an initializer.
So the declaration of static constexpr
data member is just a declaration and is not a definition - even though it has an initializer. A definition is required to cause initialization, and none is provided in the original OP code.
To drive home the point, we have the previously quoted p14.7.1 - Templates - Implicit Instantiation (emphasis mine):
The implicit instantiation of a class template specialization causes the implicit instantiation of the
declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member template
GCC correctly does not initialize noexcept_copy
as there is no definition for it, only a decleration - it is never "defined in a namespace scope", as required by p9.4.2.3.
C++17
So what changed? Well, as far as I can tell - nothing major, but there were some changes around how static data members are defined, in C++17, by adopting P0389R2 whose purpose - as far as I understand - it so introduce "inline variables" which can be declared simply and then odr-used in multiple translation units with the same storage, i.e. there's only one instance of the variable initialized with the initializer in the declaration - this is similar to "static field initialization" in other languages such as Java and makes it easier to have singleton eager initialization.
Here's from the spec:
A declaration is a definition unless [...] it declares a non-inline static data member in a class definition.
So we explicitly specify that inline
static data member's declaration is a definition. No need for an external definition out of the class scope. This certainly makes it easier for programmers.
But what inline
has to do with constexpr
? The adoption of the proposal also resulted in this specification in p10.1.5.1:
A function or static data member declared with the constexpr
specifier is implicitly an inline function or variable.
So this change actually makes it pretty explicit that the declaration of static constexpr bool noexcept_copy
is also a definition - which we mustn't instantiate in case of an implicit template instantiation.
I'm guessing this is a strong enough signal for Clang developers to not initialize the static constexpr
data members.
BTW, the example in C++17 Appendix D p.1 explains this rather explicitly:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
From this example we learn that, in C++ 2014 a declaration of a static constexpr
was not a definition, and in order for the initialization to take place, you must have a definition in a namespace scope.
So Clang is wrong outputing an error in their C++14 implementation because in the OP code, not only is it wrong to implicitly instantiate the template class static data member - there isn't even a definition for it so it shouldn't have been instantiated even if it wasn't a template class.