Templating in C++ is a mechanism that allows writing generic code, meaning it can work with different types of data without having to rewrite it for each one.
Instead of defining a specific function or class for a data type (for example, int, double, string, etc.), we define a generic version that works for different types.
It’s called that because it’s like making a “template” for functions or classes.
Templating does not imply a loss of efficiency, as the templating work occurs during compilation, not during execution.
The compiler generates specific code for each type you use with the template (this process is called template instantiation), generating customized versions of the code for the types needed.
Function Templates
Function templates allow defining generic functions that can work with different types of data.
The basic syntax of a function template is:
template <typename T>
T genericFunction(T a, T b) {
// Function code
}
Instead of T
, you could use any other name, but this name is often used.
Let’s see it with an example.
#include <iostream>
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << "Add integers: " << add(3, 4) << std::endl;
std::cout << "Add floats: " << add(3.5, 4.5) << std::endl;
return 0;
}
In this example, the function add
can work with both integers and floats because we have defined it as a template.
Internally, the compiler generates a function for int
and another for double
, ensuring that each is as efficient as if you had written them manually.
Class Templates
In addition to functions, it is also possible to use class templates, which allow us to define generic classes that can handle different types of data.
The basic syntax of a class template is very similar:
template <typename T>
class GenericClass {
private:
T data;
public:
GenericClass(T data) : data(data) {}
T getData() { return data; }
};
Let’s see it with an example:
#include <iostream>
template <typename T>
class Box {
private:
T content;
public:
Box(T content) : content(content) {}
T getContent() { return content; }
};
int main() {
Box<int> intBox(123);
Box<std::string> stringBox("Hello, C++");
std::cout << "Content of intBox: " << intBox.getContent() << std::endl;
std::cout << "Content of stringBox: " << stringBox.getContent() << std::endl;
return 0;
}
In this example, the class Box
can contain both integers and strings, or anything else (as a good box should).
Multi-parameter Template
It is also possible to define templates that accept more than one type parameter.
template <typename T, typename U>
class Pair {
private:
T first;
U second;
public:
Pair(T first, U second) : first(first), second(second) {}
T getFirst() { return first; }
U getSecond() { return second; }
};
int main() {
Pair<int, std::string> pair(1, "One");
std::cout << "First: " << pair.getFirst() << ", Second: " << pair.getSecond() << std::endl;
return 0;
}
Template Specialization
Template specialization allows defining specific implementations for certain data types.
#include <iostream>
template <typename T>
class Printer {
public:
void print(T data) {
std::cout << "Data: " << data << std::endl;
}
};
// Specialization for char*
template <>
class Printer<char*> {
public:
void print(char* data) {
std::cout << "String: " << data << std::endl;
}
};
int main() {
Printer<int> intPrinter;
intPrinter.print(42);
Printer<char*> stringPrinter;
stringPrinter.print("Hello, specialization!");
return 0;
}
In this example, the Printer
class is specialized for the char*
type, allowing a specific implementation for strings.
Template Type Deduction
Template type deduction is the process by which the C++ compiler automatically determines the type of the template parameters based on the arguments we pass.
template <typename T>
T sum(T a, T b) {
return a + b;
}
int main() {
int x = 5, y = 10;
double p = 2.5, q = 3.5;
// The compiler automatically deduces the type of T
std::cout << sum(x, y) << std::endl; // Deduced: T = int
std::cout << sum(p, q) << std::endl; // Deduced: T = double
}
In this example:
- When you call
sum(x, y)
, the compiler sees thatx
andy
are of typeint
, so it deduces thatT = int
. - When you call
sum(p, q)
, it deduces thatT = double
.
This eliminates the need to explicitly write sum<int>
or sum<double>
.
Deduction Guides
Since C++17, we can also “help” the compiler deduce types for class templates using deduction guides.
template <typename T>
class Box {
public:
T content;
Box(T c) : content(c) {}
};
// Deduction guide
Box(const char*) -> Box<std::string>;
int main() {
Box c1(42); // Deduced: T = int
Box c2("Hello"); // Deduced: T = std::string (thanks to the deduction guide)
}
This is useful when the constructor of a class does not always clearly indicate the type of the template.