In this chapter, we will learn about some various aspects of template functions. These are essential but somewhat discrete facts related to the template function. For the completeness of understanding template function, we have clubbed them together in this chapter and will discuss them with appropriate example codes step by step. In this chapter, we will cover the following topics:

  • Understanding difference between declaration and definition
  • Compiling and linking of template function
  • Organizing template function code
  • Abbreviated function templates
  • Overloading template function

Technical Requirements

To follow the examples from this book, you’ll need the following tools:

  • Most of the example in this chapter will work with standard C++11 compiler.
  • Some of the example codes in this chapter may require C++14 or higher compiler. We will explicitly point it out if there is any compiler dependency to compile the sample code whenever required.

The code files for this chapter can be found on GitHub at:

Source Code Chapter 5

Understanding difference between declaration and definition

So far, we have not identified the declaration and definition of the template function separately. In this section, we will see how the declaration and definition of the template function are different and how they can be separated out in the code. Let us start with understanding how to declare a template function in the following section.

Declaring Template function

In C or C++ programming language, we usually declare a function prototype before defining the function. The prototype of a function tells the compiler what the input parameters to the function, their data types, and the return value’s data type are. The template function declaration is similar. In a template function declaration, we tell the compiler what the templated parameters or type parameters, the non-type parameters (if any), and the type of return value of the template function are. To understand this better, let us have a look at the below code snippet:

template
T add(const T& x, const T& y);

In this code snippet, we have declared a template function add() with one type parameter T, two function parameters x and y, both of which are a const reference to T type objects. The declaration also tells us that the data type of the return value of add() template function is T. In a template function declaration, there is no function template body. The body of the template function is a part of the definition.
In the next section, we will learn how to define a template function.

Defining Template function

Defining a template function means implementing the internal logic of the template function. Like any standard function, the implementation details of a template function go into the template function body. Let us define the template function add() in the following program:

add.cpp

#include <iostream>
template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}
int main(void)
{
    std::cout << add(10, 20) << std::endl;
    return 0;
}

So in this program we have first defined the template function add() which has a template type parameter T. There are two function parameters to add() function, x and y. Both x and y are cost reference to T type data object. The template function add() perform very minimal operation, i.e., adds up the input values and returns the result. The return data type of add() is also T. In the main() function of the program we have called add() with two int type data and printed the return value of add(). In short, in declaration we tell the compiler about the input and output parameters of the template function and in definition we implement what the template function is actually supposed to perform. Let us now move on to the next section where we will learn where to declare and where to define the template function.

Declaration before definition

Note that we cannot call a template function before the template function has either been declared or defined in the code. This is also true about non-template functions. Let us first have a look at the following program to understand what we mean:

definition_after_declaration.cpp

#include <iostream>
void foo()
{
    std::cout << add(10, 50) << std::endl;
}
template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}
int main(void)
{
    foo();
    return 0;
}

This program defines a non-template function foo() first and then defines a template function add(). The template function add() has been called inside the foo() function. Because of how this code is written, the foo() function appears in the source code before the definition of the template function add(). Now, if we try to compile the program, we will see the following errors:

$ g++ definition_after_declaration.cpp -o definition_after_declaration -Wall -Werror
definition_after_declaration.cpp: In function 'void foo()':
definition_after_declaration.cpp:4:18: error: 'add' was not declared in this scope
     std::cout << add(10, 50) << std::endl;

As you can see, the compiler is complaining that the symbol add() is not declared. The compiler has not come across the symbol add() anywhere before this point in the code and hence doesn’t know what it stands for. To resolve this error, we need to tell the compiler what this symbol is about. The below are the two ways we can resolve this problem:

  • Move the definition of the template function before the foo() function so that when the foo() function calls add(), the compiler already has the full definition of add(). 
  • Alternatively, we can just declare the template function add() before we call it in the foo(), and define it later in the program. The declaration will tell the compiler that this is a template function, the input parameters of it, and its return type.

There is nothing wrong or right about the two approaches. These are the kind of programming decisions you will often take while working on a large project, and depending on what you want to do, you will design it one or the other way possible. Let us have a look at the next program:

definition_after_declaration_2.cpp

