As discussed previously, class templates are designed to generate a particular definition of the class with the specific data type(s) deduced by the compiler from the passed-in arguments during compilation. In this chapter, we will learn various aspects of class template instantiation. Understanding the class template instantiation is pivotal to the overall understanding of the functioning of templates in general. The fundamental concept of template instantiation remains the same for both function templates and class templates. However, instantiation of class template instantiation is more complex than function template.

In this chapter we will cover the following topics:

  • Revisiting class template
  • Learning about implicit instantiation
  • Learning about explicit instantiation 
  • Learning about extern templates

Technical requirements

Most of the examples in this chapter will work with the standard C++11 compiler. Some of the examples in this chapter will require support of C++14 or higher compilers.

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

Revisiting class template

In the previous chapter, we discussed how template classes are defined and used in the program but did not detail how they are instantiated and when the compiler generates binary code for the template classes. This chapter will go over some essential characteristics of template class in terms of their instantiation. In the next section, we will learn that the compiler generates no binary code if the template class has not been instantiated. In other words, if no object has been created of the template class, the compiler will not generate any binary code for the template class.

No binary code if not instantiated

We have seen in template functions that the compiler does not generate any binary code unless the function template has been called in the program. So, if there is any unused template function it doesn’t appear in the generated binary code. This keeps the binary file size optimized. Similarly, the compiler generates no binary code unless at least one object has been created of the template class. Let us have a look at the following class template:

sample_template.h

#ifndef __SAMPLE_TEMPLATE_H__
#define __SAMPLE_TEMPLATE_H__
#include <iostream>
template<typename T>
class MyTemplate {
    private:
        T data;
    public:
        MyTemplate() = default;
        MyTemplate(T val);
        T get_val() const;
        void print_val();
        void print_welcome_message();
        ~MyTemplate() = default;
};
template<typename T>
MyTemplate<T>::MyTemplate(T val) : data(val)
{
}
template<typename T>
T MyTemplate<T>::get_val() const
{
    return data;
}
template<typename T>
void MyTemplate<T>::print_welcome_message()
{
    std::cout << "Welcome to Templates" << std::endl;
}
#endif

In this example, we have defined a template class with one template type parameter, T. The template class has a member variable called data which is of type T. The template class has two member functions get_val() and print_val(). Notice that we have defined the get_val() member function but not the print_val() member function. The template class has been declared and defined in a header file which is then included in the following CPP file:

sample_program_1.cpp

#include "sample_template.h"

int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

In the .cpp file, we have added the template class header file, but we have not instantiated any class object in the main() function. In the case of template functions, we have seen that no binary code is generated for the template function by the compiler if we do not call it in the program. Similarly, as we have not created an instance of the MyTemplate, the compiler will not generate any code for the template class. We will first compile the program and then use nm tool to verify this fact like the following:

$ g++ sample_program_1.cpp -o sample_program_1 -Wall -Wextra -Wpedantic -Werror
$ nm -C sample_program_1 | grep -i MyTemplate
$

As you can see, first, we have compiled the program and created the binary object file sample_program_2. Then we used the nm tool to list all the symbols in the binary output file, piped the output of the nm tool to grep. Using the grep command, we are looking for the string MyTemplate in the nm command output. As you can see, there is no symbol generated as MyTemplate, which means the compiler has not generated any code for the template class.

Unlike non-template classes, if we do not create an object of a template class, no binary code is generated for the template class by the compiler.

However, this is not true for the non-template classes. Let us define a non-template class and see how it is different from the template class. Let us have a look at the following code:

sample_class.h

#ifndef __SAMPLE_TEMPLATE_H__
#define __SAMPLE_TEMPLATE_H__
#include <iostream>
class MyClass {
    private:
        int data;
    public:
        MyClass() = default;
        MyClass(int val);
        int get_val() const;
        ~MyClass() = default;
};
MyClass::MyClass(int val) : data(val)
{
}
int MyClass::get_val() const
{
    return data;
}
#endif

