"Effective C + +": Clause 46-Clause 47

Source: Internet
Author: User
Tags traits

    • Clause 46 defining a non-member function for a template when a type conversion is required
    • Clause 47 Please use the Traits class expression type information

Clause 46: Define a non-member function for a template when a type conversion is required

clause 24 mentions why the Non-member function has the ability to "implicitly type conversions on all arguments", and this article goes on to the rational example by templating the rational class

    Template<TypeNameT>classrational{ Public: Rational (Constt& numerator=0,Constt& denominator=1);ConstT numerator ()Const;ConstT Denominator ()Const; ...... };Template<TypeNameT>ConstRational<t>operator*(Constrational<t>& LHS,Constrational<t>& rhs) {...}; rational<int> Onehalf (1,2); rational<int> result=onehalf*2;//error, failed to compile

Examples of non-templates can be compiled, but templated examples are not. In terms of clause **24, the compiler does not know until we try to invoke what function (that is, accept two rational parameters that operator ). The compiler is trying to figure out what functions are named operato*, and they know that they can materialize a operator* and accept two rational arguments, but in order to accomplish this materialization, you must first figure out what T is. The problem is they don't have the ability.

Take a look at this example, how the compiler deduces T. In this case, the type parameters are rational and int, respectively. The first argument of operator* is declared rational, and the first argument (onehalf) passed to operator* is rational, so t must be int. The second argument type of operator* is declared rational, but the second argument type passed to operator* is int, how does the compiler extrapolate T? Perhaps you expect the compiler to use rational's non-explicit constructor to convert 2 to rational, and then deduce that T is int, but it does not because implicit type conversions are never taken into account in the template argument derivation process. Implicit conversions are actually used during a function call, but before a function can be called, the first thing to know is the existence of that function, and in order to know it, it is necessary to derive the parameter type for the relevant function template (before the appropriate functions can be materialized). However, implicit type conversions that occur through constructors are not considered in the template argument derivation process.

