template <typename T>
struct A
{
static constexpr T obj {};
static constexpr bool noexcept_copy = noexcept( T{obj} );
static void UsesCopy() { T{obj}; }
static constexpr int C = 1;
};
struct NoCopy
{
constexpr NoCopy() = default;
NoCopy(const NoCopy&) = delete;
};
int main()
{
return A<NoCopy>::C;
}
The code above is successfully compiled by GCC, but Clang gives a compilation error:
tmp.cpp:6:57: error: call to deleted constructor of 'NoCopy'
static constexpr bool noexcept_copy = noexcept( T{obj} );
^~~~~~
tmp.cpp:20:16: note: in instantiation of template class 'A<NoCopy>' requested here
return A<NoCopy>::C;
^
tmp.cpp:15:9: note: 'NoCopy' has been explicitly marked deleted here
NoCopy(const NoCopy&) = delete;
^
1 error generated.
The A::UsesCopy
function uses copy constructor as well, but the compiler does not complain about the usage of deleted function there. What is the difference between UsesCopy
function and noexcept_copy
constexpr? Both use copy constructor of NoCopy
class and both are not used but the constexpr definition produces a compilation error, the function definition does not.
PS. Clang compiles the code above with -std=c++17
or -std=c++2a
, but not with -std=c++11
or -std=c++14
.
In order for any code to appear, a template must be instantiated: the template arguments must be provided so that the compiler can generate an actual class (or function, from a function template).
The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation.
When a function template is first called for each type, the compiler creates an instantiation. Each instantiation is a version of the templated function specialized for the type. This instantiation will be called every time the function is used for the type.
To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.
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:
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 theconstexpr
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With