Introduction to Templates
Templates in C++ enable generic programming by allowing developers to write code that operates on arbitrary data types without duplicating logic. Instead of writing separate functions or classes for each type, templates provide a blueprint that the compiler instantiates with specific types at compile time. This mechanism forms the foundation of the C++ Standard Library's container and algorithm components.
Function Templates
Function templates define a generic function strcuture where type parameters serve as placeholders. The compiler generates concrete function instances when the template is invoked with specific argument types.
Syntax:
template <typename T>
return_type function_name(parameter_list) {
// implementation
}
Example - Comparing Values:
#include <iostream>
using namespace std;
template <typename Element>
Element findMaximum(Element first, Element second) {
return (first > second) ? first : second;
}
int main() {
int num1 = 25, num2 = 42;
double val1 = 3.14, val2 = 2.71;
char letterA = 'M', letterB = 'Z';
cout << "Larger int: " << findMaximum(num1, num2) << endl;
cout << "Larger double: " << findMaximum(val1, val2) << endl;
cout << "Larger char: " << findMaximum(letterA, letterB) << endl;
return 0;
}
The findMaximum template works identically for integers, floating-point numbers, and characters because all these types support comparison operations.
Multiple Template Parameters:
template <typename T1, typename T2>
void displayPair(T1 first, T2 second) {
cout << "First: " << first << endl;
cout << "Second: " << second << endl;
}
int main() {
displayPair(100, "text");
displayPair(3.14, 'A');
return 0;
}
Class Templates
Class templates extend the generic aproach to entire classes, enabling the creation of parameterized types. All member within a class template implicitly become templates themselves.
Syntax:
template <typename T>
class ClassName {
T memberVariable;
public:
ClassName(T initial);
T getMember();
};
Example - Generic Container:
#include <iostream>
using namespace std;
template <typename StorageType>
class Container {
private:
StorageType storedData;
public:
Container(StorageType initial) : storedData(initial) {}
void update(StorageType newData) {
storedData = newData;
}
StorageType retrieve() {
return storedData;
}
};
int main() {
Container<int> integerContainer(999);
Container<string> textContainer("Initial");
cout << "Integer: " << integerContainer.retrieve() << endl;
cout << "String: " << textContainer.retrieve() << endl;
integerContainer.update(888);
textContainer.update("Modified");
cout << "Updated Integer: " << integerContainer.retrieve() << endl;
cout << "Updated String: " << textContainer.retrieve() << endl;
return 0;
}
Template Specialization
When the default template implementation proves unsuitable for specific types, template specialization allows providing customized behavior for particular data types.
Full Specialization:
#include <iostream>
using namespace std;
template <typename DataType>
class DataHandler {
public:
void process(DataType input) {
cout << "Standard handler: " << input << endl;
}
};
template <>
class DataHandler<const char*> {
public:
void process(const char* input) {
cout << "String handler: " << input << endl;
}
};
int main() {
DataHandler<int> intHandler;
DataHandler<double> dblHandler;
DataHandler<const char*> strHandler;
intHandler.process(42);
dblHandler.process(2.718);
strHandler.process("Template specialization");
return 0;
}
Function Template Specialization:
template <typename T>
T calculate(T input) {
return input * 2;
}
template <>
const char* calculate(const char* input) {
return "String type cannot be doubled";
}
int main() {
cout << calculate(10) << endl;
cout << calculate(5.5) << endl;
cout << calculate("test") << endl;
return 0;
}
Non-Type Template Parameters
Templates can accept non-type parameters including integers, pointers, and references.
template <int FixedValue>
class FixedStorage {
public:
int getValue() {
return FixedValue;
}
};
int main() {
FixedStorage<100> storage1;
FixedStorage<200> storage2;
cout << "Storage 1: " << storage1.getValue() << endl;
cout << "Storage 2: " << storage2.getValue() << endl;
return 0;
}