c++templateslanguage-lawyerexplicit-specializationimplicit-instantiation

Redefinition of deleted function of implicitly instantiated class


Consider the following example from [temp.inst.3]:

template<class T>
struct C {
  void f() { T x; }
  void g() = delete;
};
C<void> c;                      // OK, definition of C<void>​::​f is not instantiated at this point
template<> void C<int>::g() { } // error: redefinition of C<int>​::​g

(godbolt)

It compiles fine on the latest compilers (GCC, Clang and MSVC), but, according to the comment here, it should result in an error due to redefinition C<int>::g, which should follow from what the aforementioned clause says (bold emphasis mine):

The implicit instantiation of a class template specialization causes

  • the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
  • the implicit instantiation of the definitions of deleted member functions, unscoped member enumerations, and member anonymous unions.

I suspect this is because there's also [temp.expl.spec.14] saying:

A member or a member template of a class template may be explicitly specialized for a given implicit instantiation of the class template, even if the member or member template is defined in the class template definition. An explicit specialization of a member or member template is specified using the syntax for explicit specialization.

Note that there's no exemption for functions defined as deleted here. This seems contradictory to me. Maybe it's implied that such explicit specializations, even though they "may be", can still cause redefinition issues if they happen due to other clauses of the Standard, but it seems not to be interpreted so by the compilers.

So, am I correct thinking there's a problem in the wording of clauses themselves here, or am I missing something and the compilers are correct (but then the example is wrong)?


Solution

  • As pointed out in the comments, the rule that deleted member functions are instantiated when the enclosing class is instantiated was added by CWG2260.

    I believe the compilers just haven't implemented this rule yet (although they don't actually have to, because the example actually has IFNDR). Searching the GCC codebase for "DR2260" and "DR 2260" (the usual way the GCC devs refer to core issues) turned up no hits, and in Clang there is an open issue about the fact that it doesn't implement the expected behaviour after CWG2260.

    According to the standard, here's why the code is IFNDR:

    Under [temp.inst]/2, a class template specialization must be instantiated when it is used in a way that requires it to be complete:

    Unless a class template specialization is a declared specialization, the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. [...]

    When the compiler sees the declaration

    template<> void C<int>::g() { }
    

    [temp.expl.spec]/5 applies:

    A member function, a member function template, a member class, a member enumeration, a member class template, a static data member, or a static data member template of a class template may be explicitly specialized for a class specialization that is implicitly instantiated; in this case, the definition of the class template shall be reachable from the explicit specialization for the member of the class template. If such an explicit specialization for the member of a class template names an implicitly-declared special member function ([special]), the program is ill-formed.

    The member function C<int>::g cannot be specialized unless C<int> is instantiated and that instantiation produces a complete type ("definition [...] shall be reachable"), therefore, writing out the explicit specialization of C<int>::g triggers implicit instantiation under [temp.inst]/2. In fact, that implicit instantiation happens, essentially, at the :: token, right before g is seen, and that means that by the time the compiler gets to g, it's too late to specialize C<int>::g since it has been instantiated already. See [temp.expl.spec]/7

    If a template, a member template or a member of a class template is explicitly specialized, a declaration of that specialization shall be reachable from every use of that specialization that would cause an implicit instantiation to take place, in every translation unit in which such a use occurs; no diagnostic is required. If the program does not provide a definition for an explicit specialization and either the specialization is used in a way that would cause an implicit instantiation to take place or the member is a virtual member function, the program is ill-formed, no diagnostic required. An implicit instantiation is never generated for an explicit specialization that is declared but not defined.

    Note that the wording of this paragraph is not perfect: there is no "use" of C<int>::g in the program, so the rule that the declaration of the explicit specialization shall be reachable from "every use" of C<int>::g that would cause an implicit instantiation is not technically violated. However, the example to this paragraph shows that it was also meant to include the case where an implicit instantiation of an enclosing entity causes an implicit instantiation of the specialized entity:

    // ...
    template<class T> struct A {
      enum E : T;
      enum class S : T;
    };
    template<> enum A<int>::E : int { eint };           // OK
    template<> enum class A<int>::S : int { sint };     // OK
    template<class T> enum A<T>::E : T { eT };
    template<class T> enum class A<T>::S : T { sT };
    template<> enum A<char>::E : char { echar };        // error: A<char>​::​E was instantiated
                                                        // when A<char> was instantiated
    template<> enum class A<char>::S : char { schar };  // OK
    

    In the example, because A::E has been defined by the time we try to explicitly specialize A<char>::E, it is too late to do so: A<char> is instantiated at the ::, and this causes the implicit instantiation of (the definition of) A::E. The same applies to the explicit specialization of C<int>::g, which is why the example to [temp.inst]/3 is also correct.