Progress7 of 20 topics

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.

cpp
1#include <iostream>
2using namespace std;
3
4// Function template for finding the maximum of two values
5template <typename T>
6T max(T a, T b) {
7 return (a > b) ? a : b;
8}
9
10int main() {
11 // Using the template function with different data types
12 int i1 = 10, i2 = 20;
13 cout << "Max(int): " << max(i1, i2) << endl; // Uses max<int>
14
15 double d1 = 10.5, d2 = 20.5;
16 cout << "Max(double): " << max(d1, d2) << endl; // Uses max<double>
17
18 string s1 = "apple", s2 = "banana";
19 cout << "Max(string): " << max(s1, s2) << endl; // Uses max<string>
20
21 return 0;
22}

Template Type Deduction

The compiler can automatically deduce the template type parameter from the arguments provided.

cpp
1#include <iostream>
2#include <string>
3using namespace std;
4
5// Function template with multiple type parameters
6template <typename T, typename U>
7auto add(T a, U b) -> decltype(a + b) { // C++11 trailing return type
8 return a + b;
9}
10
11int main() {
12 // The compiler deduces the types automatically
13 auto result1 = add(5, 3); // Both are int
14 auto result2 = add(5.5, 3); // double + int
15 auto result3 = add(5, 3.5); // int + double
16 auto result4 = add(string("Hello "), "World"); // string + const char*
17
18 cout << "Result 1: " << result1 << endl; // 8
19 cout << "Result 2: " << result2 << endl; // 8.5
20 cout << "Result 3: " << result3 << endl; // 8.5
21 cout << "Result 4: " << result4 << endl; // Hello World
22
23 return 0;
24}

Explicit Template Instantiation

You can explicitly specify the template type when needed.

cpp
1#include <iostream>
2using namespace std;
3
4template <typename T>
5T square(T value) {
6 return value * value;
7}
8
9int main() {
10 // Implicit instantiation
11 auto result1 = square(5); // square<int>(5)
12
13 // Explicit instantiation
14 auto result2 = square<double>(5); // Explicitly use double
15
16 cout << "Result 1: " << result1 << endl; // 25
17 cout << "Result 2: " << result2 << endl; // 25.0
18
19 return 0;
20}

Class Templates

Class templates allow you to create classes that can work with different data types.

cpp
1#include <iostream>
2using namespace std;
3
4// Class template for a simple pair
5template <typename T1, typename T2>
6class Pair {
7private:
8 T1 first;
9 T2 second;
10
11public:
12 Pair(T1 a, T2 b) : first(a), second(b) {}
13
14 T1 getFirst() const {
15 return first;
16 }
17
18 T2 getSecond() const {
19 return second;
20 }
21
22 void setFirst(T1 a) {
23 first = a;
24 }
25
26 void setSecond(T2 b) {
27 second = b;
28 }
29
30 void display() const {
31 cout << "(" << first << ", " << second << ")" << endl;
32 }
33};
34
35int main() {
36 // Creating different pairs with different types
37 Pair<int, double> p1(10, 20.5);
38 Pair<string, int> p2("Age", 25);
39 Pair<char, char> p3('A', 'B');
40
41 p1.display(); // (10, 20.5)
42 p2.display(); // (Age, 25)
43 p3.display(); // (A, B)
44
45 // Accessing and modifying members
46 cout << "First value of p1: " << p1.getFirst() << endl;
47 p2.setSecond(30);
48 p2.display(); // (Age, 30)
49
50 return 0;
51}

Template with Default Type

You can provide default types for template parameters.

cpp
1#include <iostream>
2using namespace std;
3
4// Class template with default type parameter
5template <typename T = int>
6class Container {
7private:
8 T element;
9
10public:
11 Container(T value) : element(value) {}
12
13 T getValue() const {
14 return element;
15 }
16
17 void setValue(T value) {
18 element = value;
19 }
20};
21
22int main() {
23 // Using explicit type
24 Container<double> doubleContainer(5.5);
25 cout << "Double container: " << doubleContainer.getValue() << endl;
26
27 // Using default type (int)
28 Container<> intContainer(10); // Same as Container<int>
29 cout << "Int container: " << intContainer.getValue() << endl;
30
31 return 0;
32}

Template Specialization

Template specialization allows you to provide a specific implementation for a particular data type.

Function Template Specialization

