Templates

Reviewed & published by Brayan K

By the end of this lesson you'll be able to write one piece of code that works with any type — function templates, class templates, multiple type parameters, defaults, and a first taste of specialization — the exact technique the entire C++ Standard Library is built on.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A template is a cookie cutter. The cutter defines the shape; the dough is the type. Press it into sugar dough and you get a sugar cookie; press it into gingerbread and you get a gingerbread cookie — same shape, different material. You write the shape once (template<typename T>) and the compiler stamps out a concrete version for every type you actually use. You don't get an int version until you press the cutter into an int.

1. Function Templates & Type Deduction

Without templates you'd copy maxOf once per type: one for int, one for double, one for string. A function template replaces all of them with a single recipe. You write template<typename T> above the function, then use T wherever a type would go. T is a placeholder: the compiler stamps out a real version the moment you call it. And thanks to type deduction, you usually don't even name the type — the compiler reads your arguments and works T out for itself.

#include <iostream>
#include <string>
using namespace std;

// A function template = one recipe the compiler stamps out per type.
// "template <typename T>" introduces T, a placeholder for ANY type.
template <typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;   // works for anything that supports >
}

int main() {
    // TEMPLATE TYPE DEDUCTION: you don't write the type — the compiler
    // looks at the arguments and figures T out for you.
    cout << maxOf(10, 20) << endl;        // 20    (T deduced as int)
    cout << maxOf(3.14, 2.71) << endl;    // 3.14  (T deduced as double)
    cout << maxOf('a', 'z') << endl;      // z     (T deduced as char)

    // You CAN also state the type explicitly with <...>:
    cout << maxOf<int>(5, 3) << endl;     // 5     (forces T = int)

    // Strings work too — string supports > (alphabetical compare):
    string x = "apple", y = "pear";
    cout << maxOf(x, y) << endl;          // pear
    return 0;
}

// ✅ Expected output:
//    20
//    3.14
//    z
//    5
//    pear

Your turn. The program below is almost complete — write the template<typename T> line and the comparison so minOf returns the smaller value. Fill in the two blanks marked ___ using the hints, then run it.

#include <iostream>
using namespace std;

// 🎯 YOUR TURN — write your own generic function template.

// 1) Declare the template parameter on the line below.
___                              // 👉 template <typename T>

// 2) Make minOf take two T's and return the SMALLER one.
T minOf(T a, T b) {
    return ___;                  // 👉 (a < b) ? a : b
}

int main() {
    cout << minOf(10, 20) << endl;       // expect 10  (T = int)
    cout << minOf(3.5, 1.2) << endl;     // expect 1.2 (T = double)
    cout << minOf('z', 'a') << endl;     // expect a   (T = char)

    // ✅ Expected output:
    //    10
    //    1.2
    //    a
    return 0;
}

2. Class Templates, Multiple Params & Defaults

Templates aren't just for functions — a class template makes a whole class generic. Box<T> can hold an int, a string, or anything else. You can declare multiple type parameters too: Pair<K, V> has two independent placeholders. And a default template argument (typename V = int) lets callers leave a parameter off and get a sensible default — just like a default function argument. One gotcha: class templates need the type written in <...> when you create an object; deduction only happens automatically for function templates.

#include <iostream>
#include <string>
using namespace std;

// A CLASS template — a generic container. T is filled in per object.
template <typename T>
class Box {
    T value;                              // a Box of "whatever T is"
public:
    Box(T v) : value(v) {}
    T get() const { return value; }
    void set(T v) { value = v; }
};

// MULTIPLE TYPE PARAMETERS — two independent placeholders, K and V.
// V has a DEFAULT TEMPLATE ARGUMENT: if you omit it, V becomes int.
template <typename K, typename V = int>
class Pair {
    K key;
    V value;
public:
    Pair(K k, V v) : key(k), value(v) {}
    void show() const { cout << key << " => " << value << endl; }
};

