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

anton_rh
  • 8,226
  • 7
  • 45
  • 73
  • Isn't that what SFINAE is for? – πάντα ῥεῖ Aug 22 '19 at 12:41
  • 1
    clang is correct here - even if not used *in the end* you DO use a call to the c-tor on the instantiated type - that which you deleted - Solution: use SFINAE – darune Aug 22 '19 at 12:46
  • `A::UsesCopy` would produce error (which depends of `T`) only when instantiated, which is not the case here. – Jarod42 Aug 22 '19 at 12:46
  • The error occurs only with Clang compilation against C++2a, and not against C++17: https://wandbox.org/permlink/11Zw3rbSNDM9ZYZZ. – Daniel Langr Aug 22 '19 at 12:48
  • 10
    [temp.spec#temp.inst-10](http://eel.is/c++draft/temp.spec#temp.inst-10) *"An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement ([stmt.if]), unless such instantiation is required."* – Jarod42 Aug 22 '19 at 12:52
  • Unsure if `constexpr` makes instantiation required though. – Jarod42 Aug 22 '19 at 13:17
  • I can't reproduce this compilation error. I only see something about `NoCopy` not being a literal type (which is easily fixable, by adding a `constexpr` default constructor). – Brian Bi Aug 22 '19 at 14:58
  • 1
    For me it work only in C++17, but not C++14. I suspect the fact that constexpr variables are now inline – Guillaume Racicot Aug 22 '19 at 15:11
  • @Brian, it doesn't compile with `-std=c++11` or `-std=c++14`, but compile with `-std=c++17` or `-std=c++2a`. PS I added `constexpr` default constructor. – anton_rh Aug 22 '19 at 15:22
  • 2
    The definition of static data members and non-deleted member functions should not be instantiated ([\[temp.inst\]/(2.1)](http://eel.is/c++draft/temp.inst#2)). But in this particular snippet, the in-class initialization of a static constexpr data member is not a definition prior to C++17. I'd say the behavior is underspecified in this case. (Though it doesn't affect `static constexpr bool` members in C++17 and later, it still matters for `static const bool` members that are not `constexpr`.) – cpplearner Aug 22 '19 at 19:53
  • 1
    This seems to be related to [a number of core issues](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2335) on the subject of just what happens while instantiating a class template and its `constexpr` members. – Davis Herring Aug 23 '19 at 02:37
  • Constructor does not have any return type. Then how `constexpr NoCopy() = default;` this line is compiled in gcc ? – K_peanutButter Sep 24 '19 at 04:17
  • @KamalPancholi, `constexpr` is not a type. It is a keyword that means that this constructor can be used in constant expressions. – anton_rh Sep 25 '19 at 04:32

2 Answers2

1

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.

Guss
  • 30,470
  • 17
  • 104
  • 128
-1

I think this problem is referred by the following standard lines on template :

An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement, unless such instantiation is required.

So the Clang compiler do the right things. GCC in this case erronously don't give you an error.

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 fact with the c++17 there was a change in the template standard.

Zig Razor
  • 3,381
  • 2
  • 15
  • 35
  • 3
    I'm not sure how this answers the question, `noexcept_copy` is a static data member, so if anything those lines seem to suggest that GCC is correct. The question, as pointed out in the comments, is whether using `constexpr` in a static data member makes it "required" or not. If there were changes in C++17 it may be useful to know what are the relevant changes that make the difference here. – jdehesa Jan 29 '20 at 09:17