Última actualización:2026-10-12
C++: plantillas de clase antes que plantillas de función¶
Hace poco vi un problema de plantillas muy interesante: un buen test de cómo dominas lo básico de las plantillas de función frente a las plantillas de clase. ¿Qué falla en el ejemplo siguiente?
¿Cómo lo arreglarías normalmente?
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;
}
La respuesta: las dos últimas especializaciones directamente no compilan.
(1) La primera, template <> void foo<float, 3>::bar(), es una especialización completa: sin problema;
(2) la segunda, template <int R> void foo<double, R>::bar(), intenta una especialización parcial — ¡pero las plantillas de función no admiten especialización parcial! El compilador la analiza como una nueva sobrecarga de plantilla de función, la firma no encaja, y error directo.
¿Cómo arreglarlo? Entran las plantillas de clase¶
La regla de C++ es: las plantillas de función solo pueden especializarse por completo, no parcialmente; las plantillas de clase pueden hacer ambas cosas. Por eso el idiom es sacar la «lógica que debe ramificar según el tipo» de la plantilla de función y meterla en una plantilla de clase (normalmente un trait/policy), especializando parcialmente la clase:
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); }
Es también la operación estándar de la STL: iterator_traits, __type_traits, todos siguen el patrón de «la plantilla de clase porta la especialización parcial».
Resumen¶
(1) Plantillas de función: solo sobrecarga + especialización completa, nada de especialización parcial;
(2) cuando necesitas «una implementación especial para cierta familia de tipos», muda la implementación a una plantilla de clase y despacha mediante especialización parcial/traits;
(3) desde C++11 muchos casos se resuelven ramificando directamente en el cuerpo de la función con if constexpr, ahorrándose hasta la plantilla de clase — pero el equilibrio frente a la especialización parcial te lo dejo para pensar.
Temas relacionados¶
- C++ Evitar el constructor por defecto | C++那些事
- C++ Idioma de la lista de inicialización de clases | C++那些事
- C++ Idioma copy-and-swap | C++那些事
- C++ Inyección de dependencias | C++那些事
- C++ enum class enumeraciones fuertemente tipadas | C++那些事
- C++ Header-Only frente a separación de implementación | C++那些事
- C++ Idioma de evaluación perezosa | C++那些事
- C++ Idioma de la tabla de consulta | C++那些事
- C++ Punteros a miembros e información de tipos | C++那些事
- C++ Idioma mixin | C++那些事