int main() {
    Box<int> a(42);                       // T = int
    Box<string> b("hi");                  // T = string
    cout << a.get() << " " << b.get() << endl;   // 42 hi

    // Class templates need the type in <...> — there is no deduction here
    // (until C++17 CTAD; we spell it out to keep things clear).
    Pair<string, double> p1("pi", 3.14);  // K=string, V=double
    Pair<string> p2("count", 7);          // V omitted -> defaults to int
    p1.show();                            // pi => 3.14
    p2.show();                            // count => 7
    return 0;
}

// ✅ Expected output:
//    42 hi
//    pi => 3.14
//    count => 7

Now you try. Creating an object from a class template means putting the type inside the angle brackets. Fill in the two blanks so price is a Box of double and name is a Box of string:

#include <iostream>
#include <string>
using namespace std;

template <typename T>
class Box {
    T value;
public:
    Box(T v) : value(v) {}
    T get() const { return value; }
};

int main() {
    // 🎯 YOUR TURN — instantiate Box with the right types.

    // 1) A Box that holds a double set to 9.99
    Box<___> price(9.99);        // 👉 the type goes in the < > brackets

    // 2) A Box that holds a string set to "C++"
    ___ name("C++");             // 👉 Box<string> name("C++");

    cout << price.get() << endl;  // expect 9.99
    cout << name.get() << endl;   // expect C++

    // ✅ Expected output:
    //    9.99
    //    C++
    return 0;
}

3. Specialization & Where Errors Appear

Sometimes the general template is wrong for one specific type. Template specialization lets you hand-write a dedicated version for exactly that type, using template<>. The classic case is comparing C-strings: the general > compares pointer addresses, not the text, so you specialize to compare the real characters. This section also shows the single most confusing thing about templates: errors appear at instantiation, not at definition. A template you never call is never type-checked — the compiler only complains when you actually use it with a type that doesn't fit.

#include <iostream>
#include <string>
using namespace std;

// The GENERAL template: maxOf works for any type with >.
template <typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;
}

// A FULL SPECIALIZATION for const char* (C-strings). Without it, > would
// compare POINTER addresses, not the text. "template <>" means: here is a
// hand-written version just for this one type.
template <>
const char* maxOf<const char*>(const char* a, const char* b) {
    return (string(a) > string(b)) ? a : b;   // compare the actual text
}

int main() {
    cout << maxOf(10, 20) << endl;            // 20   -> general template
    cout << maxOf("apple", "pear") << endl;   // pear -> the specialization

    // WHERE ERRORS APPEAR: a template is only checked when it is USED
    // (instantiated). The next line would NOT compile, because a vector has
    // no operator>, and the error points HERE, at the call site:
    //   maxOf(vector<int>{1}, vector<int>{2});  // error happens on use
    return 0;
}

// ✅ Expected output:
//    20
//    pear

🔎 Deep Dive: typename vs class

You'll see both template<typename T> and template<class T> in real code. For declaring a type parameter they mean exactly the same thing. Modern code prefers typename because the placeholder doesn't have to be a class — it can be int, double, a pointer, anything.

template <typename T> T id(T x) { return x; }  // recommended
template <class T>    T id(T x) { return x; }  // identical meaning

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCode
Function templatetemplate <typename T> T id(T x);
Call (deduced)maxOf(3, 7)
Call (explicit type)maxOf<double>(3, 7)
Class templatetemplate <typename T> class Box { ... };
Instantiate classBox<int> b(42);
Multiple paramstemplate <typename K, typename V>
Default argtemplate <typename V = int>
Specializationtemplate <> T maxOf<T>(...) { ... }

Mini-Challenge: a generic clamp()

No blanks this time — just a brief and an outline. Write a function template clamp that pins a value between a low and high bound, then prove it's generic by calling it with both ints and doubles. Check your output against the example in the comments.

#include <iostream>
#include <string>
using namespace std;

// 🎯 MINI-CHALLENGE: a generic clamp()
// 1. Write a function template "clamp" with one type parameter T.
// 2. It takes (T value, T low, T high) and returns:
//      low   if value < low
//      high  if value > high
//      value otherwise
// 3. In main, call it with ints AND doubles to prove it's generic.
//
// ✅ Expected (clamp(15, 0, 10)=10, clamp(-3, 0, 10)=0, clamp(2.5, 1.0, 5.0)=2.5):
//    10
//    0
//    2.5

