Function templates, class templates, specialization, and variadic templates. The compile-time machinery that powers the entire standard library.
You write a compare function for int. Then you need one for double. Then for string. The logic is identical every time — only the type changes. Do you copy-paste three times?
Templates let you write the logic once, with the type as a parameter. The compiler generates the type-specific code for you.
c++ // Without templates: three nearly identical functions int compare(int a, int b) { if (a < b) return -1; if (b < a) return 1; return 0; } double compare(double a, double b) { /* same */ } int compare(string a, string b) { /* same */ } // With templates: one definition covers all types template <typename T> int compare(const T &a, const T &b) { if (a < b) return -1; if (b < a) return 1; return 0; }
Click a type to "instantiate" the template. The compiler generates a new function for each type used.
A function template is a formula for generating type-specific functions. The compiler deduces the template arguments from the function call:
c++ template <typename T> T mymax(const T &a, const T &b) { return a < b ? b : a; } mymax(3, 7); // T deduced as int → mymax<int> mymax(3.14, 2.72); // T deduced as double → mymax<double> mymax(string("hi"), string("bye")); // T = string // Explicit template argument when deduction fails mymax<double>(3, 3.14); // force T = double
When the compiler sees mymax(3, 7), it instantiates the template: generates a real function mymax<int> with T replaced by int. Each distinct type produces a separate instantiation.
The compare template uses < on type T. If you instantiate it with a type that doesn't have operator<, you get a compile error (at instantiation time, not definition time). Templates implicitly require certain operations from their type parameters.
A class template is a blueprint for generating classes. Unlike function templates, the compiler cannot deduce template arguments for class templates — you must specify them explicitly:
c++ template <typename T> class Blob { public: typedef T value_type; Blob() : data(make_shared<vector<T>>()) {} Blob(initializer_list<T> il) : data(make_shared<vector<T>>(il)) {} void push_back(const T &t) { data->push_back(t); } void push_back(T &&t) { data->push_back(std::move(t)); } T& back() { return data->back(); } T& operator[](size_t i) { return (*data)[i]; } size_t size() const { return data->size(); } private: shared_ptr<vector<T>> data; }; Blob<int> ia = {0, 1, 2, 3}; Blob<string> sa = {"hi", "bye"};
Blob<int> and Blob<string> are completely separate types with no relationship to each other. The compiler generates the entire class for each distinct template argument.Member functions of class templates are themselves function templates. They are only instantiated when used — if you never call push_back, the compiler never generates code for it. This means a class template can be instantiated with a type that supports some but not all operations.
c++ // C++11: template aliases with using template <typename T> using twin = pair<T, T>; twin<int> p; // pair<int, int> twin<string> s; // pair<string, string>
Blob<int> and Blob<string> the same type?Template parameters can be types (typename T) or values (int N). They can also have defaults:
c++ // Type parameter template <typename T> void print(const T &val) { cout << val; } // Non-type parameter (must be a constant expression) template <unsigned N, unsigned M> int compare(const char (&a)[N], const char (&b)[M]) { return strcmp(a, b); } compare("hi", "world"); // N=3, M=6 (includes null terminator) // Default template arguments template <typename T, typename F = less<T>> int compare(const T &a, const T &b, F f = F()) { if (f(a, b)) return -1; if (f(b, a)) return 1; return 0; }
A class (template or not) can have member functions that are themselves templates:
c++ template <typename T> class Blob { // Member template: construct from any iterator range template <typename It> Blob(It b, It e) : data(make_shared<vector<T>>(b, e)) {} };
typename and class are interchangeable. By convention, typename is preferred because it makes clear the parameter can be any type, not just a class.When calling a function template, the compiler deduces the template arguments from the function arguments. The deduction rules are precise:
| Function Parameter | Argument | Deduced T |
|---|---|---|
const T& | int i | int (reference & const stripped) |
const T& | const int ci | int |
T& | int i | int |
T (by value) | const int ci | int (top-level const dropped) |
T&& | lvalue int i | int& (reference collapsing!) |
T&& | rvalue 42 | int |
compare(3, 3.14) is an error because T can't be both int and double. You must either cast or provide explicit template arguments.When the return type depends on the template parameters, use a trailing return type with decltype:
c++ template <typename It> auto fcn(It beg, It end) -> decltype(*beg) { return *beg; // returns a reference to the element }
compare(3, 3.14) fail for template<typename T> int compare(const T&, const T&)?Two of the most important utilities in modern C++ — std::move and std::forward — are implemented using template mechanics.
c++ // Simplified std::move implementation template <typename T> typename remove_reference<T>::type&& move(T&& t) { return static_cast<typename remove_reference<T>::type&&>(t); }
It's just a cast. std::move doesn't move anything — it casts its argument to an rvalue reference, which allows the move constructor/assignment to be called.
Forwarding means passing arguments to another function preserving their lvalue/rvalue nature and const/non-const qualifiers:
c++ template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2) { f(std::forward<T2>(t2), std::forward<T1>(t1)); } // If t1 was an lvalue, forward preserves it as lvalue // If t1 was an rvalue, forward preserves it as rvalue
T&& is a forwarding reference (also called universal reference), not an rvalue reference. It can bind to both lvalues and rvalues through reference collapsing rules. This is how perfect forwarding works.| Reference Collapsing | Result |
|---|---|
T& & | T& |
T& && | T& |
T&& & | T& |
T&& && | T&& |
A variadic template takes a variable number of template parameters, called a parameter pack:
c++ // Base case: one argument template <typename T> ostream& print(ostream &os, const T &t) { return os << t; } // Recursive case: peel off first, recurse on rest template <typename T, typename... Args> ostream& print(ostream &os, const T &t, const Args&... rest) { os << t << ", "; return print(os, rest...); // expand pack: recurse } print(cout, 42, "hello", 3.14); // → print(cout, 42, "hello", 3.14) T=int, Args={char*,double} // → print(cout, "hello", 3.14) T=char*, Args={double} // → print(cout, 3.14) T=double, base case
c++ template <typename... Args> void foo(Args... args) { cout << sizeof...(Args) << endl; // number of type params cout << sizeof...(args) << endl; // number of function args }
Combine variadic templates with perfect forwarding for maximum generality. This is how make_shared and emplace_back work:
c++ template <typename... Args> void emplace_back(Args&&... args) { // forwards each argument preserving lvalue/rvalue alloc.construct(first_free++, std::forward<Args>(args)...); }
Sometimes the generic template doesn't work (or isn't optimal) for a specific type. A specialization provides a custom implementation for that type:
c++ // Primary template template <typename T> int compare(const T &a, const T &b) { if (a < b) return -1; if (b < a) return 1; return 0; } // Full specialization for const char* template <> // empty angle brackets = specialization int compare(const char* const &a, const char* const &b) { return strcmp(a, b); // compare strings, not pointer values }
strcmp to do the right thing. Without specialization, compare("hi", "bye") would give meaningless results.Class templates (but not function templates) can be partially specialized — specialized for a subset of template arguments:
c++ // Primary template template <typename T> struct remove_reference { using type = T; }; // Partial specialization for lvalue references template <typename T> struct remove_reference<T&> { using type = T; }; // Partial specialization for rvalue references template <typename T> struct remove_reference<T&&> { using type = T; };
<type_traits> is full of partially specialized templates that manipulate types at compile time: remove_reference, add_const, is_pointer, enable_if. They're the foundation of template metaprogramming.template <> (empty angle brackets) signify before a function definition?Let's visualize how the compiler processes templates. When you write a template call, the compiler substitutes types and generates real code. Each unique type combination produces a separate instantiation.
Select a template and provide type arguments. Watch the compiler substitute types and generate code.
compare<int> and compare<string> are two different functions. This is called code bloat in the worst case — but the compiler can optimize and inline aggressively because it knows the exact types.Templates are the foundation of the entire C++ standard library. vector, shared_ptr, sort, find, make_shared — all are templates. Understanding templates is understanding how C++ works at its core.
| This Chapter | Builds Toward |
|---|---|
| Function templates | Generic algorithms (Ch 10), standard library |
| Class templates | Container design (vector, map, etc.) |
| Type deduction | auto, decltype, lambda type inference |
| Perfect forwarding | emplace_back, factory functions, make_unique |
| Variadic templates | tuple, variant, structured bindings (C++17) |
| Specialization | Type traits, SFINAE, concepts (C++20) |
You've now covered the essential chapters of C++ Primer — from basic types through templates. Return to the chapter index to review any chapter.