Suppose you write one maximum function for int, another for double, and a third for char. The logic is identical, but only the type changes. A C++ template removes that duplication by giving the compiler a recipe for producing type-specific code. It is not a value that chooses its type while the program runs: deduction picks the type at the call site, instantiation turns the recipe into concrete code, and every operation the template performs has to exist for the type it lands on.
Templates in C++: the reusable-code mental model
The declaration template <typename T> introduces T as a template parameter. When the compiler sees maximum(7, 12), it examines both arguments and deduces T = int. It can then instantiate a concrete maximum<int> function from the template. In this parameter position, typename and class mean the same thing. Prefer typename here, because it says plainly that T stands for a type.
A template is not a preprocessor macro. It remains type-checked C++ and is normally resolved through compile-time instantiation, not textual substitution. It is also different from runtime polymorphism: it needs neither a base-class pointer nor a virtual call. Templates sit late in the language, after classes, references and the standard containers, and the C++ tutorial learning path sets out that order.
Function templates: deduction, instantiation, and exact output
This complete C++17 program defines one function template and calls it with three types:
#include <iostream>
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maximum(7, 12) << '\n';
std::cout << maximum(4.5, 2.25) << '\n';
std::cout << maximum('b', 'k') << '\n';
}The output is:
12
4.5
kFor maximum(7, 12), both arguments are integers, so T = int and maximum<int> returns 12. For maximum(4.5, 2.25), T = double and the result is 4.5. For maximum('b', 'k'), T = char and the result is k. If another call also deduces T = int, it reuses the same specialisation. The compiler does not create a fresh type-specific function for every call.
There is a firm boundary: maximum(3, 4.5) fails to compile because deduction tries to make the single T both int and double. Writing maximum<double>(3, 4.5) deliberately chooses double, converts 3 to 3.0, and returns 4.5. That is one intentional repair, not a rule that explicit arguments are always better than designing a suitable two-type template.

Class templates through one worked PairStats<T> example
A class template applies the same blueprint idea to an entire class. This one stores two values and provides operations that make sense for its chosen type:
#include <iostream>
template <typename T>
class PairStats {
T first;
T second;
public:
PairStats(T a, T b) : first(a), second(b) {}
T total() const { return first + second; }
T larger() const { return (first > second) ? first : second; }
};
int main() {
PairStats<int> marks{68, 82};
PairStats<double> temperatures{36.5, 37.2};
std::cout << marks.total() << ' ' << marks.larger() << '\n';
std::cout << temperatures.total() << ' ' << temperatures.larger() << '\n';
}It prints:
150 82
73.7 37.2marks.total() computes 68 + 82 = 150 and marks.larger() returns 82, while the PairStats<double> object totals 36.5 + 37.2 = 73.7 and reports 37.2 as the larger reading. PairStats<int> and PairStats<double> are distinct class types produced from one template blueprint.
The type argument appears explicitly in PairStats<int>, while the earlier function call could deduce its type from ordinary arguments. Newer C++ versions can deduce some class template arguments when constructors or deduction guides provide enough information, but explicit arguments keep the core model clear. If constructors, member functions or const methods still feel uncertain, OOP for CS teaching exams covers them.

Non-type template parameters: an array size fixed at compile time
Templates can also accept values known at compile time. Here T is the array element type, while N is its size:
#include <cstddef>
#include <iostream>
template <typename T, std::size_t N>
T sum(const T (&values)[N]) {
T total{};
for (const T& value : values) {
total += value;
}
return total;
}
int main() {
int scores[4]{72, 81, 65, 90};
double readings[3]{18.5, 19.0, 20.5};
std::cout << sum(scores) << '\n';
std::cout << sum(readings) << '\n';
}It prints:
308
58For int scores[4]{72, 81, 65, 90}, deduction gives T = int and N = 4, so the result is 72 + 81 + 65 + 90 = 308. For double readings[3]{18.5, 19.0, 20.5}, it gives T = double and N = 3, and 18.5 + 19.0 + 20.5 = 58.0, which the stream writes as 58 because default formatting drops a trailing zero. The array-reference parameter preserves the size, unlike a plain pointer parameter, which does not carry the array length into the function.
Template specialisation and overloads: change behaviour deliberately
A specialisation replaces the primary template's behaviour for a particular type:
#include <iostream>
template <typename T>
struct Label {
static const char* text() { return "value"; }
};
template <>
struct Label<bool> {
static const char* text() { return "flag"; }
};
int main() {
std::cout << Label<int>::text() << '\n';
std::cout << Label<bool>::text() << '\n';
}It prints:
value
flagLabel<int>::text() uses the primary class template, while Label<bool>::text() matches the full specialisation. Class templates can be fully or partially specialised. Function templates can be fully specialised, but they cannot be partially specialised, so overloads are usually clearer when a function needs type-specific behaviour. Use specialisation for a genuine rule tied to a type, not as a patch for a poorly designed common interface.
Common template errors: symptom, cause, and repair
Template diagnostics become manageable when you connect each symptom to the operation that failed.
maximum(3, 4.5)reports conflicting deductions because oneTcannot simultaneously beintanddouble. Choose an intentional common type, such asmaximum<double>(3, 4.5), or redesign the function with two template types.maximum(Student{75}, Student{82})fails ifStudenthas no usableoperator>. Define the comparison that the algorithm requires, or use a comparator in a more advanced design.If the definition of
PairStats<T>exists only inpair_stats.cppandmain.cpptries to instantiatePairStats<int>, the linker may report an undefined reference. Keep template definitions visible in a header unless you are using deliberate explicit instantiation.
A long error message often describes the final failed operation far below the original call. Start with the first mention of your file, confirm the deduced type, and then locate the missing operation. That path usually exposes the real cause faster than reading every internal library line.
How assessments and coding interviews test templates
Common questions check whether you can count specialisations and recognise failed deduction. Calls identity(5) and identity(7) both use identity<int>, while identity(2.5) adds identity<double>, so the program has two specialisations. Under the one-T definition, maximum(3, 4.5) does not compile. PairStats<int> and PairStats<double> are also distinct types even when their public members have the same names.
Apply this compile-run-debug habit with the coding-round strategy for placements. For interview questions aimed at C++ itself, work through the C++ interview questions module.
C++ template exercises and the four rules worth remembering
Try three self-checks. Write swapValues<T> so a = 14, b = 9 become a = 9, b = 14. Build FixedStack<T, 3>, push 4, 7, 9, then make pop() return 9 and the new top return 7. Finally, add average() to PairStats<T> so PairStats<int>{68, 82} calculates (68 + 82) / 2.0 = 75.0 without integer truncation.
The short version:
Templates accept type or value parameters.
Deduction chooses template arguments where possible.
Instantiation creates concrete code for those arguments.
Every operation used by the template must exist for the chosen type.
Run each example, introduce one error, and repair it yourself. For a structured path through templates and the rest of the language, work through the C++ Programming course.




