Вопрос: Как можно сделать специализацию класса для нескольких типов?

Лучший ответ: Предположим, есть общий шаблон сериализации данных для работы с абстрактными потоками ввода-вывода.
Обобщенный шаблон такой сущности может выглядеть вот так.template< typename TValueType >
struct Serializer final
{
	static inline const bool Read( const BasicStream& stream, TValueType& value_storage );

	static inline const bool Write( BasicStream& stream, const TValueType& value_storage );
};
У этого шаблона может присутствовать набор частных инстанцирований для простых и стандартных типов.Примерtemplate<>
struct Serializer<int8_t> final
{
	static inline const bool Read( const BasicStream& stream, int8_t& value_storage );

	static inline const bool Write( BasicStream& stream, const int8_t& value_storage );
};

// ...

template<>
struct Serializer<float> final
{
	static inline const bool Read( const BasicStream& stream, float& value_storage );

	static inline const bool Write( BasicStream& stream, const float& value_storage );
};
Любой пользователь библиотеки всегда может определить частное инстанцирование сериализатора для своего типа точно в такой же манере. А можно просто отдать свой пользовательский тип на откуп обобщенному шаблону.
А теперь вкусненькое. Среди стандартных типов есть и std::string, и std::vector, и std::map. Если пользователь решит сериализовать весь контейнер своих пользовательских типов, нам не стоит обязывать его писать частное инстанцирование сериализатора для контейнера. Нам надо предусмотреть сериализацию любого стандартного контейнера с любым пользовательским типом.
Мы это делаем с помощью частичной специализации шаблона сериализатора.Для произвольной строкиtemplate< typename TCharType, typename TCharTraits, typename TAllocator >
struct Serializer<std::basic_string<TCharType, TCharTraits, TAllocator>> final
{
	static inline const bool Read( const BasicStream& stream, std::basic_string<TCharType, TCharTraits, TAllocator>& value_storage );

	static inline const bool Write( BasicStream& stream, const std::basic_string<TCharType, TCharTraits, TAllocator>& value_storage );
};Для std::vectortemplate< typename TElement, typename TAllocator >
struct Serializer<std::vector<TElement, TAllocator>> final
{
	static inline const bool Read( const BasicStream& stream, std::vector<TElement, TAllocator>& value_storage );

	static inline const bool Write( BasicStream& stream, const std::vector<TElement, TAllocator>& value_storage );
};Для std::pairtemplate< typename TLeftElement, typename TRightElement >
struct Serializer<std::pair<TLeftElement, TRightElement>> final
{
	static inline const bool Read( const BasicStream& stream, std::pair<TLeftElement, TRightElement>& value_storage );

	static inline const bool Write( BasicStream& stream, const std::pair<TLeftElement, TRightElement>& value_storage );
};
И так далее.
А вот как может выглядеть внешнее определение функции при частичной специализации.Пример чтения из стрима в векторtemplate< typename TElement, typename TAllocator >
inline const bool Serializer<std::vector<TElement, TAllocator>>::Read(
	const BasicStream& stream,
	std::vector<TElement, TAllocator>& value_storage
)
{
	size32_t stored_size;
	Serializer<size32_t>::Read( stream, stored_size );

	value_storage.resize( stored_size );
	for( TElement& element : value_storage )
	{
		Serializer<TElement>::Read( stream, element );
	}

	return true;
}

Худший ответ: Этот прием называется SFINAE.Repl.itПример#include <iostream>
#include <type_traits>
#include <string>

template <class T, class Enable = void>
class Foo {};

template<class T>
class Foo<T, typename std::enable_if<std::is_integral<T>::value>::type> {
  public:
  std::string wow() {
    return "WOW";
  }
};

template<class T>
class Foo<T, typename std::enable_if<std::is_class<T>::value>::type> {
  public:
  std::string wow() {
    return "Meo";
  }
};

class Bar {};

int main() {
  std::cout << Foo<int>().wow() << '\n';
  std::cout << Foo<long>().wow() << '\n';
  std::cout << Foo<Bar>().wow();

  return 0;
}WOW
WOW
Meo