Saltar a contenido

Última actualización:2026-10-12

C++: plantillas de clase antes que plantillas de función

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

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

Comentarios