Effective C++ Item 46: Templates and Generic Programming-When type conversion is required, define non-member functions for templates

1. Examples of errors in non-member function templates

  • We said in Item 24 that for the Rational class, in order to allow all multiplication operations to pass, we define the operator*() function as a non-member function version (for details, you can look back again). But for templates, these rules may not hold

  • For example, below we define both Rational and operator*() functions as templates. The code is as follows:
//下面与条款24都相同,只是改为了模板

template<typename T>

class Rational {

public:

    Rational(const T& numerator = 0, const T& denominator = 1);

    const T numerator()const;

    const T denominator()const;

};


template<typename T>

const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)

{

}
  • Now we write the following code, it cannot be compiled. E.g:
Rational<int> oneHalf(1, 2);


//在条款24中,Rational和operator*为非模板,此处代码可编译通过

//此处,Rational和operator*为模板,编译不通过

Rational<int> result = oneHalf * 2;

Analysis of the cause of the error

  • The reason for the error is: the above "oneHalf*2" did not find a matching operator*() function, which led to an error
  • the reason:
    • The type of oneHalf is Rational<int>, so it matches the operator*() function template, and instantiates the template type T of the operator*() function template to int
    • But when the second parameter is run, 2 is int, and the operator*() function template cannot infer the type of T based on int
    • Therefore, the final compiler cannot find a suitable operator*() function and causes an error
  • In non-template operator*(), operator*() can implicitly convert 2 to Rational objects (Rational provides implicit conversions, see Item 24 for details). But in the template, this implicit conversion is not allowed
  • The deduction of template argument takes the decisive factor

2. Declare the template function as friend

  • In "One", the template class and template function we used went wrong. One solution is to declare operator()* as a friend of the Rational template class.
  • Below we have made two modifications, and finally implemented the wrong code

The first modification (compiled, but an error occurred during linking)

  • We first declare operator()* as a friend of Rational . code show as below:
template<typename T>

class Rational {

public:

    //友元,函数声明

    friend const Rational operator*(const Rational& lhs, const Rational& rhs);

};


//函数定义

template<typename T>

const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);



int main()

{
    
    Rational<int> oneHalf(1, 2);

    //编译通过,但是链接时出错

    Rational<int> result = oneHalf * 2;


    return 0;

}
  • Remarks: In the life of friend above, we did not add <T> after Rational, which is the same as Rational<T>, both are ok
  • Reasons for compiling:
    • When the object oneHalf is defined, the program will generate a definition of the Rational<int> class, so the class Rational<int> is instantiated
    • After class Rational<int> is instantiated, the friend operator*() function is also automatically declared (Note: it is only declared but not defined)
    • After the friend operator*() function is declared, the function is a function, not a template, so we can pass 2 to the function (and the parameter 2 can be implicitly type converted to convert 2 to Rational)
    • So the above code can be compiled and passed, but an error occurred during linking (described below)
  • The reason for the error when linking:
    • Because when class Rational<int> is instantiated, friend only declares operator*(), but it is not defined, so the program reports an error

Second revision (correct version)

  • The above code compiles and passes, but the link passes because the definition of operator*() cannot be found
  • The way to solve the link error is to define this function at the same time as the declaration of the friend function. code show as below:
template<typename T>

class Rational {

public:

    //声明的时候,同时实现该函数(备注,该函数虽然在类中定义,但不属于成员函数)

    friend const Rational operator*(const Rational& lhs, const Rational& rhs)

    {

        return Rational(lhs.numerator()*rhs.numerator(),

        lhs.denominator()*lhs.denominator());

    }

};

 

  • Here is another benefit of friend: Although we declare the function as friend, we do not have access to any non-public members of the class, so it is safer

3. Make the friend function call the auxiliary function (related to inline)

  • We said in Item 30 that if a function is defined in a class, it will become inline, so the friend operator()* we defined in class above will also be called inline.
  • In order to minimize the impact of inline, we can:
    • We can do nothing in operator()* and move what we have done to another helper function
    • This auxiliary function should be a template
  • code show as below:

    • In order to optimize the inline of operator*() in the class, we define an auxiliary function in which to complete the original function of operator*()

    • doMultiply() does not support mixed operations (for example, multiplying a Rational object and an int), but this function is only called by operator*(), and operator*() supports mixed operations, so the function is perfect

//声明

template<typename T> class Rational


//函数声明(定义在下)

template<typename T>

const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);


template<typename T>

class Rational {

public:

    friend

    const Rational operator*(const Rational& lhs, const Rational& rhs)

    {

        //在其中调用doMultiply(),可使inline最优化

        return doMultiply(lhs, rhs);

    }

};


//完成原本operator*()要完成的功能

template<typename T>

const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)

{

    return Rational<T>(lhs.numerator()*rhs.numerator(),

    lhs.denominator()*lhs.denominator());

}

​​​​​​Four, summary

  • When we write a class template, and the "template related" functions it provides support "implicit type conversion of all parameters", please define those functions as "friend functions inside the class template"

Guess you like

Origin blog.csdn.net/www_dong/article/details/113828965