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
TandU. - 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
isBoolis defined inside the class itself by using the template type of the class. - Member function
addis 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 asPair<T, U>::addinstead of justPair::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.