cpp
1#include <iostream>
2#include <string>
3using namespace std;
4
5// Primary template
6template <typename T>
7void display(T value) {
8 cout << "Generic Template: " << value << endl;
9}
10
11// Specialization for char*
12template <>
13void display<const char*>(const char* value) {
14 cout << "Specialized Template for const char*: " << value << endl;
15}
16
17// Specialization for int
18template <>
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 output
23}
24
25int main() {
26 display(10); // Uses specialized int version
27 display(10.5); // Uses generic version
28 display("Hello World"); // Uses specialized const char* version
29 display(string("Test")); // Uses generic version
30
31 return 0;
32}

Class Template Specialization

cpp
1#include <iostream>
2using namespace std;
3
4// Primary template
5template <typename T>
6class Storage {
7private:
8 T data;
9
10public:
11 Storage(T value) : data(value) {}
12
13 void print() const {
14 cout << "Generic storage: " << data << endl;
15 }
16};
17
18// Specialization for bool
19template <>
20class Storage<bool> {
21private:
22 bool data;
23
24public:
25 Storage(bool value) : data(value) {}
26
27 void print() const {
28 cout << "Bool storage: " << (data ? "true" : "false") << endl;
29 }
30};
31
32int main() {
33 Storage<int> intStorage(42);
34 Storage<string> strStorage("Hello");
35 Storage<bool> boolStorage(true);
36
37 intStorage.print(); // Uses generic version
38 strStorage.print(); // Uses generic version
39 boolStorage.print(); // Uses specialized version for bool
40
41 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.

cpp
1#include <iostream>
2using namespace std;
3
4// Primary template
5template <typename T, typename U>
6class Converter {
7public:
8 static void convert() {
9 cout << "Generic conversion" << endl;
10 }
11};
12
13// Partial specialization for pointer types
14template <typename T, typename U>
15class Converter<T*, U*> {
16public:
17 static void convert() {
18 cout << "Pointer to pointer conversion" << endl;
19 }
20};
21
22// Partial specialization for same types
23template <typename T>
24class Converter<T, T> {
25public:
26 static void convert() {
27 cout << "Same type conversion (no conversion needed)" << endl;
28 }
29};
30
31// Full specialization
32template <>
33class Converter<int, double> {
34public:
35 static void convert() {
36 cout << "Int to double conversion" << endl;
37 }
38};
39
40int main() {
41 Converter<int, char>::convert(); // Generic
42 Converter<int*, float*>::convert(); // Pointer specialization
43 Converter<double, double>::convert(); // Same type specialization
44 Converter<int, double>::convert(); // Full specialization
45
46 return 0;
47}

Non-Type Template Parameters

Templates can also have non-type parameters, such as integers, pointers, or references.

cpp
1#include <iostream>
2#include <array>
3using namespace std;
4
5// Function template with non-type parameter
6template <int N>
7void printValue() {
8 cout << "Value: " << N << endl;
9}
10
11// Class template with non-type parameter
12template <typename T, size_t SIZE>
13class FixedArray {
14private:
15 T data[SIZE];
16
17public:
18 FixedArray() {
19 for (size_t i = 0; i < SIZE; ++i) {
20 data[i] = T();
21 }
22 }
23
24 T& operator[](size_t index) {
25 return data[index];
26 }
27
28 const T& operator[](size_t index) const {
29 return data[index];
30 }
31
32 size_t size() const {
33 return SIZE;
34 }
35};
36
37int main() {
38 // Using function template with non-type parameter
39 printValue<42>(); // Value: 42
40
41 // Using class template with non-type parameter
42 FixedArray<int, 5> intArray;
43
44 for (size_t i = 0; i < intArray.size(); ++i) {
45 intArray[i] = i * 10;
46 }
47
48 cout << "Array elements: ";
49 for (size_t i = 0; i < intArray.size(); ++i) {
50 cout << intArray[i] << " ";
51 }
52 cout << endl;
53
54 // Using std::array which uses a non-type template parameter
55 array<double, 3> stdArray = {1.1, 2.2, 3.3};
56
57 cout << "std::array elements: ";
58 for (const auto& elem : stdArray) {
59 cout << elem << " ";
60 }
61 cout << endl;
62
63 return 0;
64}

Variadic Templates (C++11)

Variadic templates allow you to define templates with a variable number of parameters.

cpp
1#include <iostream>
2using namespace std;
3
4// Base case for recursion termination
5void print() {
6 cout << endl;
7}
8
9// Variadic template function
10template <typename T, typename... Args>
11void print(T first, Args... rest) {
12 cout << first << " ";
13 print(rest...); // Recursive call with remaining arguments
14}
15
16// Sum function using variadic templates
17template <typename T>
18T sum(T value) {
19 return value;
20}
21
22template <typename T, typename... Args>
23T sum(T first, Args... rest) {
24 return first + sum(rest...);
25}
26
27int main() {
28 // Print different types
29 print(1, 2.5, "Hello", 'a', true); // 1 2.5 Hello a 1
30
31 // Sum of numbers
32 int sumInts = sum(1, 2, 3, 4, 5);
33 double sumMixed = sum(1.5, 2, 3, 4.5);
34
35 cout << "Sum of ints: " << sumInts << endl; // 15
36 cout << "Sum of mixed: " << sumMixed << endl; // 11
37
38 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).

cpp
1#include <iostream>
2#include <utility> // For std::forward
3#include <string>
4using namespace std;
5
6// A class with different constructors
7class Example {
8public:
9 // Constructor for lvalue strings
10 Example(const string& str) {
11 cout << "Constructor with lvalue reference: " << str << endl;
12 }
13
14 // Constructor for rvalue strings
15 Example(string&& str) {
16 cout << "Constructor with rvalue reference: " << str << endl;
17 }
18};
19
20// Function template with perfect forwarding
21template <typename T>
22void createExample(T&& arg) {
23 Example e(std::forward<T>(arg)); // Forward the argument
24}
25
26int main() {
27 string str = "Hello";
28
29 // Call with lvalue
30 createExample(str); // Should use the lvalue constructor
31
32 // Call with rvalue
33 createExample(string("World")); // Should use the rvalue constructor
34 createExample("Direct"); // Should use the rvalue constructor
35
36 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

cpp
1#include <iostream>
2using namespace std;
3
4// Compile-time factorial calculation
5template <unsigned int N>
6struct Factorial {
7 static constexpr unsigned int value = N * Factorial<N - 1>::value;
8};
9
10// Base case specialization
11template <>
12struct Factorial<0> {
13 static constexpr unsigned int value = 1;
14};
15
16int main() {
17 // Calculated at compile time
18 cout << "Factorial of 5: " << Factorial<5>::value << endl; // 120
19 cout << "Factorial of 10: " << Factorial<10>::value << endl; // 3628800
20
21 return 0;
22}

Type Traits

Type traits provide information about types at compile-time.

cpp
1#include <iostream>
2#include <type_traits>
3using namespace std;
4
5template <typename T>
6void checkType() {
7 cout << "Type information for: " << typeid(T).name() << endl;
8
9 if (is_integral<T>::value) {
10 cout << "- Is integral type" << endl;
11 }
12
13 if (is_floating_point<T>::value) {
14 cout << "- Is floating-point type" << endl;
15 }
16
17 if (is_pointer<T>::value) {
18 cout << "- Is pointer type" << endl;
19 }
20
21 if (is_class<T>::value) {
22 cout << "- Is class type" << endl;
23 }
24
25 cout << "- Size: " << sizeof(T) << " bytes" << endl;
26 cout << endl;
27}
28
29class TestClass {};
30
31int main() {
32 checkType<int>();
33 checkType<double>();
34 checkType<int*>();
35 checkType<TestClass>();
36
37 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.

cpp
1#include <iostream>
2#include <concepts> // C++20
3using namespace std;
4
5// Define a concept for numeric types
6template <typename T>
7concept Numeric = is_arithmetic_v<T>;
8
9// Function that works only with numeric types
10template <Numeric T>
11T add(T a, T b) {
12 return a + b;
13}
14
15// Function that works only with integral types
16template <typename T>
17 requires integral<T>
18T multiply(T a, T b) {
19 return a * b;
20}
21
22int main() {
23 cout << add(5, 3) << endl; // Works: 8
24 cout << add(5.5, 3.2) << endl; // Works: 8.7
25
26 // Uncommenting this would cause a clear error:
27 // cout << add("Hello", "World") << endl; // Error: "Hello" is not a numeric type
28
29 cout << multiply(5, 3) << endl; // Works: 15
30
31 // Uncommenting this would cause a clear error:
32 // cout << multiply(5.5, 3.2) << endl; // Error: 5.5 is not an integral type
33
34 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 more

Explore the powerful pre-built components in the STL.

Learn more

Understand memory management in C++.

Learn more