Last Updated:

Template Functions in C++

Template functions

Let's look at a simple example. Suppose we have a function that swaps the values of two variables of type int:

#include <iostream>

void my_swap ( int & first , int & second )
{
    int temp ( first ) ;
    first = second ;
    second = temp ;
}

int main ()
{
    int a = 5 ; 
    int b = 10 ;
    std::cout << a << " " << b << std::endl ;
    my_swap ( a , b ) ;
    std::cout << a << " " << b << std::endl ;
}

Now, let's say we also have two variables of type double in the main function, the values of which also need to be exchanged. The function for exchanging the values of two variables of type int will not work for us. Let's write a function for double:

void my_swap ( double & first , double & second )
{
    double temp ( first ) ;
    first = second ;
    second = temp ;
}

And now let's rewrite the main one:

int main ()
{
    int a = 5 ; 
    int b = 10 ;
    std::cout << a << " " << b << std::endl ;
    my_swap ( a , b ) ;
    std::cout << a << " " << b << std::endl ;
    double c = 77.89 ;
    double d = 54.22 ;
    std::cout << c << " " << d << std::endl ;
    my_swap ( c , d ) ;
    std::cout << c << " " << d << std::endl ;
}

As you can see, our algorithm is exactly the same, only the types of parameters and the type of the temp variable differ. Now imagine that we still need functions for short, long double, char, string, and many other types. Of course, you can just copy the first function, and correct the types to the desired ones, then we will get a new function with the necessary types. What if the function is not so simple? And then it turns out that there was an error in the first function? You can avoid all this, for example, "shamanism" with a preprocessor, but we do not need this, templates will help us.

First, let's take a look at Wikipedia and see what templates are:

Template is a C++ language tool for encoding generalized algorithms without reference to certain parameters (for example, data types, buffer sizes, default values).

So, the description of the template begins with the keyword template, followed in angle brackets ("<" and ">") by a list of template parameters. Next, there is actually a declaration of a template entity (for example, a function or class), that is, it has the form: 

template < template-parameter-list > declaration.

Now let's write a template function my_swap. Based on the template declaration structure mentioned above, it follows that our function will look like this: 

template < template_parameters > function_description.

Let's write a function:

template < typename T >
void my_swap ( T & first , T & second )
{
    T temp(first) ;
    first = second ;
    second = temp ;
}

typename in angle brackets indicates that the template parameter will be the data type. T is the name of the template parameter. Instead of typename here, you can use the word class: template < class T > In this context, the keywords typename and class are equivalent (personally, I like typename better, and someone likes class). Further, in the text of the template, wherever we use the type T, the type we need will be put instead of T.

void my_swap ( T & first , T & second ) //T is the type specified in the template parameter
{
     T temp(first) ; //temporary variable must be of the same type as the parameters
     first = second ;
     second = temp ;
}

now let's write the main function:

int main ()
{
    int a = 5 ; 
    int b = 10 ;
    std::cout << a << " " << b << std::endl ;
    my_swap<int> ( a , b ) ;
    std::cout << a << " " << b << std::endl ;
    double c = 77.89 ;
    double d = 54.22 ;
    std::cout << c << " " << d << std::endl ;
    my_swap<double> ( c , d ) ;
    std::cout << c << " " << d << std::endl ;
}

As you can see, after the name of the function in angle brackets, we specify the type that we need, it will be type T. The template is only a layout by which the compiler will generate the code on its own. If you see this construction: my_swap<type> the compiler itself will create a my_swap function with the required type. This is called template instantiation. That is, when viewed, the compiler will create a function in which T will change to int, and when viewed, a function of type double will be created. If somewhere further the compiler encounters , then it will not generate anything, because the code of this function is already there (the template with this parameter is already instantiated).

my_swap<int>my_swapmy_swap<double>my_swap<int>

Thus, if we instantiate this template three times with different types, the compiler will create three different functions.

