background.qbk 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. [/
  2. Copyright 2007 John Maddock.
  3. Distributed under the Boost Software License, Version 1.0.
  4. (See accompanying file LICENSE_1_0.txt or copy at
  5. http://www.boost.org/LICENSE_1_0.txt).
  6. ]
  7. [section:background Background and Tutorial]
  8. The following is an updated version of the article "C++ Type traits"
  9. by John Maddock and Steve Cleary that appeared in the October 2000
  10. issue of [@http://www.ddj.com Dr Dobb's Journal].
  11. Generic programming (writing code which works with any data type meeting a
  12. set of requirements) has become the method of choice for providing reusable code.
  13. However, there are times in generic programming when "generic" just isn't
  14. good enough - sometimes the differences between types are too large for an
  15. efficient generic implementation. This is when the traits technique
  16. becomes important - by encapsulating those properties that need to be
  17. considered on a type by type basis inside a traits class, we can
  18. minimize the amount of code that has to differ from one type to another,
  19. and maximize the amount of generic code.
  20. Consider an example: when working with character strings, one common operation is
  21. to determine the length of a null terminated string. Clearly it's possible to
  22. write generic code that can do this, but it turns out that there are much more
  23. efficient methods available: for example, the C library functions `strlen` and
  24. `wcslen` are usually written in assembler, and with suitable hardware support
  25. can be considerably faster than a generic version written in C++.
  26. The authors of the C++ standard library realized this, and abstracted the
  27. properties of `char` and `wchar_t` into the class `char_traits`. Generic code
  28. that works with character strings can simply use `char_traits<>::length` to
  29. determine the length of a null terminated string, safe in the knowledge
  30. that specializations of `char_traits` will use the most appropriate method
  31. available to them.
  32. [h4 Type Traits]
  33. Class `char_traits` is a classic example of a collection of type specific
  34. properties wrapped up in a single class - what Nathan Myers termed a
  35. /baggage class/[link background.references \[1\]]. In the Boost type-traits library, we[link background.references \[2\]] have written a
  36. set of very specific traits classes, each of which encapsulate a single trait
  37. from the C++ type system; for example, is a type a pointer or a reference type?
  38. Or does a type have a trivial constructor, or a const-qualifier?
  39. The type-traits classes share a unified design: each class inherits from
  40. the type __true_type if the type has the specified property and inherits from
  41. __false_type otherwise. As we will show, these classes can be used in
  42. generic programming to determine the properties of a given type and introduce
  43. optimizations that are appropriate for that case.
  44. The type-traits library also contains a set of classes that perform a
  45. specific transformation on a type; for example, they can remove a
  46. top-level const or volatile qualifier from a type. Each class that
  47. performs a transformation defines a single typedef-member `type`
  48. that is the result of the transformation. All of the type-traits
  49. classes are defined inside namespace `boost`; for brevity, namespace-qualification
  50. is omitted in most of the code samples given.
  51. [h4 Implementation]
  52. There are far too many separate classes contained in the type-traits library
  53. to give a full implementation here - see the source code in the Boost library
  54. for the full details - however, most of the implementation is fairly repetitive
  55. anyway, so here we will just give you a flavor for how some of the classes are
  56. implemented. Beginning with possibly the simplest class in the library,
  57. `is_void<T>` inherits from `__true_type` only if `T` is `void`.
  58. template <typename T>
  59. struct __is_void : public __false_type{};
  60. template <>
  61. struct __is_void<void> : public __true_type{};
  62. Here we define a primary version of the template class `__is_void`, and
  63. provide a full-specialization when `T` is `void`. While full specialization
  64. of a template class is an important technique, sometimes we need a
  65. solution that is halfway between a fully generic solution, and a full
  66. specialization. This is exactly the situation for which the standards committee
  67. defined partial template-class specialization. As an example, consider the
  68. class `boost::is_pointer<T>`: here we needed a primary version that handles
  69. all the cases where T is not a pointer, and a partial specialization to
  70. handle all the cases where T is a pointer:
  71. template <typename T>
  72. struct __is_pointer : public __false_type{};
  73. template <typename T>
  74. struct __is_pointer<T*> : public __true_type{};
  75. The syntax for partial specialization is somewhat arcane and could easily
  76. occupy an article in its own right; like full specialization, in order to
  77. write a partial specialization for a class, you must first declare the
  78. primary template. The partial specialization contains an extra <...> after the
  79. class name that contains the partial specialization parameters; these define
  80. the types that will bind to that partial specialization rather than the
  81. default template. The rules for what can appear in a partial specialization
  82. are somewhat convoluted, but as a rule of thumb if you can legally write two
  83. function overloads of the form:
  84. void foo(T);
  85. void foo(U);
  86. Then you can also write a partial specialization of the form:
  87. template <typename T>
  88. class c{ /*details*/ };
  89. template <typename T>
  90. class c<U>{ /*details*/ };
  91. This rule is by no means foolproof, but it is reasonably simple to remember
  92. and close enough to the actual rule to be useful for everyday use.
  93. As a more complex example of partial specialization consider the class
  94. `remove_extent<T>`. This class defines a single typedef-member `type` that
  95. is the same type as T but with any top-level array bounds removed;
  96. this is an example of a traits class that performs a transformation on a type:
  97. template <typename T>
  98. struct __remove_extent
  99. { typedef T type; };
  100. template <typename T, std::size_t N>
  101. struct __remove_extent<T[N]>
  102. { typedef T type; };
  103. The aim of `__remove_extent` is this: imagine a generic algorithm that is
  104. passed an array type as a template parameter, `__remove_extent` provides a
  105. means of determining the underlying type of the array. For example
  106. `remove_extent<int[4][5]>::type` would evaluate to the type `int[5]`.
  107. This example also shows that the number of template parameters in a
  108. partial specialization does not have to match the number in the
  109. default template. However, the number of parameters that appear after the
  110. class name do have to match the number and type of the parameters in the
  111. default template.
  112. [h4 Optimized copy]
  113. As an example of how the type traits classes can be used, consider the
  114. standard library algorithm copy:
  115. template<typename Iter1, typename Iter2>
  116. Iter2 copy(Iter1 first, Iter1 last, Iter2 out);
  117. Obviously, there's no problem writing a generic version of copy that works
  118. for all iterator types `Iter1` and `Iter2`; however, there are some
  119. circumstances when the copy operation can best be performed by a call to
  120. `memcpy`. In order to implement copy in terms of `memcpy` all of the
  121. following conditions need to be met:
  122. * Both of the iterator types `Iter1` and `Iter2` must be pointers.
  123. * Both `Iter1` and `Iter2` must point to the same type - excluding const and
  124. volatile-qualifiers.
  125. * The type pointed to by `Iter1` must have a trivial assignment operator.
  126. By trivial assignment operator we mean that the type is either a scalar type[link background.references \[3\]] or:
  127. * The type has no user defined assignment operator.
  128. * The type does not have any data members that are references.
  129. * All base classes, and all data member objects must have trivial assignment operators.
  130. If all these conditions are met then a type can be copied using `memcpy`
  131. rather than using a compiler generated assignment operator. The type-traits
  132. library provides a class `__has_trivial_assign`, such that
  133. `has_trivial_assign<T>::value` is true only if T has a trivial assignment operator.
  134. This class "just works" for scalar types, but has to be explicitly
  135. specialised for class/struct types that also happen to have a trivial assignment
  136. operator. In other words if __has_trivial_assign gives the wrong answer,
  137. it will give the "safe" wrong answer - that trivial assignment is not allowable.
  138. The code for an optimized version of copy that uses `memcpy` where appropriate is
  139. given in [link boost_typetraits.examples.copy the examples]. The code begins by defining a template
  140. function `do_copy` that performs a "slow but safe" copy. The last parameter passed
  141. to this function may be either a `__true_type` or a `__false_type`. Following that
  142. there is an overload of do_copy that uses `memcpy`: this time the iterators are required
  143. to actually be pointers to the same type, and the final parameter must be a
  144. `__true_type`. Finally, the version of `copy` calls `do_copy`, passing
  145. `__has_trivial_assign<value_type>()` as the final parameter: this will dispatch
  146. to the optimized version where appropriate, otherwise it will call the
  147. "slow but safe version".
  148. [h4 Was it worth it?]
  149. It has often been repeated in these columns that "premature optimization is the
  150. root of all evil" [link background.references \[4\]]. So the question must be asked: was our optimization
  151. premature? To put this in perspective the timings for our version of copy
  152. compared a conventional generic copy[link background.references \[5\]] are shown in table 1.
  153. Clearly the optimization makes a difference in this case; but, to be fair,
  154. the timings are loaded to exclude cache miss effects - without this
  155. accurate comparison between algorithms becomes difficult. However, perhaps
  156. we can add a couple of caveats to the premature optimization rule:
  157. *If you use the right algorithm for the job in the first place then optimization
  158. will not be required; in some cases, memcpy is the right algorithm.
  159. *If a component is going to be reused in many places by many people then
  160. optimizations may well be worthwhile where they would not be so for a single
  161. case - in other words, the likelihood that the optimization will be
  162. absolutely necessary somewhere, sometime is that much higher.
  163. Just as importantly the perceived value of the stock implementation will be
  164. higher: there is no point standardizing an algorithm if users reject it on
  165. the grounds that there are better, more heavily optimized versions available.
  166. [table Time taken to copy 1000 elements using `copy<const T*, T*>` (times in micro-seconds)
  167. [[Version] [T] [Time]]
  168. [["Optimized" copy] [char] [0.99]]
  169. [[Conventional copy] [char] [8.07]]
  170. [["Optimized" copy] [int] [2.52]]
  171. [[Conventional copy] [int] [8.02]]
  172. ]
  173. [h4 Pair of References]
  174. The optimized copy example shows how type traits may be used to perform
  175. optimization decisions at compile-time. Another important usage of type traits
  176. is to allow code to compile that otherwise would not do so unless excessive
  177. partial specialization is used. This is possible by delegating partial
  178. specialization to the type traits classes. Our example for this form of
  179. usage is a pair that can hold references [link background.references \[6\]].
  180. First, let us examine the definition of `std::pair`, omitting the
  181. comparison operators, default constructor, and template copy constructor for
  182. simplicity:
  183. template <typename T1, typename T2>
  184. struct pair
  185. {
  186. typedef T1 first_type;
  187. typedef T2 second_type;
  188. T1 first;
  189. T2 second;
  190. pair(const T1 & nfirst, const T2 & nsecond)
  191. :first(nfirst), second(nsecond) { }
  192. };
  193. Now, this "pair" cannot hold references as it currently stands, because the
  194. constructor would require taking a reference to a reference, which is
  195. currently illegal [link background.references \[7\]]. Let us consider what the constructor's parameters
  196. would have to be in order to allow "pair" to hold non-reference types,
  197. references, and constant references:
  198. [table Required Constructor Argument Types
  199. [[Type of `T1`] [Type of parameter to initializing constructor]]
  200. [[T] [const T &]]
  201. [[T &] [T &]]
  202. [[const T &] [const T &]]
  203. ]
  204. A little familiarity with the type traits classes allows us to construct a
  205. single mapping that allows us to determine the type of parameter from the
  206. type of the contained class. The type traits classes provide a
  207. transformation __add_reference, which adds a reference to its type,
  208. unless it is already a reference.
  209. [table Using add_reference to synthesize the correct constructor type
  210. [[Type of `T1`] [Type of `const T1`] [Type of `add_reference<const T1>::type`]]
  211. [[T] [const T] [const T &]]
  212. [[T &] [T & \[8\]] [T &]]
  213. [[const T &] [const T &] [const T &]]
  214. ]
  215. This allows us to build a primary template definition for `pair` that can
  216. contain non-reference types, reference types, and constant reference types:
  217. template <typename T1, typename T2>
  218. struct pair
  219. {
  220. typedef T1 first_type;
  221. typedef T2 second_type;
  222. T1 first;
  223. T2 second;
  224. pair(boost::__add_reference<const T1>::type nfirst,
  225. boost::__add_reference<const T2>::type nsecond)
  226. :first(nfirst), second(nsecond) { }
  227. };
  228. Add back in the standard comparison operators, default constructor,
  229. and template copy constructor (which are all the same), and you have a
  230. `std::pair` that can hold reference types!
  231. This same extension could have been done using partial template specialization
  232. of `pair`, but to specialize `pair` in this way would require three partial
  233. specializations, plus the primary template. Type traits allows us to
  234. define a single primary template that adjusts itself auto-magically to
  235. any of these partial specializations, instead of a brute-force partial
  236. specialization approach. Using type traits in this fashion allows
  237. programmers to delegate partial specialization to the type traits classes,
  238. resulting in code that is easier to maintain and easier to understand.
  239. [h4 Conclusion]
  240. We hope that in this article we have been able to give you some idea of
  241. what type-traits are all about. A more complete listing of the available
  242. classes are in the boost documentation, along with further examples using
  243. type traits. Templates have enabled C++ uses to take the advantage of the
  244. code reuse that generic programming brings; hopefully this article has
  245. shown that generic programming does not have to sink to the lowest common
  246. denominator, and that templates can be optimal as well as generic.
  247. [h4 Acknowledgements]
  248. The authors would like to thank Beman Dawes and Howard Hinnant for their
  249. helpful comments when preparing this article.
  250. [h4 [#background.references]References]
  251. # Nathan C. Myers, C++ Report, June 1995.
  252. # The type traits library is based upon contributions by Steve Cleary, Beman Dawes, Howard Hinnant and John Maddock: it can be found at www.boost.org.
  253. # A scalar type is an arithmetic type (i.e. a built-in integer or floating point type), an enumeration type, a pointer, a pointer to member, or a const- or volatile-qualified version of one of these types.
  254. # This quote is from Donald Knuth, ACM Computing Surveys, December 1974, pg 268.
  255. # The test code is available as part of the boost utility library (see algo_opt_examples.cpp), the code was compiled with gcc 2.95 with all optimisations turned on, tests were conducted on a 400MHz Pentium II machine running Microsoft Windows 98.
  256. # John Maddock and Howard Hinnant have submitted a "compressed_pair" library to Boost, which uses a technique similar to the one described here to hold references. Their pair also uses type traits to determine if any of the types are empty, and will derive instead of contain to conserve space -- hence the name "compressed".
  257. # This is actually an issue with the C++ Core Language Working Group (issue #106), submitted by Bjarne Stroustrup. The tentative resolution is to allow a "reference to a reference to T" to mean the same thing as a "reference to T", but only in template instantiation, in a method similar to multiple cv-qualifiers.
  258. # For those of you who are wondering why this shouldn't be const-qualified, remember that references are always implicitly constant (for example, you can't re-assign a reference). Remember also that "const T &" is something completely different. For this reason, cv-qualifiers on template type arguments that are references are ignored.
  259. [endsect]