When writing a class template that offers functions related to the template that support implicit type conversions on all parameters, define those functions as friends inside the class template.
If we want to do implicit type conversion on all arguments, we should use non-member functions (item 24). Now we want a templatized version of
Rational class to support the same mixed-mode arithmetic as shown in item 24, and the code could start like this:
However, this simple update to templatized version will not compile for following code:
The problem here is that implicit type conversion functions are never considered during template argument deduction. This means it is impossible for the compilers to convert the second parameter
2 into a
Rational<int> using non-explicit constructor, so compilers can’t figure out what
T is for this function template named
operator* taking two parameters of type
Rational<T>, can’t deduce parameter types for this function templates, and thus can’t instantiate the appropriate functions. In the end, the function we want to call fails to be declared, before we could apply implicit type conversions during a later function call.
Solve the compiling issue
How to declare the
operator* properly? Notice that class templates don’t depend on template argument deduction (this process only applies to function templates), so
T is always known at the time the class
Rational<T> is instantiated. Combine this knowledge with another fact that a
friend declaration in a template class refers to a specific function (not a function template), so as part of the class instantiation process, the friend function will be automatically declared. Taking advantage of these 2 points, we could update the code to a new version1:
oneHalf is declared to be of type
Rational<int>, the class
Rational<int> is instantiated, and the friend function
operator* that takes
Rational<int> parameters is then declared automatically. As a declared function, compilers can use implicit conversion functions (such as
Rational's non-explicit constructor) when calling it, making the mixed-mode call compile.
Solve the linking issue
Yes, the code compiles. Yet it won’t link.
This time, the problem is that the target function
operator* is only declared inside
Rational, not defined there. Indeed, the
operator* template outside the class is intended to provide that definition, but things don’t work this way, and linkers can’t find the definition.
To solve it, the simplest thing is to put the function body into its declaration:
This works as intended: mixed-mode calls to
operator* now compiles, link, and run.
This design is, in some sense, kind of unconventional, because the use of friendship has nothing to do with a need to access non-public parts of the class. We declared it as
friend because it is the only choise we have:
- to make type conversions possible on all arguments, we need a non-member function (item 24)
- to have the proper function automatically instantiated, we need to declare the function inside the class
- the only way to declare a non-member function inside a class is to make it a friend
Have the friend call a helper
Such functions as
operator* defined inside a class are implicitly declared
inline (item 30), so we may minimize the impact of such
inline declarations by having
operator* do nothing but call a helper function defined outside of the class, especially when the function body is complex.
Many compilers force us to put all template definitions in header files, so we may need to define
doMultiply in the header as well:
doMultiply is a template, so it won’t support mixed-mode multiplication. The workflow is like this:
- The friend function
operator*supports mixed-mode operations and is in charge of necessary type conversions, ending with two
Rationalobjects passed to
- These two objects are feed to an appropriate instantiation of the
doMultiplyhelper template, which then do the actual multiplication
It is worth to know the syntax used to decalre
Rational. Inside a class template, the name of the template can be used as shorthand for the template and its parameters, so inside
Rational<T>, we can write
Rational<T>. It will be the same effect if we declare
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs). ↩︎