Introduction to C ++ traits and traits
Preface
Traits is also called feature extraction technology. To put it simply, it extracts the return type corresponding to the object to be uploaded,Implement corresponding functions for the same interface. Because STL algorithms and containers are separated, the two are linked through the iterator. The implementation of Algorithms does not know what they are passed in. The extract is equivalent to adding a layer between the interface and implementation.EncapsulationTo hide some details and help call the appropriate method. This requires some tips (for example, bitrate ). With a small example, we should be able to better understand feature extraction.
Most of the following are sourced from STL source code analysis. Read the original book to learn more details.
Traits Programming Techniques
Let's throw a question a little bit, and then go deeper.
1. First, it is likely to be used when the iterator is used in an algorithm.Corresponding type(The type of the thing referred to by the iterator ). Assume that it is necessary to declare a variable in the algorithm and take the type of the object referred to by the iterator as the type.What should we do?
The solution is to use the function template parameter derivation mechanism.
1 template <class I, class T> 2 void func_impl (I iter, T t) {3 T tmp; // here is the type of the object specified by the iterator. The newly created object 4 //... function Implementation 5} 6 7 template <class I> 8 inline 9 void func (I iter) {10 func_impl (iter, * iter); // input the values specified by iter and iter, class automatically derives 11} 12 13 int main () {14 int I; 15 func (& I); 16}
We can see thatEncapsulationIf there is no impl encapsulation, you must explicitly indicate that the iterator points to the object type each time before creating the tmp variable. Adding a layer of encapsulation is much more refreshing.
Type of the iteratorNot just"Type of the object referred to by the iterator"One type. Based on experience, there are five types that are most commonly used.NotIn any case, any one can be obtained by using the template parameter derivation mechanism described above.
The "template parameter derivation mechanism" of a function derives only parameters, and the return value type of the function cannot be deduced. If you need to deduce the return value of the function, you will be powerless.
2. It seems to be a good idea to declare an embedded type so that we can directly obtain it.
1 template <class T> 2 struct MyIter {3 typedef T value_type; // embedded type declaration 4 //... 5}; 6 7 template <class I> 8 typename I: value_type 9 func (I ite) {10 return * ite; 11} 12 13 //... 14 MyIter <int> ite (new int (8); 15 cout <func (ite );
It looks good, but not all iterators are class type,Native pointers won't work!If it is not a class type, you cannot define an embedded type for it.
This requiresBitrate.
3. bitwise is to add some restrictions on the basis of specialization, but it is still a special template.
1 template <class I> 2 struct iterator_traits { 3 typedef typename I::value_type value_type; 4 }; 5 6 template <class I> 7 struct iterator_traits<T*> { 8 typedef T value_type; 9 };10 11 template <class I>12 typename iterator_traits<I>::value_type13 func(I ite) {14 return *ite;15 }
When func calls I, it first transmits I to the extract, and then the extract matches the most suitable value_type. (The extract will match the most special version first) so that when you pass in a native pointer, the first match will be the bitwise version with <T *>, so that value_type is T, instead of I: value_type, which is not stated in advance. In this way, you can use typename iterator_traits <I >:: value_type to know the return type.
The following is an image of STL source code analysis:
Let traits do more
There are five common types of iterators: value_type, difference_type, reference_type, and pointer_type, which can be easily extracted from traits and corresponding bitwise. However, there are also five iterator_category types, which will lead to large-scale code writing projects.
For example, func_II, func_BI, and func_RAI indicate that the Iterator types are Input Iterator, Bidirectional Iterator, and Random Access Iterator.
Now, when the client calls func (), we may need to make a judgment:
1 template<class Iterator>2 void func(Iterator& i) {3 if (is_random_access_iterator(i))4 func_RAI(i);5 if (is_bidirectional_iterator(i))6 func_BI(i);7 else8 func_II(i);9 }
However, during the execution period, you can decide which version to use.Affects Program Efficiency. It is best to select the correct version during compilation.
Heavy LoadThis function mechanism can achieve this goal.
1 struct input_iterator_tag {}; 2 struct output_iterator_tag {}; 3 struct forward_iterator_tag: public input_iterator_tag {}; 4 //... 5 // the benefit of inheritance is that when the function needs to use input_iterator_tag 6 // assume that you pass in a forward_iterator_tag, which will be searched up along the inheritance and know that the conditions are met
After declaring some column tags, We can reload the func function: func (tag ).
By now, the specific overload implementation of each type has been written, but a unified interface is required, and traits will be available now.
1 template <class Iterator> 2 inline void func (Iterator & I) 3 {4 typedef typename Iterator_traits <Iterator>: iterator_category; 5 _ func (I, category (); // each type of overload 6}
Simple instance code
Therefore, traits, on the one hand, can find suitable return types for different input classes; on the other hand, when the type pairs should have different implementation functions, it can be used to extract a type and then distribute the data.
Assume that we have a func function that can accept custom classes or original pointers as parameters and automatically output the tags used.
First, according to traits (implemented by itself or the biased version), it will extract the return type of u, and then call the corresponding constructor return_type (), to distinguish different actual functions as the overload mark of each overloaded version _ func.
- First, let's look at the compilation of interface code.
1 template <class unknown_class> 2 inline typename iterator <unknown_class>: return_type // extract to obtain the corresponding type 3 func (unknown_class u) {4 typedef typename iterator <unknown_class> :: return_type; 5 return _ func (u, return_type (); // you need to call the constructor when tag6}
- First return_type Constructor
1 template <class unknown_class>2 inline typename unknown_class_traits<unknown_class>::return_type3 return_type(unknown_class) {4 typedef typename unknown_class_traits<unknown_class>::return_type RT;5 return RT();6 }
Then implement the set tag to imitate the II and RAI mentioned above.
1 struct A {};2 struct B : A{};
- Then, traits was unveiled, with two premium versions.
1/* feature extract */2 template <class unknown_class> 3 struct unknown_class_traits {4 typedef typename unknown_class: return_type; 5 }; 6 7/* feature extract-for native pointer */8 template <class T> 9 struct unknown_class_traits <T *> {10 typedef T return_type; 11 }; 12 13/* Feature Extraction: For the directed constant */14 template <class T> 15 struct unknown_class_traits <const T *> {16 typedef const T return_type; 17 };
- Suddenly I forgot to explain the structure of the unknown_class. The custom class must be typedef.
1 template <class AorB>2 struct unknown_class {3 typedef AorB return_type;4 };
- Finally, there are various func overload versions.
1 template <class unknown_class> 2 inline typename unknown_class_traits<unknown_class>::return_type 3 __func(unknown_class, A) { 4 cout << "use A flag" << endl; 5 return A(); 6 } 7 8 template <class unknown_class> 9 inline typename unknown_class_traits<unknown_class>::return_type10 __func(unknown_class, B) {11 cout << "use B flag" << endl;12 return B();13 }14 15 template <class unknown_class, class T>16 T17 __func(unknown_class, T) {18 cout << "use origin ptr" << endl;19 return T();20 }
- With this, we can test it.
1 int main() { 2 unknown_class<B> b; 3 unknown_class<A> a; 4 //unknown_class<int> i; 5 int value = 1; 6 int *p = &value; 7 8 A v1 = func(a); 9 B v2 = func(b);10 int v3 = func(p);11 12 char ch = getchar();13 }
As you can see, for a user-defined class to pass in the same interface, it will automatically use the corresponding function, and the return value is also appropriate. It also applies to the original pointer, perfect!
Appendix
The complete code is as follows:
1 # include <iostream> 2 using namespace std; 3 4/* first define some tags */5 struct A {}; 6 struct B: {}; // The benefit of inheritance is that when the function requires parameters A, 7 // and the input parameter B, you can always find the appropriate object 8 9/* Assuming there is an unknown class */10 template <class AorB> 11 struct unknown_class {12 typedef AorB return_type; 13 }; 14 15/* feature extract */16 template <class unknown_class> 17 struct unknown_class_traits {18 typedef typename unknown_class: return_type; 19 }; 20 21/* feature extract-for native pointer */22 template <class T> 23 struct unknown_class_traits <T *> {24 typedef T return_type; 25 }; 26 27/* Feature Extraction-directed constant */28 template <class T> 29 struct unknown_class_traits <const T *> {30 typedef const T return_type; 31 }; 32 33 34/* determine which type to use */35 template <class unknown_class> 36 inline typename unknown_class_traits <unknown_class >:: return_type37 return_type (unknown_class) {38 typedef typename failed <unknown_class >:: return_type RT; 39 return RT (); 40} 41 42 template <class unknown_class> 43 inline typename unknown_class_traits <unknown_class> :: return_type44 _ func (unknown_class, A) {45 cout <"use A flag" <endl; 46 return (); 47} 48 49 template <class unknown_class> 50 inline typename unknown_class_traits <unknown_class >:: return_type51 _ func (unknown_class, B) {52 cout <"use B flag" <endl; 53 return B (); 54} 55 56 template <class unknown_class, class T> 57 T58 _ func (unknown_class, t) {59 cout <"use origin ptr" <endl; 60 return T (); 61} 62 63 template <class unknown_class> 64 inline typename unknown_class_traits <unknown_class> :: return_type65 func (unknown_class u) {66 typedef typename classes <unknown_class >:: return_type; 67 return _ func (u, return_type (); 68} 69 70 int main () {71 unknown_class <B> B; 72 unknown_class <A> a; 73 // unknown_class <int> I; 74 int value = 1; 75 int * p = & value; 76 77 A v1 = func (a); 78 B v2 = func (B); 79 int v3 = func (p); 80 81 char ch = getchar (); 82}
Conclusion
Feature Extraction took a lot of time, but it was quite fun when the program ran out.
First of all, I would like to thank Mr. Hou Jie for reading this book clearly. I am still stupid enough to understand it.
After reading this, we can see the Fourier transform of the image. Haha ~