As you can see, we have defined a non-template class that is very similar to the templated class. In the following program, we will include this template class definition:

sample_program_2.cpp

#include "sample_class.h"
int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

As you can see in this program, we have included the class header file sample_class.h, which defines the non-template class MyClass. Note that we have not created any object of the MyClass class in the program. Now let us compile the program and verify if the string MyString exists in the binary executable file as follows:

$ g++ sample_program_2.cpp -o sample_program_2  -Wall  -Wextra -Wpedantic -Werror
$ ./sample_program_2
Hello World
$ nm -C sample_program_2 | grep -i "MyClass" 2>/dev/null
000000000000095e t _GLOBAL__sub_I__ZN7MyClassC2Ei
00000000000008ba T MyClass::MyClass(int)
00000000000008ba T MyClass::MyClass(int)
00000000000008d2 T MyClass::get_val() const

We have first compiled the program to create the binary executable file sample_program_2. Then we used the nm tool to list all the symbols in the binary executable file, passed its output to the grep command, and searched the string MyClass in the output of the nm tool. As you can see, the string MyClass exists in the sample_program_2 binary file. That means the compiler has generated binary code for the non-template class MyClass even if it has not been instantiated in the program (no object has been created).

So, we have learned that the binary code generation by the compiler differs for non-template and template classes. If we do not create any object of the template class, then no binary code will be generated. In the next section, we will learn more about template class instantiation.

Instantiation at object creation

The template class is instantiated when we create an object of the class. Let us have a look at the following program, which creates an object of the template MyTemplate defined previously:

sample_program_3.cpp

#include "sample_template.h"
int main()
{
    MyTemplate<int> myObj(10);
    std::cout << "val = " << myObj.get_val() << std::endl;
    return 0;
}

We have reused the MyTemplate template class in this program, which we defined earlier in the header file. In the main() function, we have created an object named myObj of the MyTemplate template for type T as the following:

MyTemplate<int> myObj(10);

We have specified the type of instantiation as int within the angle brackets. Also, we have passed a value of 10 in the single parameter constructor of the template class. Finally, in the main() function, we have called the get_val() member function of the template class to print the value of the member variable val. Now, let us compile and run the program as the following:

$ g++ sample_program_3.cpp -o sample_program_3  -Wall  -Wextra -Wpedantic -Werror
$ ./sample_program_3
val = 10

As we can see, the program prints the value of the val member variable as 10, which is correct because we have created the object myObj passing the value as 10. Now, let us check using the nm tool as before whether the compiler has generated the binary code for the template class:

$ nm -C sample_program_3 | grep -i "MyTemplate" 2>/dev/null
0000000000000a62 W MyTemplate<int>::MyTemplate(int)
0000000000000a62 W MyTemplate<int>::MyTemplate(int)
0000000000000a7a W MyTemplate<int>::get_val() const

Here, we have used the nm tool to list all the symbols from the binary file sample_program_3 and passed the output of nm to the grep command as input to search the string MyTemplate. From the grep command output, it is evident that the compiler has generated binary code for the template class MyTemplate. From the output of the grep command it is also evident that the compiler has generated binary code for the member function get_val(). However, the member function print_val() is not in the listing. So what happened to the print_val() function? In the next section, we will discuss what might have happened to the missing the print_val() function.

Unused member function not instantiated

As in the case of template functions, a class member function is not instantiated that is,. no binary code is generated for the member function of a template class if it have not been called in the program. Let us have a look into the binary executable file again, using nm tool and the grep command we will list the symbols which matches with the string MyTemplate as the following:

$ nm -C sample_program_3 | grep -i "MyTemplate" 2>/dev/null
0000000000000a62 W MyTemplate<int>::MyTemplate(int)
0000000000000a62 W MyTemplate<int>::MyTemplate(int)
0000000000000a7a W MyTemplate<int>::get_val() const

