Last updated:2026-10-12
C++ Prefer Class Templates over Function Templates¶
I recently came across a very interesting template question — a good test of how well you have mastered the basics of function templates versus class templates. What goes wrong with the example below?
How would you usually fix it?
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;
}
The answer: the last two specializations simply do not compile.
(1) The first, template <> void foo<float, 3>::bar(), is a full specialization — no problem there;
(2) the second, template <int R> void foo<double, R>::bar(), attempts a partial specialization — but partial specialization of function templates is not allowed! The compiler parses it as a new function template overload, the signature does not match, and it errors out.
The Fix: Enter Class Templates¶
The C++ rule is: function templates can only be fully specialized, never partially; class templates can do both. So the idiom is to pull "logic that must branch on types" out of the function template into a class template (usually a trait/policy) and partially specialize the class:
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); }
This is also standard practice in the STL: iterator_traits, __type_traits — all of them follow the "class template carries partial specializations" pattern.
Summary¶
(1) Function templates: overloading + full specialization only, no partial specialization;
(2) when you need "a special implementation for a certain family of types", move the implementation into a class template and dispatch via partial specialization/traits;
(3) since C++11, many cases can branch directly inside the function body with if constexpr, skipping the class template entirely — but its trade-off versus partial specialization is left for you to ponder.
Related Topics¶
- C++ Avoiding the Default Constructor | C++那些事
- C++ Class Initializer List Idiom | C++那些事
- C++ copy-and-swap Idiom | C++那些事
- C++ Dependency Injection | C++那些事
- C++ enum class Strongly Typed Enums | C++那些事
- C++ Header-Only vs Implementation Separation | C++那些事
- C++ Lazy Evaluation Idiom | C++那些事
- C++ Lookup Table Idiom | C++那些事
- C++ Member Pointers and Type Information | C++那些事
- C++ Mixin Idiom | C++那些事