What Are you, Anyway

zhaozj2021-02-12  173

What are you, Anyway Author:? Stephen C. Dewhurst Translator: Zhang Tao Chi

Original source: http://www.cuj.com/documents/s=8464/20308Dewhurst/ After a difficult discussion template metaprogramming, return to our study. In this part, let's learn more vague syntax issues for template programming: How to guide compilers when there is no sufficient information in the compiler. Here, we will discuss the "Rebind" mechanism used to eliminate ambiguity in standard containers. At the same time, we will also have a warm discussion for some potential template programming technology. Even experienced C programmers, are often plagued by complex syntax of templates. In the template syntax, we must first understand: eliminating the ambiguity of compiler analysis is the most basic grammatical confusion. Types of names, names of name, let us take a look at a template example that does not implement standard containers, this example is simple.

Template Class Ptrlist {public: //...TYPEDEF T * ELEMT; VOID INSERT (ELEMT); private: // ...};

Often template classes embed Type Names information, so we can get instantiated template information by the correct NESTED NAME.

Typedef ptrlist statelist; // ... statelist :: elemt currentState = 0;

ELENT for embedded types allows us to access the type of element recognized by the PTRList template. Even if we initialize Ptrlist with State type, the element type will be State *. In other cases, Ptrlist can be implemented with a pointer element. A relatively mature PTRList should be varied as the initialized element type. With Nested Type, you can help us package PTRLIST to avoid users understand the internal implementation. There is also an example below:

Template Class Scollection {public: //...TYPEDEF ETYPE ELEMT; VOID INSERT (Const ETYPE &); Private: // ...}; Scollection is the same as Ptrlist, compliance with standard naming terms. Comply with these terms is useful so that we can write a lot of elegant algorithms to use these containers (translation: like a standard template library). For example: You can write a method of filling this container array with an appropriate element type. Template Void Fill (Cont & C, Cont :: ELEMT A [], INT LEN) {// error! for (int i = 0; i

蹩 foot compiler

Unfortunately, we have a syntax error here. The compiler cannot identify the Cont :: Elemt this Type Name. The problem is that in the context of Fill (), there is not enough information to let the compiler know that ELEMT is a Type Name. In the standard, in this case, it is considered that NESTED NAME is not Type Name. Now that you have just started, if you don't understand, don't take it. Let's take a look at the information obtained by the compiler in different contexts. First, let's take a look at the case where there is no template: class mycontainer {public: typedef state elemt; // ...}; // ... mycontainer :: elemt * Anelemptr = 0;

Since the compiler can detect the context of the MyContainer Class to determine a member type with ELEMT, you can confirm that MyContainer :: ELEMT is indeed a Type Name. In the instantiated template class, in fact, it is also as simple as this situation.

Typedef ptrlist statelist; // ... statelist :: elemt astate = 0; ptrlist :: elemt annotherstate = 0;

For compilers, an instantiated template class is the same as a normal class. NESTED NAME in the storage ptrlist is the same in MyContainer, there is no difference. In other cases, the compiler is also checking the context to see ELEMT is Type Name. However, after we enter the context of Template, things become more complicated. Because there is no sufficient accurate information. Consider the following program clip:

Template Void AfuncTemplate (T & Arg) {... T :: ELEMT ...

When the compiler encounters T :: ELEMT, it doesn't know what this is. From the state of the template, the compiler knows that t is a type name. It passes :: operator can also guess T is a type name. However, this is all the compilers know. Because there is no more information about T. For example: We can use ptrlist to call a template function, here, T :: ELEMT will be a Type Name.

Ptrlist states; // ... AFUNCTEMPLATE (STATES); // T :: ELEMT IS PTRLIST :: ElemTBut Suppose WEERE TO Instantiate AfuncTemplate WITH A DIFFERENT TYPE? Struct X {Double Elemt; //. ..}; x ANX;

// ... AFUNCTEMPLATE (ANX); // T :: ELEMT IS x :: ELEMT

In this example, T :: ELEMT is the data type, not type name. What will the compiler do? In the standard, in this case, the compiler will think that NESTED NAME is not Type Name. A syntax error will be caused in the above Fill () template function.

CLUE IN THE Compiler In order to deal with this situation, we must clear the compiler:

This NESTED NAME is Type Name. as follows:

Template Void AfuncTemplate (T & Arg) {... TypeName T :: ELEMT ...

Here, we use the keyword Typename to tell the Name behind the compiler, which is Type Name. This makes the compiler to analyze the Template correctly. Note: We tell the compiler: ELEMT instead of t, is Type Name. Of course, the compiler can also know that T is Type Name. Similarly, if we write: Typename A :: B :: C :: D :: E

In this way, we are equivalent to telling the compiler, e is Type Name. Of course, if the type of template function is incompatible does not satisfy the Template decomposition requirements, it will cause a compilation error in a compile time.

Struct Z {// no met name named elemt ...}; z az; // ... AfuncTemplate (AZ); // error! no member z :: ELEMTAFUNCTEMPLATE (ANX); // Error! x :: Elemt IS NOT A TYPE NameafuncTemplate (State); // OK. Ptrlist :: elemt is a type name

Now we can rewrite the Fill () template function,

Void Fill (Cont & C, TypeName Cont :: ELEMT A [], int LEN) {// okfor (int i = 0; i

Gotcha: Failure To Employ Typename with Permissive Compilers Note: Use TypenAme to embed Type Name, if the compiler does not get enough information, use TypenAme outside the template to use TypenAme. Ptrlist :: elemt elem; // oktypename ptrlist :: elemt elem; // error! This is a common error in the context of the template. Consider a template, in its internal implementation, select one in two types, for example: select :: r r1; // oktypename select :: r r2; // error! // ...}

Since the compiler can get information about all template parameters, even don't even need to write Typename before SELECT. If you rewrite F () with template, we can use Typename. Template Void f () {Select :: r r1; // # 1: ok, typename not requiredTypename Select :: r r2; // # 2: Superfluorsselect :: r r; // # 3: Error! NEED TYPENAMEPENAME SELECT :: R R4; // # 4: OK // .. }

In the case 2, Typename can not be written, so it is possible.

The most problematic is the situation 3, many compilers can detect this error, will explain this embedded R as Type Name (indeed it is a Type Name, but without wishing it to explain as a Type name), if, this paragraph The code appears on the standard compiler, then it will be found. For this reason, when you use C template, if you have to use a non-standard compiler, you'd better use the Advanced Standard Compiler to check your code. Intermezzo: Expanding Monostate Protopattern On the template problem, let's stop first, let's take a look at the search technology. When we want to avoid Monostate, it is often a good alternative technique for Singleton. Monostate is a good alternative to Singleton when avoiding the trouble brought by global variables.

Class Monostate {public: int GETNUM () const {return Num_;} void setnum (int Num) {Num_ = Num;} const st: string & getname () const {return name_;} private static int num_; static std: : String name_;};

Just like Singleton, Monostate provides simple COPY of objects, unlike typical Singleton, this sharing mechanism is not implemented by constructor. But by storing static members. Note: Monostate is different from traditional use of static member mechanisms, and traditional methods are to store static member variables through static member functions. Monostate offers nonstatic member functions to store static member variables. (Translation: Good method, let's see how it is achieved) Monostate M1; Monostate M2; // ... m1.setnum (12); cout << m2.getnum () << endl; // shift 12

Each different type of Monostate share the same state. Monostate does not use any special syntax, unlike Singleton implementation.

Singleton :: Instance (). Setnum (12); cout << singleton :: instance (). Getnum () << end1; expanding monostate If we want to add new static members in Monostate, then how to achieve? The ideal case is that the operation does not need to change the source code, or even recompile the irrelevant code. Let's take a look at how to use Template to implement this task.

Class Monostate {public: Template T & GET () {static t member; returnember;}}

Note: This template function can be initialized as needed, unfortunately, it can't be a virtual function. This version of Monostate implements "Lazy Creation" for sharing static members.

MONOSTATE M; M.GET () = 12; // Create An Int Membermonostate M2; cout << m2.get (); // access previously-created Memberm2.Get ) = "Hej!" // Create a string member

Note: Not like the traditional Singleton's "lazy code", this "lazy code" works on compile time, not the run. Indexed Expanding Monostate This method is still very unsatisfactory, at least if users want to have multiple shared special types of members, what should I do? An improvement method is to add a parameter "index" to the template member function.

Class indexedmonostate {public: Template T & Get ();

Template T & indexedmonostate :: get () {static t member;}

Now we can have multiple special types of members, but this interface can also be more perfect.

IndexedMonostate IM1, IM2; Im2.Get () = 12; im2.get () = im2.get () 1; named expanding monostate we need is Record the user's type of Monostate member. This type is also the type of packaging of the template function and the actual type of Static member.

Template struct name {typedef t type;};

This Name class looks very simple, but it is enough to meet the requirements.

Typedef name Grossamount; typedef name percentage

Now we can read types, and you can also bind members of the member and Index. Note: This index corresponds to the actual value is not substantial, as long as [Type, INDEX] is unique. A named Monostate assumes that the type of member can decompress from its initialization type.

Class namedmonostate {public: Template TypeName N :: Type & get () {static typename n :: type member; returnemr;}}

This technique for improving the user interface is the simplicity and convenience of sacrificing the original technology (Note: TypeName is a Type Name that tells embedded N :: Type is a Type Name).

It can be used like this:

NamedMonostate NM1, NM2; NM1.GET () = 12; nm2.GET () = nm1.get () 12.2; cout << nm1.get () * nm2. Get () << endl;

Finally, we can modify the interface to use Monostate.

class GSNamedMonostate {public: template void set (const typename N :: Type & val) {// This const_cast is actually safe, // since we are always actually getting // a non-const object (Unless N.: : TYPE IS / / CONST, THEN GET A Compile Error Here.) Const_cast (Get ()) = Val;} Template

Is this protopttern?

In fact, like we have just begun to mention, this is search technology. Similarly, we have no right to call such a model. A design pattern is a practical result packing success. This "protopttern" is usually applied in the context, so it cannot be applied to a more wide "Pattern" software. Since we can't point out the place of success, we can only expand the Monostate mode as much as possible.

Template Names in Templates Let's go back to the compiler problem of the analysis template. The problem of compiler analysis is not only embedded Type Names, but we often see a similar problem embedded in Template Names. Calling a class, or a class template must have such a member. This member is a class, or a template function.

For example: an extended Monostate using a template member function can be initialized as needed:

Typedef name Grossamount; typef name percentage; gsnamedMonostate nm1, nm2; nm1.set (12); nm2.set (Nm1.Get () 12.2 ); cout << nm1.get () * nm2.get () << endl;

In the code above, the compiler does not encounter any difficulties in checking the template GET. Where nm1 and nm2 are the type name of GSNAMEDMONOSTATE, the compiler can query the type of GET and SET in the class.

However, consider writing such an elegant function: it can be used to displace the extended Monostate Object.

Template Void Populate () {m m; M.GET (); // syntax error! m * mp = & m; mp-> get (); // syntax error!

} Once again, the problem is in the compiler, I don't know enough information, except, know it is Type Name. In particular, if there is not enough Get <> information, the compiler will think it is not type, not a template name. Therefore, the middle brackets of M.GET () are interpreted to be larger than the number, and less than the number, not the template parameter list. In this case, the solution is to tell the compiler <> is a list of template parameters instead of other operands. Template void populate () {m m; m.Template get (); // okm * mp = & m; mp-> template get (); // ok}

Is it incredible, just like analyzing using Typename, this Template special usage is only necessary in the case of it. Hints for rebinding allocators We also encountered the same analysis of the embedded template class, in the implementation of STL Allocator, is such a classic example.

Template Class Analloc {public: //...Template Class Rebind {public: typedef analloc };}; // ...};

This template class has an embedded name, and this Name itself is a template class. This is the use of STL's framework to create allocators, just like allocators initialize different data types for a container. E.g:

Typedef Anallocate Allocates IntstypedEf AI: Rebind ::; // new one allocates doublestypedEf analloc ad; // legal! this is the Same Type

Perhaps, this seems to be a little excess. However, using the rebind mechanism allows us to work with existing allocator as different data types, and do not need to know the current Allocator type and to instocate data type.

Typedef somealloc :: rebind :: Other newalloc;

If someAlloc provides convenient to STL's Allocators, it has an embedded rebind template class. Essentially: "We don't know how allocator's type, don't know the type of assignment, however, I want an allocator like allocates listNodes. This work is often ignored in the template until the Template initializes, the type and value of the variable can be determined. Considering the implementation of STL various compilation List containers, our template list has two template parameters, one element type (T), and Allocator Type (a). (Like standard containers, our List provides default allocator).

Template > Class Ourlist {structure {//...} ;t> :: Other nodealloc; // error!

As a typical LISTS-based container, our LIST actually does not assign and operates element TS. Instead, allocation and operation T type container. This is what we described above. We have allocator, it knows how to assign T type objects, however, we want to allocate Ourlist :: Node. However, when we try to be so rebind, we will have a syntax error. This issue is once again because the compiler does not have enough information. Therefore, the compiler believes that the embedded rebind name is not a template NAME, and <> is interpreted to be greater than, less than the operation. However, this is just the beginning of our problem. Even if the compiler knows that rebind is Template Name, it will also think that it is not Type Name. Therefore, you must write TypeDef so. Typedef Typename A :: Template Rebind :: Other NodeAlloc; Keyword Template tells the compiler This rebind is a template name, keyword Typename tells the compiler The entire pointing a Type Name, it is simple. References and notes: [1] This interface is not always a good idea. Reference Gotcha # 80: Get / SET Interfaces IN C Gotchas (Addison-Wesley, 2003). [2] In fact, you may not do this, although Populate is a very interesting template function from the perspective of philosophy, Populate is a very interesting template function. It is a service for many templates at compile time. In this way, it is not necessary to call the function at the compilation time (the translation: The virtual function is initialized at runtime) However, if the function is not called, it will not be initialized, this initialization will not be completed. Other viable methods are to get the address of the function, instead of calling a function, or make a clear initialization, so if the function does not need, it will exist. [3] If you are not familiar with STL's Allocator, don't worry, in the future discussion, you don't need to be familiar with it. Allocator is a class, but it is used to manage memory for STL containers. Allocators is a typical implementation of template classes.

About the Author

Stephen C. Dewhurst () is the president of Semantics Consulting, Inc., located among the cranberry bogs of southeastern Massachusetts. He specializes in C consulting, and training in advanced C programming, STL, and design patterns . Steve Is Also One of the Featured Instructors of The C seminar (

转载请注明原文地址:https://www.9cbs.com/read-6221.html

New Post(0)