Последнее обновление:2026-10-12
C++: классы-шаблоны вместо шаблонов функций¶
Недавно попался очень интересный вопрос про шаблоны — хорошая проверка того, насколько уверенно вы владеете базами шаблонов функций и классов. Что сломается в примере ниже?
Как вы обычно это чините?
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, вообще без класса-шаблона — но как это соотносится с частичной специализацией, решать вам.
Похожие темы¶
- Отказ от конструктора по умолчанию в C++ | C++那些事
- Идиома списков инициализации класса в C++ | C++那些事
- Идиома copy-and-swap в C++ | C++那些事
- Внедрение зависимостей в C++ | C++那些事
- Сильнотипизированные перечисления enum class в C++ | C++那些事
- Header-Only в C++ против разделения реализации | C++那些事
- Идиома ленивых вычислений в C++ | C++那些事
- Идиома таблиц поиска в C++ | C++那些事
- Указатели на члены и информация о типах в C++ | C++那些事
- Идиома Mixin в C++ | C++那些事