Page Content

Tutorials

Templates In C++: An Essential Guide to Generic Programming

Templates in C++

Generic programming is made possible via templates, a core component of C++ that lets you write code that uses generic types instead of specific ones. In contrast to functions or classes, they serve as a template or formula that the compiler uses to produce certain classes or functions. We refer to this process of generating particular versions from a template as instantiation.

Templates are mostly used to avoid rewriting function or class code for each new type. This makes programs more resilient, reusable, and maintainable. Templates reduce runtime overhead by optimising code at compile time and providing type safety.

Function Templates

The formula from which the compiler can create type-specific versions of a function is provided by a function template. When functions must operate with diverse data types while carrying out the same normal task, this is especially helpful.

Syntax for a Function Template: In a template specification, the template keyword is followed by a list of parameters in angle brackets (<>). There can be no empty list here. The generic types in the function declaration are called by the placeholder name(s), such as type or T.

Example: A basic template for a compare function.

#include <iostream>
template <typename T> // 'T' is a type parameter
int compare(const T &v1, const T &v2) {
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}
int main() {
    // Implicit instantiation of compare<int>
    std::cout << compare(1, 0) << std::endl; // The compiler deduces T as 'int' 
    // Implicit instantiation of compare<double>
    std::cout << compare(3.14, 2.71) << std::endl; // The compiler deduces T as 'double'
    // Implicit instantiation of compare<char>
    std::cout << compare('a', 'z') << std::endl; // The compiler deduces T as 'char'
    return 0;
}

Output

1
1
-1

A particular version of compare for int will be instantiated by the compiler by replacing T with int when this comparison function is invoked, for instance, with two int parameters.

If they differ in the quantity or kind of their parameters, function templates can also be overloaded by other templates or by regular, nontemplate functions.

Class Templates

The pattern for creating classes is provided by class templates. The Standard Template Library (STL) uses them for vector, list, map, and stack container classes for data storage. Class templates must specify template argument types in angle brackets since the compiler cannot deduce them like function templates.

Syntax for a Class Template: Similar to function templates, a class template definition begins with template followed by a template parameter list.

Example: The Container class template is one example.

#include <iostream>
#include <cctype> // For toupper in char specialization
template <typename T> // 'T' is a type parameter
class Container {
private:
    T elt;
public:
    Container(const T arg) : elt(arg) {}
    T inc() { return elt + 1; }
    void display() { std::cout << "Generic container value: " << elt << std::endl; }
};
// Template specialization for Container<char> 
template <> // The empty angle brackets indicate full specialization
class Container<char> {
private:
    char elt;
public:
    Container(const char arg) : elt(arg) {}
    char uppercase() { return toupper(elt); } // Specific behavior for char 
    void display() { std::cout << "Char container value: " << elt << std::endl; }
};
int main() {
    // Instantiation of Container<int>
    Container<int> icont(5); // Compiler creates a 'Container' class where T is 'int' 
    std::cout << icont.inc() << std::endl; // Output: 6
    // Instantiation of Container<double>
    Container<double> dcont(10.5); // Compiler creates a 'Container' class where T is 'double'
    std::cout << dcont.inc() << std::endl; // Output: 11.5
    // Instantiation of Container<char> using specialization
    Container<char> ccont('r'); // Compiler uses the specialized 'Container<char>' class 
    std::cout << ccont.uppercase() << std::endl; // Output: R (uses specific 'uppercase' method)
    return 0;
}

Output

6
11.5
R

In order to utilize this class, you need to define the type, as in:

Container<int> icont(5); // Instantiates Container with T as int
cout << icont.inc() << endl; // Output: 6

This demonstrates how Container instances for various types, such as int, may be created using a single class template.

For classes, you can also offer template specialisation. In situations where the generic template definition is ineffective or inappropriate, this enables you to specify distinct implementations for a particular type or group of types.

Example of Template Specialization for Container<char>:

template <> // Indicates a full specialization
class Container <char> {
private:
    char elt;
public:
    Container(const char arg) : elt(arg) {}
    char uppercase() { return toupper(elt); } // Specific behavior for char
};
int main() {
    Container<int> icont(5);
    Container<char> ccont('r');
    cout << icont.inc() << endl;       // Uses general template, Output: 6
    cout << ccont.uppercase() << endl; // Uses specialized template, Output: R
    return 0;
}

In contrast to the generic inc(), Container provides a special uppercase() method, as this illustrates.

Member Templates are functions that are itself templates that are part of a class, whether it be an ordinary class or a class template. Templates can accept a variable number of template arguments with variable templates (C++11 and later). This is helpful in printf-like functions, for example, where the types and quantity of inputs are unknown in advance.

Instantiation of Class-Template Member Functions

A class template’s member functions are by default only instantiated when the program actually utilizes them. The aforementioned main code, for instance, calls icont.inc(), which instantiates the inc() method of Container. For Container, that particular member function would not be constructed if icont.display() were never called.

Explicit Instantiation

C++ allows explicit instantiation as needed, but usually implicitly. The compiler must be told to produce a specific template instance. Preventing repeated instantiations across several source files reduces compilation times and code bloat in large systems or libraries.

Syntax and Example: After the template keyword, the whole declaration of the particular function or class to be instantiated is presented in an explicit instantiation declaration.

// In a header file (e.g., my_templates.h)
template <typename T>
void printValue(const T& val) {
    std::cout << "Value: " << val << std::endl;
}
template <typename T>
class MyClass {
public:
    void doSomething(T val) {
        std::cout << "MyClass doing something with: " << val << std::endl;
    }
};
// In a source file (e.g., template_instantiations.cpp)
// Explicit instantiation definition for printValue<int>
template void printValue<int>(const int& val); 
// Explicit instantiation definition for MyClass<std::string>
template class MyClass<std::string>; 
// This instantiates all members of MyClass<std::string>, even if not used 
// An 'extern' explicit instantiation declaration means it will be defined elsewhere
extern template class MyClass<double>; // This instantiation will not happen in this file 

A class template’s members, including inline member functions, are instantiated by the compiler regardless of whether they are utilised in the program when an explicit instantiation definition is encountered (for example, template class MyClass;).

Key Considerations for Instantiation

Error Detection: Errors in templates that are connected to type, like trying to perform an operation that the instantiated type does not support (like using < on a custom class that does not define operator<), are usually found during the instantiation stage.

Header Files: Template definitions (for both functions and class member functions) are usually wholly included within header files, in contrast to non-template code, where declarations are frequently separated from definitions. For the compiler to conduct instantiation in each translation unit where the template is used, it must have access to the entire template definition.

You can also read What Is Mean By Dynamic Memory Management In C++?

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index