C++

How to Use C++ Templates

In basic C++ programming, the data type, e.g., int or char, must be indicated in a declaration or a definition. A value such as 4 or 22 or -5 is an int. A value such as ‘A’ or ‘b’ or ‘c’ is a char. The template mechanism allows the programmer to use a generic type for a set of actual types. For example, the programmer may decide to use the identifier T for int or char. It is possible for a C++ algorithm to have more than one generic type. With, say, T for the int or char, U may stand for the float or pointer type. A class, such as the string or vector class, is like a data type, and the instantiated objects are like values of the data type, which is the specified class. So, the template mechanism also allows the programmer to use a generic type identifier for a set of classes.

A C++ template creates an algorithm independent of the type of data employed. So, the same algorithm, with many occurrences of the same type, can use different types at different executions. The entities of variable, function, struct, and class can have templates. This article explains how to declare templates, how to define templates, and how to apply them in C++. You should already have knowledge of the aforementioned entities to understand the topics covered in this article.

Types

Scalar

The scalar types are void, bool, char, int, float, and pointer.

Classes as Types

A particular class can be considered as a type and its objects as possible values.

A generic type represents a set of scalar types. The list of scalar types is extensive. The int type, for example, has other related types, such as short int, long int, etc. A generic type can also represent a set of classes.

Variable

An example of a template declaration and definition is as follows:

template<typename T> T pi = 3.14;

Before continuing, note that this kind of statement cannot appear in the main() function or any block scope. The first line is the template-head declaration, with the programmer-chosen generic type-name, T. The next line is the definition of the identifier, pi, which is of the generic type, T. Precision, of whether the T is an int or a float or some other type, can be done in the C++ main() function (or some other function). Such precision will be done with the variable pi, and not T.

The first line is the template-head declaration. This declaration begins with the reserved word, template, and then the open and closed angle brackets. Within the angle brackets, there is at least one generic type identifier, such as T, above. There can be more than one generic type identifier, with each preceded by the reserved word, typename. Such generic types in that position are called template parameters.

The following statement can be written in main() or in any other function:

cout << pi<float> << '\n';

And the function would display 3.14. The expression pi<float> decides the exact type of T for the variable pi. Specialization decides the particular data type for the template parameter. Instantiation is the C++ internal process of creating the particular type, such as float, in this case. Do not confuse between instantiating a template parameter and instantiating a class. In the template topic, many data types can have one generic type-name, while many classes can have one generic class-name. However, the generic class-name for classes is simply referred to as a class, and not as a classname. Also, a value is to a data type, such as the int, as an instantiated object is to a class, such as the String class.

At specialization, the chosen data type, such as float, is placed in angle brackets after the variable. If there is more than one template parameter in the template-head declaration, there will be a corresponding number of data types in the same order in the specialization expression.

At specialization, a type is known as a template argument. Do not confuse between this and the function argument for function call.

Default Type

If no type is given at specialization, the default type is assumed. So, from the following expression:

template<typename U = const char*> U pi = "love";

This cam be used as such with a default type:

cout << pi<> << '\n';

Whereby “love” for the constant pointer to char is used as default from the template. Note in the declaration that U = const char*. The angle brackets will be empty at specialization (no type given); the actual type is considered a const pointer to char, the default type. If some other type were needed at specialization, then the type name would be written in the angle brackets. When the default type is desired at specialization, repeating the type in the angle brackets is optional, i.e., the angle brackets can be left empty.

Note: the default type can still be changed at specialization by having a different type.

struct

The following example shows how a template parameter can be used with a struct:

template<typename T> struct Ages
{
  T John = 11;
  T Peter  = 12;
  T Mary  = 13;
  T Joy   = 14;
};

These are ages of students in a grade (class). The first line is the template declaration. The body in braces is the actual definition of the template. The ages can be outputted in the main() function with the following:

Ages<int> grade7;
cout << grade7.John << ' ' << grade7.Mary << '\n';

The output is: 11 13. The first statement here performs the specialization. Note how it has been made. It also gives a name for an object of the struct: grade7. The second statement has ordinary struct object expressions. A struct is like a class. Here, Ages is like a class name, while grade7 is an object of the class (struct).

If some ages are integers and others are floats, then the struct needs two generic parameters, as follows:

template<typename T, typename U> struct Ages
{
  T John = 11;
  U Peter  = 12.3;
  T Mary  = 13;
  U Joy   = 14.6;
};

