Перейти к содержанию

Последнее обновление:2026-10-12

C++: классы-шаблоны вместо шаблонов функций

https://stackoverflow.com/questions/5688355/partial-specialisation-of-member-function-with-non-type-parameter

Недавно попался очень интересный вопрос про шаблоны — хорошая проверка того, насколько уверенно вы владеете базами шаблонов функций и классов. Что сломается в примере ниже?

Как вы обычно это чините?

template <typename T, int R>
struct foo {
  foo(const T& v) : value_(v) {}

  void bar() {
    std::cout << "Generic" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << value_ << std::endl;
  }

  T value_;
};

template <>
void foo<float, 3>::bar() {
  std::cout << "Float" << std::endl;
  for (int i = 0; i < 3; ++i) std::cout << value_ << std::endl;
}

template <int R>
void foo<double, R>::bar() {
  std::cout << "Double" << std::endl;
  for (int i = 0; i < R; ++i) std::cout << value_ << std::endl;
}

Ответ: последние две специализации просто не компилируются.

(1) Первая, template <> void foo<float, 3>::bar(), — полная специализация, тут всё в порядке;

(2) вторая, template <int R> void foo<double, R>::bar(), пытается сделать частичную специализацию — но частичная специализация шаблонов функций запрещена! Компилятор разбирает её как новую перегрузку шаблона функции, подпись не совпадает, и сразу ошибка.

Как починить? Вступают классы-шаблоны

Правило C++ таково: шаблоны функций можно только полностью специализировать, частично — нельзя; шаблоны классов умеют и то и другое. Поэтому идиома такая: «логику, которая должна ветвиться по типам», выносят из шаблона функции в класс-шаблон (обычно это trait/policy) и частично специализируют класс:

template <typename T, int R>
struct bar_impl {
  static void run(foo<T, R>& f) {
    std::cout << "Generic" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << f.value_ << std::endl;
  }
};

template <int R>
struct bar_impl<double, R> {  // 类模板偏特化:合法!
  static void run(foo<double, R>& f) {
    std::cout << "Double" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << f.value_ << std::endl;
  }
};

template <typename T, int R>
void foo<T, R>::bar() { bar_impl<T, R>::run(*this); }

Это стандартная практика и в STL: iterator_traits, __type_traits — все по схеме «частичную специализацию несёт класс-шаблон».

Итог

(1) Шаблоны функций: только перегрузка + полная специализация, без частичной;

(2) если нужна «особая реализация для семейства типов», переносите реализацию в класс-шаблон и разбирайте через частичную специализацию/traits;

(3) начиная с C++11 во многих случаях можно ветвиться прямо в теле функции с помощью if constexpr, вообще без класса-шаблона — но как это соотносится с частичной специализацией, решать вам.

Похожие темы

Комментарии