As we have seen class templates for struct, we have used template types for data members. We can also use same template types for member functions for class template. For example,

#include <iostream>
 
template <typename T, typename U>
class Pair
{
    T m_a{};
    U m_b{};
 
public:
    Pair() = default;
    Pair(const T &a, const U &b)
        : m_a{a}, m_b{b}
    {
    }
 
    bool isEqual(const Pair<T, U> &anotherPair)
    {
        return m_a == anotherPair.m_a && m_b == anotherPair.m_b;
    }
 
    void print()
    {
        std::cout << "{" << m_a << ", " << m_b << "}\n";
    }
 
    Pair<T, U> add(const Pair<T, U> &anotherPair);
};
 
template <typename T, typename U>
Pair<T, U> Pair<T, U>::add(const Pair<T, U> &anotherPair)
{
    return Pair{m_a + anotherPair.m_a, m_b + anotherPair.m_b};
}
 
int main(int argc, char const *argv[])
{
    Pair p1{1, 2.4};
    p1.print();
 
    std::cout << std::boolalpha << p1.isEqual(Pair{1, 2.4}) << "\n";
 
    p1.add(Pair{1, 2.4}).print();
    return 0;
}

Let’s understand this code bit by bit:

  • We have defined a class template with template types T and U.
  • We have defined the data members with these template types.
  • As this is non-aggregate type, we have defined a constructor by using const reference of template types. References because if any expensive to copy type as argument is passed, it should still work efficiently.
  • Member function isBool is defined inside the class itself by using the template type of the class.
  • Member function add is declared inside the class but defined outside. As it is defined outside, we need to again create function template. Also, we need to provide function scoped with the class template name as Pair<T, U>::add instead of just Pair::add.
  • When we create the class template object, we do not require to define template type deduction rule (ctad) as there is a matching constructor to provide information to deduct the type from the initializer list.

Note

As aggregate types such as struct, does not have constructor so we need to explicitly provide ctad guide rule for compiler to deduce the template types.

Injected class name

There is one change that we can do with our defined member functions. We can use Pair directly as argument type instead of full class template name Pair<T, U>. This is because Pair is an unqualified class name called injected class name and in the context of full class template name Pair<T, U>, subsequent of Pair can be specified directly without providing full template name.

For example:

We can change this

template <typename T, typename U>
Pair<T, U> Pair<T, U>::add(const Pair<T, U> &anotherPair)
{
    return Pair{m_a + anotherPair.m_a, m_b + anotherPair.m_b};
}

To

template <typename T, typename U>
Pair<T, U> Pair<T, U>::add(const Pair &anotherPair)
{
    return Pair{m_a + anotherPair.m_a, m_b + anotherPair.m_b};
}

Also, member functions in class can be changed from

    bool isEqual(const Pair<T, U> &anotherPair)
    {
        return m_a == anotherPair.m_a && m_b == anotherPair.m_b;
    }
    Pair<T, U> add(const Pair<T, U> &anotherPair);

To

     bool isEqual(const Pair &anotherPair)
    {
        return m_a == anotherPair.m_a && m_b == anotherPair.m_b;
    }
 
    Pair add(const Pair &anotherPair);

As member function defined outside is scoped with Pair<T,U>::add, so it is in the context of Pair<T, U> and thus we can directly specify Pair as parameter type.

For member function defined inside are in the scope of class Pair<T, U> (because of template <typename T, typename U>), we can directly use Pairfor subsequence mentions.

Using in multiple files

For class templates with member functions to work, both the class and member function definitions should reside at the same location. This is because compile needs to know both the class definition and member function template definition to instantiate the templates.

When member function template is defined in the class itself, compiler knows it. So, no problem.

When member function template is defined outside the class, it should generally be defined immediately below the class template.

In multi file setup, both class template and member function template should reside the header file.

Tip

As we know from function templates, functions instantiated from the template are implicitly inline. So, there is no need to worry about the violations to ODR if we define the member function templates inside the header file.

References

  1. https://www.learncpp.com/cpp-tutorial/class-templates-with-member-functions/