1. Dispatch
There is a template function below, assuming an animal shelter organization provides it and they accept all the poor animals that are homeless, so they provide a function to the outside world to accept registration. The function looks like this:
Template // T indicates the animal void AcceptAnimals (T animal) {... // do something };
But if they want to separate the cat and the dog (after all, raising a cat and a dog are not the same. They may buy a chain for a dog, while a gentle cat may not ). One feasible method is to provide two functions: AcceptDog and AcceptCat. However, this solution is not elegant (think about it, the registrant may have both a cat and a dog, in this way, he has to call different functions for registration. If the number of classes is still increasing, this will lead to an increase in the number of interfaces provided externally. Therefore, registrants have to remember the cumbersome names, obviously, the name AccpetAnimal is not simply remembered ). If you want to keep this template function and use it as the unique interface provided to the outside world, we need some way to obtain the features of type T (trait ), and adopt different strategies according to different features. Here we have the second solution:
It is agreed that all animal classes (such as class Cat and class Dog) must have an internal typedef type indicating their own identity. The types used as identifiers are as follows:
Struct cat_tag {}; // This is only an empty class, which is used to stimulate function overloading and will be explained later in struct dog_tag {}; // same as above
Therefore, all dogs must look like this:
Class Dog {public: typedef dog_tag type; // type (identity) Flag, indicating this is a Dog class. If it is a cat class, it is typedef cat_tag type ;...}
Then, the animal shelter organization can provide internal functions for separate processing of cats and dogs, such:
Templatevoid Accept (T dog, dog_tag) // The second parameter is an unknown parameter, just to stimulate function overloading {...} templatevoid Accpet (T cat, cat_tag) // same {...}
Therefore, the previous Accept function can be rewritten as follows:
Templatevoid Accept (T animal) // This is the only interface provided to the outside world {1 Accept (animal, typename T: type (); // If T is a dog class, if typename T: type is dog_tag, // typename T: type () is a temporary object of the dog_tag class. // according to the function overload rules, this will call the Accept (T, dog_tag) version, // This is the policy for turning to the dog. // If T is a cat class, typename T: type is cat_tag, from the derivation above, // This will call the version of Accept (T, cat_tag) and turn to the policy for processing cats} // The typename keyword tells the compiler T :: type is a type rather than a static member.
All type derivation and function overloading are completed during the compilation period, and you do not need to spend any runtime costs (except the cost of creating dog_tag and cat_tag temporary objects, but after optimization by the compiler, this cost may also disappear.) The Code has high readability and maintainability. "But, wait !" You said, "Where is the traits ?", Typename T: type is actually a traits, but it only lacks a layer of encapsulation. If you make some improvements like this:
Templatestruct AnimalTraits {typedef T: type;}; % ENDOCDE % the code at 1 can be written as Accept (animal, typename AnimalTraits: type (); 2. efficiency is usually necessary to take special measures to improve efficiency, such as copy in STL. The prototype is like this: % CODE {"cpp"} % templateIterOut copy (IterIn first, IterIn last, IterOut dest) {// set [first, last) copy the elements in the interval to the position starting with dest: return copy_opt (first, last, dest, ptr_category (first, dest )); // ptr_category is used to extract the iterator category for // optimization to an appropriate degree}
Copy_opt has two versions. One of them is optimized for arrays of the basic type. If copying occurs between char arrays, you do not need to assign values to each element, based on the continuity of the Array Distribution in the memory, you can use the memmove function that is extremely fast. Ptr_category has many overloaded versions. If memmove can be used, an empty class such as scalar_ptr is returned to stimulate function overloading. The original version returns the null non_scalar_ptr object. The two versions of copy_opt are as follows:
TemplateIterOut copy (IterIn first, IterIn last, IterOut dest, scalar_ptr) {...} // use memmovetemplateIterOut? Copy (IterIn? First, IterIn last, IterOut dest, non_scalar_ptr) {...} // step by step copy
In fact, allocation is usually required to improve efficiency.
3. enable some code to be compiled
This may be confusing. Can I compile the code without the help of traits? Yes. Consider the std: pair code (to make the code concise, ignore most of it ):
Template struct pair {T1 first; T2 second; 2 pair (const T1 & nfirst, const T2 & nsecond) // If T1 or T2, because there is no reference for "first (nfirst), second (nsecond) {} // reference" (Bjarne Stroustrup, father of C ++, has been written to the C ++ standard... // The Committee submits a proposal to regard "reference" as "reference",}; // This may be legalized in the future ).
Here, you can use a traits (add_reference In the boost Library) to avoid such errors. This traits contains a typedef. If the T of add_reference is a reference, then typedef T type; if it is not a reference, typedef T & type; in this way, the code at 2 can be written:
pair(add_reference::type nfirst,add_reference::type nsecond)。
All types can be compiled.
Traits in the Boost Library
The Traits in Boost is perfect and can be divided into several categories: 1. primary Type Categorisation (Primary Type classification) 2. secondary Type Categorisation (subtype classification) 3. type Properties (Type attribute) 4. relationships Between Types (relationship Between Types) 5. transformations Between Types (conversions Between Types) 6. synthesizing Types (Type Synthesis) 7. function Traits)
Because some of the traits are only simple templates, this article only introduces some technically strong traits. Because the definition of traits often repeats a lot of code, this article only analyzes its underlying mechanism when necessary. All source codes are taken from the corresponding header files. To make the source code concise, all macros have been expanded. As traits skills are closely related to compilation platforms, some platforms may not support template-specific conversion. Here we assume that the compiler complies with the C ++ standard. On my VC7.0, the following code is compiled and works properly.
1. Primary Type Classification
is_array<>(boost/type_traits/is_array.hpp)
Definition
Template is_array {static const bool value = false ;}; // default template is_array {static const bool value = true ;}; // bitrate
Annotation
The C ++ standard allows integer types as template parameters. The N above is like this. This also indicates that the number of template parameters (typename T and size_t N in this example) that appear in the specific template version is not necessarily the same as that of the default one (typename T in this example, however, the number of parameters after the class name must be the same as the default number (is_array, T [N] is a parameter, which is the same as the default number ).
Is_array: value/true, with T = int, N = 10, use the special version is_array: value // false, with T = int, use the default version is_class <> (... /is_class.hpp) // omitting the boost/type_traits in front, the same below
Definition
Template struct is_class_impl // underlying implementation, because different compiling environments may have different underlying implementations. My compiling environment {// is VC7.0, and other underlying implementations are omitted. Template static: boost: type_traits: yes_type is_class_tester (void (U: *) (void); template static: boost: type_traits :: no_type is_class_tester (...); //... static const bool value =: boost: type_traits: ice_and is equivalent to logic and 3 sizeof (is_class_tester (0) = sizeof (:: boost: type_traits: yes_type),: boost: type_traits: ice_not <: boost: is_union: value> :: the value // is_union <> must be determined by the compiler >:: value //, which can be regarded as false in all cases}; templatestruct is_class {static const bool value = is_class_impl: value; // All implementations are in is_class_imp };
Annotation
: Boost: type_traits: yes_type is a typedef: typedef char yes_type; so sizeof (yes_type) is 1.
: Boost: type_traits: no_type is a struct: struct no_type {char padding [8] ;}; sizeof (no_type) is 8. They
It is generally used as the return value type of the overload function. By checking the size of the return value type, you can find out which function is called.
Are defined in boost/type_traits/detail/yes_no_type.hpp.
Is_class_impl has two static functions. The first function can be used only when the template parameter U is a class, because its parameter type is void (U: *) (void ), that is, the pointer to the member function. The second function has a list of non-quantitative parameters. C ++ standard says that only when all other overloaded versions cannot match will the version with any parameter list be matched. Therefore, if T is a class, the type void (T: *) (void) exists. Therefore, the reload resolution of is_class_tester (0) is to call the first function, it is legal to assign 0 to any type of pointer. If T is not a class, there will be no pointer type such as void (T: *) (void), so the first function cannot be implemented now. In this way, for is_class_tester (0) only the second function can be called.
Now pay attention to the expression sizeof (is_class_tester (0) = sizeof (boost: type_traits: yes_type) at 3 ). According to the above inference, if T is a class, is_class_tester (0) actually calls the first overloaded version and returns yes_type, the expression is evaluated as true. If T is not a class, is_class_tester (0) calls the second overloaded version and returns no_type. The expression is evaluated as false. This is exactly what we want. It is worth noting that in the world of sizeof, no expression is actually evaluated. The Compiler only exports the type of the result of the expression and then gives the size of the type. The sizeof (is_class_tester (0) compiler does not actually call the function code to evaluate the value, but only cares about the return value type of the function. So declaring this function is enough. Another important note is that two overloaded versions of is_class_tester use template functions. The reason why the first version uses the template form is that, if it is not done like this, it is static yes_type is_class_tester (void (T: *) (void); then when T is not a class, this traits cannot be compiled. The reason is simple. If T is not a class, void (T: *) (void) does not exist. However, when the template is used, when T is not a class, the overloaded version will not be compiled because it cannot be made available. The C ++ standard allows the template not to be used to be compiled (made available ). In this way, the compiler can only use the second version, which is exactly what we mean. The second version is the template because the first version is the template, because the call to is_class_tester at 3 is as follows: is_class_tester (0 ), if the second version is not a template, the code can only be parsed as a call to the is_class_tester template function (that is, the first version), and the reload parsing will no longer exist.
"Wait !" You are aware of some problems: "You do not need to explicitly specify the template parameters to call template functions !" Okay, that is, you try to write:
Template static: boost: type_traits: yes_type is_class_tester (void (U: *) (void); // template
Static: boost: type_traits: no_type is_class_tester (...); // non-template
Then call is_class_tester (0) (originally is_class_tester (0) in the row marked with 3. Yes, I have to admit that this indeed constitutes a condition for function overloading, it is indeed gratifying to pass the compilation, but the result is definitely not what you want! You will find that all types of is_class: value are now 0. That is to say, the compiler always calls is_class_tester (..); this is because when one or more of the loaded versions of the called functions are templates, the compiler must first try to make the template functions available rather than reload the resolutions, in the process of trying to make it available, the compiler will deduce the template parameters. The type of 0 is deduced by the compiler as int (although 0 can be assigned to the pointer, however, the type of 0 cannot be deduced as a pointer type, because there may be no number of pointer types. In fact, C ++ is a strongly typed language, and the object can only belong to one type ), the parameter type void (U: *) (void) of the first function cannot match int at all (because if yes, why is the template parameter U deduced ?). Therefore, after the first version fails to become available, the compiler can only use a second version that is not a template. As you can see, the results are annoying. However, if you write is_class_tester (0), you actually explicitly present is_class_tester every template function (except those that cannot take T as the template parameter as the current ), and they are all included in the Hou menu that accepts the heavy-load resolution, and then the compiler will do only the heavy-load resolution. (For details about how the compiler implements heavy-load resolutions when it contains an overloaded version of template functions, see Function Templates of C ++ Primer ).
The above technology that uses function overload to achieve some purpose is used in many places in type_traits and even the entire Boost library.
Primary types include: is_void <>, is_integral <>, is_float <>, is_pointer, is_reference <>, is_union <>, is_enum <>,
Is_function <>. See the documentation provided by Boost.
2. Subtype Classification
Is_member_function_pointer <> (.../is_member_function_pointer.hpp)
Define (.../detail/is_mem_fun_pointer_impl.hpp)
Template
Struct is_mem_fun_pointer_impl // default version
{
Static const bool value = false;
};
Template // bitwise version, matching member functions without Parameters
Struct is_mem_fun_pointer_impl {static const bool value = true ;};
Template // a member function that matches a parameter
Struct is_mem_fun_pointer_impl {static const bool value = true ;};
. Etc... // other versions are only partial features that match the number of member functions with different parameters. For more information, see source files.
Template
Struct is_mem_function_pointer
{Static const bool value = is_mem_fun_pointer_impl: value ;};
Annotation
Assume that you have a class X, and you determine this as follows: is_mem_function_pointer: value; then the compiler first
The template parameter class T of is_mem_function_pointer is deduced as int (X: *) (int), and then transmitted to is_mem_fun_pointer_impl
And then the compiler looks for the optimal matching item in the latter's biased version: is_mem_fun_pointer_impl where R = int,
T = X, T0 = int. Value = true for the specific version;
Secondary types include: is_arithmetic <>, is_fundamental <>, is_object <>, is_scalar <>, and is_compound <>. See
Documentation provided by Boost.
3. Type attribute
Is_empty <> (.../is_empty.hpp)
Definition
Template
Struct empty_helper_t1: public T // If T is an empty class, the size of the derived class is the size of the derived part.
{// That is, sizeof (int) * 256
Empty_helper_t1 ();
Int I [256];
};//
Struct empty_helper_t2 {int I [256] ;}; // The size is sizeof (int) * 256
// Compare the values of the preceding two classes to determine whether T is an empty class. If they are of the same size, T is an empty class. Otherwise, it is not empty.
// It is worth noting that if an empty Class E is defined, sizeof (E) is 1 (this byte is used only in memory ).
// Identifies different objects of the class. If sizeof (E) is 0, it means that the locations of different objects in the memory are no different.
// Illegal ). However, if another non-empty class inherits from E, the memory of this byte is not required. That is to say,
// The size is equal to the size of the derived part, rather than one extra byte.
Template
Struct empty_helper // This helper class is used if T is not a class
{// If T is a class, use the following special version. The above section describes how to judge whether T is a class.
Static const bool value = false; // is_class <> traits.
};
Template
Struct empty_helper
{
5 static const bool value = (sizeof (empty_helper_t1) = sizeof (empty_helper_t2 ));
};
Template
Struct is_empty_impl
{
Typedef typename remove_cv: type cvt; // remove_cv removes the const volatile attribute of T, because
// Public cannot be followed by const volatile T.
Static const bool value = (: boost: type_traits: ice_or <// ice_or <> equivalent to logical or
4: boost: detail: empty_helper: value >:: value // cvt is used as the base class and cannot have a cv Modifier
, BOOST_IS_EMPTY (cvt) // The macro is simply defined as false, but has no effect on the result.
>:: Value );
};
Annotation
At Mark 4, if is_class: value is true (that is, T is a class), empty_helper: value >:: value is actually determined to be empty_helper, which adopts the special version, to 5, the conclusion appears. Otherwise, T is not a class, the default version is used, and the result value is false.
Is_polymorphic <> (.../is_polymorphic.hpp)
Is_plymorphic <> based on a basic fact: A polymorphism class contains a virtual function table pointer (vptr), which points to a virtual function table (vtbl ). The latter stores a series of function pointers pointing to virtual functions and runtime type recognition information. A virtual function table pointer usually occupies 4 bytes (all pointers in 32 addressing environments occupy 4 bytes ). Otherwise, if the class is not polymorphism, there is no overhead for this pointer. Based on this principle, we can conclude that if Class X is not a polymorphism class (without vtbl or vptr), if it derives a class Y from it, and Y contains only one virtual function, this causes sizeof (Y)> sizeof (X) (this is because the first appearance of a virtual function causes the compiler to add vptr to Y ). If X is a polymorphism, sizeof (Y) = sizeof (X) (in this case, Y actually has vtbl and vptr inherited from X. All the compiler has to do is include the new virtual functions into vtbl ).
Definition
Template
Struct is_polymorphic_imp1 // use this version when T is a class
{
Typedef typename remove_cv: type ncvT;
Struct d1: public ncvT // ncvT is the type after removing the T const volatile modifier, because it cannot be followed
{// Modifier
D1 (); // There is no virtual function in this class
~ D1 () throw ();
Char padding [256];
}; // No virtual functions in d1
Struct d2: public ncvT // Add a virtual function to d2
{
D2 ();
Virtual ~ D2 () throw (); // Add a virtual function. If ncvT is non-polymorphism, vptr is added, occupying 4 more bytes.
Char padding [256];
};
Static const bool value = (sizeof (d2) = sizeof (d1); // If T is a polymorphism class, valu is true
};
Template
Struct is_polymorphic_imp2 // use this version when T is not a class
{Static const bool value = false ;}; // Since T is not a class, there is no polymorphism, so it is always false
Template
Struct is_polymorphic_selector // This selector selects the Judgment Method Based on the authenticity of is_class
{
Template
Struct rebind // If is_class is false, is_polymorphic_imp2 <> determines
{// This will cause the result to always be false
Typedef is_polymorphic_imp2 type; // use _ imp2
};
};
Template <>
7 struct is_polymorphic_selector // use this special version when is_class is true
{
Template
Struct rebind // If is_class is true, it is determined by is_polymorphic_imp1 <>
{
Typedef is_polymorphic_imp1 type; // use _ imp1
};
};
Template
Struct is_polymorphic_imp // is_polymorphic <> fully implemented by it
{
6 typedef is_polymorphic_selector <: boost: is_class: value> selector; // select selector
8 typedef typename selector: template rebind binder ;//
9 typedef typename binder: type imp_type;
Static const bool value = imp_type: value;
};
Annotation
If T is a class, is_class: value is true, then the row is actually:
Typedef is_polymorphic_selector selector;
This will be resolved to is_polymorphic_selector <>'s second overloaded version 7, where the template rebind will hand over the judgment task to is_polymorphic_imp1 <>, so the eight-row binder is actually is_polymorphic_selector: rebind. The imp_type of the nine rows is actually is_polymorphic_imp1, and the result is as expected. If T is not a class, according to a similar derivation process, it is finally deduced to is_polymorphic_imp2: value, which is false.
"Hi! This is too cumbersome !" You complained: "It can be simplified !". I know, you may think of using boost: ct_if (ct_if yes? : The compilation version of the ternary operator. If typedef ct_if: value result is used like this, when CompileTimeBool is true, the result is TypeIfTrue; otherwise, the result is TypeIfFalse. The implementation of ct_if <> is very simple, and the template is only special ). So you write:
Typedef typename boost: ct_if <
Boost: is_class: value,
Is_polymorphic_imp1, // is_class: type when the value is true
Is_polymorphic_imp2, // is_class: type when the value is false
>:: Type imp_type;
Static const bool value = imp_type: value;
This is true in my VC7.0 environment and works normally, but there is a small problem: If T is not a class, for example, T is an int, then, the compiler type derivation assigns is_polymorphic_imp1 to the second template parameter of ct_if <>. In this process, will the compiler present is_polymorphic_imp1 (or, in other words, the compiler will not check its definition). If it is implemented, then the internal struct d1: public ncvT will also become struct d1: public int. If so, there will be compilation errors because the C ++ standard does not allow the appearance of public int. In fact, my compiler does not report an error, that is, it does not check the definition of is_polymorphic_imp1.
But I don't know how to use the C ++ standard. However, the methods used by the Boost library are guaranteed by the standard.
Type attributes include: alignment_of, is_const, is_volatile, is_pod, and has_trivial_constructor.
4. Relationship between types
Is_base_and_derived <> (boost/type_traits/is_base_and_derived.hpp)
Template
Struct bd_helper
{
Template
Static type_traits: yes_type check (D const volatile *, T); // two overloaded functions
Static type_traits: no_type check (B const volatile *, int );
};
Template
Struct is_base_and_derived_impl2
{
Struct Host
{
Operator B const volatile * () const; // This conversion operator takes effect when the object is a const object
Operator D const volatile *();
};
- Static const bool value
sizeof(bd_helper::check(Host(), 0)) =Sizeof (type_traits: yes_type );
};
The above is the underlying mechanism of is_base_and_derived <>. Next I will explain the mechanism it depends on. Suppose there is a class inheritance system:
Struct B {};
Struct B1: B {};
Struct B2: B {};
Struct D: private B1, private B2 {}; // converting D * to B1 * will result in access violation, and the private base class cannot be accessed.
First, there are some terms: // but I will explain why this does not happen.
SC-Standard Conversion
UDC-User-Defined Conversion
A user-defined conversion sequence consists of one SC followed by one UDC followed by another SC. Both the SC at the beginning and end can be converted to itself (for example, D-> D). At 10, a default Host () is handed over to the bd_helper: check function.
For static no_type check (B const volatile *, int), we have the following feasible implicit conversion sequence:
C-> C const (SC-Qualification Adjustment)-> B const volatile * (UDC) // C indicates Host ()
C-> D const volatile * (UDC)-> B1 const volatile */B2 const volatile *->
B const volatile * (SC-Conversion)
For static yes_type check (D const volatile *, T), we have the following conversion sequence:
C-> D const volatile * (UDC)
In C ++ standards, when selecting the optimal matching function in the heavy load resolution, only the standard conversion (SC) sequence is considered, and this sequence ends when a UDC is encountered. For the first function, compare C-> C const with C-> C, and select the latter. Because the latter is a real subset of the former. Therefore, after removing the first conversion sequence, we get:
C-> D const volatile * (UDC)-> B1 const volatile */B2 const volatile *->
B const volatile * (SC-Conversion)
C-> D const volatile * (UDC)
Here we use the shortest sequence principle and select the latter, which indicates that the compiler does not even need to consider the multi-path to B Conversion or access restrictions, therefore, the ambiguity and access violation will not happen. The conclusion is that if D inherits from B, select yes_type check ().
If D is not inherited from B, the conversion given by the static no_type check (B const volatile *, int) compiler is:
C-> C const-> B const volatile * (UDC)
For the static yes_type check (D const volatile *, T) compiler:
C-> D const volatile * (UDC)
Both of them are good (both require a UDC). However, since static no_type check (B const volatile *, int) is not a template function, it is selected by the compiler. The conclusion is that if D is not inherited from B, select no_type check ().
In my VC7.0 environment, if the operator B const volatile * () const of the Host is removed, the result will always be false.
Unfortunately, this understanding does not belong to me. They come from comments in Boost source code.
Is_convertible <> (boost/type_traits/is_convertible.hpp)
Definition
Template <typename From>
Struct does_conversion_exist
{
Template <typename To> struct result _
{
Static no_type _ m_check (...); // call it when there is no transformation From
Static yes_type _ m_check (To); // call it as long as the transformation exists
Static From _ m_from; // This is only a declaration, so it does not occupy space and has no overhead.
Enum {value = sizeof (_ m_check (_ m_from) = sizeof (yes_type )};
};
};
Template <> struct does_conversion_exist {// This is a special version prepared for void, because it cannot be declared
Template <typename To> struct result _ {// void _ m_from
Enum {value =: boost: is_void: value}; // only void can be converted to void"
};
};
// Is_convertible <> fully uses does_conversion_exist <> as the underlying mechanism, so it is omitted.
Annotation
Does_conversion_exist <> also uses the same technology as is_class_impl <>. Therefore, annotations are omitted. This technology was originally invented by Andrei Alexandrescu. For details, see Modern Design C ++ (<> -- Hou Jie, Yu chunjing ).
The Inter-type relationship traits also has: is_same <>, which is only a simple template with special features.
5. Transformations Between Types (conversions Between Types) 6. Synthesizing Types (Type Synthesis) 7. Function Traits (Function traits) mechanism is relatively simple. See the documentation or header file provided by Boost.
Traits is the genie in the generic world: small and exquisite. Traits is also the most subtle thing in generic programming. They often rely on the rules of compilation-related resolutions, the C ++ standard, and magical template features. This also results in different performance on different platforms. More often, they cannot work on some platforms at all. However, since they are based on the C ++ standard and the compiler is becoming more and more compliant with the standard, these problems are only temporary. Traits is also one of the basic components for building a generic world. They tend to make the design elegant, exquisite, or even perfect.