Home / Articles / Introduction to Templates
Introduction to Templates
Template Functions
Templates in C++ allow for generic programming — code that is not dependent on a specific data type. Consider an add function for integers:
int add(int first, int second)
{
return first + second;
}
To add floats, a second function would be needed. The only difference between the two is the type. Templates let you write this once:
template <typename T>
T add(T first, T second)
{
return first + second;
}
The line template <typename T> declares a generic type T. Wherever T appears in the function, it is replaced with the actual type determined from the call. Calling add(1, 2) resolves T to int; calling add(1.9, 2.9) resolves T to float.
Complete example:
#include <iostream>
using namespace std;
template <typename T>
T add(T first, T second)
{
return first + second;
}
int main()
{
cout << add(1, 2) << endl;
cout << add(1.9, 2.9) << endl;
return 0;
}
Behind the Scenes
Template type resolution happens at compile time, not at runtime. When the compiler encounters a template function call, it generates a concrete copy of the function (an instance) with T replaced by the actual type. This is called instantiating the template.
If add is called with both int and float arguments, two separate functions are generated and included in the executable. This can be verified with nm:
$ g++ -g template.cc -o template
$ nm -A template | grep add
template: 0000000100000cfa T __Z3addIdET_S0_S0_
template: 0000000100000ce8 T __Z3addIiET_S0_S0_
Two instances — one for double (d) and one for int (i).
Template Overloading
Template functions can be overloaded just like regular functions:
template <typename T>
T add(T first, T second)
{
return first + second;
}
template <typename T>
T add(T first, T second, T third)
{
return first + second + third;
}
int main()
{
std::cout << add(1, 2) << std::endl;
std::cout << add(1, 2, 3) << std::endl;
return 0;
}
Specialised Templates
A template can be given a custom implementation for a specific type. This is called template specialisation. The specialised definition starts with template <> and names the type explicitly:
template <>
Rectangle add<Rectangle>(Rectangle first, Rectangle second)
{
Rectangle v;
v.breadth = first.breadth + second.breadth;
v.length = first.length + second.length;
return v;
}
Differences between specialisation and explicit instantiation:
- Specialisation allows a different implementation; explicit instantiation uses the same template body.
- Specialisation requires a full function body; explicit instantiation only names the types.
- Syntax for specialisation:
template <> return-type func-name<type>(params) { ... } - Syntax for explicit instantiation:
template return-type func-name(type params);(no<>aftertemplate)
Explicit Instantiation
You can force the compiler to generate a template instance without a call site:
// Explicit instantiation for float
template float add(float first, float second);
This is useful when a template lives in a library (.a, .so, or .lib) whose consumers are not yet known at library compile time.
Function Resolution Priority
When a non-template function, a specialised template, and a generic template all exist with the same name, the compiler resolves calls in this order:
- Non-template function first
- Specialised template function
- Generic template function
Non-type Arguments
Template functions can mix generic type parameters with concrete types:
template <typename T>
T power(T base, int exponent)
{
T retValue = 1;
for (int i = 1; i <= exponent; i++)
retValue *= base;
return retValue;
}
Multiple Type Parameters
A template function can have more than one type parameter:
template <typename T1, typename T2>
int get_index(T1 iterator, T2 data)
{
...
}
Template Classes
Classes can also be defined as templates. The type parameter is used to define member types and function signatures:
template <typename T>
class Message
{
public:
T contents;
Message(T contents)
{
this->contents = contents;
}
};
int main()
{
Message<int> msg(10);
cout << msg.contents << endl;
return 0;
}
Inheritance with Template Classes
A derived class that inherits from a template base class must also be declared as a template:
template <typename T>
class Packet : public Message<T>
{
public:
int message_id;
Packet(int message_id, T contents) : Message<T>(contents)
{
this->message_id = message_id;
}
};
Key points:
- The derived class is declared
template <typename T>even if it adds no template-typed members of its own. - The base class name in the derived class definition is written as
Message<T>. - The base class constructor is invoked in the initialisation list as
Message<T>(contents).
Passing Template Class Instances as Arguments
Functions that receive template class instances must themselves be template functions:
template <typename T>
void debugPrint(Packet<T> pkt)
{
cout << "Msg Id: " << pkt.message_id << "\n";
cout << "Contents: " << pkt.contents << "\n";
}
Default Type Arguments
Default values for type parameters can be specified:
template <typename T = int>
class Message
{
public:
T contents;
Message(T contents) { this->contents = contents; }
};
int main()
{
Message<> msg(10); /* T defaults to int */
cout << msg.contents << endl;
return 0;
}
Default type arguments are supported for class templates but not for function templates.