Infer a template type based on function parameters

In fact, we can call the function without specifying the type in angle brackets. In some cases, the compiler can do it for you.my_swap

consider a function call without specifying a type:

int a = 5 ;
int b = 10 ;
my_swap ( a , b ) ;

Our template function accepts parameters of type T&, based on the template, the compiler sees that you pass arguments of type int to the function, so it can independently determine that in this place you mean a function my_swap with type int. These are deducing template arguments.

 

Now let's write a more complicated example. For example, an array sorting program (we will use "bubble" sorting). Naturally, the sorting algorithm is the same, but the types of elements in the array will differ. To exchange values, we will use our template function my_swap. Begin:

 

#include <iostream>

template<typenameT>
void my_swap ( T & first , T & second ) //T is the type specified in the template parameter
{
    T temp(first) ; //temporary variable must be of the same type as the parameters
    first = second ;
    second = temp ;
}

//The function will take a pointer to the data
//and number of data array elements
//You can see the sorting algorithm itself on the Internet.
//We will not apply any optimizations and argument checks, we just need a demonstration.
template < class ElementType > //Used class, but typename is also possible - doesn't matter
void bubbleSort(ElementType * arr, size_t arrSize)
{
    for(size_t i = 0; i < arrSize - 1; ++i)
        for(size_t j = 0; j < arrSize - 1; ++j)
            if (arr[j + 1] < arr[j])
                my_swap ( arr[j] , arr[j+1] ) ;
}

template<typenameElementType>
void out_array ( const ElementType * arr , size_t arrSize )
{
    for ( size_t i = 0 ; i < arrSize ; ++i )
       std::cout << arr[i] << ' ' ;
    std::cout << std::endl ;
}


int main()
{
    const size_t n = 5 ;
    int arr1 [ n ] = { 10 , 5 , 7 , 3 , 4 } ;
    double arr2 [ n ] = { 7.62 , 5.56 , 38.0 , 56.0 , 9.0 } ;
    std::cout << "Source arrays:\n" ;
    out_array ( arr1 , n ) ;//The compiler itself will deduce the template parameter based on the first function argument
    out_array ( arr2 , n ) ;

    bubbleSort ( arr1 , n ) ;
    bubbleSort ( arr2 , n ) ;

    std::cout << "Sorted arrays:\n" ;
    out_array ( arr1 , n ) ;
    out_array ( arr2 , n ) ;
}

Output of the program:

Source arrays: 10 5 7 3 4 7.62 5.56 38 56 9 Sorted arrays: 3 4 5 7 10 5.56 7.62 9 38 56

As you can see, the compiler itself generates out_array for the required type. It also generates the bubbleSort function itself. And in bubbleSort we use the template function my_swap, the compiler will generate its code automatically. Convenient, isn't it?

Introduction to Template Classes

It's not just functions that can be templated. Consider template classes. Let's start with a simple example. We'll add a function to our previous code that will look for the maximum and minimum in the array. When creating a function, we "rest" on the problem - how to return two pointers? You can pass them to the function as parameters, or you can return an object that will contain two pointers. The first option, with a large number of return values, will overwhelm the function with parameters, so I propose to make a structure:

struct my_pointer_pair
{
   тип * first ;
   тип * second ;
} ;

And what type of pointers will there be? You can make them void*, but then you will have to constantly cast them to the desired type, and the code will become similar to "Doshirak". What if we made this structure formulaic? Try:

template < typename T, typename U >
struct my_pointer_pair
{
   T * first ;
   U * second ;
}  ;

Now, when the compiler sees the code my_pointer_pair<type1,type2> will generate us the structure code with the corresponding types. In this example, we will have pointers of the same type, but we will make the structure such that the pointer types can be different. This can be useful in other examples (in this case, I just wanted to show that a template can have more than just one parameter).