#include <iostream>
template<typename T>
T add(const T& x, const T& y);
void foo()
{
    std::cout << add(10, 50) << std::endl;
}
template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}
int main(void)
{
    foo();
    return 0;
}

As you can see we have added a declaration of the template function add() at the beginning of the file as the following:

template
T add(const T& x, const T& y);

And then in the foo() function we have called it. Now if we compile the program there will be no error as shown in the following:

$ g++ definition_after_declaration_2.cpp -o definition_after_declaration  -Wall  -Wextra -Wpedantic -Werror
$ ./definition_after_declaration
60

In the next section, we will learn more about the compilation and linking of template functions.

Compilation and Liking of template function

In the previous section, we learned that the declaration and the definition of a template function can be separated out. We can just declare a template function without defining it, then call it anywhere in the code and then define later in the code.
While separating the declaration and the definition of the template function may help in organizing the source code better in some case, it is essential to remember the following point:

Note:
Declaration and definition of template function cannot be separated out into two different files or into two separate compilation units.

This point needs further breaking down of the concept and clearly understand what we can and cannot do. Let us say we have a template function myfoo(), which we declare in a header file A.h, define in a cpp file B.cpp. Now the following is what you cannot do:

  • Include the header file A.h in another cpp file X.cpp.
  • Call the template function myfoo() in the file X.cpp.
  • Compile the cpp file X.cpp and cpp file B.cpp separately and then link them together.

The above will generate a linking error. Remember, this is a difference between template functions and non-template functions. While the above is valid for any non-template functions, it is not valid for template functions. To understand fully understand the problem here, we need to first understand the compilation and linking process. In the following sub-section, we will learn the steps involved in C++ source code compilation and linking.

Linking of non-template function

Compilation and linking of the source code are complex processes. These depend a lot on the compiler type, the version being used. Also, the organization of the source code introduces more complexities. The organization of the source code, approach to compiling the codebase depends on the project we work on, the development environment, and maybe some other organizational or environmental factors. We will discuss here not to suggest how one organizes the source code or which approach to take for compiling the code but to emphasize some of the fundamental facts about the compilation and linking process in C++ so that you can make an informed decision. To drive the point home, we had to break down the source code in a certain way. Let us have a look at the following program to understand how linking works in the case of non-template functions. First we will declare a non-template function add() in a header file called function.h as the following:

function.h

int add(const int& x, const int& y);

Then we will define the function add() in a cpp file as called function.cpp shown in the following:

function.cpp

int add(const int& x, const int& y)
{
    return x + y;
}

Finally, we will write another cpp file called use_function.cpp which can make use of this function add() as shown in the following:

use_function.cpp

#include <iostream>
#include "function.h"
int main()
{
    std::cout << add(20, 30) << std::endl;
    return 0;
}

In this program, we have included the header file function.h and called the add() function. Now if we compile this program, we will have no error and the program will work just fine as shown in the following:

$ g++ use_function.cpp function.cpp -o use_function -Wall -Werror
$ ./use_function
50

Let us understand this process in detail:

  • When we declare a non-template function in a header file and include the header file in a cpp file, the preprocessor copies and pastes the content of the header file to the cpp file. So, in our case, we have included the function.h header file in the use_function.cpp file. So, after the use_function.cpp file has gone through the preprocessing step, it would have become a larger file with the content from the header file function.h as well as its own content.
  • When we declare a non-template function in a header file and include the header file in a cpp file, the pre-processor copies and pastes the content of the header file to the cpp file. So, in our case, we have included the function.h header file in the use_function.cpp file. So, after the use_function.cpp file has gone through the pre-processing step, it would have become a larger file with the content from the header file function.h as well as its own content.
  • When function.cpp is compiled the compiler generates the object file for it which has the definition for the add() function.
  • Once all the cpp files are compiled, in our case, when both the use_function.cpp and the function.cpp are compiled, the linker takes both the object files and links the missing definition of add() to the caller of the function. And hence, there is no error. The following is the pictorial representation of the compilation and linking process for non-template functions.

Figure 5.1 – Linking of non-template function

As you can see in the Figure 5.1, the input source files function.cpp and use_function.cpp are being compiled by the compiler. Their respective object files, function.o and the use_function.o have been generated and handed over to the linker. The linker then links the object files together, resolve the symbols and generate the binary executable. With that understanding about compilation and linking of non-template functions in place, we will now learn about the compilation and linking of template functions.

