C++ Class Templates with Examples

By , last updated November 17, 2018

In this tutorial we will cover class templates in C++ with simple examples.

What is a template in C++?

Templates in C++ are classes and functions that can operate with generic types. C++ template class can be used for anything a regular class is able to do. The difference is that one class can take in many different data types. Class templates are much like their sibling C++ template functions. Class templates always start their definition with a template keyword. Since there is no way to overload classes, its not permitted to have several classes with the same name, templated or regular.

C++ Template Class Example

A C++ class template starts with a template keyword and a number of arguments it can accept. This is a definition of a class template in C++.

// Class template Foo
template<typename T> 
class Foo {};

// Class Bar
class Bar {};

This is a completely legal Cpp Class Template. It doesn’t do much, but when instantiating (using) the class, you must specify the type. This is explicit template instantiation.

// Use Foo with int and double, and Bar as a regular class.
Foo<int> foo;
Foo<double> foo_double;
Bar bar;

The name of the template argument can be almost anything, and there can be any number of template arguments. Though a class template with thousands of template arguments have a limited usability.

How to use templates in C++

The Foo templated class above is not useful for anything. There are probably a few cases, but I’m not aware of any. Next up is to create a template, where the template argument is used for something.

template<typename T>
class Foo
{
    public:
        T data;
};

In this Foo, the member data is exactly the type T. You can instantiate Foo<T> with int or double. You can even instantiate Foo with your own classes and/or structures. There is really no limit to what T will accept. There are ways to limits what T will accept, but those techniques are reserved for more advanced chapters. All of these are legal instantiations of the Foo above.

Foo<int>         foo1; // Plain old data
Foo<double>      foo2; // POD
Foo<std::string> foo3; // Foo with a string

class Bar {};
Foo<Bar> foo4;         // Foo with Bar
Foo<Foo<Bar>> foo5;    // Foo with a Foo with Bar

As you can see, templates are versatile and can be used for many things, not only regular data types such as int or double. What you can do with a regular class, you can do with a class template. But I did promise a real world example on class template.

Class Template with multiple parameters Example

Here’s an example of a C++ Class Template with Constructor and several parameters:

template<typename T, typename U, typename W>
class MultiParameterTemplate
{
public:
    MultiParameterTemplate() = default;    // default constructor
    MultiParameterTemplate(T t, U u, W w) : m_t(t), m_u(u), m_w(w) {};
 
private:
    T    m_t;
    U    m_u;
    W    m_w;
};
 
// Helper method
template<typename T, typename U, typename W>
auto make_multi_parameter_template(T && t, U && u, W && w)
{
    return MultiParameterTemplate<T, U, W>(t, u, w);
}
 
void test_multiparameter()
{
    // Default constructor
    MultiParameterTemplate<int, std::string, std::vector<int>> mpt;
 
    // Will be legal in the future
    //auto mpt2 = MultiParameterTemplate(int(42), std::string("42"), std::vector<int>{42, 43, 44});
 
    // Create with a helper method
    auto mpt3 = make_multi_parameter_template(int(42), std::string("42"), std::vector<int>{42, 43, 44});
}

Real World Class Template Example

This class and code snippet will be used to collect data, and when the data is collected, all data will be sorted and printed. All the work is done by the class template, and there is only one implementation of the class. There is not an implementation specific for integral numbers or custom structs.

// Generic class sort
#include <vector>
#include <string>
#include <iostream>

template<typename T>
class generic_class_sort {
    public:
        void add(const T &val) {
            data.push_back(val);
        }

        void add(const std::initializer_list<T> & values)
        {
            data.insert(data.end(), values);
        }

        void sort() {
            // No predicate, use default predicate (std::less)
            std::sort(data.begin(), data.end());
        }

        void print() {
            // Print all Ts
            for (const T & t : data)
            {
                std::cout << t << " ";
            }

            std::cout << "\n";
        }

    private:
        std::vector<T> data;
};

void test_class_sort() {
    generic_class_sort<int> ints;
    ints.add({ 5,6,4,3,6,7,2,1 });
    ints.sort();
    ints.print();

    // Output: 1 2 3 4 5 6 6 7

    generic_class_sort<std::string> strings;
    strings.add({ "e", "a", "d", "c", "f", "b" });
    strings.sort();
    strings.print();

    // Output: a b c d e f
}

Where to Use Class Templates in C++

Class templates are used when some functionality can’t be contained into one function. Some notable examples are std::vector and std::list. Because of class templates, it’s possible to have type safe containers. It also makes accidental mixing impossible. This code is illegal:

// Warning: Illegal code ahead
#include <vector>
#include <cstdint>

std::vector<int32_t> ints;
std::vector<int64_t> longs;

ints = longs;

From this illegal program, both Microsoft Visual C++ and GCC will emit errors.

// Error in Visual Studio
// Error C2679 binary '=': no operator found which takes a right-hand 
//   operand of type 'std::vector<int64_t,std::allocator<_Ty>>' '
//   (or there is no acceptable conversion)

// Error in GCC:
// error: no match for 'operator=' 
// (operand types are 'std::vector<int>' and 'std::vector<long int>')

Class template specialization

Class template specialization is specialization of a class template. A notable example from the standard library is the array class. std::array is a compile time array of a type T with size N. It allocates an array on the stack and it has iterator support. It is more strongly typed than a regular C array and the size is a compile time constant. It is difficult to unintentionally introduce memory errors with std::array than the other array types.

The usage is std::array<int, 10> ten_ints;. The special case is when N is 0. When N is 0, all methods in std::array does nothing, and the class itself will not store anything, except for the minimum storage size of 1. All methods are empty and does nothing, and all methods returning some values, return default values. All of which will be beautifully optimized away.

A template specialization of a class requires a primary class and a type or parameters to specialize.

// Primary template class
template<typename T>
class FooBar
{
    void DoStuff(T t) {}

    T data = T();
};

A specialized template class behaves like a new class. There is no inheritance from the primary class. It doesn’t share anything with the primary template class, except the name. Any and all methods and members will have to be implemented.

// Specialized template class for FooBar<int>
template<>
class FooBar<int>
{
};

With the instantiation:

FooBar<double> foo_double;
FooBar<int> foo_int;

The compiler just sees the types FooBar<double> and FooBar<int> as two different types. The only common thing is the name. The compiler will look for the most specific match in the template lookup process. FooBar<int> matches exactly, and is selected.

Further reading: C++ Templates

C++ Templates Resources

Senior Software Engineer developing all kinds of stuff.

Comments

Be the first to comment.

Leave a Reply


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*