int main ()
{
    my_pointer_pair<int,double> obj = { new int(10) , new double(67.98) } ;//Создаем объект типа my_pointer_pair<int,double>
    std::cout << *obj.first << ' ' << *obj.second << std::endl ;
    delete obj.first ;
    delete obj.second ;
}

The compiler will not automatically determine the types for the class template, so you must specify them yourself.

Now let's write the code of a templated function to find the maximum and minimum:

//Our template will have one parameter - the type of array elements (T)
//The return value is an object of type my_pointer_pair< T , T >
//those. first and second in my_pointer_pair will be of type T*.
template<typenameT>
my_pointer_pair< T , T > my_minmax_elements ( T * arr , size_t arrSize )
{
     my_pointer_pair< T , T > result = { 0 , 0 } ;
     if ( arr == 0 || arrSize < 1 )
          return result ;
     result.first = arr ;
     result.second = arr ;
     for ( size_t i = 1 ; i < arrSize ; ++i )
     {
         if ( arr[i] < *result.first )
             result.first = arr+i ;
         if ( arr[i] > *result.second )
            result.second = arr+i ;
     }
     return result ;
}

Now we can call this function:

my_pointer_pair< int , int > mm = my_minmax_elements ( arr1 , n ) ;

For classes, we must explicitly specify template parameters. In the C++11 standard, the legacy auto keyword has changed its meaning and now serves to automatically output the type depending on the type of initializer, so we can write like this:

auto mm = my_minmax_elements ( arr1 , n ) ;

I propose to write another function that will output the object my_pointer_pair to the standard output stream:

template < typename T1 , typename T2 >
void out_pair ( const my_pointer_pair< T1 , T2 > & mp )
{
    if ( mp.first == 0 || mp.second == 0 )
        std::cout << "not found" << std::endl ;
    else
        std::cout << "min = " << *mp.first << " max = " << *mp.second << std::endl ;
}

Now let's put it all together:

#include <iostream> template<typenameElementType> void out_array ( const ElementType * arr , size_t arrSize ) { for ( size_t i = 0 ; i < arrSize ; ++i ) std::cout << arr[i] << ' ' ; std::cout << std::endl ; } template < typename T, typename U > struct my_pointer_pair { T * first ; U*second; } ; //Our template will have one parameter - the type of array elements (T) //The return value is an object of type my_pointer_pair< T , T > //those. first and second in my_pointer_pair will be of type T*. template<typenameT> my_pointer_pair< T , T > my_minmax_elements ( T * arr , size_t arrSize ) { my_pointer_pair< T , T > result = { 0 , 0 } ; if ( arr == 0 || arrSize < 1 ) return result ; result.first = arr ; result.second = arr ; for ( size_t i = 1 ; i < arrSize ; ++i ) { if ( arr[i] < *result.first ) result.first = arr+i ; if ( arr[i] > *result.second ) result.second = arr+i ; } return result ; } template<typenameT> void out_pair ( const my_pointer_pair< T , T > & mp ) { if ( mp.first == 0 || mp.second == 0 ) std::cout << "not found" << std::endl ; else std::cout << "min = " << *mp.first << " max = " << *mp.second << std::endl ; } int main() { const size_t n = 5 ; int arr1 [ n ] = { 10 , 5 , 7 , 3 , 4 } ; double arr2 [ n ] = { 7.62 , 5.56 , 38.0 , 56.0 , 9.0 } ; std::cout << "Arrays:\n" ; out_array ( arr1 , n ) ;//The compiler itself will deduce the template parameter based on the first function argument out_array ( arr2 , n ) ; out_pair ( my_minmax_elements ( arr1 , n ) ) ; out_pair ( my_minmax_elements ( arr2 , n ) ) ; }

Output of the program:

Arrays: 10 5 7 3 4 7.62 5.56 38 56 9 min = 3 max = 10 min = 5.56 max = 56

Templates and STL

 