Linking of template function

The compilation and linking of the template function are different from that of the non-template function. The difference stems from the fact that template functions require instantiation by the compiler. To understand the difference better, Let us first declare a template function add() in a header file called template.h as the following:

template.h

template<typename T>
T add(const T& x, const T& y);

We have declared a template function with one type parameter, T. There are two function parameters in add() template function, x, and y, both of which are const reference to T type data. The data type of the return value of template function add() is T. Next, we define the template function add() in a separate cpp file called template.cpp as the following:

template.cpp

template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}

Again, a very minimalistic definition of the template function add(). It takes two function parameters, x, and y as input and returns the summation of the two values to the caller. Finally, we have written another cpp file called use_template.cpp where includes the header file template.h and in the main() function, call the template function add() as the following:

use_template.cpp

#include <iostream>
#include "template.h"
int main()
{
    std::cout << add(10, 20) << std::endl;
    return 0;
}

The idea is to compile the use_template.cpp and the template.cpp files together to first create their respective object files by the compiler and then link the object files together by the linker, as we saw in the case of the non-template version of the add() function previously. However, we try to compile will see the following linking error:

$ g++ use_template.cpp template.cpp -o use_template -Wall -Werror
/tmp/cczJUP1N.o: In function `main':
use_template.cpp:(.text+0x34): undefined reference to `int add<int>(int const&, int const&)'
collect2: error: ld returned 1 exit status

As you can see in the preceding block, the linker ld has failed to resolve the symbol add(). The reason is no instance of the template function has been generated in any of the cpp file. But why? When we include the template.h header file in the use_template.cpp file, the preprocessor includes the declaration of the add() template function in the use_template.cpp file. While compiling the cpp file, the compiler comes to know the prototype of the template function add(). Then when the compiler encounters the add(10, 20) call in the main() function, it performs argument deduction, i.e., substitutes the template parameter T with the int type and creates a reference to the add() function with two int data type as below:

‘int add(int const&, int const&)’

However, it cannot instantiate the template function add() as the definition is not available in this file. The assumption here by the compiler is that the definition will be available in some other object file later, and the linker will resolve the reference to add() by liking it to its definition. Now, let us see what is going on with the compilation of the template.cpp file.

The compiler compiles the template.cpp file as a separate compilation unit. Each cpp file, along with all the header files included in it constitutes a separate translation unit. In the template.cpp file, the add() template function has been defined but not called anywhere. As a result, the compiler did not instantiate it in this file either. So, none of the object files contains the instance of the add() template function for the int data type which the linker is looking for. Hence, the linker complains about the following missing definition:

use_template.cpp:(.text+0x34): undefined reference to `int add<int>(int const&, int const&)'

The following is a pictorial representation of the compilation and linking steps of a template function:

Figure 5.2 – Linking of template function

To verify this point we can modify the template.cpp file and introduce a call to the add() function as the following:

template_2.cpp

template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}
void myfoo(void)
{
    add(20, 30);
}  

As you can see, we have introduced a function myfoo() in the template_2.cpp file, which is a modified version of the template.cpp file. The myfoo() calls the add() template function with arguments 20 and 30, which are int type data. When the compiler compiles the template_2.cpp, it will encounter the call to the add() template function in the myfoo() function. The compiler will know that the template function must be instantiated for int type data by argument deduction. So in this case, in the object file of the temlate_2.cpp file, we now have an instance of the add() template function for int data type which has the following prototype:

int add(const int&, const int&);

So now, if we compile the use_template.cpp and template_2.cpp together, we should not see the earlier error. Let us compile and see if that is correct:

$ g++ use_template.cpp template_2.cpp -o use_template  -Wall  -Wextra -Wpedantic -Werror
$ ./use_template
30

As you can see there is no error this time. This proves our point that separating the declaration and the template function definition doesn’t work the way it works for the non-template functions. Because of this reason, we need to have a strategy to organize the template function source code and control its instantiation so that we can use the template functions in the rest of the project. In the next section, we will learn about the possible ways to organize template function.

Organizing Template function code

One way to organize your template function code will be to define the template function in a header file and then include the header file in the source file which uses the template function. To do that let us first define the add() template function in a header file called sample_header.h as the following:

sample_header.h

#ifndef __ADD_H__
#define __ADD_H__
template<typename T>
T add(const T& x, const T& y)
{
    return x + y;
}
#endif

Next, we will add the deader file in a cpp file called sample_foo_1.cpp. This cpp file sample_foo_1.cpp will define a function foo_1() which in turn calls the add() template function with arguments 20 and 30 as shown in the following:

sample_foo_1.cpp

#include <iostream>
#include "sample_header.h"
void foo_1()
{
    std::cout << add(20, 30) << std::endl;
}

Similarly, we will write another cpp file called sample_foo_2.cpp and include the header file sample_header.h file in it. In the sample_foo_2.cpp file, we will write a function foo_2() and call the add() template function with arguments 1 and 2, as shown in the following code snippet:

sample_foo_2.cpp

#include <iostream>
#include "sample_header.h"
void foo_2()
{
    std::cout << add(1, 2) << std::endl;
}

Finally, we write the main CPP file sample_main.cpp, where we define the main() function. The main() function is the entry point in any CPP program. Also, we will make use of all the previously defined functions, i.e., foo_1() and foo_2(), in this file. The sample_main.cpp file looks like the following:

sample_main.cpp

#include <iostream>
#include "sample_header.h"
void foo_1();
void foo_2();
int main()
{
    std::cout << add(100, 200) << std::endl;
    foo_1();
    foo_2();
    return 0;
}

Now if we compile and run the program, we will see an output similar to the following:

$ g++ sample_main.cpp sample_foo_1.cpp sample_foo_2.cpp -o sample_main -Wall -Werror
$ ./sample_main
300
50
3

As you can see, the three source files – sample_main.cpp, sample_foo_1.cpp, and sample_foo_2.cpp – are compiled and linked together to generate the binary executable sample_main without any linking issue. Couple of things to notice here:

  • We have defined the template function in the header file sample_header.h. Wherever we need to call the template function we have included this header file, like in the cpp files sample_foo_1.cpp and sample_foo_2.cpp. In this approach as we are including the full definition of the template function by including the header file, the compiler has no problem instantiating the template function when required.
  • While compiling, we are passing three source cpp files to the compiler as input. The compiler will treat each of these cpp files along with the header files included in it as a separate translation unit.
  • At the compilation stage, the symbols in one translation unit are not visible from other translation units.
  • If a symbol is referenced but not defined in one translation unit, then in the linking phase, the linker looks for that symbol in all other translation units. If found, then the linker resolves the undefined symbol by linking it with its definition.
  • Functions like foo_1() and foo_2() always have external linkage. External linkage means the scope of this symbol is not limited to this translation unit but across all translation units. In this case, because foo_1() and foo_2() have external linkage, it is sufficient to declare these functions in the sample_main.cpp to call them.

In the next section, we will learn about another important aspect of template function, which is called abbreviated function templates.

Abbreviated function templates

We can abbreviate the template definition with the help of the keyword auto. The compiler performs the type deduction of auto in a very similar way as it does for the templates. Let us have a look at the following example to learn how it works:

function_template_auto.cpp

#include <iostream>
void foo(auto a)
{
    std::cout << a << std::endl;
}
int main()
{
    foo(100);
    foo('a');
    foo(22.2);
    return 0;
}

If you compile and run the program, you will see a similar output as the following:

$ g++ function_template_auto.cpp --std=c++17 -o function_template_auto -Wall -Werror -Wextra
$ ./function_template_auto
100
a
22.2

As you can see in the program, we have defined a template function foo(). Instead of declaring a type parameter (for example, T) using the keyword typename, we have declared the template function parameter a using the keyword auto. Then in the main() function, we have passed three different types of input values to the template function foo() – first an integer type value (a value of 100), then a character type value (value of a) and finally a double type value (value of 22.2). The compiler has deduced the type of the template function parameter ‘a’ in each case and has instantiated three different versions of the template function foo(). To verify this, you can use the nm tool (as you learned in Chapter 2, Function Template) as the following:

$ nm -C function_template_auto | grep -i foo
0000000000000a24 W void foo<char>(char)
0000000000000a5b W void foo<double>(double)
00000000000009f0 W void foo<int>(int)

If we remove all the calls to foo() from the main() function and then compile the function_template_auto.cpp again, we will see no binary code been generated for foo() in the generated binary file function_template_auto.

Note:
At the time of writing this book, abbreviated template functions are only supported as a compiler extension and are not a part of the C++ language yet.

You won’t be able to compile the code if you specify –std=c++11 at the command line as the following:

$ g++ function_template_auto.cpp --std=c++11 -o function_template_auto -Wall -Werror -Wextra
function_template_auto.cpp:3:10: error: use of 'auto' in parameter declaration only available with -std=c++14 or -std=gnu++14
 void foo(auto a)

This means the abbreviated templates are not supported in C++11. If you change the compiler type to –std=c++14 or –std=c++17 instead, the code will compile as the use of auto in template function declaration is supported in the C++14 and C++17 compilers as a language extension. To check that, you will have to use the compiler flag pedantic. Let us see how it works:

$ g++ function_template_auto.cpp --std=c++14 -o function_template_auto -Wall  -Wextra -Wpedantic
function_template_auto.cpp:3:10: warning: ISO C++ forbids use of 'auto' in parameter declaration [-Wpedantic]

As you can see, we have removed the -Werror flag and added the -Wpedantic flag and the compiler is showing a warning. If you add the -Werror flag as well, the compilation will be forced to fail. In the next section we will learn about overloading template function.

Overloading template function

Template functions can also be overloaded just like the non-template functions. Furthermore, template functions can be overloaded along with their non-template counterparts. When available, the compiler gives preference to the non-template version of the function over the template version. Let us have a look at the following program:

overload_templ_func.cpp

#include <iostream>
#include <string.h>
auto my_size_of(const char *str, size_t& len)
{
    printf("%s:%d: size = ", __FILE__, __LINE__);
    len = strlen(str);
}
template<typename T>
auto my_size_of(T x)
{
    printf("%s:%d: size = ", __FILE__, __LINE__);
    return sizeof(x);
}
template<typename T, typename U>
size_t my_size_of(T x, U y)
{
    printf("%s:%d: size = ", __FILE__, __LINE__);
    return sizeof(x) + sizeof(y);
}
int main()
{
    size_t len;
    const char * str = "PACKT";
    my_size_of(str, len);
    std::cout << len << std::endl;
    std::cout << my_size_of(10) << std::endl;
    std::cout << my_size_of(4.5) << std::endl;
    std::cout << my_size_of(10, 2.5) << std::endl;
    return 0;
}

In this program, first we have defined a non-template function named my_size_of(), which takes a pointer to a const string object and a reference to a size_t type variable called len. The function my_size_of() calculates the length of the input string object using the library function strlen() and stores the length in the input reference variable len. Because the len variable is passed by reference by the caller, when the my_size_of() function returns the caller can see the calculated length in the len variable.
Next, we have defined a template function with the same name my_size_of() with a template parameter T, and a template function parameter x of type T. In the function body, we calculate the size of the input parameter x using sizeof() operator and return the calculated size to the caller.
Then we have overloaded the template function my_size_of() with two different template type parameters – T and U and two function parameters – x of type T, and y of type U. In the function body, we calculate the size of x and size of y using the sizeof() operator individually on them, add their sizes together and then return the added size back to the caller.
In the main() function, we have first called the my_size_of() function passing the first argument as a const type string PACKT and passed the variable len by reference. Next, we have called the my_size_of() function passing a value of 10 (which is of type int), then we called my_size_of() passing a value of 4.5 (which is of type double) and finally we called my_size_of() passing a value of 10 as the first argument and a value of 2.5 as the second argument. Now if we compile and run the program, we will see a output similar to the following:

$ ./overload_templ_func
overload_templ_func.cpp:5: size = 5
overload_templ_func.cpp:12: size = 4
overload_templ_func.cpp:12: size = 8
overload_templ_func.cpp:19: size = 12 

As you can see, the compiler has chosen the right version of the overloaded functions depending on the type and number of the arguments. For making it clear we have printed the name of the file along with the line number. From the output of the program, you can see the compiler has called the non-template version of the my_size_of() function for the string type argument.
Then in the next two calls (one with the integer type argument and the other one with the double type argument), the compiler has chosen the single parameter template version of the my_size_of() function.
In the last call with one int type argument and the other with double type argument the compiler has chosen the version of the template function my_size_of() with two type parameters.
So, in this program we have learned that overloading of template functions is possible and template function overloading works hand in hand with overloading of a non-template function. However, remember the following point about overloading of template function:

Note:
If a non-template version of the same function is available, the compiler gives priority to the non-template version of the function over the template version. 

To understand this point let us see the following example:

non_template_priority.cpp

#include <iostream>
int max(int x, int y)
{
    printf("Non-template version called.\n");
    return x > y ? 1 : 0;
}
template<typename T>
T max(T x, T y)
{
    printf("Template version called.\n");
    return x > y ? 1 : 0;
}
int main()
{
    int a = 10, b = 20;
    auto ret = max(a, b) ? " bigger" : " smaller";
    if(ret)
        std::cout << "a is bigger " << std::endl;
    else
        std::cout << "b is bigger" << std::endl;
    return 0;
}

In this program, first we have defined a non-template function max() which takes two int type function parameters x and y and returns if x is greater than y.
Then we have defined a template function max() with a single type parameter T. Template function max() takes two function parameters x and y – both of type T and returns if x is bigger than y.
In the main function we have called max() by passing two int type variables a and b. Now, if you compile and run this program, you will see a similar output like the following:

$ g++ non_template_priority.cpp -o non_template_priority  -Wall  -Wextra -Wpedantic -Werror
$ ./non_template_priority                                                        
Non-template version called.
a is bigger

As you can see from the program’s debug prints, the compiler has chosen the non-template version of the function max(). We have passed two int-type arguments to the max() function while calling it in the main() function. As there exists a non-template version of the max() function, which takes two int type inputs, the compiler has chosen the non-template version of the function over the template version.

Summary

In this chapter, we learned about miscellaneous topics on function templates. We started our discussion with the difference between function template declaration and definition. We learned that declaration telling the prototype of the function template without implementing the function body. On the contrary, the definition is where the template function’s internal logic is implemented. We have seen how declaring the template function before the definition can help us better organize the template function’s source code. Then we moved on to the general concept of compilation and linking. We learned about object files, translation units, and how symbols are not transparent to the compiler across the translation unit. Hence, the compiler depends on the linker for resolving unknown symbols in a translation unit. We learned that it is not possible to separate the declaration and the definition of a template function into two translation units. Then we learned about how we can better organize the template function code with the help of the header file. We can define the template definition in the header file and include the header file in any source CPP file. Then we learned briefly about abbreviated function templates and overloading of function templates. So far, we have been concentrating on the function templates. In the next chapter, we will learn about class templates. We will understand class templates, why we use them, and how to implement custom class templates.

Questions

  1. The main problem with function template is that you must declare them before you define them. [True/False]
  2. It is okay if we define a function template in a cpp file and then call the function template later in the file. [True/False]
  3. We can declare a function template in a header file, then include the header file another cpp file and call the function template without having any compilation or linking error. [True/False]
  4. Translation unit is a cpp file along with all the header files included in the cpp file. [True/False]
  5. The compiler doesn’t know about symbols in one translation unit while working on another translation unit. [True/False]
  6. The linker has the visibility across all the translation units. [True/False]
  7. The compiler doesn’t instantiate a template function if the template function hasn’t been called anywhere in the file. [True/False]
  8. If we just define a template function in a cpp file and then compile the file with a C++ compiler, the compiler will generate binary code for the template function even if the template function hasn’t been called anywhere in the file. [True/False]
  9. It is perfectly fine to declare a template function at the beginning of a source file, then call the template function in another function in the same file and then finally define the template function in the file. The order doesn’t matter. [True/False]
  10. In abbreviated template function we use the keyword auto to indicate that the compiler must deduce the type of the parameter from the passed argument in the template function call during compilation. [True/False]
  11. In template function overloading we can have multiple template functions with same name but different parameters. [True/False]
  12. Template function overloading along with non-template function overloading is known as compile time or static polymorphism. [True/False]

Answers

  1. False
  2. True
  3. False
  4. True
  5. True
  6. True
  7. True
  8. True
  9. True
  10. True
  11. True
  12. True

Leave a Reply