A class template lets you write one class definition that works with any type the user picks at compile time. Instead of writing a separate Cart class for products, another for orders, and a third for wishlist entries, you write Cart<T> once and let the compiler generate the version you need. This is how std::vector, std::pair, and most of the STL are built.
A class template starts with the template<...> keyword, followed by the class definition where the type parameter is used as if it were a real type. The example below is a minimal shopping cart that holds items of any type.
The type argument sits inside the angle brackets at the call site: Cart<std::string> and Cart<int>. Each one is a distinct type. A Cart<int> cannot be assigned to a Cart<std::string>, even though they came from the same template. The compiler treats them as if you had hand-written two separate classes.
Every distinct instantiation produces a new class in the compiled binary. Using Cart<int>, Cart<double>, and Cart<std::string> creates three separate copies of the code. Templates trade binary size for type safety and performance.
A template can take more than one type parameter. A discount entry that maps a coupon code to a percentage value is a natural fit.
Both parameters are independent. KeyType could be std::string while ValueType is double. They don't need to share any relationship.
Short member functions can live inside the class body, as shown above. Longer ones are usually defined outside the class. Out-of-class definitions need their own template<...> line and have to qualify the class name with <T>.
Two rules apply to Inventory<T>::addItem. First, the template<typename T> line is mandatory above every out-of-class definition. Second, the class name in the qualifier carries the template parameter: it is Inventory<T>::addItem, not Inventory::addItem. The function name itself does not need the angle brackets because the compiler knows from Inventory<T> which version it is defining.
One practical wrinkle: class template definitions and their member functions usually live in header files, not split between .h and .cpp. The compiler needs to see the full definition every time it instantiates the template with a new type. The section on separate compilation later in the course covers this.
Just like default function parameters, template parameters can have defaults. This has worked for class templates since C++03 (function templates got defaults later, in C++11).
The empty angle brackets Wishlist<> are still required when you want the default. Defaults are useful for STL types like std::vector<T, Allocator = std::allocator<T>>, where the second parameter is rarely changed.
Inside a class template, exposing the type parameter under a stable name lets callers refer to it without re-deriving it. STL containers do this with value_type, size_type, iterator, and related aliases.
Why bother? Because someone using Cart<T> in their own code may not know what T is. A generic function that accepts any cart-like container can use typename SomeCart::value_type instead of hardcoding std::string. This is the same approach the STL uses everywhere.
A class template can have its own templated member functions, often called member templates. The classic use case is a converting constructor that builds a Cart<T> from a Cart<U> when U can convert to T.
The template<typename U> line introduces a fresh parameter independent of the enclosing T. The constructor is not a copy constructor (the types differ); it is a templated converting constructor that the compiler instantiates with U = int when you write Cart<double>(intCart).
A member template generates a new instantiation for every distinct U used with it. Converting from Cart<int>, Cart<float>, and Cart<short> into Cart<double> produces three separate constructor instantiations in the binary.
Granting friendship across a class template is one of the trickier parts of templates. The compiler distinguishes between making one specific function a friend and making a whole family of functions friends, and the syntax is easy to get wrong.
The cleanest approach is to define the friend inline, right inside the class template:
The inline version works because every instantiation of Order<T> defines its own non-template operator<<, with T already bound. There is no ambiguity for the compiler.
The pitfall shows up when you try to declare the operator outside the class and grant friendship to it. Consider this version:
What's wrong with this code?
The friend declaration names a non-template function operator<<(std::ostream&, const Order<T>&) for some specific T. But the definition below is a function template. The two do not match, so the function template is not actually a friend, and o.orderId access fails because orderId is private.
Fix: Tell the friend declaration that the friend is itself a template by adding angle brackets:
The <T> after operator<< says "the friend is the operator<< template instantiated with this same T". The forward declarations at the top are needed so the compiler knows operator<< is a template by the time it sees the friend line.
This syntax is fiddly. Most code sidesteps the problem by defining the friend inline (the first version), which is the pattern used in the STL and in Boost.
Since C++17, the compiler can often deduce class template arguments from constructor arguments, so you can drop the angle brackets at the call site.
For your own class templates, CTAD often works when the constructor mentions the template parameter directly:
The flow below shows how CTAD picks the type:
When the compiler cannot figure out the type from the arguments, you supply a deduction guide, but that is beyond the scope of this lesson. On C++17 or later, drop the brackets when the type is obvious. Older code and code with ambiguous constructors still needs them.
A class template sometimes needs to be mentioned before it is defined, often because two templates reference each other or because a friend declaration is needed. The syntax mirrors the full declaration but stops at the semicolon.
The compiler only needs the forward declaration when the use is name-only: pointers, references, function signatures, friend declarations. Anything that touches the class members (calling a method, sizing an object, dereferencing) needs the full definition.
The diagram below shows what each instantiation of Cart<T> looks like as a separate class in the compiled program:
The template itself is a blueprint; the boxes underneath are the actual classes the compiler generates when you use them. Each one has its own items vector typed for its T, and its own addItem function compiled for that type.
typename KeywordWhen a name inside a class template is accessed through the type parameter, the compiler does not know yet whether that name refers to a type, a value, or a function. If it is a type, the typename keyword tells the compiler so.
Without the typename keyword on the line that declares first, the compiler treats CartType::value_type as a value, then fails when it is used as a type. This is called a dependent name: its meaning depends on which template argument is plugged in.
Since C++20, the rule was relaxed in some contexts (type-id positions in function declarations, member declarations), but writing typename explicitly always works and makes the intent clear. When in doubt, add it.
Class templates can take more than types. They can also take compile-time values like integers, enums, or pointers. A fixed-capacity cart that takes its size as a template parameter is one example:
That is a non-type template parameter. Templates are not limited to types.
10 quizzes