Now to resolve the compiler's challenge in template argument derivation, you can use the Friend function in template class, because a friend declaration within a template class can refer to a specific function. In other words, class rational can show that operator* is its friend function. Class templates does not rely on the template argument derivation (the latter is only performed on function templates), so the compiler is always able to know t when class rational is materialized. So it is possible to simplify the whole problem by making rational class declare the appropriate operator* as a friend function.

 template  <typename  t> class  rational{        Public : ... friend  const  Rational operator  * (const  rational& lhs,< Span class= "Hljs-keyword" >const  rational& RHS);    //Declaration }; template  <typename  t> const  rational<t> operator  * ( const  rational<t>& lhs,const  rational<t>& RHS)    //definition  {...};  

This time the mixed call to operator* can be compiled. Onehalf is declared as a rational,class rational is materialized, and as part of the process, the friend function operator* (accepts rational parameters) is automatically declared. The latter is a function rather than a function template, so the compiler uses implicit conversions when calling it (converting int to rational), so mixed calls can be compiled. Although the compilation, but there will be a link problem, this later on. Let's take a look at the syntax for declaring operator * within rational.

Within a class template, the template name can be used as a shorthand for template and its parameters, so within rational we can abbreviate rational to rational. If you write like this, it works as well.

    template<typename T>    class Rational{    public:        ……        friendconstoperator*(const Rational<T>& lhs,const Rational<T>& rhs);//声明    };

Now look back at the problem of the link you just said. Although the compiler until the function we call is the one that accepts rational operator *, this function is only declared, not defined. We wanted to provide a definition for the operator * outside of this class, but that wouldn't work. If we declare ourselves a function (as in Rational template), it is the responsibility to define that function. If there is no definition, the linker cannot find it. One of the simplest ways is to merge the definition of operator * into its declaration:

    template<typename T>    class Rational{    public:        ……        friendconstoperator*(const Rational& lhs,const Rational& rhs);//声明+定义        {            return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());        }    };

Although this technique uses friend, it differs from the traditional friend use "Access class's Non-public member". In order for a type conversion to occur with all arguments, we need a non-member function (* * clause **24); In order for this function to be automatically materialized, we need to declare it inside class , and the only way to declare the Non-member function inside class is to make it a friend.

Functions defined inside class are inline functions, including friend functions like operator *. To minimize the impact of an inline declaration, you can have operator * Call a helper function defined outside the class.

    Template<TypeNameT>classRational;//forward decelarion    Template<TypeNameT>ConstRational<t> domultiply (Constrational<t>& LHS,Constrational<t>& RHS);Template<TypeNameT>classrational{ Public: ......friend ConstRationaloperator*(Constrational& LHS,Constrational& RHS);//declaration + definition{returnDomultiply (LHS,RHS); }    };

Many compilers will force you to put the template definition into the header file, so sometimes you need to define the domultiply

    T>    const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs)    {        return Rational<T>(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());    }

Domultiply is a template, naturally does not support mixed multiplication, in fact, there is no need to support. It is just called by operator *, operator * supports mixed multiplication.

Summarize

    • When writing a class template, which provides the "related to this template" function to support "implicit type conversion of all parameters," define those functions as a friend function inside the class template.
Article 47: Please use traits class performance type information

The STL consists primarily of templates of containers, iterators, and algorithms, and also includes several instrumental templates, one of which advance is used to move an iterator to a given distance:

    template<typenametypename DistT>    void advance(IterT& iter, DistT d);//d大于零,向前移动,小于零则向后移动

On the surface, it's just a iterate+=d action, but the iterator has 5, and only the random access (randomly accessed) iterator supports the + = operation. Other types are not so powerful, only repeat + + and –.

The parts of the STL source for iterators can be referenced here. Here is a review of the 5 iterators.

    • The input iterator, which is read only, can only read the object it points to, and only once. It can only move forward, one step at a time. It mimics the reading pointer to the input file (read pointer); The istream_iterators in the C + + program is the representative of this class.
    • The output iterator, in contrast to the input iterator, is write only. It can only move forward, one step at a time, and can only be scribbled once to the object it points to, mimicking a scribble pointer to the output file (write pointer); Ostream_iterators is representative of this category.
    • Forward iterators. This iterator derives from the input iterator, so there is all the functionality of the input iterator. And he can read and write to the object more than once.
    • The bidirectional iterator inherits from the forward iterator, and its functionality also includes moving backwards. The list, set, Multiset, map, and Multimap iterators in the STL are this type of iterator.
    • The random access iterator inherits from the bidirectional iterator. The great thing about it is that you can jump forward or backward at any distance, which is similar to the original pointer, and the built-in pointer can be used as a random access iterator. The iterator for vectors, deque, and string is this class.

In this 5 classification, the C + + standard library provides a proprietary volume label structure (tag struct) to confirm:

    struct input_iterator_tag {};      struct output_iterator_tag {};      structpublic input_iterator_tag {};      structpublic forward_iterator_tag {};      structpublic bidirectional_iterator_tag {};

After understanding the iterator type, it is time to implement the advance function. Implementation is efficient, for the random access iterator, the forward D distance is one step away, while the other types need to go forward or backward repeatedly

 template  <typename  Iter, typename  distt> void  advance (itet& iter,distt D) {if  (it        Er is a random access iterator) Iter+=d;        else  {if  (D>=0 ) while  (d--) ++iter;                 else  while         (d++)--iter; }    }

In the above implementation, to determine whether ITER is a random access iterator, you know whether the Itert type is a random access type. This requires traits, which allows us to obtain certain types of information during compilation. Traits is a technology that is a common protocol for C + + programmers to follow. One of the technical requirements is that it behaves as well as the built-in types and custom types. Traits must be able to execute on a built-in type, meaning that "nested information within a type" is out because we cannot nest information inside the original value pointer. So the traits information for the type must be outside the type itself. The standard technique is to put it into a template and one or more of its special versions. There are several templates in the STL, and the iterators are iterator_traits:

    template<typename IterT>//用来处理迭代器分类    struct iterator_traits;

Although Iterator_traits is a struct, it is often called traits classes. It works by declaring a typedef named iterator_category within a struct iterator_traits for each type of itert, to confirm the classification of the iterator for Itert. Iterator_traits in two parts to achieve the above mentioned. 1, it requires a user-defined iterator nested a typedef, named Iterator_category, to determine which volume label structure (tag struct), such as deque and list

 Template<TypeNameT>class deque{ Public:classiterator{ Public:typedefRandom_access_iterator_tag iterator_category;        ...... }; ...... };Template<TypeNameT>class List{ Public:classiterator{ Public:typedefBidirectional_iterator_tag iterator_category;        ...... }; ...... };Template<TypeNameItert>//itert's iterator_category is used to show what Itert says he is.    structiterator_traits{for the use of//typedef typename, see * * Terms **42        typedef TypeNameItert::iterator_category iterator_category; ...... };

This makes sense for user-defined types, but does not work for pointers, but pointers are iterators, but pointers cannot nest typedef. Here is the 2nd part of Iterator_traits, dedicated to supporting pointers.

To support Pointer iterators, Iterator_traits provides a partial version (partial template specialization) specifically for the type.

    template<typename IterT>    struct iterator_traits<IterT*>//针对内置指针    {        typedef random_access iterator_tag iterator_category;        ……    };

It is now possible to implement a traits class step.

    • Identify certain types of information that we would like to be able to obtain in the future. For iterators to be the first, it is possible to obtain their classification.
    • Select a name for this information. For iterators is iterator_category.
    • Provides a template and a set of special versions that contain the types and related information you want to support.

Now we can implement the advance.

    template<typenametypename DistT>    void advance(IterT& iter,DisT d)    {        if(typeid(typenamestd::iterator_traits<IterT>::iterator_category)==        typeid(std::random_access_iterator_tag))        ……    }

Although the logic is correct, but not what we want, aside from the compilation problem (* * clause **48), there is a more fundamental problem: Itert type is learned during compilation, so Iterator_traits::iterator_category is determined during compilation. But the IF statement is approved during run time. The things that can be done during compilation are pushed to the run, which not only wastes time, but also causes the file bloat to execute.

To be determined during compilation, you can use overloads. Overloads are determined during compilation, and the compiler finds the most matching function to invoke the

    Template<TypeNameItert,TypeNameDist>voidDoadvance (itert& iter, Dist D,STD:: Random_access_iterator_tag) {iter+=d; }Template<TypeNameItert,TypeNameDist>voidDoadvance (itert& iter, Dist D,STD:: Bidirectional_iterator_tag) {if(d>=0) while(d--) ++iter;Else          while(d++)--iter; }Template<TypeNameItert,TypeNameDist>voidDoadvance (itert& iter, Dist D,STD:: Input_iterator_tag) {if(d<0)Throw STD:: Out_of_range ("Negative Distance"); while(d++)--iter; }Template<TypeNameItert,TypeNameDistt>voidAdvance (itert& Iter,distt D) {doadvance (iter,d,TypeName::STD:: Iterator_traits<itert>::iterator_category (); }

Because Forward_iterator_tag inherits from Input_iterator_tag, the Input_iterator_tag version of the function can handle the forward iterator because public inheritance is is-a Relationship.

Now let's summarize how to use the traits class

    • Create a set of overloaded functions or function templates (such as doadvance) that differ only in their traits parameters. Each function implements a match with the traits information it receives.
    • Create a control function or function template (for example, advance), call the function above and pass the traits class information.

Traits is widely used in STL, in addition to the above mentioned iterator_traits, there are char_traits to save the character type related information, numeric_limits is used to hold the value of the information related to the type.

TR1 (* * clause **54 import many new traits classes to provide type information, such as is_fundamental to determine if T is a built-in type, is_array to determine if T is an array, is_base_of

"Effective C + +": Clause 46-Clause 47

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.