Class templates

Class vs function template

A templated class is defined similarly to a templated function. All we have to do is write the template definition before the class definition and then the template types and values can be used within its methods, and to define member variables.

Unlike with templated functions, the types to use to build the version of the templated class may not be implicit (Guessed by the compiler). We must explicitly give the types to use in place of the generic (template) types. To do so, we must add, when defining an object, after the name of the class, the types to use, separated by commas, inside <>.

Defining a templated class

By placing a template definition before a class definition, we make it a templated class. The template definition is the same as with functions: They keyword template followed by, between <> and separated by commas, the template types and template values.

Vector3.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef VECTOR3_HPP #define VECTOR3_HPP // Defines a templated class using a template type named T. template<class T> class Vector3 { public: // Returns the summation of all members as a value of template type T. T sum() const { return x + y + z; } // Defines 3 members of template type T. T x; T y; T z; }; #endif

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream> #include "Vector.hpp" int main() { Vector3<int> vec; vec.x = 5; vec.y = 7; vec.z = 32; std::cout << vec.sum() << std::endl; std::cout << vec.x << " " << vec.y << " " << vec.z << std::endl; return 0; }

In the example above, one major and one small OOP rule has been broken. Firstly, the member variables are declared as public and secondly the name of the members do no start with an underscore. This is one of the few cases where declaring members as public is acceptable. Since it is a small class which (almost only) goal is to provide 3 variables (A vector of power 3) inside one object, declaring its members private or protected would simply make the class less convenient, without any real benefit as directly changing the value of its members is not risky. Also, since its members are public and directly accessed, there is no real need to name them like member variables. In that case, the class can be seen as a special structure that provides methods.

In the last example, the compiler has, internally, created the following class:

1
2
3
4
5
6
7
8
9
10
11
12
class Vector3<int> { public: int sum() const { return x + y + z; } int x; int y; int z; };

A class template may use may template types. It is done the same way as with templated functions. Let us redefined the class Vector3, but with 3 different template types.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream> template<class T, class V, class O> class Vector3 { public: T sum() const { return x + y + z; } T x; V y; O z; }; int main() { Vector3<int, float, char> vec; vec.x = 70; vec.y = 5.32; vec.z = 56; std::cout << vec.sum() << std::endl; std::cout << vec.x << " " << vec.y << " " << vec.z << std::endl; return 0; }

Note that the "8" printed by the member z is actually the character '8' and not the number. Here is the class created by the compiler, from the example above:

1
2
3
4
5
6
7
8
9
10
11
12
class Vector3<int, float, char> { public: int sum() const { return x + y + z; } int x; float y; char z; };

Here is an example of a templated class using template values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream> template<class T, unsigned long long S> class StaticArray { public: void fill(T value) { for(unsigned long long c =0; c < S; c++) _array[c] = value; } // Defining the [] operator, taking one 'middle' operand. T & operator[](unsigned long long index) { return _array[index]; } private: // Static array of type T and size S. T _array[S]; }; int main() { StaticArray<float, 6> array; array.fill(123.123); // Uses the [] operator to access the fifth element from the array. std::cout << array[4] << std::endl; return 0; }

Everything in the same file

Unlike with non-templated classes, the methods of templated classes must be defined inside the same file as the class definition, otherwise a compilation error will happen. Also, if a method is defined outside the class definition, which is allowed as long as it is defined inside the same file, the template definition must be rewritten before its definition and the template types/values from the definition must be given, as template arguments, to the namespace of the method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream> template<class A, class B, unsigned C> class TemplClass { public: // Declaring the method from the templated class. void method(); }; /* Defining the method from the templated class. */ // Redefining the template definition. template<class A, class B, unsigned C> // Giving the template types/values to the namespace of the class. void TemplClass<A, B, C>::method() { std::cout << sizeof(A) << " " << sizeof(B) << " " << C << "\n"; } int main() { TemplClass<std::string, double, 128> templClass; templClass.method(); return 0; }

Class template specialization

The same way templated functions can be specialized for when specific types/values are given as template arguments, we can specialize methods of templated classes. This is done by defining the specialized method outside of the class, putting an empty template definition before it and giving the template types/values of the specialized method as template arguments to its namespace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream> template<class T> class Class { public: void method() { std::cout << "Generic method.\n"; } }; template<> void Class<int>::method() { std::cout << "Specialized method for when the given template type argument is 'int'.\n"; } int main() { Class<float> c; Class<char> c2; Class<int> c3; c.method(); c2.method(); c3.method(); return 0; }

Templated methods

Above, we defined templated classes. It is possible to define templated methods. It is done by putting a template definition before the definition and declaration of the method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream> class A { public: template<class T> void method(T arg) { std::cout << arg << ' ' << sizeof(T) << "\n"; } }; int main() { A a; a.method(5); a.method(43.92); a.method("Null-terminated string."); return 0; }

Here is an example where the templated method is defined outside of the class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream> class A { public: template<class T, class Z> void method(T a, Z b); }; template<class T, class Z> void A::method(T a, Z b) { std::cout << a << ' ' << b << "\n"; } int main() { A a; a.method(78, 32.0); a.method("Some text.", 'L'); return 0; }

Argument list

Earlier, we saw that we can use templates to make an argument of a function represent a list of arguments.

Here is an example where the constructor of a class takes a list of arguments and give it to the constructor of its member object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream> // Some class with 3 constructor overloads. class A { public: A(int a) { std::cout << "A(int a)\n"; } A(float a, float b) { std::cout << "A(float a, float b)\n"; } A(const char* ptr) { std::cout << "A(const char* ptr)\n"; } }; class B { public: // Template type representing a list of arguments. template<class ... Args> // Passes the received arguments to the constructor of its member object '_a'. B(Args ... args) : _a(args...) { } private: A _a; }; int main() { B b1(56); B b2(34.0f, 3.67f); B b3("A null-terminated string."); return 0; }

In the example above, the template definition is put before the constructor, because it is only the constructor that is templated, not the class.