A relevant code for the main() function is as follows:

Ages<int, float> grade7;
cout << grade7.John << ' ' << grade7.Peter << '\n';

The output is: 11 12.3. At specialization, the order of the types (arguments) must correspond to the order of the generic types in the declaration.

The template declaration can be separated from the definition, as follows:

template<typename T, typename U> struct Ages
{
  T John;
  U Peter;
  T Mary;
  U Joy;
};

Ages<int, float> grade7 = {11, 12.3, 13, 14.6};

The first code segment is purely a declaration of a template (there are no assignments). The second code segment, which is just a statement, is the definition of the identifier, grade7. The left-hand-side is the declaration of the identifier, grade7. The right-hand-side is the initializer list, which assigns corresponding values to the struct members. The second segment (statement) can be written in the main() function, while the first segment remains outside the main() function.

Non-Type

Examples of non-data types include the int, pointer to object, pointer to function, and auto types. There are other non-types, which this article does not address. A non-type is like an incomplete type, whose value is given later and cannot be changed. As a parameter, it begins with a particular non-type, followed by an identifier. The value of the identifier is given later, at specialization, and cannot be changed again (like a constant, whose value is given later). The following program illustrates this:

#include <iostream>
using namespace std;

template<typename T, typename U, int N> struct Ages
{
  T John = N;
  U Peter = 12.3;
  T Mary = N;
  U Joy  = 14.6;
};

int main()
{
    Ages<int, float, 11> grade7;
    cout << grade7.John << ' ' << grade7.Joy << '\n';

    return 0;
}

At specialization, the first type, int, in the angle brackets is there more for formality, to make sure that the number and order of parameters correspond to the number and order of types (arguments). The value of N has been given at specialization. The output is: 11 14.6.

Partial Specialization

Let us assume that a template has four generic types and that, among the four types, there is a need for two default types. This can be achieved using the partial specialization construct, which does not employ the assignment operator. So, the partial specialization construct gives default values to a subset of generic types. However, in the partial specialization scheme, a base class (struct) and a partial specialization class (struct) are needed. The following program illustrates this for one generic type out of two generic types:

#include <iostream>
using namespace std;

//base template class
template<typename T1, typename T2>
struct Ages
{
};

//partial specialization
template<typename T1>
struct Ages<T1, float>
{
            T1 John = 11;
            float Peter  = 12.3;
            T1 Mary  = 13;
            float Joy   = 14.6;
};

int main()
{
    Ages<int, float> grade7;
    cout << grade7.John << ' ' << grade7.Joy << '\n';

    return 0;
}

Identify the base class declaration and its partial class definition. The template-head declaration of the base class has all the generic parameters necessary. The template-head declaration of the partial specialization class has the generic type only. There is an extra set of angle brackets used in the scheme that comes just after the name of the class in the partial specialization definition. It is what actually does the partial specialization. It has the default type and the non-default type, in the order written in the base class. Note that the default type can still be given a different type in the main() function.

The relevant code in the main() function can be as follows:

    Ages<int, float> grade7;
    cout << grade7.John << ' ' << grade7.Joy << '\n';

The output is: 11 14.6.

Template Parameter Pack

A parameter pack is a template parameter that accepts zero or more template generic types for the corresponding data types. The parameter pack parameter begins with the reserved word typename or class. This is followed by three dots, and then the identifier for the pack. The following program illustrates how a template parameter pack can be used with a struct:

#include <iostream>
using namespace std;

    template<typename ... Types> struct Ages
        {
            int John = 11;
            float Peter  = 12.3;
            int Mary  = 13;
            float Joy   = 14.6;
        };

int main()
{

    Ages<int> gradeB;
    cout << gradeB.John << ' ' << gradeB.Mary << '\n';

    Ages<float> gradeC;
    cout << gradeC.Peter << ' ' << gradeC.Joy << '\n';

    Ages<int, float> gradeD;
    cout << gradeD.John << ' ' << gradeD.Joy << '\n';

    Ages<> gradeA;  //like default
    cout << gradeA.John << ' ' << gradeA.Joy << '\n';

    return 0;
}

The output is:

11 13
12.3 14.6
11 14.6
11 14.6

Function Templates

The template features mentioned above apply in a similar way to function templates. The following program shows a function with two generic template parameters and three arguments:

#include <iostream>
using namespace std;

