Your browser doesn't support JavaScript Templates – Windows Programming

Templates

A template is a parameterised definition used to create a generic function or class. Templates allow the same function or class to work with different data types without having to write a separate version for each type.

A function template starts with the keyword template, followed by one or more template parameters enclosed in angle brackets. The template parameter can then be used in place of a normal data type within the function definition.

The general form of a function template is:−

template <class T> T someFunction(T arg) { }

The template parameter T represents a data type. When the function is called, the compiler can determine the appropriate type from the arguments and instantiate a version of the function for that type.

The following example uses a function template to return the larger of two values. The same template is used with int, double, and string values:−

#include <iostream>
#include <string>

using namespace std;

// Creates a template with the parameter type T
template <typename T>

// Template function accepts two parameters of type T
T Max(T a, T b)
{
    if (a > b)
    {
        return a;
    }

    return b;
}

int main()
{
    int i = 20;
    int j = 10;

    // The template is instantiated for int values
    cout << "Max(i, j): " << Max(i, j) << endl;

    double f1 = 13.5;
    double f2 = 20.7;

    // The template is instantiated for double values
    cout << "Max(f1, f2): " << Max(f1, f2) << endl;

    string s1 = "a";
    string s2 = "b";

    // The template is instantiated for string values
    cout << "Max(s1, s2): " << Max(s1, s2) << endl;

    return 0;
}

When Max(i, j) is called, the compiler determines that T is int and instantiates the function for int values.

When Max(f1, f2) is called, T is double, so the function is instantiated for double values.

Finally, when Max(s1, s2) is called, T is string, so the function is instantiated for string values.

The important point is that one function template can be used with several different data types, provided those types support the operations used by the template. In this example, the type must support the > operator because Max() uses it to compare the two values.

A Function with Multiple Generic Types

A comma-separated list can be used to define more than one generic type in a template declaration. Each template parameter can represent a different data type.

The following example defines a function template with two generic types, type1 and type2. The function accepts two parameters, which can be different types:

#include <iostream>

using namespace std;

// Creates a template with two different parameter types
template <class type1, class type2>

void myfunc(type1 x, type2 y)
{
    cout << x << ' ' << y << '\n';
}

int main()
{
    // type1 is const char* and type2 is int
    myfunc("5x2=", 10);

    // type1 is int and type2 is const char*
    myfunc(10, "=9+1");

    return 0;
}

In the first call, type1 is deduced as const char* and type2 as int. In the second call, type1 is int and type2 is const char*.

The same function template can therefore be used with different combinations of data types.

For example, if actual char values were required, the function could also be called like this:

myfunc('A', 10); myfunc(10, 'B');

Here, type1 and type2 are deduced as char and int, or int and char, respectively.

Declaring Templates with Default Parameters

A template function can combine generic template parameters with ordinary function parameters. Ordinary function parameters can also have default values.

In the following example, FunctionX() has one generic parameter, X, and one ordinary int parameter. The ordinary parameter has a default value of 0.

#include <iostream>
using namespace std;
template <typename X> 
//creates template function with template parameter and standard parameter with default value 0
void FunctionX(X value1,int value2=0)
{
if (value2!=NULL)
{
cout << value1 << " ";
cout << value2 ; 
}
else
{
cout << "float value=" << value1 << endl;
}
}
int main()
{
float b=4.33;
// calls template function using only template parameter.
FunctionX(b); 
// calls template function using template parameter and standard parameter.
FunctionX( "float value cast to int =",b);
return 0;
}

To the first call, FunctionX(b), the compiler deduces X as float. Since no second argument is supplied, value2 uses its default value of 0.

In the second call, X is deduced as const char*, because the first argument is a string literal. The second argument, b, is a float and is converted to int because value2 is declared as an int.

Default Template Parameters

A template parameter can also have a default type. This is different from giving a function parameter a default value.

For example:

#include <iostream>

using namespace std;

template <typename X = int>
void FunctionX(X value)
{
    cout << value << endl;
}

int main()
{
    FunctionX(10);       // X is deduced as int
    FunctionX(4.33);     // X is deduced as double
    FunctionX('A');      // X is deduced as char

    return 0;
}

In this example, X has a default type of int:

template <typename X=int>

The function can still be used with other types because the compiler can deduce X from the argument. Therefore, the default int is only used when no other type is provided or can be deduced.

Overloading a Generic Function

Template functions can be overloaded with other functions having the same name but different parameter types.

In the following example, the function template T() can accept two values of the same type. A separate non-template version of T() is also provided that specifically accepts two int parameters.

When T() is called with int values, the non-template overloaded function is selected. When it is called with const char* values, the template function is used.

#include <iostream>

using namespace std;

