Ir para o conteúdo

Última atualização:2026-10-12

C++: templates de classe em vez de templates de função

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

Vi recentemente um problema de templates muito interessante — um bom teste de como você domina o básico de templates de função versus templates de classe. O que dá errado no exemplo abaixo?

Como você normalmente consertaria?

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;
}

A resposta: as duas últimas especializações simplesmente não compilam.

(1) A primeira, template <> void foo<float, 3>::bar(), é uma especialização completa — sem problema;

(2) a segunda, template <int R> void foo<double, R>::bar(), tenta uma especialização parcial — mas templates de função não admitem especialização parcial! O compilador a analisa como uma nova sobrecarga de template de função, a assinatura não bate e dá erro direto.

Como consertar? Entram os templates de classe

A regra do C++ é: templates de função só podem ser totalmente especializados, nunca parcialmente; templates de classe fazem as duas coisas. Por isso o idiom é tirar a «lógica que precisa ramificar por tipo» do template de função e colocá-la num template de classe (normalmente um trait/policy), especializando parcialmente a classe:

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); }

Essa também é a operação padrão da STL: iterator_traits, __type_traits, todos seguem o padrão de «o template de classe carrega a especialização parcial».

Resumo

(1) Templates de função: só sobrecarga + especialização completa, nada de especialização parcial;

(2) quando precisar de «uma implementação especial para certa família de tipos», mude a implementação para um template de classe e despache via especialização parcial/traits;

(3) desde o C++11 muitos casos dão para ramificar direto no corpo da função com if constexpr, economizando até o template de classe — mas o trade-off frente à especialização parcial fica para você pensar.

Tópicos relacionados

Comentários