Included with the compiler you are provided with a standard template library (Standart Template Library). It contains many templated functions and classes. For example, the list class, the pair class, the swap function, the sorting function, the dynamically expandable vector, etc. All these are templates and you can use them. For a small example, let's take std::vector:

 

#include <vector>
#include <algorithm>
#include <iostream>

int main ()
{
    std::vector <int> arr;
    arr.push_back ( 5 ) ;      arr.push_back ( 7 ) ;
    arr.push_back ( 3 ) ;
    arr.push_back ( 8 ) ;
    std::cout << "Source vector:\n" ;
    for ( size_t i = 0 , size = arr.size() ; i < size ; ++i )
        std::cout << arr[i] << ' ' ;
    std::cout << std::endl ;

    std::sort ( arr.begin() , arr.end() ) ;       
std::cout << "Sorted vector:\n" ; for ( size_t i = 0 , size = arr.size() ; i < size ; ++i ) std::cout << arr[i] << ' ' ; std::cout << std::endl ; }

Notice that when they wrote std::vector, the authors had no idea what type of elements you would be storing.

Templates are too big and powerful a tool and it is not possible to describe everything in one article. This was just a small introduction to the world of templates. Delving into the templates, you will be amazed at how powerful this tool is and what opportunities it provides.

P.S. Express your opinion about the article, criticism, additions/corrections and questions of interest in the comments.

P.P.S. Please ask questions "the console is closing, what to do?", "The Russian language does not show. What to do?", "how does sorting work?", "what is a size_t", "what is std::" and the like to ask either in Google or search on this site in other articles. No need to clutter comments with this nonsense. If you don't know this, maybe it's better to tighten up your knowledge first?

 

Template Member Functions

 

Class member functions can also be templated. For example, we have a class with a static function , which calculates the absolute value of a number:Mathabs

struct Math
{
    static int abs ( int value )
    {
        return (value<0?-value:value) ;
    }
} ;

This implementation is only for type , but parameters can be of other types. Making the entire class template doesn't make sense, so we'll only make the member function templated:int

#include <iostream>

struct Math
{
    template < typename T >
    static T abs ( const T & value )
    {
        return (value<0?-value:value) ;
    }
} ;


int main()
{
    std::cout << Math::abs(-3.67) << std::endl ;
}

As you can see, it's simple. Not only static functions can be templated. But it is necessary to take into account that virtual functions cannot be templated:

struct my_class
{
    template < typename T>
    virtual void foo() {} //<-- Ошибка
} ;

Also, operators and constructors can be templated:

#include <iostream>

template < typename T >
struct my_class
{
    my_class ( const T & val = T() ) : m_x (val)
    {}

    my_class ( const my_class & src ) : m_x ( src.m_x )
    {}

    my_class & operator= ( const my_class & rhv )
    {
        m_x = rhv.m_x ;
    }

    bool operator== ( const my_class & rhv )
    {
        return m_x == rhv.m_x ;
    }

    T getX() const
    {
        return m_x ;
    }

private:
    T m_x ;
} ;



int main()
{
    my_class<int> obj1(6) ;
    my_class<short> obj2 (6) ;
    std::cout << obj1.getX() << std::endl ;
    std::cout << obj2.getX() << std::endl ;
}

my_class has one significant drawback - these are objects of completely different types. If we try to compare objects and , we get a compile-time error, because the compiler does not know how to compare these objects.obj1obj2

Of course, you can compare the results of a function call, but this still does not solve all the problems - you cannot assign one object to another or construct one object from another (if their types are different).getX

To get out of the situation, we will define template versions of constructors and operators.

#include <iostream>

template < typename T >
struct my_class
{
    //#1
    template < typename U > friend class my_class ;

    template < typename U >
    my_class ( const U & val = U() ) : m_x (val)
    {}

    //#2
    my_class ( const my_class & src ) : m_x ( src.m_x )
    {}

    //#3
    template < typename U >
    my_class ( const my_class<U> & src ) : m_x ( src.m_x )
    {}

