C++ Unleashed: From Zero to Hero
Previous chapter: Object-Oriented Programming
Templates and Generic Programming
Templates and generic programming are powerful features in C++ that allow you to write flexible and reusable code. Templates enable functions and classes to operate with generic types, making your programs more abstract and adaptable.
In this chapter, we’ll cover:
- Function Templates
- Class Templates
- Template Specialization
- Variadic Templates
- Concepts (C++20)
Function Templates
Function templates allow you to write a single function that works with any data type.
Basic Syntax
template <typename T>
ReturnType FunctionName(T parameter) {
// Function body
}
template <typename T>
: Introduces a template parameterT
.T
: Represents a generic data type.
Example: Generic Swap Function
template <typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
Usage:
int main() {
int x = 10, y = 20;
swapValues(x, y);
double a = 1.5, b = 2.5;
swapValues(a, b);
return 0;
}
Function Templates with Multiple Parameters
You can have multiple template parameters.
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
Usage:
int main() {
auto result = add(5, 3.2); // result is of type double
return 0;
}
Template Argument Deduction
The compiler automatically deduces the template arguments based on the function arguments.
Explicit Template Arguments:
swapValues<int>(x, y);
- Usually not necessary unless deduction fails.
Class Templates
Class templates allow you to define a class with generic types.
Basic Syntax
template <typename T>
class ClassName {
public:
// Member functions and variables using T
};
Example: Generic Array Class
template <typename T, size_t N>
class Array {
private:
T data[N];
public:
void set(size_t index, const T& value) {
if (index < N) {
data[index] = value;
}
}
T& get(size_t index) {
if (index < N) {
return data[index];
}
throw std::out_of_range("Index out of range");
}
size_t size() const {
return N;
}
};
Usage:
int main() {
Array<int, 5> intArray;
intArray.set(0, 10);
intArray.set(1, 20);
std::cout << intArray.get(0) << std::endl; // Outputs 10
std::cout << intArray.get(1) << std::endl; // Outputs 20
return 0;
}
Member Function Definitions Outside the Class
When defining member functions outside the class, include the template parameters.
template <typename T, size_t N>
void Array<T, N>::set(size_t index, const T& value) {
// Function body
}
Template Specialization
Template specialization allows you to define a custom implementation of a template for a specific type.
Full Specialization
Providing a completely different implementation for a specific type.
Example:
template <typename T>
class TypeInfo {
public:
static std::string name() {
return "Unknown";
}
};
// Specialization for int
template <>
class TypeInfo<int> {
public:
static std::string name() {
return "Integer";
}
};
// Specialization for double
template <>
class TypeInfo<double> {
public:
static std::string name() {
return "Double";
}
};
Usage:
int main() {
std::cout << TypeInfo<char>::name() << std::endl; // Outputs "Unknown"
std::cout << TypeInfo<int>::name() << std::endl; // Outputs "Integer"
std::cout << TypeInfo<double>::name() << std::endl; // Outputs "Double"
return 0;
}
Partial Specialization
Specializing a template for a subset of types.
Example:
template <typename T, typename U>
class Pair {
public:
T first;
U second;
};
// Partial specialization when both types are the same
template <typename T>
class Pair<T, T> {
public:
T first;
T second;
void display() {
std::cout << "Pair of " << typeid(T).name() << std::endl;
}
};
Usage:
int main() {
Pair<int, double> p1;
Pair<int, int> p2;
p2.display(); // Uses the specialized version
return 0;
}
Variadic Templates
Variadic templates allow functions and classes to accept an arbitrary number of template parameters.
Function Variadic Templates
Example: Print Function
void print() {
std::cout << std::endl;
}
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " ";
print(args...);
}
Usage:
int main() {
print(1, 2.5, "Hello", 'A'); // Outputs: 1 2.5 Hello A
return 0;
}
Explanation:
- The
print
function recursively unpacks the arguments. - The base case is the
print()
function with no parameters.
Class Variadic Templates
Example: Tuple Class
template <typename... Types>
class Tuple {
// Implementation
};
Using std::tuple
The Standard Library provides std::tuple
, a variadic template class.
#include <tuple>
int main() {
std::tuple<int, double, std::string> t(1, 2.5, "Hello");
int i = std::get<0>(t);
double d = std::get<1>(t);
std::string s = std::get<2>(t);
return 0;
}
Concepts (C++20)
Concepts provide a way to specify template requirements, improving readability and error messages.
Basic Syntax
template <typename T>
concept ConceptName = requires(T a) {
// Expressions and requirements
};
Example: Numeric Concept
#include <concepts>
template <typename T>
concept Numeric = std::is_arithmetic_v<T>;
template <Numeric T>
T add(T a, T b) {
return a + b;
}
Usage:
int main() {
int result = add(5, 10); // OK
// std::string s = add("a", "b"); // Error: std::string is not Numeric
return 0;
}
Using requires
Clauses
template <typename T>
requires std::is_integral_v<T>
T factorial(T n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
Standard Library Concepts
std::integral
std::floating_point
std::signed_integral
std::unsigned_integral
std::default_initializable
Example:
#include <concepts>
template <std::integral T>
T gcd(T a, T b) {
while (b != 0) {
T temp = b;
b = a % b;
a = temp;
}
return a;
}
Practical Example: Generic Sorting Function
Let’s create a generic sorting function using templates.
#include <iostream>
#include <vector>
#include <algorithm>
template <typename T>
void sortVector(std::vector<T>& vec) {
std::sort(vec.begin(), vec.end());
}
int main() {
std::vector<int> intVec = {5, 2, 9, 1, 5};
sortVector(intVec);
for (const auto& val : intVec) {
std::cout << val << " "; // Outputs: 1 2 5 5 9
}
std::cout << std::endl;
std::vector<std::string> strVec = {"apple", "orange", "banana"};
sortVector(strVec);
for (const auto& val : strVec) {
std::cout << val << " "; // Outputs: apple banana orange
}
std::cout << std::endl;
return 0;
}
Exercises
Generic Max Function
Write a function template
maxValue
that returns the maximum of two values.template <typename T> T maxValue(const T& a, const T& b) { return (a > b) ? a : b; }
Stack Class Template
Implement a simple stack class template.
template <typename T> class Stack { private: std::vector<T> elements; public: void push(const T& item) { elements.push_back(item); } void pop() { if (!elements.empty()) { elements.pop_back(); } } T& top() { return elements.back(); } bool isEmpty() const { return elements.empty(); } };
Usage:
int main() { Stack<int> intStack; intStack.push(1); intStack.push(2); intStack.push(3); while (!intStack.isEmpty()) { std::cout << intStack.top() << " "; intStack.pop(); } // Outputs: 3 2 1 return 0; }
Function Variadic Template
Create a variadic template function
sum
that calculates the sum of all its arguments.template <typename T> T sum(T value) { return value; } template <typename T, typename... Args> T sum(T first, Args... args) { return first + sum(args...); }
Usage:
int main() { auto result = sum(1, 2, 3, 4, 5); // result = 15 std::cout << "Sum: " << result << std::endl; return 0; }
Summary
In this chapter, you’ve learned about:
Function Templates:
- Writing generic functions that work with any data type.
- Template argument deduction and multiple template parameters.
Class Templates:
- Creating generic classes to handle data of any type.
- Implementing member functions inside and outside the class.
Template Specialization:
- Customizing templates for specific types using full and partial specialization.
Variadic Templates:
- Writing functions and classes that accept an arbitrary number of template arguments.
- Implementing recursive functions to process variadic arguments.
Concepts (C++20):
- Defining template requirements using concepts.
- Using standard library concepts and
requires
clauses for constraints.
Templates and generic programming enable you to write flexible and reusable code, reducing redundancy and improving maintainability. Mastery of these features allows you to create powerful abstractions and efficient libraries.
In the next chapter, we’ll explore Modern C++ Features, including type deduction, range-based loops, and other enhancements that make C++ programming more expressive and safer.
Next chapter: Modern C++ Features