Item 46: When type conversion is required, non-member functions should be defined in the class template.
Item 46: Define non-member functions inside templates when type conversions are desired.
As mentioned in Item 24, if all parameters require implicit type conversion, this function should be declared as a non-member function. Item 24 isRational
Andoperator*
As shown in the example, this article extends this idea to class templates and function templates. However, in the class template, functions that require implicit conversion of all parameters should be declared as friends and defined in the class template.
Templated Rational
Since it is the promotion of Item 24, we first putRational
Andoperator*
Templated. The following code is obtained:
template
class Rational {public: Rational(const T& numerator = 0, const T& denominator = 1); const T numerator() const; const T denominator() const; };template
const Rational
operator*(const Rational
& lhs, const Rational
& rhs){}
Item 20 explains whyRational
The parameter is a constant reference; Item 28 explains whynumerator()
The returned value is not a reference; Item 3 explains whynumerator()
The returned result isconst
.
Template parameter deduction Error
The above code is the result of direct templatification of Item24. It looks perfect, but it is problematic. For example, we have the following calls:
Rational
oneHalf(1, 2); // OKRational
result = oneHalf * 2; // Error!
Why is the second error? Because the compiler cannot export the appropriate template parameters for instantiationRational
. The derivation of template parameters includes two parts:
- According
onHalf
, Its type isRational
, It is easy to know and acceptoneHalf
Ofoperator*
Template parametersT
It should beint
;
- According
2
The template parameter derivation is not so smooth, the compiler does not know how to instantiateoperator*
To make it acceptint
Type2
.You may want the compiler2
Is derivedRational
And then perform implicit conversion. In the compiler, template derivation and function calling are two processes: implicit type conversion occurs in function calling, and the compiler needs to instantiate a function before function calling. In the process of template instantiation, the compiler cannot deduceT
.
Declared as a friend FunctionTo let the compiler knowT
In the class templatefriend
Declaration to reference an external function.
template
class Rational {public: friend const Rational operator*(const Rational& lhs, const Rational& rhs);};template
const Rational
operator*(const Rational
& lhs, const Rational
& rhs){}
InRational
Declared infriend
No template parameters addedT
This is a simple method, and it is equivalent:friend const Rational operator*(const Rational & lhs, const Rational & rhs);
.
Because after the class template is instantiated,T
Always known, so thatfriend
The function signature will beRational
Template Class declaration. In this way,result = oneHalf * 2
The compilation is successful, but the link will fail. Although it is clear in the classfriend operator*
However, the compiler does not instantiate the function corresponding to the Declaration. Because the function is declared by ourselves, the compiler assumes that we are obligated to define the function ourselves.
Define in the classThen we are declaringoperator*
Directly give a definition:
template
class Rational {public: friend const Rational operator*(const Rational& lhs, const Rational& rhs){ return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }};
Such a mixed callresult = oneHalf * 2
Finally, you can compile, link, and run the program. The problem here must have been clear:
- To support implicit type conversion for all parameters,
operator*
A non-member function needs to be declared;
- To allow the compiler to export template parameters,
operator*
Must be declared in the class;
- The only way to declare a non-member function in a class is to declare it
friend
;
- When declaring a function, we have the obligation to define the function, so the function definition should also be placed in
friend
Declaration. Call helper functionsAlthoughoperator*
It can be run successfully, but the function defined in the class definition is an inline function. For details, see Item 30. Ifoperator*
The function body becomes very large, so the inline function is no longer suitable. In this case, we canoperator*
Call an external auxiliary function:
template
class Rational;template
const Rational
doMultiply(const Rational
& lhs, const Rational
& rhs);template
class Rational{public: friend Rational
operator*(const Rational
& lhs, const Rational
& rhs){ return doMultiply(lhs, rhs); }};
doMultiply
Hybrid mode calling is still not supported, howeverdoMultiply
Onlyoperator*
Call.operator*
The hybrid mode will be compatible, and then unifiedRational
Type parameter to calldoMultiply
.