    my_class & operator= ( const my_class & rhv )
    {
        m_x = rhv.m_x ;
    }

    template < typename U >
    my_class & operator= ( const my_class<U> & rhv )
    {
        m_x = rhv.m_x ;
    }

    template < typename U >
    bool operator== ( const my_class<U> & rhv )
    {
        return m_x == rhv.m_x ;
    }

    T getX() const
    {
        return m_x ;
    }

private:
    T m_x ;
} ;


int main()
{
    my_class<int> obj1(6) ;
    my_class<short> obj2 (8) ;
    my_class<double> obj3(obj1) ;
    obj3 = obj2 ;
    std::cout << (obj1 == obj2) << std::endl ;
    std::cout << (obj3 == obj1) << std::endl ;
}

As you can see, transformations are now possible, of course, if transformations between objects in the classes themselves are possible, i.e. it will not be possible to write in this case, because there is no corresponding conversion from the double type to the .

m_xmy_class<std::string> obj4(obj3);std::string

As you may have noticed, I removed the non-template constructor and comparison operator from the class, leaving only template versions of them. But they could have been left behind to better organize work with the same types (for example, to avoid conversion overhead).

Also, note that the non-template copy constructor (#2) remains in the class, because if you remove it, the compiler will generate it on its own, i.e. the template constructor (#3) does not replace the copy constructor (#2) and if you do not define the copying constructor explicitly, the compiler will generate it on its own. The same applies to the assignment operator. In C++11, the same applies to move versions.

Also, it is worth noting that with different values of the template arguments, we get different types, which means that it does not have access to the private data of the class (and to protected, if it is not an heir). Therefore, to access private data, you must declare these classes as friends (#1). If we remove this ad, we get an error of the form "m_x is private".

my_class < T >my_class < U >

This example also shows that a templated class can have template member functions. I would also like to note that a templated class can have virtual functions, but virtual functions cannot be templated.

The template arguments are non-types.

Template arguments can be more than just types. Let's look at a small example

#include <iostream>

template < size_t N >
struct my_type
{
    void foo () const
    {
        std::cout << N << std::endl ;
    }
};

int main()
{
    my_type<5> o1 ;
    my_type<7> o2 ;
    o2.foo() ;
    o1.foo() ;
}

After execution, we get the values 7 and 5. These parameters are set at compile time and it is not entirely obvious what this may be for. Soon we will look at the application of such arguments, look at the class template , in which the non-type argument specifies the size of the set:std::bitset

std::bitset<16> bs ;

As you know, with different arguments to the template, the compiler will generate different types. That is, and are different types. It's the same with non-type parameters, i.e. and are also different types. We will also need this property in the future. But let's digress from that for a while.

my_class <int>my_class <double>

my_type<5>my_type<7>

First, let's define what can be used as a non-type argument. To do this, let's turn to the standard.

14.1/4

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • integral or enumeration type,
  • pointer to object or pointer to function,
  • lvalue reference to object or lvalue reference to function,
  • pointer to member,
  • std::nullptr_t.

Wow, what a choice. But alas, there are other limitations.

14.3.2/1

A template-argument for a non-type, non-template template-parameter shall be one of:

  • an integral constant expression (including a constant expression of literal class type that can be used as an integral constant expression as described in 5.19); or
  • the name of a non-type template-parameter; or
  • a constant expression (5.19) that designates the address of an object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
  • a constant expression that evaluates to a null pointer value (4.10); or
  • a constant expression that evaluates to a null member pointer value (4.11); or
  • a pointer to member expressed as described in 5.3.1.

That's not all, but it's enough to start. Let's try to pass a reference to the object to the template parameter:

#include <iostream>

class my_class
{
     int m_x ;
public:
     my_class( int x ) : m_x(x)
     {
     }

     int getX() const
     {
         return m_x ;
     }
} ;

//The template argument is a reference to an object of type my_class
template<my_class&mc>
struct my_type
{
     void foo() const
     {
         //Use the object passed in the template argument
         std::cout << mc.getX() << std::endl ;
     }
};

my_class obj1(7) ;
my_class obj2(-5) ;

int main()
{
     my_type<obj1> o1 ; //o1 is now "linked" to obj1
     my_type<obj2> o2 ; //o2 is now "linked" to obj2
     //Call appropriate functions
     o1.foo() ;
     o2.foo() ;
}

For starters, this information is enough for us.

Template parameters.

Let's take a quick look at passing a template as an argument to the template. Let's create a function that takes a binary predicate as an argument and references to two variables. If the predicate returns true, swap the passed variables:

#include <iostream>
#include <functional>
#include <algorithm>

template < typename BinPred , typename T >
void foo (  T & obj1 , T & obj2 , BinPred pred )
{
    if ( pred(obj1,obj2) )
        std::swap (obj1,obj2) ;
}

int main ()
{
    int x = 10 ;
    int y = 30 ;
    //foo<std::less> ( x , y ) ;
    foo ( x , y , std::less<int>() ) ;
    std::cout << x << ' ' << y << std::endl ;
}

Now let's try to pass the predicate as a template parameter. To do this, you must modify the . Template template arguments are defined asstd::lessfoo

template < template-parameter-list > class ..._opt identifier_opt
template < template-parameter-list > class identifier_opt = id-expression

In this case, the use of a keyword is fundamental and typename will not replace it.class

template < template <typename> class BinPred , typename T >
void foo (  T & obj1 , T & obj2 )
{
    if ( BinPred<T>()(obj1,obj2) )
        std::swap(obj1,obj2) ;
}

template < typename > class BinPred - Here it is, our boilerplate argument. As you can see, the template argument has one argument, its name does not matter, so it is missing. — here we create an object of the class and call it a function with two parameters.BinPred<T>()(obj1,obj2)BinPred<T>()operator()

Now let's rewrite the main function:

int main()
{
     int x = 10 ;
     int y = 30 ;
     foo<std::less> ( x , y ) ; //For std::less you don't need to specify the type of the parameter, because we pass the template itself
     std::cout << x << ' ' << y << std::endl ;
}

So we took another tiny step in learning patterns.


Updated:

Also for "unpacking" tuples,
Example:

#include <iostream>
#include <tuple>


namespace details__
{
    template<typename Tuple, std::size_t ... Indexes>
    void foo_impl(const Tuple &t, std::integer_sequence<std::size_t, Indexes...>)
    {
        using ARR_TYPE = int[];
        (void)ARR_TYPE{
            (std::cout << (Indexes==0?"":", ") << std::get<Indexes>(t), 0)...
        };
    }
}



template<class ... Args>
void foo(const std::tuple<Args...> &t)
{
    ::details__::foo_impl(t, std::make_index_sequence<sizeof...(Args)>());
}


int main()
{
    auto t = std::make_tuple(1, 5.6, 33, 88, 44.5, "char? o_O");
    ::foo(t);
}

http://rextester.com/GYWJ53857

Also, for example, you can "hijack"
the pointer to private members of the class,
without his knowledge. There is such a "hole". )))

And you can still come up with 100,500 applications,
showed only the first thing that came to mind.

template templates

For example, to output the container type:

template<template<typename ...> class Cont, typename ... Args>
void foo(const Cont<Args...> &)
{
}

 

In this case, the use of the class keyword is fundamental and typename will not replace it.

In the standard (I won't say exactly which one, C++14, like), the wording was changed.

template < template-parameter-list > class ..._opt identifier_opt
template < template-parameter-list > class identifier_opt = id-expression

Turned into

template < template-parameter-list > type-parameter-key ..._opt identifier_opt
template < template-parameter-list > type-parameter-key identifier_opt = id-expression
type-parameter-key:
    class
    typename

So now it's interchangeable in that context as well.