As you can see, the compiler has generated binary code only for the get_val() member function but not for the print_val() or the print_welcome_message().

Note 
The compiler doesn’t generate any code for the member functions of a class template that are not used in the program. 

As we have not called get_val() and print_val() in the main() function of the sample_program_3.cpp, the compiler hasn’t generated any code for these. Let us modify the program and call the print_welcome_message()  function from the main() as shown in the following code listing:

sample_program_4.cpp

#include "sample_template.h"
int main()
{
    MyTemplate<int> myObj(10);
    myObj.print_welcome_message();
    std::cout << "val = " << myObj.get_val() << std::endl;
    return 0;
}

As we can see in this program, the only thing we have added is the call to the member function print_welcome_message(). As we have called this member function, the compiler should generate code for this. Let us check if the compiler has done that using the nm, and the grep tools as before:

$ g++ sample_program_4.cpp -o sample_program_4  -Wall  -Wextra -Wpedantic -Werror
$ nm -C sample_program_4 | grep -i "MyTemplate" 2>/dev/null
0000000000000a86 W MyTemplate<int>::print_welcome_message()
0000000000000a6e W MyTemplate<int>::MyTemplate(int)
0000000000000a6e W MyTemplate<int>::MyTemplate(int)
0000000000000abe W MyTemplate<int>::get_val() const

From the grep command output, it is evident the compiler has generated the code for the member function print_welcome_message(). Now, our next focus is on the print_val() member function. The member function print_val() has not come up in any of our searches so far. You might have guessed that because we have not called the print_val() member function, the compiler has not instantiated it. Let us call the print_val() function in the following program:

sample_program_5.cpp

#include "sample_template.h"
int main()
{
    MyTemplate<int> myObj(10);
    myObj.print_val();
    return 0;
}

As you can see, we have created an object of the template class MyTemplate<T> named myObj with the data type int and then called the print_val() member function. Let us now try to compile this program like the following:

$ g++ sample_program_5.cpp -o sample_program_5 -Wall  -Wextra -Wpedantic -Werror /tmp/cceIupyB.o: In function `main':
sample_program_5.cpp:(.text+0x30): undefined reference to `MyTemplate<int>::print_val()'

At this point, the compiler is throwing compilation errors as the member function print_val() is not defined. Prior to this point, we didn’t call print_val() anywhere in the program; consequently, the compiler ignored the missing definition. Now that print_val() has been referenced in the code, the compiler is looking for its definition.

Note
Compiler ignores any member function of a class template which has not been defined unless it is called in the program.

However, if the member function has been defined, the compiler will check for unknown symbols and any syntactic errors even if it has not been used. Let us have a look at the following template:

sample_template_error.h

#ifndef __SAMPLE_TEMPLATE_ERROR__
#define __SAMPLE_TEMPLATE_ERROR__
#include <iostream>
template<typename T>
class MyTemplate {
    public:
        MyTemplate() = default;
        void foo();
        ~MyTemplate() = default;
    private:
};
template<typename T>
void MyTemplate<T>::foo()
{
    std::cout << var
}
#endif

Notice that in the template we have defined, we have left couple of errors in the member function foo() intentionally to see the compiler’s reaction during the compilation process. In the foo() member function body, we have used an undeclared symbol var and we have intentionally dropped the semicolon at the end of the statement in the foo() member function body. Now, let us use this template in the following program:

sample_program_error.cpp

#include "sample_template_error.h"
int main()
{
    MyTemplate<int> myObj;
    return 0;
}

In this program, we have created an object of the template class MyTemplate<T> in the main() function but we have not called the member function foo(). Now let us try to compile the program as the following:

$ g++ sample_program_error.cpp -o sample_program_error -Wall  -Wextra -Wpedantic -Werror
In file included from sample_program_error.cpp:1:0:
sample_template_error.h: In member function 'void MyTemplate<T>::foo()':
sample_template_error.h:16:18: error: 'var' was not declared in this scope

