C++ Templates are powerful tools for creating flexible, reusable, and type-safe code. It is the core of Generic Programming and the basis for the entire Standard Template Library (STL).
This article provides 22 essential C++ exercises designed to solidify your understanding of Templates and Generic Programming.
Each exercise includes a Problem Statement, Hint, Solution code, and detailed Explanation, ensuring you don’t just copy code, but genuinely understand how and why it works.
What You’ll Practice
The coding challenges cover the below spectrum of template concepts:
- Fundamentals: Writing generic Function Templates (e.g., max, swap) and Class Templates (e.g., Stack, Pair).
- Advanced Features: Handling Non-Type Parameters, implementing Template Specialization, and utilizing Variadic Templates for arbitrary arguments.
- Compiler Logic: Understanding Template Overloading and Template Overload Resolution (including SFINAE).
- Applications: Creating generic algorithms independent of element type.
+ Table Of Contents
Table of contents
- Exercise 1: Generic max function
- Exercise 2: Generic swap function
- Exercise 3: Generic Array Print
- Exercise 4: Generic Search
- Exercise 5: Generic Sum
- Exercise 6: Function Template with Multiple Types
- Exercise 7: std::string Concatenation
- Exercise 8: Template with typename and class
- Exercise 9: Explicit Instantiation
- Exercise 10: Template Overloading
- Exercise 11: Simple Generic Pair
- Exercise 12: Generic Stack
- Exercise 13: Generic Queue
- Exercise 14: Generic Array Class
- Exercise 15: Generic Calculator
- Exercise 16: Class Template with Non-Type Parameter
- Exercise 17: Templated Linked List Node
- Exercise 18: Generic Vector Class with Iterators
- Exercise 19: Templated Class with Default Argument
- Exercise 20: Full Template Specialization (Class)
- Exercise 21: Function Template Specialization (Explicit)
- Exercise 22: Variadic Templates (Simple Print)
Exercise 1: Generic max function
Problem Statement: Create a function template named myMax that accepts two parameters of the same type and returns the larger of the two values. The function should work correctly for standard data types like int, double, and char.
This exercise introduces function templates.
Expected Output:
Intenger Numbers: 5 10, Max int: 10
Double Numbers: 3.14 2.718, Max double: 3.14
Charcters: A Z, Max char: Z
Strings: Apple Banana, Max string: Banana
+ Hint
- The template declaration should use the syntax
template <typename T>. - Inside the function, you can use the standard conditional operator or an
if/elsestatement to compare the two values.
+ Show Solution
#include <iostream>
#include <string>
// 1. Function Template Definition
template <typename T>
T myMax(T a, T b) {
return (a > b) ? a : b;
}
int main() {
// Testing with int
int i1 = 5, i2 = 10;
std::cout << "Max int: " << myMax(i1, i2) << std::endl;
// Testing with double
double d1 = 3.14, d2 = 2.718;
std::cout << "Max double: " << myMax(d1, d2) << std::endl;
// Testing with char
char c1 = 'A', c2 = 'Z';
std::cout << "Max char: " << myMax(c1, c2) << std::endl;
// Testing with string
std::string s1 = "Apple", s2 = "Banana";
std::cout << "Max string: " << myMax(s1, s2) << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- By using
template <typename T>, the compiler can generate a specialized version of themyMaxfunction for any data typeTthat supports the greater-than operator (>). - When you call
myMax(i1, i2), the compiler automatically deduces thatTisintand creates a functionmyMax<int>. - This approach eliminates the need to write separate, nearly identical functions for each data type.
Exercise 2: Generic swap function
Problem Statement: Implement a function template named mySwap that takes two parameters by reference, and swaps their values. Demonstrate its usage with int and float variables.
Expected Output:
Before swap: x = 10, y = 20
After swap: x = 20, y = 10
Before swap: f1 = 5.5, f2 = 1.1
After swap: f1 = 1.1, f2 = 5.5
+ Hint
- The parameters of the function template should be references to the generic type
T, using the syntaxT&. This ensures that the function modifies the original variables, not just copies of them. - A temporary variable of type
Twill be necessary to perform the swap operation.
+ Show Solution
#include <iostream>
// 1. Function Template Definition
template <typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
// Testing with int
int x = 10, y = 20;
std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
mySwap(x, y);
std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
// Testing with float
float f1 = 5.5f, f2 = 1.1f;
std::cout << "Before swap: f1 = " << f1 << ", f2 = " << f2 << std::endl;
mySwap(f1, f2);
std::cout << "After swap: f1 = " << f1 << ", f2 = " << f2 << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- The
mySwapfunction is a classic use case for templates. - By accepting the arguments as references (
T&), the function avoids copying large objects and allows the function to modify the original variables passed from the calling scope. - The template mechanism ensures that the temporary variable
tempis of the exact type necessary for the variables being swapped, maintaining type safety while providing generic functionality.
Exercise 3: Generic Array Print
Problem Statement: Create a function template named printArray that takes an array of any type and an integer representing the size of the array, and then prints every element of the array on a single line, separated by spaces.
Expected Output:
Int Array: [ 1 2 3 4 5 ]
Double Array: [ 1.1 2.2 3.3 ]
String Array: [ hello world template ]
+ Hint
- The function template needs two parameters: the array (
T*orT[]) and the size (intorsize_t). - Use a simple
forloop from index 0 up tosize - 1to iterate and print the elements.
+ Show Solution
#include <iostream>
#include <string>
// 1. Function Template Definition
template <typename T>
void printArray(const T arr[], int size) {
std::cout << "[ ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << "]" << std::endl;
}
int main() {
// Testing with int array
int intArray[] = {1, 2, 3, 4, 5};
int intSize = sizeof(intArray) / sizeof(intArray[0]);
std::cout << "Int Array: ";
printArray(intArray, intSize);
// Testing with double array
double doubleArray[] = {1.1, 2.2, 3.3};
int doubleSize = sizeof(doubleArray) / sizeof(doubleArray[0]);
std::cout << "Double Array: ";
printArray(doubleArray, doubleSize);
// Testing with string array
std::string stringArray[] = {"hello", "world", "template"};
int stringSize = sizeof(stringArray) / sizeof(stringArray[0]);
std::cout << "String Array: ";
printArray(stringArray, stringSize);
return 0;
}Code language: C++ (cpp)
Explanation:
- This template allows a single function definition to handle arrays of different types (
int,double,std::string, etc.). - The type parameter
Tis substituted by the compiler based on the array type passed in. The use ofconst T arr[]ensures that the function cannot modify the elements of the array, which is a good practice for a display function. - The array size must be passed explicitly because arrays often decay to pointers when passed to a function, losing their size information.
Exercise 4: Generic Search
Problem Statement: Write a function template named findInArray that takes an array of type T, the array size, and a value of type T to search for. The function should return the index of the first occurrence of the value in the array, or -1 if the value is not found.
Expected Output:
Index of 30: 2
Index of 5.5: -1
Index of beta: 1
+ Hint
- The return type of the function should be
intorsize_tto represent the index. - The body of the function should use a loop to compare each array element with the target value using the equality operator (
==).
+ Show Solution
#include <iostream>
#include <string>
// 1. Function Template Definition
template <typename T>
int findInArray(const T arr[], int size, const T& target) {
for (int i = 0; i < size; ++i) {
if (arr[i] == target) {
return i; // Found at index i
}
}
return -1; // Not found
}
int main() {
// Testing with int array
int intArray[] = {10, 20, 30, 40, 50};
int intSize = 5;
int targetInt = 30;
std::cout << "Index of " << targetInt << ": "
<< findInArray(intArray, intSize, targetInt) << std::endl;
// Testing with double array
double doubleArray[] = {1.1, 2.2, 3.3, 4.4};
int doubleSize = 4;
double targetDouble = 5.5;
std::cout << "Index of " << targetDouble << ": "
<< findInArray(doubleArray, doubleSize, targetDouble) << std::endl;
// Testing with string array
std::string stringArray[] = {"alpha", "beta", "gamma"};
int stringSize = 3;
std::string targetString = "beta";
std::cout << "Index of " << targetString << ": "
<< findInArray(stringArray, stringSize, targetString) << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- The
findInArraytemplate provides a generic linear search algorithm. - It requires the type
Tto support the equality operator (==). - The target value is passed as a
const T&to avoid unnecessary copying. This is efficient for large user-defined types. - If the search is successful, it returns the index; otherwise, it returns -1, which is a common convention to signal failure in array searches.
Exercise 5: Generic Sum
Problem Statement: Implement a function template named sum that takes an array of any numeric type (int, float, double) and its size, and returns the sum of all its elements.
Expected Output:
Sum of int array: 15
Sum of double array: 7
Sum of float array: 0.6
+ Hint
- The return type and the accumulator variable within the function should be of the generic type
T. - Start the accumulator at the neutral element for addition (zero) and iterate through the array, adding each element.
+ Show Solution
#include <iostream>
// 1. Function Template Definition
template <typename T>
T sum(const T arr[], int size) {
T total = T(); // Initialize to zero (0 for numeric types)
for (int i = 0; i < size; ++i) {
total += arr[i];
}
return total;
}
int main() {
// Testing with int array
int intArray[] = {1, 2, 3, 4, 5};
int intSize = 5;
std::cout << "Sum of int array: " << sum(intArray, intSize) << std::endl; // Output: 15
// Testing with double array
double doubleArray[] = {1.5, 2.5, 3.0};
int doubleSize = 3;
std::cout << "Sum of double array: " << sum(doubleArray, doubleSize) << std::endl; // Output: 7
// Testing with float array
float floatArray[] = {0.1f, 0.2f, 0.3f};
int floatSize = 3;
std::cout << "Sum of float array: " << sum(floatArray, floatSize) << std::endl; // Output: 0.6
return 0;
}Code language: C++ (cpp)
Explanation:
- This template demonstrates how generic programming can implement common algorithms like accumulation.
- The line
T total = T();is a generic way to default-initialize the accumulator variable to zero, which works correctly for all built-in numeric types. - This function requires the type
Tto support the addition-assignment operator (+=) and to be default-constructible. - The template ensures that the sum calculation is performed using the correct type, avoiding potential data loss or overflow that could occur from mixing types.
Exercise 6: Function Template with Multiple Types
Problem Statement: Create a function template named compareAndPrint that takes two parameters of potentially different types, T1 and T2. The function should explicitly convert both parameters to a common type (e.g., double) for comparison and then print which original value was larger.
Expected Output:
Comparing 10 (10) and 9.9 (9.9): 10 is greater.
Comparing A (65) and 60 (60): A is greater.
Comparing 100.5 (100.5) and 100.49 (100.49): 100.5 is greater.
+ Hint
- The template signature should use two type parameters:
template <typename T1, typename T2>. - Inside the function, cast both arguments to
doublebefore performing the comparison to handle mixed-type input reliably.
+ Show Solution
#include <iostream>
// 1. Function Template Definition with two type parameters
template <typename T1, typename T2>
void compareAndPrint(T1 val1, T2 val2) {
// Convert both to double for a reliable comparison
double d1 = static_cast<double>(val1);
double d2 = static_cast<double>(val2);
std::cout << "Comparing " << val1 << " (" << d1 << ") and "
<< val2 << " (" << d2 << "): ";
if (d1 > d2) {
std::cout << val1 << " is greater." << std::endl;
} else if (d2 > d1) {
std::cout << val2 << " is greater." << std::endl;
} else {
std::cout << "They are equal (after cast)." << std::endl;
}
}
int main() {
// Comparing int and double
compareAndPrint(10, 9.9);
// Comparing char and int
compareAndPrint('A', 60); // 'A' ASCII is 65
// Comparing float and double
compareAndPrint(100.5f, 100.49);
return 0;
}Code language: C++ (cpp)
Explanation:
- This template demonstrates the use of multiple type parameters (
T1andT2). This is essential for generic functions that operate on inputs of different types. - By casting both inputs to
doublebefore comparison, we ensure a consistent and robust comparison logic, especially when dealing with mixed numeric types likeintandfloat. If we hadn’t cast, implicit type conversion rules might lead to unexpected results or warnings.
Exercise 7: std::string Concatenation
Problem Statement: Write a function template named combine that accepts two values of the same type T and returns the result of applying the addition operator (+) to them. Test the function with int (which performs addition) and with std::string (which performs concatenation).
Expected Output:
Int combine (5 + 12): 17
Double combine (3.5 + 2.1): 5.6
String combine: Generic Programming
+ Hint
- The template signature should be
template <typename T>and the return type should beT. - The function body simply needs to return the result of
a + b. You will need to include the<string>header for the string test to work.
+ Show Solution
#include <iostream>
#include <string>
// 1. Function Template Definition
template <typename T>
T combine(const T& a, const T& b) {
return a + b;
}
int main() {
// Testing with int (Addition)
int i_res = combine(5, 12);
std::cout << "Int combine (5 + 12): " << i_res << std::endl;
// Testing with double (Addition)
double d_res = combine(3.5, 2.1);
std::cout << "Double combine (3.5 + 2.1): " << d_res << std::endl;
// Testing with std::string (Concatenation)
std::string s1 = "Generic ", s2 = "Programming";
std::string s_res = combine(s1, s2);
std::cout << "String combine: " << s_res << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- This template showcases the power of Generic Programming in leveraging operator overloading. The
combinefunction doesn’t know what the+operator does, only that it must exist for typeT. - For numeric types,
+performs arithmetic addition. Forstd::string, the+operator is overloaded to perform concatenation. - This single template definition works correctly for both use cases because the required operator behavior is defined for the respective types.
Exercise 8: Template with typename and class
Problem Statement: Write a simple function template that prints a value of a generic type T. Write the template definition first using the keyword class and then change it to use the keyword typename. Explain the modern preference for typename in this context.
Expected Output:
Using 'class': 100
Using 'typename': 3.14
+ Hint
- For this exercise, the function template signature is the key focus.
- The compiler treats
classandtypenameidentically in the context of defining a type parameter for a template. The preference fortypenamecomes from its clearer semantic meaning.
+ Show Solution
#include <iostream>
// 1. Using 'class' keyword (Older convention, still valid)
template <class T>
void printWithClass(const T& value) {
std::cout << "Using 'class': " << value << std::endl;
}
// 2. Using 'typename' keyword (Modern convention)
template <typename T>
void printWithTypename(const T& value) {
std::cout << "Using 'typename': " << value << std::endl;
}
int main() {
printWithClass(100);
printWithTypename(3.14);
// Demonstrate a key scenario where 'typename' is REQUIRED
// (though not directly in the template parameter list here)
// For example: typename T::nested_type member;
return 0;
}Code language: C++ (cpp)
Explanation:
- When declaring a type parameter in a template parameter list (e.g.,
<class T>or<typename T>), bothclassandtypenameare syntactically equivalent in C++. However,typenameis generally preferred in modern C++ because it more clearly indicates that the parameterTis a placeholder for any type, including primitive types likeintand types that are not classes. - The compiler needs the
typenamekeyword in a more complex scenario: when referring to a nested dependent type inside a template (e.g.,typename T::iterator) to explicitly tell the compiler thatiteratoris a type, not a static member variable.
Exercise 9: Explicit Instantiation
Problem Statement: Create a function template named powerOfTwo that calculates 2N where N is the input value of a generic type T. In a separate section of your code (or a separate file, conceptually), provide an explicit instantiation of this function template for the float type.
Expected Output:
Power of two (float): 32
Power of two (int): 1024
Power of two (double): 8
+ Hint
- Explicit instantiation uses the syntax
template return_type function_name<specific_type>(parameter_list);. - In a real-world scenario, this is used to generate the necessary code in one compilation unit (e.g., a
.cppfile) to improve compilation speed when using the template elsewhere. - Since floating-point types can’t use the bit-shift operator (
<<), usestd::powand cast the result to the return type.
+ Show Solution
#include <iostream>
#include <cmath>
// 1. Function Template Definition
template <typename T>
T powerOfTwo(T n) {
// For general numeric types, use std::pow
return static_cast<T>(std::pow(2.0, static_cast<double>(n)));
}
// 2. Explicit Instantiation for float (Usually in a separate .cpp file)
template float powerOfTwo<float>(float n);
// Optional: Explicit Instantiation for int (to contrast)
template int powerOfTwo<int>(int n);
int main() {
// The compiler will use the explicitly instantiated version for float
float f_result = powerOfTwo(5.0f);
std::cout << "Power of two (float): " << f_result << std::endl; // Output: 32
// The compiler will use the explicitly instantiated version for int
int i_result = powerOfTwo(10);
std::cout << "Power of two (int): " << i_result << std::endl; // Output: 1024
// The compiler will implicitly instantiate for double
// (if an explicit version isn't available)
double d_result = powerOfTwo(3.0);
std::cout << "Power of two (double): " << d_result << std::endl; // Output: 8
return 0;
}Code language: C++ (cpp)
Explanation:
- Explicit instantiation is a mechanism that forces the compiler to generate the code for a specific version of a template at a specific point, even if that version hasn’t been called yet.
- The line
template float powerOfTwo<float>(float n);tells the compiler to create thepowerOfTwofunction whereTis replaced byfloat. - This is primarily used to separate template definitions and implementations into different files and to potentially reduce overall compilation time by centralizing template instantiation.
Exercise 10: Template Overloading
Problem Statement: Create a generic function template named process that takes one parameter of type T and prints a message indicating it’s the generic version. Then, create a regular, non-template overload of the same function that takes a single int parameter and prints a message indicating it’s the specialized int version. Observe which function the compiler chooses when called with an int and a double.
Expected Output:
Non-template INT overload called with value: 5
Generic template version called for type: d with value: 3.14
Generic template version called for type: i with value: 10
+ Hint
- Function overloading rules are applied to templates as well.
- A non-template function that is an exact match for the arguments is always preferred over generating a function from a template. This is a form of C++’s “Most Specialized” rule.
+ Show Solution
#include <iostream>
#include <typeinfo> // Used for typeid().name()
// 1. Function Template Definition (Generic Version)
template <typename T>
void process(T value) {
std::cout << "Generic template version called for type: " << typeid(T).name()
<< " with value: " << value << std::endl;
}
// 2. Non-Template Overload (Specific Version)
void process(int value) {
std::cout << "Non-template INT overload called with value: " << value << std::endl;
}
int main() {
// Call 1: Passes an int - Calls the non-template overload
int i = 5;
process(i);
// Call 2: Passes a double - Calls the generic template version (T is deduced as double)
double d = 3.14;
process(d);
// Call 3: Explicitly calling the template for int
// This forces the generic version to be chosen, even though the overload exists.
process<int>(10);
return 0;
}Code language: C++ (cpp)
Explanation:
- This exercise demonstrates template overloading and the compiler’s overload resolution rules. When
process(i)is called with anint, the compiler finds two options: the generic template and the non-templateprocess(int)overload. - The non-template function is considered a better match because it requires no template instantiation and is a direct, exact match for the type.
- Therefore, the non-template overload is preferred and called. When
process(d)is called with adouble, only the generic template can create an exact match (process<double>), so the generic version is called.
Exercise 11: Simple Generic Pair
Problem Statement: Create a class template named Pair that can hold two values of potentially different types, T1 and T2. Include a public constructor that takes arguments for these two values and initializes them. Finally, include public member functions getFirst() and getSecond() to retrieve the stored values.
Expected Output:
Pair 1: 10, 5.5
Pair 2: Model, A
Pair 3: 50, 25
+ Hint
- The class template signature will need two type parameters:
template <typename T1, typename T2>. - Use the constructor’s member initializer list to initialize the private member variables of types
T1andT2.
+ Show Solution
#include <iostream>
#include <string>
// 1. Class Template Definition
template <typename T1, typename T2>
class Pair {
private:
T1 first;
T2 second;
public:
// Constructor
Pair(T1 a, T2 b) : first(a), second(b) {}
// Accessors
T1 getFirst() const {
return first;
}
T2 getSecond() const {
return second;
}
};
int main() {
// Pair of int and double
Pair<int, double> p1(10, 5.5);
std::cout << "Pair 1: " << p1.getFirst() << ", " << p1.getSecond() << std::endl;
// Pair of string and char
Pair<std::string, char> p2("Model", 'A');
std::cout << "Pair 2: " << p2.getFirst() << ", " << p2.getSecond() << std::endl;
// Pair of same type (e.g., int, int)
Pair<int, int> p3(50, 25);
std::cout << "Pair 3: " << p3.getFirst() << ", " << p3.getSecond() << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- This exercise introduces class templates with multiple type parameters. Unlike function templates where the compiler can often deduce types, class templates require explicit type specification (e.g.,
Pair<int, double>) when creating an object. - The use of two type parameters,
T1andT2, makes thePairclass flexible enough to hold any combination of data types, a core concept in generic container design, similar to the standard library’sstd::pair.
Exercise 12: Generic Stack
Problem Statement: Implement a class template named Stack that uses a std::vector internally to store elements of any type T. The stack must support the following public operations: push(T element), T pop() (which removes and returns the top element), and bool isEmpty() (to check if the stack is empty).
Expected Output:
Popped int: 20
Is intStack empty? No
Popped string: Templates
+ Hint
- The internal storage should be a private member:
std::vector<T> data;. Forpop(), ensure you check for an empty stack before attempting to access or remove an element. - The
pop()operation should use the vector’sback()andpop_back()methods.
+ Show Solution
#include <iostream>
#include <vector>
#include <stdexcept>
// 1. Class Template Definition
template <typename T>
class Stack {
private:
std::vector<T> data;
public:
// Push operation
void push(T element) {
data.push_back(element);
}
// Pop operation
T pop() {
if (isEmpty()) {
throw std::out_of_range("Stack::pop() on empty stack");
}
T top_element = data.back();
data.pop_back();
return top_element;
}
// Check if empty
bool isEmpty() const {
return data.empty();
}
};
int main() {
// Stack of integers
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
std::cout << "Popped int: " << intStack.pop() << std::endl; // 20
std::cout << "Is intStack empty? " << (intStack.isEmpty() ? "Yes" : "No") << std::endl;
// Stack of strings
Stack<std::string> stringStack;
stringStack.push("Hello");
stringStack.push("Templates");
std::cout << "Popped string: " << stringStack.pop() << std::endl; // Templates
return 0;
}Code language: C++ (cpp)
Explanation:
- This template demonstrates how to create generic containers. By templating the
Stackclass, it can hold any data typeT(integers, strings, custom objects, etc.) without rewriting the core LIFO (Last-In, First-Out) logic. - The use of
std::vector<T>provides a dynamic, type-safe internal storage mechanism. Thepop()method includes error checking (throwingstd::out_of_range) to handle the possibility of trying to remove an element from an empty stack, making the generic class robust.
Exercise 13: Generic Queue
Problem Statement: Implement a class template named Queue (First-In, First-Out) that can hold elements of any type T. It should expose the following public methods: enqueue(T element) (add to the back), T dequeue() (remove and return the front element), and bool isEmpty(). You may use a standard library container like std::vector or std::list internally.
+ Hint
- If you use
std::vector<T>,enqueuewill bepush_back(). Fordequeue, you will need to access the front element usingfront()and then remove it usingerase()on the first element’s iterator, which is slightly less efficient than using astd::listorstd::deque. - A simpler way is to use
std::list<T>, wheredequeueusesfront()andpop_front().
+ Show Solution
#include <iostream>
#include <list>
#include <stdexcept>
// 1. Class Template Definition using std::list for efficient front operations
template <typename T>
class Queue {
private:
std::list<T> data;
public:
// Add to the back (Enqueue)
void enqueue(T element) {
data.push_back(element);
}
// Remove and return the front element (Dequeue)
T dequeue() {
if (isEmpty()) {
throw std::out_of_range("Queue::dequeue() on empty queue");
}
T front_element = data.front();
data.pop_front();
return front_element;
}
// Check if empty
bool isEmpty() const {
return data.empty();
}
};
int main() {
// Queue of characters
Queue<char> charQueue;
charQueue.enqueue('A');
charQueue.enqueue('B');
charQueue.enqueue('C');
std::cout << "Dequeued 1: " << charQueue.dequeue() << std::endl; // A
std::cout << "Dequeued 2: " << charQueue.dequeue() << std::endl; // B
return 0;
}Code language: C++ (cpp)
Explanation:
- Similar to the stack, the templated
Queueclass allows the underlying FIFO (First-In, First-Out) structure to be reused across different data types. - By choosing
std::list<T>internally, we ensure that both adding to the back (push_back) and removing from the front (pop_front) are efficient constant-time operations. - This design exemplifies choosing the appropriate standard container within a generic structure to maintain performance guarantees for all types
T.
Exercise 14: Generic Array Class
Problem Statement: Design a class template named GenericArray that dynamically allocates an array of type T and its size is specified in the constructor. The class must include a destructor to manage memory and overload the subscript operator ([]) for element access.
Expected Output:
Int Array elements: 10 0 0 0 50
Double Array element at index 1: 3.14
+ Hint
- The private members should be a pointer
T* array_ptrand an integersize. - The constructor must use the
newoperator to allocate the memory. The destructor must usedelete[]to free the memory. - Overload
operator[]to return a reference to the element at the given index (T& operator[](int index)).
+ Show Solution
#include <iostream>
#include <stdexcept>
// 1. Class Template Definition
template <typename T>
class GenericArray {
private:
T* array_ptr;
int size;
public:
// Constructor
GenericArray(int s) : size(s) {
if (size <= 0) throw std::invalid_argument("Array size must be positive.");
array_ptr = new T[size](); // () initializes to zero/default value
}
// Destructor (Crucial for dynamic memory management)
~GenericArray() {
delete[] array_ptr;
}
// Overloaded subscript operator for read/write access
T& operator[](int index) {
if (index < 0 || index >= size) {
throw std::out_of_range("Index out of bounds.");
}
return array_ptr[index];
}
// Getter for size (optional, but useful)
int getSize() const {
return size;
}
};
int main() {
// Array of 5 integers
GenericArray<int> intArray(5);
intArray[0] = 10;
intArray[4] = 50;
std::cout << "Int Array elements: ";
for (int i = 0; i < intArray.getSize(); ++i) {
std::cout << intArray[i] << " ";
}
std::cout << std::endl; // Output: 10 0 0 0 50
// Array of 3 doubles
GenericArray<double> doubleArray(3);
doubleArray[1] = 3.14;
std::cout << "Double Array element at index 1: " << doubleArray[1] << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- This template is a simple example of a smart container that manages raw dynamic memory for any type
T. By overloadingoperator[]to return a reference (T&), the syntaxintArray[0] = 10;works naturally for both reading and writing. - The most critical aspect is the Rule of Three/Five: since the class manages raw memory (
new T[]), it must provide a custom destructor (delete[] array_ptr) to prevent memory leaks when the object goes out of scope.
Exercise 15: Generic Calculator
Problem Statement: Create a class template named Calculator with two private member variables, num1 and num2, both of type T. Include a constructor to initialize these members. The class must provide public member functions for arithmetic operations: add(), subtract(), multiply(), and divide(), all returning a value of type T.
Expected Output:
Int Addition: 13
Int Division: 3
Double Addition: 13
Double Division: 3.33333
+ Hint
- All member functions will be straightforward, performing the respective arithmetic operation on
num1andnum2. - Remember that the generic type
Tmust support the standard arithmetic operators (+,-,*,/) for this template to compile correctly.
+ Show Solution
#include <iostream>
// 1. Class Template Definition
template <typename T>
class Calculator {
private:
T num1;
T num2;
public:
// Constructor
Calculator(T n1, T n2) : num1(n1), num2(n2) {}
// Arithmetic Operations
T add() const {
return num1 + num2;
}
T subtract() const {
return num1 - num2;
}
T multiply() const {
return num1 * num2;
}
T divide() const {
// Simple division, assuming T handles division appropriately (e.g., int vs float)
return num1 / num2;
}
};
int main() {
// Calculator for integers
Calculator<int> intCalc(10, 3);
std::cout << "Int Addition: " << intCalc.add() << std::endl; // 13
std::cout << "Int Division: " << intCalc.divide() << std::endl; // 3 (integer division)
// Calculator for doubles
Calculator<double> doubleCalc(10.0, 3.0);
std::cout << "Double Addition: " << doubleCalc.add() << std::endl; // 13.0
std::cout << "Double Division: " << doubleCalc.divide() << std::endl; // 3.333...
return 0;
}Code language: C++ (cpp)
Explanation:
- The
Calculatortemplate is a practical example of how templates ensure code reusability for algorithms independent of the data type. - The logic for addition, subtraction, etc., is the same regardless of whether the numbers are integers or floating-point types. The template relies on the fact that the chosen type
Tprovides meaningful definitions for the arithmetic operators. The resulting instantiated class (e.g.,Calculator<int>) is completely type-safe.
Exercise 16: Class Template with Non-Type Parameter
Problem Statement: Create a class template named FixedSizeArray that takes both a type parameter T and an integer non-type parameter N for the size of the array. The array should be a private standard C-style array of size N. Include a member function T get(int index) to access elements.
Expected Output:
Array size: 10
Element at index 0: 100
Element at index 2: 3.14
+ Hint
- The template signature will be
template <typename T, int N>. - The non-type parameter
Nmust be a constant value known at compile time. The private member array declaration will beT data[N];
+ Show Solution
#include <iostream>
#include <stdexcept>
// 1. Class Template Definition with a non-type parameter N
template <typename T, int N>
class FixedSizeArray {
private:
T data[N]; // N must be known at compile time
public:
// Constructor (initializes all elements to default value T())
FixedSizeArray() {
for (int i = 0; i < N; ++i) {
data[i] = T();
}
}
// Access element
T get(int index) const {
if (index < 0 || index >= N) {
throw std::out_of_range("Index out of bounds.");
}
return data[index];
}
// Setter for simplicity
void set(int index, T value) {
if (index < 0 || index >= N) return;
data[index] = value;
}
// Getter for size
int size() const { return N; }
};
int main() {
// Fixed array of 10 integers
FixedSizeArray<int, 10> arr10;
arr10.set(0, 100);
arr10.set(9, 999);
std::cout << "Array size: " << arr10.size() << std::endl;
std::cout << "Element at index 0: " << arr10.get(0) << std::endl;
// Fixed array of 5 doubles
FixedSizeArray<double, 5> arr5;
arr5.set(2, 3.14);
std::cout << "Element at index 2: " << arr5.get(2) << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- This template demonstrates the use of a non-type template parameter (
int N). This allows the size of the container to be part of the type itself, determined at compile time. - This is highly efficient as memory is allocated directly on the stack (if the object is not created using
new) and bounds checking can potentially be optimized. - Note that
FixedSizeArray<int, 10>andFixedSizeArray<int, 5>are considered two completely separate and incompatible types by the compiler.
Exercise 17: Templated Linked List Node
Problem Statement: Define a class template for a Node in a singly linked list. The node must hold a value of generic type T and a pointer to the next node (which must be a pointer to a Node<T>). Include a constructor to initialize the value.
Expected Output:
Integer List: 10 20 30
+ Hint
- The node pointer must refer to the same type of node. Inside the template class, the pointer to the next node is declared as
Node<T>* next;. - The constructor should initialize the value and set the next pointer to
nullptrby default.
+ Show Solution
#include <iostream>
// 1. Class Template Definition
template <typename T>
class Node {
public:
T data;
Node<T>* next; // Pointer to the next node of the same type T
// Constructor to initialize data and set next to null
Node(T val) : data(val), next(nullptr) {}
};
int main() {
// Creating integer nodes
Node<int>* head = new Node<int>(10);
Node<int>* second = new Node<int>(20);
Node<int>* third = new Node<int>(30);
// Linking the nodes
head->next = second;
second->next = third;
// Traversing the list
Node<int>* current = head;
std::cout << "Integer List: ";
while (current != nullptr) {
std::cout << current->data << " ";
current = current->next;
}
std::cout << std::endl;
// Clean up memory
delete head;
delete second;
delete third;
return 0;
}Code language: C++ (cpp)
Explanation:
- This template demonstrates how a class template can be self-referential. The pointer
Node<T>* next;within theNode<T>definition is key to forming the linked list structure. - By templating the node, a single definition can create the building blocks for a list of integers, doubles, or complex user-defined objects, making the linked list structure highly generic.
Exercise 18: Generic Vector Class with Iterators
Problem Statement: Design a simple generic container class MyVector<T> that internally uses a dynamic array (T*). Include basic iterator support by defining a nested type alias iterator as a pointer to T (T*). Implement the public methods begin() and end() to return the start and end iterators.
Expected Output:
Vector elements (using iterators): 1 2 3 4 5
Sum using std::accumulate: 15
+ Hint
- The internal dynamic array is managed similarly to Exercise 14. The
begin()function simply returns the pointer to the first element (T* array_ptr). - The
end()function returns the pointer one past the last element (T* array_ptr + size).
+ Show Solution
#include <iostream>
#include <numeric>
// 1. Class Template Definition
template <typename T>
class MyVector {
private:
T* array_ptr;
int size;
public:
// Define the iterator type (simplest form: a pointer to T)
using iterator = T*;
MyVector(int s) : size(s) {
array_ptr = new T[size]();
}
~MyVector() { delete[] array_ptr; }
// Iterator interface functions
iterator begin() {
return array_ptr;
}
iterator end() {
return array_ptr + size; // One past the last element
}
// Overloaded subscript operator for access
T& operator[](int index) {
return array_ptr[index];
}
};
int main() {
// Create a vector of 5 integers
MyVector<int> vec(5);
vec[0] = 1;
vec[1] = 2;
vec[2] = 3;
vec[3] = 4;
vec[4] = 5;
// Use iterators (pointer arithmetic)
std::cout << "Vector elements (using iterators): ";
for (MyVector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// Use std::accumulate with the custom iterators
int sum = std::accumulate(vec.begin(), vec.end(), 0);
std::cout << "Sum using std::accumulate: " << sum << std::endl;
return 0;
}Code language: C++ (cpp)
Explanation:
- This template is a simplified view of how the Standard Template Library (STL) containers work. By defining a public nested type alias
iterator(here, simplyT*) and providingbegin()andend()methods, the custom container becomes compatible with standard C++ algorithms likestd::accumulate,std::sort, and the range-based for loop. - This compatibility is the ultimate goal of generic programming in C++, allowing algorithms to work seamlessly across various container types.
Exercise 19: Templated Class with Default Argument
Problem Statement: Create a class template named HoldingCell with two type parameters, T1 and T2. Set the second type parameter, T2, to have a default type argument of int. The class should hold two private members of types T1 and T2 and a constructor to initialize them.
Expected Output:
--- Cell 1 (Default int) ---
Value 1 (Type NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE): ID
Value 2 (Type i): 42
--- Cell 2 (Explicit double) ---
Value 1 (Type c): G
Value 2 (Type d): 9.81
+ Hint
- The default argument is placed directly in the template parameter list:
template <typename T1, typename T2 = int>. - Test by instantiating the class both by explicitly specifying
T2(e.g.,HoldingCell<float, double>) and by omitting it (e.g.,HoldingCell<char>).
+ Show Solution
#include <iostream>
#include <string>
// 1. Class Template Definition with default type T2 = int
template <typename T1, typename T2 = int>
class HoldingCell {
private:
T1 value1;
T2 value2;
public:
// Constructor
HoldingCell(T1 v1, T2 v2) : value1(v1), value2(v2) {}
void display() const {
std::cout << "Value 1 (Type " << typeid(T1).name() << "): " << value1 << std::endl;
std::cout << "Value 2 (Type " << typeid(T2).name() << "): " << value2 << std::endl;
}
};
int main() {
// 1. Omitting T2: T2 defaults to int
HoldingCell<std::string> cell1("ID", 42);
std::cout << "--- Cell 1 (Default int) ---" << std::endl;
cell1.display();
// 2. Explicitly providing T2: T2 is set to double
HoldingCell<char, double> cell2('G', 9.81);
std::cout << "--- Cell 2 (Explicit double) ---" << std::endl;
cell2.display();
return 0;
}Code language: C++ (cpp)
Explanation:
- This exercise demonstrates default template arguments. This feature enhances the usability of class templates by allowing the user to omit less frequently changed type parameters, which is a form of syntactic sugar.
- When instantiating
HoldingCell<std::string>, the compiler automatically substitutesintforT2. If a type is explicitly provided, as inHoldingCell<char, double>, the explicit type overrides the default argument.
Exercise 20: Full Template Specialization (Class)
Problem Statement: Create a generic class template named Printer that has one type parameter T and a single member function print(T value) that prints the value. Then, provide a full template specialization of the Printer class specifically for the type char* (C-style string). The specialized version’s print() function should display the string contents enclosed in double quotes.
Expected Output:
Generic Printer: 42
Generic Printer: 3.14
C-String Specialized Printer: "Hello Templates"
+ Hint
- Full specialization syntax for a class is:
template <> class ClassName<SpecificType> { ... };. - Use
std::coutto print the double quotes around thechar*value in the specialized version. The primary template needs no definition for the specialization to work.
+ Show Solution
#include <iostream>
#include <cstring>
// 1. Primary Class Template Definition
template <typename T>
class Printer {
public:
void print(T value) const {
std::cout << "Generic Printer: " << value << std::endl;
}
};
// 2. Full Template Specialization for char*
template <>
class Printer<char*> {
public:
void print(char* value) const {
std::cout << "C-String Specialized Printer: \"" << value << "\"" << std::endl;
}
};
int main() {
// Uses the generic template (T is int)
Printer<int> intPrinter;
intPrinter.print(42);
// Uses the generic template (T is double)
Printer<double> doublePrinter;
doublePrinter.print(3.14);
// Uses the specialized template (T is char*)
char* myString = strdup("Hello Templates");
Printer<char*> charPtrPrinter;
charPtrPrinter.print(myString);
free(myString);
return 0;
}Code language: C++ (cpp)
Explanation:
- Full template specialization allows a programmer to provide a completely separate, custom implementation for a specific type while keeping the general template definition for all other types. In this case, the generic
Printerworks for most types, but the special handling required for C-style strings (char*) is implemented intemplate <> class Printer<char*>. - This is necessary because raw C-style pointers are often printed as memory addresses by the generic implementation, whereas the user typically wants to see the actual string content.
Exercise 21: Function Template Specialization (Explicit)
Problem Statement: Create a generic function template named printValue that takes a parameter of type T and prints its value. Then, provide an explicit template specialization of this function specifically for the type bool. The specialized version should print the strings “True” or “False” instead of the numeric values 1 or 0.
Expected Output:
Boolean Specialization: True
Generic Print: 10
Generic Print: 10.5
+ Hint
- Unlike class templates, function templates only support full specialization (not partial specialization). The syntax is:
template <> return_type function_name<SpecificType>(parameter_list) { ... }. - The parameter list for
printValue<bool>should take aboolparameter.
+ Show Solution
#include <iostream>
// 1. Primary Function Template Definition
template <typename T>
void printValue(T value) {
std::cout << "Generic Print: " << value << std::endl;
}
// 2. Explicit Template Specialization for bool
template <>
void printValue<bool>(bool value) {
std::cout << "Boolean Specialization: " << (value ? "True" : "False") << std::endl;
}
int main() {
// Calls the specialized function (int is 1, so True should be printed)
bool b = 1;
printValue(b); // Compiler deduces T is bool, selects the specialization
// Calls the generic function (T is int)
int i = 10;
printValue(i);
// Calls the generic function (T is float)
float f = 10.5f;
printValue(f);
return 0;
}Code language: C++ (cpp)
Explanation:
Function templates use explicit specialization to provide unique logic for a specific type. While the generic version of printValue would print a boolean as its underlying integer value (0 or 1), the specialized version for bool provides the more readable textual representation (“True” or “False”). When printValue(b) is called, the compiler checks for the best match and selects the specialized printValue<bool> over generating the generic version.
Exercise 22: Variadic Templates (Simple Print)
Problem Statement: (C++11+) Write a variadic function template called print_all that can accept zero or more arguments of any type and print them all, separated by spaces. You must use a base case and a recursive case to unpack the parameter pack.
Expected Output:
--- Example 1 ---
10 3.14 A hello world
--- Example 2 ---
1 500
--- Example 3 (Empty) ---
+ Hint
- The base case handles zero arguments:
void print_all() {}. The recursive case handles one or more arguments:template <typename T, typename... Args> void print_all(T first, Args... rest). - Use
std::coutto printfirstand then recursively callprint_all(rest...).
+ Show Solution
#include <iostream>
#include <string>
// 1. Base Case: Stops the recursion when the pack is empty
void print_all() {
std::cout << std::endl;
}
// 2. Recursive Case: Unpacks one argument and recurses with the rest
template <typename T, typename... Args>
void print_all(T first, Args... rest) {
std::cout << first << " ";
// Recursive call to handle the rest of the arguments
print_all(rest...);
}
int main() {
// Calling with mixed types and various numbers of arguments
std::cout << "--- Example 1 ---" << std::endl;
print_all(10, 3.14, 'A', "hello world");
std::cout << "--- Example 2 ---" << std::endl;
print_all(true, 500);
std::cout << "--- Example 3 (Empty) ---" << std::endl;
print_all();
return 0;
}Code language: C++ (cpp)
Explanation:
- Variadic templates (introduced in C++11) allow functions or classes to accept an arbitrary number of arguments of arbitrary types, captured in a parameter pack (
Args...). - The parameter pack must be unpacked using a recursive pattern: the recursive function takes the first argument (
T first) and calls itself with the rest of the pack (rest...). The base function (the one with no arguments) serves as the stopping condition, preventing infinite recursion.

Leave a Reply