// Generic template function
template <class X>
void T(X a, X b)
{
    cout << "Inside template T." << endl;
    cout << a << b << endl;
}

// Non-template overload specifically for int values
void T(int& a, int& b)
{
    cout << "Inside overloaded function" << endl;
    cout << a << " X " << b << " = " << a * b << endl;
}

int main()
{
    int i = 10;
    int j = 20;

    const char* word1 = "hello ";
    const char* word2 = "world ";

    // Calls the generic template function
    T(word1, word2);

    // Calls the non-template overloaded function
    T(i, j);

    return 0;
}

The first call:

T(word1, word2);

uses the template function. The compiler deduces X as const char*.

The second call:

T(i, j);

matches both the template and the non-template function, but the non-template overload is selected because it provides a direct match for the int arguments.

The result is that the int version performs multiplication, while other suitable types can use the generic template.

It is important to distinguish overloading from overriding. Overloading means providing multiple functions with the same name but different parameter lists. Overriding is a feature of inheritance where a derived class provides a new implementation of a virtual function inherited from a base class.

Variadic functions

Variadic templates allow a function or class template to accept an arbitrary number of template arguments. They were introduced in C++11.

The following example uses a variadic function template to add together an arbitrary number of values:

#include <iostream>

using namespace std;

// Base case - accepts one argument
template <typename T>
double sum(T t)
{
    return t;
}

// Recursive case - accepts one argument followed by any number of arguments
template <typename T, typename... Rest>
double sum(T t, Rest... rest)
{
    return t + sum(rest...);
}

int main()
{
    double dTotal = 0;

    dTotal = sum(33, 3.14, 4.56, 1.1111, 44, 55);

    cout << "dResult = " << dTotal << endl;

    return 0;
}

The function is defined in two parts. The first is the base case:

template <class T> double sum(T t) { return t; }

This function accepts a single argument and simply returns it. It provides the stopping point for the recursion.

The second is the recursive case:

template <typename T,typename ... Rest> double sum(T t,Rest... rest) { return t + sum(rest...); }

Here, T represents the type of the first argument, while Rest… represents a parameter pack containing all the remaining arguments.

When the following call is made:

sum(33, 3.14, 4.56, 1.1111, 44, 55);

the recursive function takes the first argument and passes the remaining arguments to another call to sum():

33 + sum(3.14, 4.56, 1.1111, 44, 55)

This continues until only one argument remains. At that point, the base-case function is called:

The result is approximately:

33 + 3.14 + 4.56 + 1.1111 + 44 + 55
140.7111

The sizeof... operator can be used with a parameter pack to determine how many elements it contains. For example:

cout << sizeof...(rest) << endl;

In the first invocation of the recursive function, rest contains five arguments, so sizeof...(rest) returns 5. Each subsequent recursive call has one fewer argument in its parameter pack.

Variadic templates are useful when a function needs to work with an arbitrary number of arguments. They can avoid the need to write separate functions for different numbers of parameters, making the resulting code more flexible and reusable.

Class Template

It is also possible to create classes that use templates. A class template defines a class that can work with different data types. A template parameter can be used as a type for data members, function parameters and return values within the class.

The following example creates a class template called Compare. It stores two values of the template type and provides a member function that returns the larger value.

The class template is declared using:

#include <iostream>

using namespace std;

// Class template
template <class T>
class Compare
{
    T x, y;       // Data members use the template type

public:

    // Constructor
    Compare(T first, T second)
    {
        x = first;
        y = second;
    }

    // Member function declaration
    T getMax();
};

// Member function definition
template <class T>
T Compare<T>::getMax()
{
    T retval;

    retval = x > y ? x : y;

    return retval;
}

int main()
{
    // Creates a Compare object using int as the template type
    Compare<int> templateObject(10, 22);

    cout << templateObject.getMax() << endl;

    // Creates a Compare object using char as the template type
    Compare<char> templateObjectC('e', 'z');

    cout << templateObjectC.getMax() << endl;

    return 0;
}

The class template is declared using:

template <class T> class Compare

Here, T is a template parameter that represents a data type. It is used for the two data members:

T x, y;

It is also used for the constructor parameters and the return type of getMax().

When the following object is created:

Compare <int> templateObject(10, 22);

the template parameter T is replaced by int. The resulting class therefore stores two int values.

The second object is created with:

Compare <char> templateObject('e', 'z');

Here, T is char, so the class stores two char values.

The same class template can therefore be used with different data types without having to write a separate class for each type.

The member function is defined outside the class using:

template <class T> T Compare<T>::getMax();

Compare<T>::getMax() tells the compiler that getMax() is a member of the class template Compare and that it uses the same template parameter T as the class.