As you can see, the compiler is complaining about the undeclared symbol var. So the compiler is checking for unknown symbols. Declare the symbol var as int (or any other data type) as the following in the sample_template_error.h file:

template<typename T>
void MyTemplate<T>::foo()
{
    int var = 10;
    std::cout << var
}

Now try to compile the program again as the following:

$ g++ sample_program_error.cpp -o sample_program_error -Wall  -Wextra -Wpedantic -Werror
In file included from sample_program_error.cpp:1:0:
sample_template_error.h: In member function 'void MyTemplate<T>::foo()':
sample_template_error.h:18:1: error: expected ';' before '}' token

Now that we have resolved the unknown symbol issue, the compiler is complaining about the missing semicolon as shown in the error messages. If you put a semicolon at the end of the statement in the foo() member function, then the error will go away.

Note 
The compiler will check any unknown symbol and syntactic errors in the body of any member function of a class template defined irrespective of where they have been called or not in the program.

In the next section, we will learn about two different types of instantiations of template class, implicit and explicit instantiation.

Learning about implicit instantiation

So far, we have seen that if we create an object of a template class, the compiler silently creates an instance of the template class with the specific data type. This type of instantiation is known as implicit instantiation. So, in the creation of an object of a template class, two types of instantiation happen

  • class template instantiation and 
  • the instantiation of the instantiated class itself, that is, the creating an object of the instantiated class. 

Let us revisit the sample_program_3.cpp. In this program, we have created an instance of the MyTemplate<T> class template, defined in the sample_template.h header file as follows:

MyTemplate<int> myObj(10);

When the compiler sees that statement, it understands that we want to create an instance of the template class MyTemplate<T> for int type data, so it creates an instance of the template class with int data type as shown in the following picture:

Figure 7.1 – Implicit instantiation of template class

Once the template has been instantiated with the int type data, the compiler then creates an instance (object) of the class using the single parameter constructor with the passed value of 10. That is the second instantiation that essentially creates an instance or an object of the earlier instantiated class. The following is a pictorial representation of the same:

Figure 7.2 – Implicit instantiation of template class

In implicit instantiation of the template class, we do not have to tell the compiler to do so. The compiler silently instantiates the template class before creating the object of the class. To verify this, we can use the compilation flag -fno-implicit-templates. This flag disables implicit instantiation of the template class. Let us try this flag as the following:

