93
votes

I need to specialize template member function for some type (let's say double). It works fine while class X itself is not a template class, but when I make it template GCC starts giving compile-time errors.

#include <iostream>
#include <cmath>

template <class C> class X
{
public:
   template <class T> void get_as();
};

template <class C>
void X<C>::get_as<double>()
{

}

int main()
{
   X<int> x;
   x.get_as();
}

here is the error message

source.cpp:11:27: error: template-id
  'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
  'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
  template<class C> template<class T> void X::get_as()

How can I fix that and what is the problem here?

Thanks in advance.

3
this is illegal in the current standard, to specialize, you have to specialize the class as well...Nim
but it works if the class is not template. Is it illegal too?ledokol
nope, that is perfectly fine, it's only for class templates that this rule applies (AFAIK).Nim

3 Answers

114
votes

It doesn't work that way. You would need to say the following, but it is not correct

template <class C> template<>
void X<C>::get_as<double>()
{

}

Explicitly specialized members need their surrounding class templates to be explicitly specialized as well. So you need to say the following, which would only specialize the member for X<int>.

template <> template<>
void X<int>::get_as<double>()
{

}

If you want to keep the surrounding template unspecialized, you have several choices. I prefer overloads

template <class C> class X
{
   template<typename T> struct type { };

public:
   template <class T> void get_as() {
     get_as(type<T>());
   }

private:
   template<typename T> void get_as(type<T>) {

   }

   void get_as(type<double>) {

   }
};
27
votes

If one is able to used std::enable_if we could rely on SFINAE (substitution failure is not an error)

that would work like so (see LIVE):

#include <iostream>
#include <type_traits>

template <typename C> class X
{
public:
    template <typename T, 
              std::enable_if_t<!std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as T" << std::endl; }

    template <typename T, 
              std::enable_if_t<std::is_same_v<double,T>, int> = 0> 
    void get_as() { std::cout << "get as double" << std::endl; }
};

int main() {
   X<int> d;
   d.get_as<double>();

   return 0;
}

The ugly thing is that, with all these enable_if's only one specialization needs to be available for the compiler otherwise disambiguation error will arise. Thats why the default behaviour "get as T" needs also an enable if.

3
votes

Probably the cleanest way to do this in C++17 and on-wards is to use a if constexpr in combination with the std::is_same_v type trait without explicitly specialisation at all:

#include <iostream>
#include <type_traits>

template <typename C>
class X {
  public:
    template <typename T> 
    void get_as() { 
      // Implementation part for all types
      std::cout << "get as ";

      // Implementation part for each type separately
      if constexpr (std::is_same_v<double, T>) {
        std::cout << "'double'";
      } else if constexpr (std::is_same_v<int, T>) {
        std::cout << "'int'";
      } else {
        std::cout << "(default)";
      }

      // Implementation part for all types
      std::cout << std::endl;
      return;
    }
};

int main() {
  X<int> d {};
  d.get_as<double>(); // 'double'
  d.get_as<int>();    // 'int'
  d.get_as<float>();  // (default)

  return EXIT_SUCCESS;
}

Try it here!


If you need to have a return type as well you could declare the return type as auto:

template <typename T> 
auto get_as() { 
  if constexpr (std::is_same_v<double, T>) {
    return 0.5;
  } else {
    return 0;
  }
}