'Why can I partially specialize but can't fully specialize member templates in C++?

IMO, C++ template rules seem too restrictive and compiler implementation defined. But here I have a specific behavior I've had a hard time wrapping my head around.

In the following problem, I consciously try to avoid explicitly specializing the parent class.

The problem is that, I can partially specialize a member, but can't specialize it fully. Which is really counter intuitive, because you can easily add a dummy template to a fully specialized template and make it partially specialized. What's going on here?

This matters because, as we know, you can't specialize member functions without specializing the class as well (which can be seen as a combination of this problem, need to be partially specialized, and the fact that c++ doesn't allow partially specialized functions. I don't know whether these are related but at least they're consistent), thus if you want a functionoid in your class that you can specialize you're stuck with using functors. And on top of that you need to add a dummy template parameter just to make that work!

This works:

template <class T>
class A
{
  template<typename Y, typename Z>
  struct C{
      void operator()(int x);
  };

  template<typename Z>
  struct C<int, Z>{
      void operator()(int x);
  };
};

template <class T>
template <typename Z>
void A<T>::C<int, Z>::operator()(int x){

}

But this doesn't:

template <class T>
class A
{
  template<typename Y>
  struct C{
      void operator()(int x);
  };

  template<>
  struct C<int>{
      void operator()(int x);
  };
};

template <class T>
template <>
void A<T>::C<int>::operator()(int x){

}

Sean F. in the comments pointed out that it's hard for a compiler to choose a specialization. But the problem here is that partially specializing doesn't make that problem go away. So that can't be the answer.



Solution 1:[1]

As a general rule, template function specialization is a bad idea. Use overloads or tag dispatching. For example:

template<class T>struct tag_t{};

template<class T>
auto foo(T& t){ return foo( tag_t<T>{}, t ); }

now we can use overloads to dispatch:

void foo( tag_t<int>, int& i ){ i+=3; }
template<class T>
void foo( tag_t<T>, T& t ){ t*=2; }

there is no specialization going on here. We use overload resolution rules to pick the implementation. And this works fine with members.

We only use specialization on top-level classes. Even there I am often tempted to pick implementations by tag dispatch and use of decltype, as overload resolution often gives better pattern matching.

If you really need a member class, replace your member class with a top-level class and some friend declarations.

template<class T, class Y>
struct A_C;
template <class T>
class A
{
  template<class T0, class Y0>
  friend class A_C<T0, Y0>;
  template<class Y>
  using C=A_C<T,Y>;
};
template<class T, class Y>
struct A_C {
  // implementation of A::C
};

now you have pretty unrestriced specialization of A_C here. None of your problems.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1