35% complete
C++ Templates
Templates are one of C++'s most powerful features, enabling generic programming and allowing you to write code that works with any data type while maintaining type safety and performance.
What You'll Learn
- How to create and use function templates
- How to implement class templates
- Template specialization techniques
- Template parameters and arguments
- Variadic templates (C++11 and later)
- Template metaprogramming concepts
Introduction to Templates
Templates allow you to define functions and classes that can work with any data type without having to rewrite the same code for each type. The compiler generates the appropriate code for each type used, ensuring type safety while eliminating code duplication.
Key Benefits of Templates
- Code Reusability: Write once, use with multiple data types
- Type Safety: Errors are caught at compile-time, not runtime
- Performance: No runtime overhead as the code is generated at compile-time
- Abstraction: Focus on the algorithm rather than specific data types
Function Templates
Function templates allow you to write a function that can operate on different data types without having to rewrite the same code for each type.
1#include <iostream>2using namespace std;34// Function template for finding the maximum of two values5template <typename T>6T max(T a, T b) {7 return (a > b) ? a : b;8}910int main() {11 // Using the template function with different data types12 int i1 = 10, i2 = 20;13 cout << "Max(int): " << max(i1, i2) << endl; // Uses max<int>1415 double d1 = 10.5, d2 = 20.5;16 cout << "Max(double): " << max(d1, d2) << endl; // Uses max<double>1718 string s1 = "apple", s2 = "banana";19 cout << "Max(string): " << max(s1, s2) << endl; // Uses max<string>2021 return 0;22}
Template Type Deduction
The compiler can automatically deduce the template type parameter from the arguments provided.
1#include <iostream>2#include <string>3using namespace std;45// Function template with multiple type parameters6template <typename T, typename U>7auto add(T a, U b) -> decltype(a + b) { // C++11 trailing return type8 return a + b;9}1011int main() {12 // The compiler deduces the types automatically13 auto result1 = add(5, 3); // Both are int14 auto result2 = add(5.5, 3); // double + int15 auto result3 = add(5, 3.5); // int + double16 auto result4 = add(string("Hello "), "World"); // string + const char*1718 cout << "Result 1: " << result1 << endl; // 819 cout << "Result 2: " << result2 << endl; // 8.520 cout << "Result 3: " << result3 << endl; // 8.521 cout << "Result 4: " << result4 << endl; // Hello World2223 return 0;24}
Explicit Template Instantiation
You can explicitly specify the template type when needed.
1#include <iostream>2using namespace std;34template <typename T>5T square(T value) {6 return value * value;7}89int main() {10 // Implicit instantiation11 auto result1 = square(5); // square<int>(5)1213 // Explicit instantiation14 auto result2 = square<double>(5); // Explicitly use double1516 cout << "Result 1: " << result1 << endl; // 2517 cout << "Result 2: " << result2 << endl; // 25.01819 return 0;20}
Class Templates
Class templates allow you to create classes that can work with different data types.
1#include <iostream>2using namespace std;34// Class template for a simple pair5template <typename T1, typename T2>6class Pair {7private:8 T1 first;9 T2 second;1011public:12 Pair(T1 a, T2 b) : first(a), second(b) {}1314 T1 getFirst() const {15 return first;16 }1718 T2 getSecond() const {19 return second;20 }2122 void setFirst(T1 a) {23 first = a;24 }2526 void setSecond(T2 b) {27 second = b;28 }2930 void display() const {31 cout << "(" << first << ", " << second << ")" << endl;32 }33};3435int main() {36 // Creating different pairs with different types37 Pair<int, double> p1(10, 20.5);38 Pair<string, int> p2("Age", 25);39 Pair<char, char> p3('A', 'B');4041 p1.display(); // (10, 20.5)42 p2.display(); // (Age, 25)43 p3.display(); // (A, B)4445 // Accessing and modifying members46 cout << "First value of p1: " << p1.getFirst() << endl;47 p2.setSecond(30);48 p2.display(); // (Age, 30)4950 return 0;51}
Template with Default Type
You can provide default types for template parameters.
1#include <iostream>2using namespace std;34// Class template with default type parameter5template <typename T = int>6class Container {7private:8 T element;910public:11 Container(T value) : element(value) {}1213 T getValue() const {14 return element;15 }1617 void setValue(T value) {18 element = value;19 }20};2122int main() {23 // Using explicit type24 Container<double> doubleContainer(5.5);25 cout << "Double container: " << doubleContainer.getValue() << endl;2627 // Using default type (int)28 Container<> intContainer(10); // Same as Container<int>29 cout << "Int container: " << intContainer.getValue() << endl;3031 return 0;32}
Template Specialization
Template specialization allows you to provide a specific implementation for a particular data type.
Function Template Specialization
1#include <iostream>2#include <string>3using namespace std;45// Primary template6template <typename T>7void display(T value) {8 cout << "Generic Template: " << value << endl;9}1011// Specialization for char*12template <>13void display<const char*>(const char* value) {14 cout << "Specialized Template for const char*: " << value << endl;15}1617// Specialization for int18template <>19void display<int>(int value) {20 cout << "Specialized Template for int: " << value << " (decimal)" << endl;21 cout << " " << hex << value << " (hexadecimal)" << endl;22 cout << dec; // Reset to decimal output23}2425int main() {26 display(10); // Uses specialized int version27 display(10.5); // Uses generic version28 display("Hello World"); // Uses specialized const char* version29 display(string("Test")); // Uses generic version3031 return 0;32}
Class Template Specialization
1#include <iostream>2using namespace std;34// Primary template5template <typename T>6class Storage {7private:8 T data;910public:11 Storage(T value) : data(value) {}1213 void print() const {14 cout << "Generic storage: " << data << endl;15 }16};1718// Specialization for bool19template <>20class Storage<bool> {21private:22 bool data;2324public:25 Storage(bool value) : data(value) {}2627 void print() const {28 cout << "Bool storage: " << (data ? "true" : "false") << endl;29 }30};3132int main() {33 Storage<int> intStorage(42);34 Storage<string> strStorage("Hello");35 Storage<bool> boolStorage(true);3637 intStorage.print(); // Uses generic version38 strStorage.print(); // Uses generic version39 boolStorage.print(); // Uses specialized version for bool4041 return 0;42}
Partial Specialization
Partial specialization allows you to specialize a template for a subset of possible types. This is only available for class templates, not function templates.
1#include <iostream>2using namespace std;34// Primary template5template <typename T, typename U>6class Converter {7public:8 static void convert() {9 cout << "Generic conversion" << endl;10 }11};1213// Partial specialization for pointer types14template <typename T, typename U>15class Converter<T*, U*> {16public:17 static void convert() {18 cout << "Pointer to pointer conversion" << endl;19 }20};2122// Partial specialization for same types23template <typename T>24class Converter<T, T> {25public:26 static void convert() {27 cout << "Same type conversion (no conversion needed)" << endl;28 }29};3031// Full specialization32template <>33class Converter<int, double> {34public:35 static void convert() {36 cout << "Int to double conversion" << endl;37 }38};3940int main() {41 Converter<int, char>::convert(); // Generic42 Converter<int*, float*>::convert(); // Pointer specialization43 Converter<double, double>::convert(); // Same type specialization44 Converter<int, double>::convert(); // Full specialization4546 return 0;47}
Non-Type Template Parameters
Templates can also have non-type parameters, such as integers, pointers, or references.
1#include <iostream>2#include <array>3using namespace std;45// Function template with non-type parameter6template <int N>7void printValue() {8 cout << "Value: " << N << endl;9}1011// Class template with non-type parameter12template <typename T, size_t SIZE>13class FixedArray {14private:15 T data[SIZE];1617public:18 FixedArray() {19 for (size_t i = 0; i < SIZE; ++i) {20 data[i] = T();21 }22 }2324 T& operator[](size_t index) {25 return data[index];26 }2728 const T& operator[](size_t index) const {29 return data[index];30 }3132 size_t size() const {33 return SIZE;34 }35};3637int main() {38 // Using function template with non-type parameter39 printValue<42>(); // Value: 424041 // Using class template with non-type parameter42 FixedArray<int, 5> intArray;4344 for (size_t i = 0; i < intArray.size(); ++i) {45 intArray[i] = i * 10;46 }4748 cout << "Array elements: ";49 for (size_t i = 0; i < intArray.size(); ++i) {50 cout << intArray[i] << " ";51 }52 cout << endl;5354 // Using std::array which uses a non-type template parameter55 array<double, 3> stdArray = {1.1, 2.2, 3.3};5657 cout << "std::array elements: ";58 for (const auto& elem : stdArray) {59 cout << elem << " ";60 }61 cout << endl;6263 return 0;64}
Variadic Templates (C++11)
Variadic templates allow you to define templates with a variable number of parameters.
1#include <iostream>2using namespace std;34// Base case for recursion termination5void print() {6 cout << endl;7}89// Variadic template function10template <typename T, typename... Args>11void print(T first, Args... rest) {12 cout << first << " ";13 print(rest...); // Recursive call with remaining arguments14}1516// Sum function using variadic templates17template <typename T>18T sum(T value) {19 return value;20}2122template <typename T, typename... Args>23T sum(T first, Args... rest) {24 return first + sum(rest...);25}2627int main() {28 // Print different types29 print(1, 2.5, "Hello", 'a', true); // 1 2.5 Hello a 13031 // Sum of numbers32 int sumInts = sum(1, 2, 3, 4, 5);33 double sumMixed = sum(1.5, 2, 3, 4.5);3435 cout << "Sum of ints: " << sumInts << endl; // 1536 cout << "Sum of mixed: " << sumMixed << endl; // 113738 return 0;39}
Perfect Forwarding (C++11)
Perfect forwarding allows a function template to forward its arguments to another function while preserving their value categories (lvalue/rvalue).
1#include <iostream>2#include <utility> // For std::forward3#include <string>4using namespace std;56// A class with different constructors7class Example {8public:9 // Constructor for lvalue strings10 Example(const string& str) {11 cout << "Constructor with lvalue reference: " << str << endl;12 }1314 // Constructor for rvalue strings15 Example(string&& str) {16 cout << "Constructor with rvalue reference: " << str << endl;17 }18};1920// Function template with perfect forwarding21template <typename T>22void createExample(T&& arg) {23 Example e(std::forward<T>(arg)); // Forward the argument24}2526int main() {27 string str = "Hello";2829 // Call with lvalue30 createExample(str); // Should use the lvalue constructor3132 // Call with rvalue33 createExample(string("World")); // Should use the rvalue constructor34 createExample("Direct"); // Should use the rvalue constructor3536 return 0;37}
Template Metaprogramming
Template metaprogramming is a technique where templates are used to perform computations at compile time rather than at runtime.
Compile-Time Factorial
1#include <iostream>2using namespace std;34// Compile-time factorial calculation5template <unsigned int N>6struct Factorial {7 static constexpr unsigned int value = N * Factorial<N - 1>::value;8};910// Base case specialization11template <>12struct Factorial<0> {13 static constexpr unsigned int value = 1;14};1516int main() {17 // Calculated at compile time18 cout << "Factorial of 5: " << Factorial<5>::value << endl; // 12019 cout << "Factorial of 10: " << Factorial<10>::value << endl; // 36288002021 return 0;22}
Type Traits
Type traits provide information about types at compile-time.
1#include <iostream>2#include <type_traits>3using namespace std;45template <typename T>6void checkType() {7 cout << "Type information for: " << typeid(T).name() << endl;89 if (is_integral<T>::value) {10 cout << "- Is integral type" << endl;11 }1213 if (is_floating_point<T>::value) {14 cout << "- Is floating-point type" << endl;15 }1617 if (is_pointer<T>::value) {18 cout << "- Is pointer type" << endl;19 }2021 if (is_class<T>::value) {22 cout << "- Is class type" << endl;23 }2425 cout << "- Size: " << sizeof(T) << " bytes" << endl;26 cout << endl;27}2829class TestClass {};3031int main() {32 checkType<int>();33 checkType<double>();34 checkType<int*>();35 checkType<TestClass>();3637 return 0;38}
Best Practices and Common Issues
Best Practices
- Use templates to avoid code duplication but keep them simple
- Add appropriate constraints to template parameters when possible
- Use static_assert to catch type errors at compile-time
- Use meaningful template parameter names (e.g., 'T' for type, 'N' for integral value)
- Consider performance implications (template instantiation can increase compile time and binary size)
Common Issues
- Cryptic error messages when template instantiation fails
- Increased compile time with heavy template use
- Code bloat due to multiple instantiations
- Poor debugging experience due to template instantiation complexity
- Limited ability to separate template declaration and implementation across files
Improved Error Messages with Concepts (C++20)
C++20 introduced Concepts, which provide a way to constrain template parameters with more readable error messages.
1#include <iostream>2#include <concepts> // C++203using namespace std;45// Define a concept for numeric types6template <typename T>7concept Numeric = is_arithmetic_v<T>;89// Function that works only with numeric types10template <Numeric T>11T add(T a, T b) {12 return a + b;13}1415// Function that works only with integral types16template <typename T>17 requires integral<T>18T multiply(T a, T b) {19 return a * b;20}2122int main() {23 cout << add(5, 3) << endl; // Works: 824 cout << add(5.5, 3.2) << endl; // Works: 8.72526 // Uncommenting this would cause a clear error:27 // cout << add("Hello", "World") << endl; // Error: "Hello" is not a numeric type2829 cout << multiply(5, 3) << endl; // Works: 153031 // Uncommenting this would cause a clear error:32 // cout << multiply(5.5, 3.2) << endl; // Error: 5.5 is not an integral type3334 return 0;35}
Summary
In this tutorial, you've learned:
- How to create function templates for generic algorithms
- How to implement class templates for generic containers
- How to use template specialization for type-specific optimizations
- Working with non-type template parameters
- Advanced features like variadic templates and perfect forwarding
- Template metaprogramming concepts for compile-time computation
- Best practices and common issues when working with templates
Templates are a powerful feature of C++ that enable generic programming, code reuse, and type safety. They are extensively used in the Standard Template Library (STL) and many other modern C++ libraries.
Practice Exercise
Try implementing a generic Stack class template that can work with any data type. Add methods for push, pop, top, and checking if the stack is empty. Test it with different data types including built-in types and custom classes.
Related Tutorials
Learn about object-oriented programming in C++.
Learn moreExplore the powerful pre-built components in the STL.
Learn moreUnderstand memory management in C++.
Learn more