template<typename T, typename U> void func (T no, U cha, const char *str ) {
  cout << "There are " << no << " books worth " << cha << str << " in the store." << '\n';
}

int main() {
    func(12, '$', "500");
    return 0;
}

The output is as follows:

There are 12 books worth $500 in the store.

Separation from Prototype

The function definition can be separated from its prototype, as the following program shows:

#include <iostream>
using namespace std;

template<typename T, typename U> void func (T no, U cha, const char *str );

template<typename T, typename U> void func (T no, U cha, const char *str ) {
  cout << "There are " << no << " books worth " << cha << str << " in the store." << '\n';
}

int main() {

  func(12, '$', "500");
  return 0;
}

Note: The function template declaration cannot appear in the main() function or in any other function.

Overloading

Overloading of the same function can take place with different template-head declarations. The following program illustrates this:

#include <iostream>
using namespace std;

template<typename T, typename U> void func (T no, U cha, const char *str ) {
  cout << "There are " << no << " books worth " << cha << str << " in the store." << '\n';
}

template<typename T> void func (T no, const char *str ) {
  cout << "There are " << no << " books worth $" << str << " in the store." << '\n';
}

int main()
{
    func(12, '$', "500");
    func(12, "500");

    return 0;
}

The output is:

There are 12 books worth $500 in the store.

There are 12 books worth $500 in the store.

Class Templates

The features of the templates mentioned above apply in a similar way to class templates. The following program is the declaration, definition, and use of a simple class:

#include <iostream>
using namespace std;

class TheCla
{
public:
  int num;
  static char ch;

  void func (char cha, const char *str) {
    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
  }
  static void fun (char ch) {
    if (ch == 'a')
        cout << "Official static member function" << '\n';
  }
};

int main()
{
    TheCla obj;
    obj.num = 12;
    obj.func('$', "500");

    return 0;
}

The output is as follows:

There are 12 books worth $500 in the store.

The following program is the above program with a template-head declaration:

#include <iostream>
using namespace std;

template<class T, class U> class TheCla
{
public:
  T num;
  static U ch;

  void func (U cha, const char *str) {
    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
  }
  static void fun (U ch) {
    if (ch == 'a')
      cout << "Official static member function" << '\n';
   }
};

int main()
{
    TheCla<int, char> obj;
    obj.num = 12;
    obj.func('$', "500");

    return 0;
}

Instead of the word typename in the template parameter list, the word class can be used. Note the specialization in the declaration of the object. The output is still the same:

There are 12 books worth $500 in the store.

Separating Declaration

The class template declaration can be separated from the class code, as follows:

template<class T, class U> class TheCla;

template<class T, class U> class TheCla {
public:
  T num;
  static U ch;

  void func (U cha, const char *str) {
    cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
  }
  static void fun (U ch) {
    if (ch == 'a')
       cout << "Official static member function" << '\n';
    }
};

Dealing with Static Members

The following program shows how to access a static data member and a static member function:

#include <iostream>
using namespace std;

template<class T, class U> class TheCla {
public:
  T num;
  static U ch;

  void func (U cha, const char *str) {
     cout << "There are " << num << " books worth " << cha << str << " in the store." << '\n';
  }
  static void fun (U cha){
    if (ch == 'a')
      cout << "Official static member function" << cha << '\n';
  }
};

template<class T, class U> U TheCla<T, U>::ch = 'a';

int main()
{
    TheCla<int, char>::fun('.');
    return 0;
}

Assigning a value to a static data member is a declaration and cannot be in main(). Note the use and positions of the generic types and the data generic type in the assignment statement. In addition, note that the static data member function has been called in main(), with the actual template data types. The output is the following:

Official static member function.

Compiling

The declaration (header) and the definition of a template must be in one file. That is, they must be in the same translation unit.

Conclusion

C++ templates make an algorithm independent of the type of data employed. The entities of variable, function, struct, and class can have templates, which involve declaration and definition. Creating a template also involves specialization, which is when a generic type takes an actual type. The declaration and the definition of a template must both be in one translation unit.

About the author

Chrysanthus Forcha

Discoverer of mathematics Integration from First Principles and related series. Master’s Degree in Technical Education, specializing in Electronics and Computer Software. BSc Electronics. I also have knowledge and experience at the Master’s level in Computing and Telecommunications. Out of 20,000 writers, I was the 37th best writer at devarticles.com. I have been working in these fields for more than 10 years.