Search code examples
c++enumslanguage-lawyerpartial-specialization

Partial specialization of nested class on enum defined inside a template class


Please see the following code:

template <class T>
struct X {
  enum class E { e0 };

  template <class U, E e>
  struct Y {};

  template <class U>
  struct Y<U, E::e0> {
    static int f() { return 0; }
  };
};

int main() {
  X<int>::Y<int, X<int>::E::e0>::f();
}

VC++ 15.7.5 generates an error message:

1>  test.cpp
1>  some_directory\test.cpp(15): error C2039: 'f': is not a member of 'X<int>::Y<int,X<int>::E::e0>'
1>  some_directory\test.cpp(15): note: see declaration of 'X<int>::Y<int,X<int>::E::e0>'
1>  some_directory\test.cpp(15): error C3861: 'f': identifier not found

However, GCC & Clang seem to accept this code.

Which one is correct?

EDIT

On older version of VC++ generates the following error message:

source_file.cpp(9): error C2754: 'X<T>::Y<U,>': a partial specialization cannot have a dependent non-type template parameter
source_file.cpp(12): note: see reference to class template instantiation 'X<T>' being compiled
source_file.cpp(16): error C2039: 'f': is not a member of 'X<int>::Y<int,X<int>::E::e0>'
source_file.cpp(16): error C3861: 'f': identifier not found

So I think the reason VC++ refuses to compile is that enum class E is defined inside a template class. Indeed, the error disappears (both in the old version and the recent 15.7.5) if I move enum class E outside X.

Is this case really a case of partial specialization on a dependent non-type template parameter?


Solution

  • GCC and Clang are correct to accept it. This is all according to [temp.class.spec]/5, which also has a supporting example:

    A class template partial specialization may be declared in any scope in which the corresponding primary template may be defined ([namespace.memdef], [class.mem], [temp.mem]). [ Example:

    template<class T> struct A {
      struct C {
        template<class T2> struct B { };
        template<class T2> struct B<T2**> { };      // partial specialization #1
      };
    };
    
    // partial specialization of A<T>​::​C​::​B<T2>
    template<class T> template<class T2>
      struct A<T>::C::B<T2*> { };                   // #2
    
    A<short>::C::B<int*> absip;                     // uses partial specialization #2
    

     — end example ]

    A workaround with MSVC may be to try and specialize the member template at namespace scope.


    As for your edit, I would say MSVC is still wrong. The relevant standardese is at [temp.class.spec]/8:

    Within the argument list of a class template partial specialization, the following restrictions apply:

    • The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization. [ Example:

      template <class T, T t> struct C {};
      template <class T> struct C<T, 1>;              // error
      
      template< int X, int (*array_ptr)[X] > class A {};
      int array[5];
      template< int X > class A<X,&array> { };        // error
      

       — end example ]

    Applying it to your example in struct Y<U, E::e0>: does the type of the enumeration depend on another template argument to Y? The answer is no. It certainly depends on X<T>, but that is another template specialization. Not the same template we are partially specializing when writing our definition.