int main() {
    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

Which line correctly declares a function template with one type parameter T?

  • template typename T
  • template <typename T>
  • typename <template T>
  • template T<typename>

Answer: template <typename T>. template <typename T> introduces T, a placeholder for any type, before the function or class.

In the call maxOf(10, 20) with template <typename T> T maxOf(T, T), what type is T deduced as?

  • double
  • int
  • long
  • It must be stated explicitly

Answer: int. Both arguments are int literals, so template type deduction makes T = int. No explicit <int> is needed.

What does template <typename V = int> add to a class template?

  • A default template argument for V
  • A forced specialization
  • A runtime default value
  • A second base class

Answer: A default template argument for V. = int is a default template argument: if the caller omits V, it becomes int, just like a default function argument.

Is there any difference between template <typename T> and template <class T> for declaring a type parameter?

  • typename allows non-class types only
  • class allows non-class types only
  • No difference at all
  • class is faster to compile

Answer: No difference at all. For declaring a type parameter they are identical. typename is preferred because the placeholder need not be a class.

When is a function template actually type-checked for a given type?

  • At its definition
  • When it is instantiated (used) with that type
  • Only at link time
  • Never

Answer: When it is instantiated (used) with that type. A template is only checked when instantiated, which is why errors appear at the call site, not the definition.

Creating an object from a class template Box<T> requires what?

  • Nothing — the type is always deduced
  • The type written in angle brackets, e.g. Box<int>
  • A specialization first
  • A virtual destructor

Answer: The type written in angle brackets, e.g. Box<int>. Before C++17 CTAD, class templates need the type spelled out, e.g. Box<int> b(42). Function templates deduce; classes (here) do not.

Why specialize maxOf for const char* (C-strings)?

  • To make it run faster
  • Because the general > compares pointer addresses, not the text
  • Because const char* has no operator>
  • To avoid a linker error

Answer: Because the general > compares pointer addresses, not the text. For const char*, the general > compares pointer addresses. The specialization compares the actual characters instead.

What syntax begins a full template specialization?

  • template <T>
  • specialize <T>
  • template <>
  • template default <>

Answer: template <>. template <> introduces a full specialization — a hand-written version for one specific type.

Why must template definitions usually live in header files?

  • Headers compile faster
  • The compiler needs the full body visible where the template is used
  • Templates cannot appear in .cpp files at all
  • To avoid name mangling

Answer: The compiler needs the full body visible where the template is used. Each concrete version is generated at the point of use, so the full definition must be visible there — hiding it in a .cpp causes undefined-reference linker errors.

What does maxOf(5, 3.14) cause, given template <typename T> T maxOf(T a, T b)?

  • T = double automatically
  • T = int automatically
  • A deduction failure — T can't be both int and double
  • A runtime exception

Answer: A deduction failure — T can't be both int and double. One argument is int, the other double, so T cannot be deduced to a single type. Make the types match or force maxOf<double>(5, 3.14).

Continue this course

Frequently asked questions

What's the difference between typename and class in a template?

Nothing, for declaring a type parameter. template <typename T> and template <class T> are identical. typename is the more modern, clearer choice because the placeholder doesn't have to be a class — it can be int, double, or any type.

Why must template definitions go in header files?

The compiler can only generate the int, double, or string version of a template when it can see the full template body at the point you use it. If the body is hidden in a .cpp file, other files get a 'undefined reference' linker error. Keep template code in the header (.h/.hpp).

Why are template error messages so long and cryptic?

A template is only type-checked when it's instantiated (used) with a concrete type, so the error appears deep inside the generated code, not at the template definition. Read from the top, find the first error, and look at the call site that triggered it — that's usually the real culprit.

Does the compiler deduce types for class templates too?

Function templates have always deduced types from their arguments (maxOf(10, 20) needs no <int>). Class templates required you to spell out Box<int> until C++17 added CTAD (class template argument deduction), which lets you write Box b(42); in modern code. This lesson spells the types out for clarity.

When should I write a template specialization?

When the general template would behave wrongly or inefficiently for one specific type. The classic example is comparing C-strings (const char*): the general > compares pointer addresses, so you specialize to compare the actual text instead.

Related lessons