Última atualização:2026-10-12
C++: templates de classe em vez de templates de função¶
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¶
- C++ evitar o construtor padrão | C++那些事
- Idiom de lista de inicialização de classes em C++ | C++那些事
- Idiom copy-and-swap em C++ | C++那些事
- Injeção de dependência em C++ | C++那些事
- C++ enum class enumerações fortemente tipadas | C++那些事
- C++ Header-Only versus separação de implementação | C++那些事
- Idiom de avaliação preguiçosa em C++ | C++那些事
- Idiom de consulta em tabela em C++ | C++那些事
- Ponteiros para membros e type info em C++ | C++那些事
- Idiom Mixin em C++ | C++那些事