$ g++ sample_program_3.cpp -o sample_program_3 -Wall  -Wextra -Wpedantic -Werror -fno-implicit-templates
/tmp/cc966sKx.o: In function `main':
sample_program_3.cpp:(.text+0x25): undefined reference to `MyTemplate<int>::MyTemplate(int)'
sample_program_3.cpp:(.text+0x47): undefined reference to `MyTemplate<int>::get_val() const'

The compiler didn’t do any implicit instantiation of the template class because of the special compilation flag, and hence the linker could not resolve the symbols like get_val(). Therefore, we are getting linking error. In the next section, we will see how we can explicitly instantiate template class and how that can be useful for us in certain situations.

Learning about Explicit instantiation

To understand the explicit instantiation of template classes, we need to understand the compilation and linking process in general. In the next section, we will discuss the compilation and linking process in brief.

Learning about compilation and linking process

The compiler compiles source codes in groups. The group of lines of code compiled together is called the translation unit, which is essentially the content of a single source file with all the header files included. The output of the compilation process is the object file. The compiler hands over the object files to the linker. Linker links the object files together, resolves any undefined symbol, and generates the final executable file. The steps involved in the compilation and linking process has been shown in the following picture:

Figure 7.3 – Compilation and linking steps

In Figure 7.3, we can see two .cpp files Source1.cpp and Source2.cpp, are being compiled by the compiler, and two object files generated as Source1.o and Source2.o, and then the linker is taking the two object files as input, linking them together and generating the final executable binary file. The source file Source1.cpp and Source2.cpp constitute two different translation units. We will not go into the deep details of the translation unit here, but for all practical purposes, a translation unit is the source .cpp file with all its header files included. With the understanding of the steps involved in compilation and linking, let us look into what happens in the compilation of template classes.

Multiple translation unit and template class instantiation

In the case of template classes, the compiler instantiates the template class separately in each translation unit. For example, in a program, if Source1.cpp has a reference to a template class MyTemplate<T>, for let’s say data type int, and Source2.cpp also has a reference to the template class MyTemplate<T>, for same data type int, then the compiler will instantiate two separate instances of the template MyTemplate<T> for the same data type int in two different translation units. Let us have a look at it in a pictorial representation as the following:

Figure 7.4 – template class instantiation and object file generation

As you can see, the template class MyTemplate<T> has been instantiated with the same int data types by the compiler twice in two different object files. Let us see this in the real code as the following:

sample_template_explicit.h

#ifndef __SAMPLE_TEMPLATE_EXPLICIT__
#define __SAMPLE_TEMPLATE_EXPLICIT__
#include <iostream>
template<typename T>
class MyTemplate {
    public:
        MyTemplate() = default;
        MyTemplate(T x);
        void print_val();
        ~MyTemplate() = default;
    private:
        T val;
};
template<typename T>
MyTemplate<T>::MyTemplate(T x) : val(x) {}
template<typename T>
void MyTemplate<T>::print_val()
{
    std::cout << val << std::endl;
}
#endif

We have defined a template class in a header file sample_template_explicit.h. Now let us include the header file in the source file sample_program_explicit_1.cpp, then define a function foo() inside the file. Inside the foo() function we created an object myObj of the template class MyTemplate<T> for int data type and called the member function print_val() as shown in the following listing:

sample_program_explicit_1.cpp

#include "sample_template_explicit.h"
void foo()
{
    MyTemplate<int> myObj1(10);
    myObj1.print_val();
}

Now, let us write another .cpp file sample_program_explicit_2.cpp which includes the header file, instantiates the template class MyTemplate<T> with int data type, and then calls the foo() function. The following is the code listing which does all of these:

sample_program_explicit_2.cpp

#include "sample_template_explicit.h"
extern void foo();
int main()
{
    MyTemplate<int> myObj2(20);
    myObj2.print_val();
    foo();
    return 0;
}

We can compile, link these two source cpp files and create the executable binary file with the following command:

$ g++  sample_program_explicit_1.cpp  sample_program_explicit_2.cpp -o sample_program_explicit -Wall  -Wextra -Wpedantic -Werror

But when we compile like that the compiler internally generates the object files, passes those to the linker and then the linker links the object files to create the final binary which is sample_program_explicit in this case. We cannot see the object files as the compilation and the linking process are interlinked in this scenario and cannot be distinguished with the above compilation command. To ask the compiler to stop just after compilation we can use -c compiler flag as below:

$ g++ -c sample_program_explicit_1.cpp  sample_program_explicit_2.cpp -Wall  -Wextra -Wpedantic -Werror
$ ls -la *.o
-rw-r--r-- 1 VBR09 tpl_sky_pdd_jira_user 3752 May 27 16:15 sample_program_explicit_1.o
-rw-r--r-- 1 VBR09 tpl_sky_pdd_jira_user 3816 May 27 16:15 sample_program_explicit_2.o

As you can see the compiler has created the respective object files sample_program_explicit_1.o  and sample_program_explicit_2.o  in this case. By using the -c flag, we are instructing the compiler to compile the source file but not link. Now, as we have both the object files let us check if the compiler has generated two separate instantiated copies of the template class MyTemplate<T> for the data type int:

$ nm -g -C --defined-only *.o
sample_program_explicit_1.o:
0000000000000000 T foo()
0000000000000000 W MyTemplate<int>::print_val()
0000000000000000 W MyTemplate<int>::MyTemplate(int)
0000000000000000 W MyTemplate<int>::MyTemplate(int)


sample_program_explicit_2.o:
0000000000000000 W MyTemplate<int>::print_val()
0000000000000000 W MyTemplate<int>::MyTemplate(int)
0000000000000000 W MyTemplate<int>::MyTemplate(int)
0000000000000000 T main

As you can see, the compiler has instantiated two separate copies of the template class MyTemplate<T> for the same data type int in two different translation units. When the linker processes these object files, it keeps only one of these copies and discards the other.

Problem with implicit instantiation

The compiler’s instantiations in each of the source CPP files are implicit instantiation in the previous examples. We do not explicitly tell the compiler to instantiate, but the compiler automatically does it for us while compiling the source. From the discussion, it should be evident that the problem that may arise from implicit instantiation is that multiple instances of the template class for the same data type may exist in different translation units. We have only two source files in our example, hence just two duplicate copies of the same template instance. Imagine if the duplication happens in hundreds or more source files in a large project. Problems that will occur due to this are: 

  • increase in compilation time. 
  • Increase in object file size. 

To overcome this problem with implicit instantiation, we can resort to explicit instantiation. In the next section, we will learn how explicit instantiation can help us in this regard.

Learning about syntax of explicit instantiation

To avoid the redundant instantiations of template class in multiple source files, we can explicitly instantiate the template class for all the required data types in one source file and then reuse those instances in all other source files. The following figure shows the basic syntax of explicit instantiation of template class:

Figure 7.5 – Syntax of explicit instantiation

The explicit instantiation starts with the keyword template followed by the name of the template class then the required data type is specified within the angle brackets. When the compiler sees this statement, it instantiates the template class with the specified data type. In the next section, we will learn explicit instantiation with an example.

Learning explicit instantiation with example

Let us understand the explicit instantiation mechanism with an example. We will divide the example code into the following logical parts: 

  • the template class declaration will go into a header file (*.h)
  • the template class definition will go in a C++ source file (*.cpp) 

Let us now declare the template class in a header file as the following:

sample_template_declaration.h

#ifndef __SAMPLE_TEMPLATE_2__
#define __SAMPLE_TEMPLATE_2__
#include <iostream>
template<typename T>
class MyTemplate {
    public:
        MyTemplate() = default;
        MyTemplate(T x);
        ~MyTemplate() = default;
    private:
        T val;
};
#endif

The template class is very similar to what we had defined previously. Now let us implement the definitions of the template class in the sample_template_definition.cpp file. In the cpp file we will first include the header file sample_template_declaration.h and define the template class member functions. Then we will explicitly instantiate the template class. While compiling this .cpp file, the compiler will create instances of the template class for each of the explicitly declared data types. Let us look at the .cpp file listed as follows:

sample_template_definition.cpp

#include "sample_template_declaration.h"
template<typename T>
MyTemplate<T>::MyTemplate(T x) : val(x) {}
template class MyTemplate<int>;
template class MyTemplate<char>;

In this case, the only member function we have to define is the single parameter constructor of the template class. If the template class has more member functions, then the definitions of those functions will go in this file. After the member function definition, we have explicitly instantiated two different versions of the template class – one for int and the other instance for the char data type.

Now that our template class is ready to be used in other source files. Let us write another .cpp file, sample_program_explicit_inst.cpp. In this file, we will create an object of the template class by calling its single parameter constructor with an int type value and another object by passing char type value as the following:

sample_program_explicit_inst.cpp

#include "sample_template_declaration.h"
int main()
{
    MyTemplate<int> myObj(10);
    MyTemplate<char> myObj2('A');
    return 0;
}

It’s important to note that the compiler has no access to the definition of the template class MyTemplate<T> in while compiling this CPP file because we have included the header file sample_template_declaration.h, which only contains the declaration of the template but no definition. Consequently, when the compiler encounters the below references to the template in this file it cannot instantiate the template class:

    MyTemplate<int> myObj(10);
    MyTemplate<char> myObj2('A');

To verify this let us first generate the object files for this CPP files as the following:

$ g++ -c sample_program_explicit_inst.cpp -o sample_program_explicit_inst.o -Wall  -Wextra -Wpedantic -Werror

Now, let us check what these object files contains with the help of the nm tool as the following:

$ nm -g -C --defined-only sample_program_explicit_inst.o
sample_program_explicit_inst.o:
0000000000000000 T main

Similarly, let us generate the object file for the sample_template_definition.cpp file as the following:

$ g++ -c sample_template_definition.cpp -o sample_template_definition.o -Wall  -Wextra -Wpedantic -Werror

With the help of the nm tool we can peek into the object file created as the following:

$ nm -g -C --defined-only sample_template_definition.o                           
0000000000000000 W MyTemplate<char>::MyTemplate(char)
0000000000000000 W MyTemplate<int>::MyTemplate(int)

As you can see, this time, the compiler has only instantiated the template class in the sample_template_definition.o object file for int and char data type. Finally, let us compile both the source code together and then link the object files to create binary executable as shown in the following command set:

$ g++ sample_template_definition.cpp sample_program_explicit_inst.cpp -o sample_program_explicit_inst -Wall  -Wextra -Wpedantic -Werror
$ ./sample_program_explicit_inst

Notice that the compiler didn’t throw any error even if it didn’t have any visibility of the template class definition in the source file sample_program_explicit_inst.cpp. The compiler knows a template class named MyTemplate<T> exists from the declaration in the header file. Hence, it defers the symbol resolution till the liking stage. Once the object files are created internally, the linker links the object files together and then resolves the unknown symbols in the program by cross-referencing from both the object files. The sample_template_definition.cpp file explicitly instantiated the template class for the int and char data types. Hence, the linker gets the required instantiations for the template class in the object file generated for sample_template_definition.cpp. So, in this section we have leant how to overcome the multiple instantiations of the template class by using explicit instantiation. In the next section, we will learn how to use extern keyword to force the compiler not to instantiate template class in the current translation unit.

Learning about extern templates

In the previous example, we had only declared the template class in the header file, but it was not defined. If the complete definition of the template class is available in the same translation unit, then the compiler must instantiate the template class in that translation unit if there is a reference to the template class. So, if the header file contains both the template class declaration and definition, then the compiler is obliged to instantiate the template class every translation unit the header file is included. To avoid this, C++11 introduced extern templates. Using an extern template, you can force the compiler not to instantiate the template class in the current translation unit. Let us now define a template in the following header file:

sample_templ_header.h

#ifndef __SAMPLE_TEMPL_HEADER__
#define __SAMPLE_TEMPL_HEADER__
template<typename T>
class MyTemplate {
    public:
        MyTemplate() = default;
        MyTemplate(T x);
        T get_val();
        ~MyTemplate() = default;
    private:
        T val;
};
template<typename T>
MyTemplate<T>::MyTemplate(T x) : val(x) {}
template<typename T>
T MyTemplate<T>::get_val() { return val;}
#endif

Now, we will add this header file in the following .cpp file and then explicitly instantiate the template class for int and char type data:

sample_source.cpp

#include "sample_templ_header.h"
template class MyTemplate<int>;
template class MyTemplate<char>;

In another .cpp file, sample_main.cpp, we will first include the sample_templ_header.h file and then create an object of the template class. Now, as the sample_templ_header.h has the complete definition of the template class, the compiler will instantiate the template class as soon as we try to create the object of that class. Remember in the previous example, the header file contained only the declaration but no definition of the template class. Here, in this file the header file contains the complete definition of the template class. So in the previous case when we tried to create an object of the template class the compiler had no way to instantiate the template class as the definition of the template class was not available to the compiler. But in this example when we try to create an object of the template class the definition of the template class is available and hence the compiler must instantiate the template class.To stop the instantiation, we will declare the template with type int and char to be extern. The use of the keyword extern will force the compiler not to instantiate the template function internally. Let us see the source code which uses the extern keyword:

sample_main.cpp

#include "sample_templ_header.h"
extern template class MyTemplate<int>;
extern template class MyTemplate<char>;
int main()
{
    MyTemplate<int> obj1(10);
    MyTemplate<char> obj2('A');
    return 0;
}

We can verify whether the compiler instantiates the template class only once creating the object files of the .cpp files and then, with the help of the nm tools as we have seen before. Let us see how to verify that with the following set of commands:

$ g++ -c sample_main.cpp -o sample_main.o -Wall  -Wextra -Wpedantic -Werror
$ g++ -c sample_source.cpp -o sample_source.o -Wall  -Wextra -Wpedantic -Werror
$ nm -g -C --defined-only *.o
sample_main.o:
0000000000000000 T main
sample_source.o:
0000000000000000 W MyTemplate<char>::get_val()
0000000000000000 W MyTemplate<char>::MyTemplate(char)
0000000000000000 W MyTemplate<char>::MyTemplate(char)
0000000000000000 W MyTemplate<int>::get_val()
0000000000000000 W MyTemplate<int>::MyTemplate(int)
0000000000000000 W MyTemplate<int>::MyTemplate(int)

As you can see, the compiler has only instantiated the template class once. You can compile the whole program and generate the binary executable with the following command:

$ g++ sample_source.cpp sample_main.cpp -o sample_main  -Wall  -Wextra -Wpedantic -Werror

We are discussing various mechanisms of template class instantiation in this chapter. Choosing one over the other would depend on the application for which you are defining the template. Depending on the usage of the template class, one mechanism may be better suited over the other. At this stage, if you make yourself familiar with each of these mechanisms, it would be easier to decide to choose which mechanism to use in what situation.

Summary

In this chapter, we dived deep down into the aspect of template class instantiation. The chapter begins with some of the familiar concepts of template instantiation, which we learned in Chapter 2, Function Templates while discussing the template function templates. As the chapter progressed we gradually moved to the nitty-gritty of template instantiation specific to class templates. 

If you have followed the chapter carefully then you should have learned when and how the template class and template class member functions are instantiated. You should also have the understanding of implicit and explicit instantiations. This chapter also touches upon the fundamental concepts of C+ source file compilation, object file creation, linking, and executable binary file creation for the completeness of the discussion. 

We also discussed about the creation of multiple instances of the same template class in different translation units due to implicit instantiation. You should have noticed that explicit instantiation can help reduce the template class’s compilation time and object size. You should also have a fair understanding of what an extern template is and how it helps to reuse previously instantiated template class. 

In the next chapter, we will learn about the inheritance of a template class and friend of a template class.

Questions

  1. No binary code is generated for C++ class if no object has been created of that class in the program. [True/False]

Ans: False

  1. No binary code is generated for template class if no object of that class has been created. [True/False]

Ans: True

  1. If a member function of a template class is not called anywhere in the program, the member function is not instantiated. [True/False]

Ans: True

  1. Template class instantiation happens as soon the compiler sees the definition of the template class in the source code. [True/False]

Ans: False

  1. In implicit instantiation we must use the keyword template to force the compiler to do the template class instantiation. [True/False]

Ans: False

  1. The problem of implicit instantiation is that there is a possibility of increased compilation time and object file size. [True/False]

Ans: True

  1. To avoid the problem of redundant template instances and increased compilation time, we can use explicit instantiation of the template class. [True/False]

Ans: True

  1. The extern templates are useful if both the declaration and definition of the template class is available in the header file. [True/False]

Ans: True

  1. Object files are the output of the compilation process not linking process. [True/False]

Ans: True

  1. Unknown symbols are resolved during compilation process and then in the linking process only different object files are linked together. [True/False]

Ans: False

Leave a Reply