poly_collection.qbk 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208
  1. [library Boost.PolyCollection
  2. [quickbook 1.6]
  3. [authors [López Muñoz, Joaquín M]]
  4. [copyright 2016-2019 Joaquín M López Muñoz]
  5. [category containers]
  6. [id poly_collection]
  7. [dirname poly_collection]
  8. [purpose
  9. High-performance containers for polymorphic objects.
  10. ]
  11. [license
  12. Distributed under the Boost Software License, Version 1.0.
  13. (See accompanying file LICENSE_1_0.txt or copy at
  14. [@http://www.boost.org/LICENSE_1_0.txt])
  15. ]
  16. ]
  17. [def _Boost.TypeErasure_ [@boost:/libs/type_erasure Boost.TypeErasure]]
  18. [def _Boost.TypeIndex_ [@boost:/libs/type_index Boost.TypeIndex]]
  19. [def _CopyConstructible_ [@http://en.cppreference.com/w/cpp/named_req/CopyConstructible [* `CopyConstructible`]]]
  20. [def _duck_typing_ [@https://en.wikipedia.org/wiki/Duck_typing ['duck typing]]]
  21. [def _EqualityComparable_ [@http://en.cppreference.com/w/cpp/named_req/EqualityComparable [* `EqualityComparable`]]]
  22. [def _ForwardIterator_ [@http://en.cppreference.com/w/cpp/named_req/ForwardIterator [* `ForwardIterator`]]]
  23. [def _MoveConstructible_ [@http://en.cppreference.com/w/cpp/named_req/MoveConstructible [* `MoveConstructible`]]]
  24. [def _MoveAssignable_ [@http://en.cppreference.com/w/cpp/named_req/MoveAssignable [* `MoveAssignable`]]]
  25. [def _std::for_each_ [@http://en.cppreference.com/w/cpp/algorithm/for_each `std::for_each`]]
  26. [def _std::function_ [@http://en.cppreference.com/w/cpp/utility/functional/function `std::function`]]
  27. [def _std::unordered_multiset_ [@http://en.cppreference.com/w/cpp/container/unordered_multiset `std::unordered_multiset`]]
  28. [def _RandomAccessIterator_ [@http://en.cppreference.com/w/cpp/named_req/RandomAccessIterator [* `RandomAccessIterator`]]]
  29. [template ticket[number]'''<ulink url="https://svn.boost.org/trac/boost/ticket/'''[number]'''">'''#[number]'''</ulink>''']
  30. [template github[module number]'''<ulink url="https://github.com/boostorg/'''[module]'''/issues/'''[number]'''">'''#[number]'''</ulink>''']
  31. [template github_pr[module number]'''<ulink url="https://github.com/boostorg/'''[module]'''/pull/'''[number]'''">'''PR#[number]'''</ulink>''']
  32. [section Introduction]
  33. Dynamic polymorphism in C++ requires that objects (such as instances
  34. of classes derived from an abstract base) be accessed through an indirection pointer
  35. because their actual /type/ and /size/ are not known at the point of usage. As a
  36. consequence, regular containers cannot store polymorphic objects directly: the usual
  37. workaround is to have containers of pointers to heap-allocated elements. In modern
  38. computer architectures this pattern incurs two types of inefficiency:
  39. * The lack of memory contiguity produced by heap allocation degrades CPU cache
  40. performance.
  41. * Executing virtual operations on a sequence of polymorphic objects whose
  42. actual types differ from one to the next results in failures in
  43. [@https://en.wikipedia.org/wiki/Branch_predictor branch prediction] and a
  44. lower execution speed.
  45. When the particular traversal order is not relevant to the user application,
  46. Boost.PolyCollection proposes an alternative data structure that restores memory
  47. contiguity and packs elements according to their concrete type. Three container
  48. class templates are provided:
  49. * `boost::base_collection`
  50. * `boost::function_collection`
  51. * `boost::any_collection`
  52. respectively dealing with three different types of dynamic polymorphism
  53. available in C++:
  54. * Classic base/derived or OOP polymorphism.
  55. * Function wrapping in the spirit of _std::function_.
  56. * So-called _duck_typing_ as implemented by _Boost.TypeErasure_.
  57. The interface of these containers closely follows that of standard containers.
  58. Additionally, the library provides versions of many of the standard library
  59. algorithms (including
  60. [@http://en.cppreference.com/w/cpp/algorithm/for_each `std::for_each`])
  61. with improved performance and a special feature called /type restitution/
  62. that allows user code to provide clues on the concrete types of the elements
  63. stored for further opportunities of increased efficiency related to inlining and
  64. [@http://hubicka.blogspot.com.es/2014/01/devirtualization-in-c-part-1.html ['devirtualization]].
  65. [note Boost.PolyCollection is a header-only library. C++11 support is required.
  66. The library has been verified to work with Visual Studio 2015, GCC 4.8 and
  67. Clang 3.3.]
  68. [endsect]
  69. [section An efficient polymorphic data structure]
  70. Suppose we have a `base` abstract class with implementations `derived1`, `derived2`
  71. and `derived3`. The memory layout of a `std::vector<base*>` (or similar constructs
  72. such as `std::vector<std::unique_ptr<base>>` or
  73. [@boost:/libs/ptr_container/ `boost::ptr_vector<base>`]) looks like
  74. the following:
  75. [$poly_collection/img/ptr_vector.png]
  76. Elements that are adjacent in the vector are not necessarily allocated contiguously,
  77. much less so if the vector has undergone mid insertions and deletions. A typical
  78. processing operation
  79. std::vector<base*> v;
  80. ...
  81. for(base* b: v){
  82. ... // access base's virtual interface
  83. }
  84. is impacted negatively by two factors:
  85. * Scattering of elements throughout memory reduces CPU caching efficiency,
  86. which in general favor regular access loops to contiguous memory areas.
  87. * [@https://en.wikipedia.org/wiki/Branch_predictor Branch prediction] tries
  88. to minimize the effect of running conditional code (such as an
  89. `if`-`else` statement or the invocation of a `base` virtual function) by
  90. speculatively executing a given branch based on past history. This
  91. mechanism is rendered mostly useless when `derived1`, `derived2` and
  92. `derived3` elements are interspersed along the sequence without a definite pattern.
  93. These limitations are imposed by the very nature of dynamic polymorphism:
  94. as the exact types of the elements accessed through `base`'s interface are not
  95. known, an indirection through `base*` (a particular form of /type erasure/)
  96. is required. There is however a critical observation: even though derived types
  97. are not known when traversing a `std::vector<base*>`, the information is typically
  98. available /at compile time/ at the point of insertion in the vector:
  99. std::vector<base*> v;
  100. ...
  101. v.insert(new derived2{...}); // the type derived2 is "forgotten" by v
  102. A suitably designed container can take advantage of this information to
  103. arrange elements contiguously according to their exact type, which results
  104. in an internal data structure (a map of pointers to
  105. [@http://en.cppreference.com/w/cpp/types/type_info `std::type_info`] objects,
  106. actually) pointing to as many vectors or /segments/ as there are derived classes:
  107. [$poly_collection/img/segment_map.png]
  108. Traversing such a structure reduces to looping over all the segments one after
  109. another: this is extremely efficient both in terms of caching and branch
  110. prediction. In the process we have however lost the free-order capability of a
  111. `std::vector<base*>` (free order can only be retained at the segment level), but
  112. if this is not relevant to the user application the potential performance gains
  113. of switching to this structure are large.
  114. The discussion has focused on base/derived programming, also known as OOP, but it
  115. also applies to other forms of dynamic polymorphism:
  116. * _std::function_ abstracts callable entities with the same given signature
  117. under a common interface. Internally, pointer indirections and virtual-like
  118. function calls are used. Memory fragmentation is expected to be lower than
  119. with OOP, though, as implementations usually feature the so-called
  120. [@http://talesofcpp.fusionfenix.com/post-16/episode-nine-erasing-the-concrete#a-note-on-small-buffer-optimization [' small buffer optimization]]
  121. to avoid heap allocation in some situations.
  122. * The case of `std::function` can be seen as a particular example of a
  123. more general form of polymorphism called _duck_typing_, where unrelated types
  124. are treated uniformly if they conform to the same given /interface/ (a specified
  125. set of member functions and/or operations). Duck typing provides the power of
  126. OOP while allowing for greater flexibility as polymorphic types need not derive
  127. from a preexisting base class or in general be designed with any particular
  128. interface in mind --in fact, the same object can be duck-typed to different
  129. interfaces. Among other libraries, _Boost.TypeErasure_ provides duck typing
  130. for C++. Under the hood, duck typing requires pointer indirection and virtual
  131. call implementation techniques analogous to those of OOP, and so there are
  132. the same opportunities for efficient container data structures as we have
  133. described.
  134. Boost.PolyCollection provides three different container class templates
  135. dealing with OOP, `std::function`-like polymorphism and duck typing as
  136. implemented by Boost.TypeErasure:
  137. * `boost::base_collection`
  138. * `boost::function_collection`
  139. * `boost::any_collection`
  140. The interfaces of these containers are mostly the same and follow the usual
  141. conventions of standard library containers.
  142. [endsect]
  143. [section Tutorial]
  144. [section Basics]
  145. [import ../example/rolegame.hpp]
  146. [section `boost::base_collection`]
  147. [import ../example/basic_base.cpp]
  148. (Code samples from [@boost:/libs/poly_collection/example/basic_base.cpp `basic_base.cpp`].)
  149. Imagine we are developing a role playing game in C++ where sprites are rendered on
  150. screen; for the purposes of illustration we can model rendering simply as
  151. outputting some information to a `std::ostream`:
  152. [rolegame_1]
  153. The game features warriors, juggernauts (a special type of warrior) and goblins,
  154. each represented by its own class derived (directly or indirectly) from `sprite`:
  155. [rolegame_2]
  156. Let us populate a polymorphic collection with an assortment of sprites:
  157. [basic_base_1]
  158. There are two aspects to notice here:
  159. * `boost::base_collection` does not have a `push_back` member function like, say,
  160. `std::vector`, as the order in which elements are stored cannot be freely
  161. chosen by the user code \u2014we will see more about this later. The insertion mechanisms
  162. are rather those of containers like _std::unordered_multiset_, namely `insert` and
  163. `emplace` with or without a position /hint/.
  164. * Elements are not created with `new` but constructed on the stack and passed
  165. directly much like one would do with a standard non-polymorphic container.
  166. [important Elements inserted into a `boost::base_collection` (or the other
  167. containers of Boost.PolyCollection) must be copyable and assignable; strictly
  168. speaking, they must at least model _MoveConstructible_ and either be
  169. _MoveAssignable_ or not throw on move construction. This might
  170. force you to revisit your code as it is customary to explicitly forbid
  171. copying at the base level of a virtual hierarchy to avoid
  172. [@https://en.wikipedia.org/wiki/Object_slicing ['slicing]].]
  173. Rendering can now be implemented with a simple `for` loop over `c`:
  174. [basic_base_2]
  175. The output being:
  176. [pre
  177. juggernaut 0,juggernaut 4,juggernaut 7,goblin 1,goblin 3,goblin 5,warrior 2,warrior 6
  178. ]
  179. As we have forewarned, the traversal order does not correspond to that of insertion.
  180. Instead, the elements are grouped in /segments/ according to their concrete type.
  181. Here we see that juggernauts come first, followed by goblins and warriors. In
  182. general, no assumptions should be made about segment ordering, which may be
  183. different for this very example in your environment. On the other hand, elements
  184. inserted into an already existing segment always come at the end (except if a hint
  185. is provided). For instance, after inserting a latecomer goblin with `id==8`:
  186. [basic_base_3]
  187. the rendering loop outputs (new element in red):
  188. [pre
  189. juggernaut 0,juggernaut 4,juggernaut 7,goblin 1,goblin 3,goblin 5,[role red goblin 8],warrior 2,warrior 6
  190. ]
  191. Deletion can be done in the usual way:
  192. [basic_base_4]
  193. Now rendering emits:
  194. [pre
  195. juggernaut 0,juggernaut 4,goblin 1,goblin 3,goblin 5,goblin 8,warrior 2,warrior 6
  196. ]
  197. [endsect]
  198. [section `boost::function_collection`]
  199. [import ../example/basic_function.cpp]
  200. (Code samples from [@boost:/libs/poly_collection/example/basic_function.cpp `basic_function.cpp`].
  201. C++14 `std::make_unique` is used.)
  202. Well into the development of the game, the product manager requests that two new
  203. types of entities be added to the rendering loop:
  204. * Overlaid messages, such as scores, modelled as `std::string`s.
  205. * Pop-up windows coming from a third party library that, unfortunately, do not
  206. derive from `sprite` and use their own `display` member functon for rendering:
  207. [rolegame_3]
  208. We then decide to refactor the code so that sprites, messsages and windows are
  209. stored in dedicated containers:
  210. [basic_function_1]
  211. and define our rendering container as a `boost::function_collection` of
  212. /callable entities/ \u2014function pointers or objects with a function
  213. call `operator()`\u2014 accepting a `std::ostream&` as a parameter
  214. [basic_function_2]
  215. which we fill with suitable adaptors for `sprite`s, `std::string`s and
  216. `window`s, respectively. Lambda functions allow for a particularly terse code.
  217. [basic_function_3]
  218. The rendering loop now looks like this:
  219. [basic_function_4]
  220. and produces the following for a particular scenario of sprites, messages and
  221. windows:
  222. [pre
  223. juggernaut 0,goblin 1,warrior 2,goblin 3,"stamina: 10,000","game over",'''[pop-up 1]''','''[pop-up 2]'''
  224. ]
  225. The container we have just created works in many respects like a
  226. `std::vector<std::function<render_callback>>`, the main difference being
  227. that with `boost::function_collection` callable entities of the same type
  228. are packed together in memory
  229. [footnote Note that all `sprite`s come into one segment: this is why
  230. goblins #1 and #3 are not adjacent. Exercise for the reader: change
  231. the code of the example so that sprites are further segmented according
  232. to their concrete type.], thus increasing performance (which is the whole
  233. point of using Boost.PolyCollection), while a vector of `std::function`s
  234. results in an individual allocation for each entity stored
  235. [footnote Except when small buffer optimization applies.].
  236. In fact, the `value_type` elements held by a
  237. `boost::function_collection` are actually /not/ `std::function`s,
  238. although they behave analogously and can be converted to `std::function` if needed:
  239. [basic_function_5]
  240. There is a reason for this: elements of a polymorphic collection cannot be freely
  241. assigned to by the user as this could end up trying to insert an object into a
  242. segment of a different type. So, unlike with `std::function`, this will not work:
  243. [basic_function_6]
  244. [endsect]
  245. [section `boost::any_collection`]
  246. [import ../example/basic_any.cpp]
  247. (Code samples from [@boost:/libs/poly_collection/example/basic_any.cpp `basic_any.cpp`].)
  248. [note Here we just touch on the bare essentials of _Boost.TypeErasure_ needed
  249. to present `boost::any_collection`. The reader is advised to read
  250. Boost.TypeErasure documentation for further information.]
  251. After measuring the performance of the latest changes, we find that rendering
  252. is too slow and decide to refactor once again: if we could store all the
  253. entities --sprites, messages and windows-- into one single container, that would
  254. eliminate a level of indirection. The problem is that these types are totally
  255. unrelated to each other.
  256. _Boost.TypeErasure_ provides a class template `boost::type_erasure::any<Concept>`
  257. able to hold an object of whatever type as long as it conforms to the interface
  258. specified by `Concept`. In our case, we find it particularly easy to implement
  259. a common interface for rendering by providing overloads of `operator<<`
  260. [basic_any_1]
  261. so that we can use it to specify the interface all entities have to adhere to:
  262. [basic_any_2]
  263. The collection just created happily accepts insertion of heterogeneous entities
  264. (since interface conformity is silently checked at compile time)
  265. [basic_any_3]
  266. and rendering becomes
  267. [basic_any_4]
  268. with output
  269. [pre
  270. '''[pop-up 1]''','''[pop-up 2]''',juggernaut 0,goblin 1,goblin 3,warrior 2,"stamina: 10,000","game over"
  271. ]
  272. As was the case with `boost::function_collection`, this container is similar
  273. to a `std::vector<boost::type_erasure::any<Concept>>` but has better performance
  274. due to packing of same-type elements. Also, the `value_type` of a
  275. `boost::any_collection<Concept>` is /not/ `boost::type_erasure::any<Concept>`,
  276. but a similarly behaving entity
  277. [footnote Actually, it is
  278. `boost::type_erasure::any<Concept2,boost::type_erasure::_self&>` for some
  279. internally defined `Concept2` that extends `Concept`.].
  280. In any case, we are not accessing sprites through an abstract `sprite&`
  281. anymore, so we could as well dismantle the virtual hierarchy and implement
  282. each type autonomously: this is left as an exercise to the reader.
  283. [endsect]
  284. [endsect]
  285. [section Deeper into the segmented nature of Boost.PolyCollection]
  286. [import ../example/segmented_structure.cpp]
  287. (Code samples from [@boost:/libs/poly_collection/example/segmented_structure.cpp `segmented_structure.cpp`].
  288. C++14 `std::make_unique` is used.)
  289. [section Type registration]
  290. Getting back to our [link poly_collection.tutorial.basics.boost_base_collection `boost::base_collection`]
  291. example, suppose we want to refactor the populating code as follows:
  292. [segmented_structure_1]
  293. Unexpectedly, this piece of code throws an exception of type
  294. [link poly_collection.reference.header_boost_poly_collection_exc.class_unregistered_type `boost::poly_collection::unregistered_type`].
  295. What has changed from our original code?
  296. Suppose a `warrior` has been created by `make_sprite`. The statement
  297. `c.insert(*make_sprite())` is passing the object as a `sprite&`: even though
  298. `boost::base_collection` is smart enough to know that the object is actually
  299. derived from `sprite` (by using
  300. [@http://en.cppreference.com/w/cpp/language/typeid `typeid()`]) and
  301. [@https://en.wikipedia.org/wiki/Object_slicing slicing] is to be avoided,
  302. there is no way that a segment for it can be created without accessing
  303. the type `warrior` /at compile time/ for the proper internal class templates
  304. to be instantiated
  305. [footnote If this is conceptually difficult to grasp, consider the potentially
  306. more obvious case where `warrior` is defined in a dynamic module linked to
  307. the main program: the code of `boost::base_collection`, which has been compiled
  308. before linking, cannot even know the size of this as-of-yet unseen class, so
  309. hardly can it allocate a segment for the received object.].
  310. This did not happen in the pre-refactoring code because objects were passed
  311. as references to their true types.
  312. A type is said to be /registered/ into a polymorphic collection if
  313. there is a (potentially empty) segment created for it. This can be checked
  314. with:
  315. [segmented_structure_2]
  316. Registration happens automatically when the object is passed as a reference
  317. to its true type or
  318. [link poly_collection.tutorial.insertion_and_emplacement.emplacement `emplace`]'d,
  319. and explicitly with
  320. `register_types`:
  321. [segmented_structure_3]
  322. Once `T` has been registered into a polymorphic collection, it remains
  323. so regardless of whether objects of type `T` are stored or not, except if
  324. the collection is moved from, assigned to, or swapped.
  325. As slicing is mainly an issue with OOP, `unregistered_type` exceptions are
  326. expected to be much less frequent with `boost::function_collection` and
  327. `boost::any_collection`. Contrived examples can be found, though:
  328. [segmented_structure_4]
  329. [endsect]
  330. [section Segment-specific interface]
  331. For most of the interface of a polymorphic collection, it is possible to only
  332. refer to the elements of a given segment by providing either their type or
  333. `typeid()`. For instance:
  334. [segmented_structure_5]
  335. Note that any of these (except
  336. [link poly_collection.tutorial.deeper_into_the_segmented_nature.reserve `reserve`])
  337. will throw `boost::poly_collection::unregistered_type` if the type is not
  338. registered. Segment-specific addressability also includes traversal:
  339. [endsect]
  340. [section Local iterators]
  341. The following runs only over the `warrior`s of the collection:
  342. [segmented_structure_6]
  343. Output:
  344. [pre
  345. warrior 2,warrior 6
  346. ]
  347. `begin|end(typeid(T))` return objects of type `local_base_iterator`
  348. associated to the segment for `T`. These iterators dereference to the same
  349. value as regular iterators (in the case of `boost::base_collection<base>`,
  350. `base&`) but can only be used to traverse a given segment (for instance,
  351. `local_base_iterator`'s from different segments cannot be compared between them).
  352. In exchange, `local_base_iterator` is a _RandomAccessIterator_, whereas
  353. whole-collection iterators only model _ForwardIterator_.
  354. A terser range-based `for` loop can be used with the convenience `segment`
  355. member function:
  356. [segmented_structure_7]
  357. Even more powerful than `local_base_iterator` is `local_iterator<T>`:
  358. [segmented_structure_8]
  359. This appends a `"super"` prefix to the `rank` data member of existing warriors:
  360. [pre
  361. superwarrior 2,superwarrior 6
  362. ]
  363. The observant reader will have noticed that in order to access `rank`, which
  364. is a member of `warrior` rather than its base class `sprite`, `first` (or
  365. `x` in the range `for` loop version) has to refer to a `warrior&`, and this
  366. is precisely the difference between `local_iterator<warrior>` (the type
  367. returned by `begin<warrior>()`) and `local_base_iterator`.
  368. `local_iterator<warrior>` is also a _RandomAccessIterator_: for most respects,
  369. \[`begin<T>()`, `end<T>()`) can be regarded as a range over an array of `T`
  370. objects. `local_iterator<T>`s can be explicitly converted to
  371. `local_base_iterator`s. Conversely, if a `local_base_iterator` is associated
  372. to a segment for `T`, it can then be explictly converted to a
  373. `local_iterator<T>` (otherwise the conversion is undefined behavior).
  374. The figure shows the action scopes of all the iterators associated to
  375. a polymorphic collection (`const_` versions not included):
  376. [$ poly_collection/img/poly_collection_iterators.png]
  377. We have yet to describe the bottom part of the diagram.
  378. Whereas `segment(typeid(T))` is used to range over the /elements/
  379. of a particular segment, `segment_traversal()` returns an object for ranging over
  380. /segments/, so that the whole collection can be processed with a nested
  381. segment-element `for` loop like the following:
  382. [segmented_structure_9]
  383. Segment decomposition of traversal loops forms the basis of
  384. [link poly_collection.tutorial.algorithms improved-performance algorithms].
  385. [endsect]
  386. [section Reserve]
  387. Much like `std::vector`, segments can be made to reserve memory in
  388. advance to minimize reallocations:
  389. [segmented_structure_10]
  390. If there is no segment for the indicated type (here, `goblin`), one is
  391. automatically created. This is in contrast with the rest of segment-specific
  392. member functions, which throw
  393. [link poly_collection.tutorial.deeper_into_the_segmented_nature.type_registration
  394. `boost::poly_collection::unregistered_type`].
  395. `reserve` can deal with all segments at once. The following
  396. [segmented_structure_11]
  397. instructs every /existing/ segment to reserve 1,000 elements. If a
  398. segment is created later (through element insertion or with
  399. [link poly_collection.tutorial.deeper_into_the_segmented_nature.type_registration
  400. type registration]), its capacity will be different.
  401. [note Unlike standard containers, collection-level `capacity()` and
  402. `max_size()` are not provided because their usual semantics cannot be
  403. applied to Boost.PolyCollection: for instance, `capacity()` is typically
  404. used to check how many elements can be inserted without reallocation,
  405. but in a segmented structure that depends on the exact types of the
  406. elements and whether they are registered or not.
  407. ]
  408. [endsect]
  409. [endsect]
  410. [section Insertion and emplacement]
  411. [import ../example/insertion_emplacement.cpp]
  412. (Code samples from [@boost:/libs/poly_collection/example/insertion_emplacement.cpp `insertion_emplacement.cpp`].)
  413. We already know that `insert(x)`, without further positional information,
  414. stores `x` at the end of its associated segment. When a regular iterator `it`
  415. is provided, insertion happens at the position indicated if both `it` and
  416. `x` belong in the same segment; otherwise, `it` is ignored. For instance,
  417. if our sprite collection has the following entities:
  418. [pre
  419. juggernaut 0,juggernaut 4,juggernaut 7,goblin 1,goblin 3,goblin 5,warrior 2,warrior 6
  420. ]
  421. then this code:
  422. [insertion_emplacement_1]
  423. puts the new `juggernaut` at the beginning:
  424. [pre
  425. [role red juggernaut 8],juggernaut 0,juggernaut 4,juggernaut 7,goblin 1,...
  426. ]
  427. whereas the position hint at
  428. [insertion_emplacement_2]
  429. is ignored and the new `goblin` gets inserted at the end of its segment:
  430. [pre
  431. juggernaut 8,...,juggernaut 7,goblin 1,goblin 3,goblin 5,[role red goblin 9],warrior 2,...
  432. ]
  433. [link poly_collection.tutorial.deeper_into_the_segmented_nature.local_iterators Local iterators]
  434. can also be used to indicate the insertion position:
  435. [insertion_emplacement_3]
  436. [pre
  437. juggernaut 8,juggernaut 0,[role red juggernaut 10],juggernaut 4,juggernaut 7,goblin 1,...
  438. ]
  439. There is a caveat, though: when using a local iterator, the element inserted
  440. /must belong to the indicated segment/:
  441. [insertion_emplacement_4]
  442. Member functions are provided for range insertion, with and without a position
  443. hint:
  444. [insertion_emplacement_5]
  445. As [link poly_collection.tutorial.deeper_into_the_segmented_nature.type_registration already explained],
  446. care must be taken that types be pre-registered into the collection if
  447. they are not passed as references to their actual type. So, why did not
  448. this line
  449. [insertion_emplacement_6]
  450. throw `boost::poly_collection::unregistered_type`? As it happens, in the
  451. special case where the inserted range belongs to a polymorphic collection
  452. of the same type, registration is done automatically
  453. [footnote That is, Boost.PolyCollection has enough static information to
  454. do type registration without further assistance from the user.].
  455. [#poly_collection.tutorial.insertion_and_emplacement.emplacement]
  456. Emplacement is slightly different for Boost.PolyCollection than with
  457. standard containers. Consider this attempt at emplacing a `goblin`:
  458. [insertion_emplacement_7]
  459. If examined carefully, it is only natural that the code above not compile:
  460. Boost.PolyCollection cannot possibly know that it is precisely a `goblin`
  461. that we want to emplace and not some other type constructible from an `int`
  462. (like `warrior`, `juggernaut` or an unrelated class). So, the type of
  463. the emplaced element has to be specified explicitly:
  464. [insertion_emplacement_8]
  465. As with `insert`, a position can be indicated for emplacing:
  466. [insertion_emplacement_9]
  467. Note the naming here: `emplace_hint` is used when the position is indicated with
  468. a regular iterator, and for local iterators it is `emplace_pos`.
  469. [endsect]
  470. [section Exceptions]
  471. [import ../example/exceptions.cpp]
  472. (Code samples from [@boost:/libs/poly_collection/example/exceptions.cpp `exceptions.cpp`].)
  473. Besides the usual exceptions like
  474. [@http://en.cppreference.com/w/cpp/memory/new/bad_alloc `std::bad_alloc`] and
  475. those generated by user-provided types, polymorphic collections can throw the following:
  476. * `boost::poly_collection::unregistered_type`
  477. * `boost::poly_collection::not_copy_constructible`
  478. * `boost::poly_collection::not_equality_comparable`
  479. The situations in which the first is raised have been
  480. [link poly_collection.tutorial.deeper_into_the_segmented_nature.type_registration already discussed];
  481. let us focus on the other two.
  482. We have a new type of sprite in our game defined by the non-copyable
  483. class `elf`:
  484. [rolegame_4]
  485. and we use it without problems until we write this:
  486. [exceptions_1]
  487. The first insertion works because the `elf` object passed is /temporary/
  488. and can be /moved/ into the container, but the second statement actually
  489. needs to /copy/ the `elf` elements in `c` to `c2`, hence the exception.
  490. The potentially surprising aspect of this behavior is that standard
  491. containers signal this kind of problems by /failing at compile time/.
  492. Here we cannot afford this luxury because the exact types contained in a
  493. polymorphic collection are not known until run time (for instance,
  494. if `elf` elements had been erased before copying `c` to `c2` everything
  495. would have worked): basically, the deferral of errors from compile time
  496. to run time is an intrinsic feature of dynamic polymorphism.
  497. In the same vein, equality comparison requires that elements themselves
  498. be equality comparable:
  499. [exceptions_2]
  500. The above is unremarkable once we notice we have not defined `operator==`
  501. for any `sprite`. The problem may go unnoticed for quite some time, however,
  502. because determining that two polymorphic collections are equal (i.e.
  503. all their non-empty segments are equal) can return `false` without
  504. comparing anything at all (for instance, if segment sizes differ), in which
  505. case no exception is thrown.
  506. [note Operators for `<`, `<=`, `>` and `>=` comparison are not provided
  507. because /segment/ order is not fixed and may vary across otherwise
  508. identical collections. The situation is similar to that of standard
  509. [@http://en.cppreference.com/w/cpp/named_req/UnorderedAssociativeContainer unordered
  510. associative containers.]
  511. ]
  512. These three are all the intrinsic exceptions thrown by Boost.PolyCollection.
  513. The implication is that if a type is _CopyConstructible_,
  514. _MoveAssignable_ (or move construction does not throw) and _EqualityComparable_, then
  515. the entire interface of Boost.PolyCollection is unrestrictedly available for it
  516. [footnote Provided, of course, that the type has the /right/ to be in the collection,
  517. that is, it is derived from the specified base, or callable with the specified
  518. signature, etc.].
  519. [endsect]
  520. [section Algorithms]
  521. [import ../example/algorithms.cpp]
  522. (Code samples from [@boost:/libs/poly_collection/example/algorithms.cpp `algorithms.cpp`].
  523. C++14 generic lambda expressions are used.)
  524. The ultimate purpose of Boost.PolyCollection is to speed up the processing of
  525. large quantities of polymorphic entities, in particular for those operations
  526. that involve linear traversal as implemented with a `for`-loop or using the
  527. quintessential _std::for_each_ algorithm.
  528. [algorithms_1]
  529. Replacing the container used in the program from classic alternatives to
  530. Boost.PolyCollection:
  531. * `std::vector<base*>` (or similar) → `boost::base_collection<base>`
  532. * `std::vector<std::function<signature>>` → `boost::function_collection<signature>`
  533. * `std::vector<boost::type_erasure::any<concept_>>` → `boost::any_collection<concept_>`
  534. is expected to increase performance due to better caching and branch prediction
  535. behavior. But there is still room for improvement.
  536. Consider this transformation of the code above using a double
  537. segment-element loop (based on the
  538. [link poly_collection.tutorial.deeper_into_the_segmented_nature.local_iterators local iterator]
  539. capabilities of Boost.PolyCollection):
  540. [algorithms_2]
  541. Although not obvious at first sight, this version of the same basic operation
  542. is actually /faster/ than the first one: for a segmented structure such as used
  543. by Boost.PolyCollection, each iteration with the non-local iterator passed to
  544. `std::for_each` involves:
  545. # hopping to next position in the segment,
  546. # checking for /end of segment/ (always),
  547. # if at end (infrequent), selecting the next segment,
  548. # checking for /end of range/ (always),
  549. whereas in the second version, iteration on the inner loop, where most
  550. processing happens, is a simple increment-and-check operation, that is,
  551. there is one less check (which happens at the much shorter outer loop). When
  552. the workload of the algorithm (the actually useful stuff done with the elements
  553. themselves) is relatively light, the overhead of looping can be very
  554. significant.
  555. To make it easier for the user to take advantage of faster segment-element
  556. looping, Boost.PolyCollection provides its own version of `for_each` based
  557. on that technique:
  558. [algorithms_3]
  559. `boost::poly_collection::for_each` has the same interface and behavior as
  560. `std::for_each` except for the fact that it only works for (non-local)
  561. iterators of a polymorphic container
  562. [footnote For any other type of iterator, it is guaranteed not to compile.].
  563. Versions of other standard algorithms are provided as well:
  564. [algorithms_4]
  565. In fact, variants are given of most (though not all) of the algorithms in
  566. [@http://en.cppreference.com/w/cpp/algorithm `<algorithm>`]
  567. among those that are compatible with polymorphic collections
  568. [footnote For example, algorithms requiring bidirectional iterators or a higher
  569. category are not provided because polymorphic collections have forward-only
  570. iterators.].
  571. Check the [link poly_collection.reference.header_boost_poly_collection_alg reference]
  572. for details.
  573. [section Type restitution]
  574. By /type restitution/ we mean the generic process of getting a concrete entity
  575. from an abstract one by providing missing type information:
  576. [algorithms_5]
  577. Type restitution has the potential to increase performance. Consider for
  578. instance the following:
  579. [algorithms_6]
  580. Behaviorwise this code is equivalent to simply executing `std::cout<<r<<"\n"`,
  581. but when type restitution succeeds the statement `std::cout<<str<<"\n"` is
  582. skipping a virtual-like call that would have happened if `r` were used instead.
  583. From a general point of view, supplying the compiler with extra type
  584. information allows it to perform more optimizations than in the abstract case,
  585. inlining being the prime example.
  586. All Boost.PolyCollection algorithms accept an optional list of types for
  587. restitution. Let us use the
  588. [link poly_collection.tutorial.basics.boost_any_collection `boost::any_collection`]
  589. scenario to illustrate this point:
  590. [algorithms_7]
  591. Output:
  592. [pre
  593. warrior 2,warrior 6,[pop-up 1],[pop-up 2],juggernaut 0,juggernaut 4,
  594. juggernaut 7,goblin 1,goblin 3,goblin 5,"stamina: 10,000","game over"
  595. ]
  596. This rendering loop differs from the non-restituting one in two aspects:
  597. * A list of types is provided as template arguments to the algorithm. This is
  598. an indication that the types /may/ be actually present in the collection, not
  599. a promise. Also, the list of types need not be exhaustive, that is, some
  600. unlisted types could be present in the collection \u2014in the example above,
  601. the loop traverses *all* elements (including `std::string`s and `window`s),
  602. not only those corresponding to restituted types. In general, the more types
  603. are restituted, the greater the potential improvement in performance.
  604. * The lambda function passed is a generic one accepting its argument as
  605. `const auto&`
  606. [footnote This requires C++14, but the same effect can be achieved in C++11
  607. providing an equivalent, if more cumbersome, functor with a templatized
  608. call operator.].
  609. Internally, `boost::poly_collection::for_each` checks for each segment if its
  610. type, say `T`, belongs in the type restitution list: if this is the case, the lambda
  611. function is passed a `const T&` rather than the generic
  612. `const boost::any_collection::value_type&`. For each restituted type we are
  613. saving indirection calls and possibly getting inlining optimizations, etc.
  614. As we see in the
  615. [link poly_collection.performance performance section], the speedup can be
  616. very significant.
  617. Type restitution works equally for the rest of collections of
  618. Boost.PolyCollection. When using `boost::base_collection`, though, the case
  619. of improved performance is more tricky:
  620. [algorithms_8]
  621. The problem here is that, even though the argument to the lambda function will
  622. be restituted (when appropriate) to `warrior&`, `juggernaut&` or `goblin&`,
  623. using it still involves doing a virtual function call in `s.render(std::cout)`.
  624. Whether this call is resolved to a non-virtual one depends on the quality
  625. of implementation of the compiler:
  626. * If the concrete class is marked as
  627. [@http://en.cppreference.com/w/cpp/language/final `final`], the compiler /in
  628. principle/ has enough information to get rid of the virtual function call.
  629. * Other than this,
  630. [@http://hubicka.blogspot.com.es/2014/01/devirtualization-in-c-part-1.html ['devirtualization]]
  631. capabilities /may/ be able to figure out that the type restitution scenario
  632. is actually casting the element to its true type, in which case, again, virtual
  633. calls are not needed.
  634. [endsect]
  635. [endsect]
  636. [endsect]
  637. [section Performance]
  638. [def _vs2015_x86_ Visual Studio 2015 x86]
  639. [def _vs2015_x64_ Visual Studio 2015 x64]
  640. [def _gcc63_x64_ GCC 6.3 x64]
  641. [def _clang40_x64_ Clang 4.0 x64]
  642. [template perf_fig[name environment file]
  643. [section [environment]]
  644. [role aligncenter
  645. '''<inlinemediaobject>
  646. <imageobject><imagedata fileref="'''poly_collection/img/[file].png'''"/></imageobject>
  647. </inlinemediaobject>'''
  648. ]
  649. [role aligncenter [*[name], [environment]]]
  650. [endsect]
  651. ]
  652. [import ../example/perf.cpp]
  653. (Testing program at [@boost:/libs/poly_collection/example/perf.cpp `perf.cpp`].)
  654. We ran tests to measure the performance of the containers of
  655. Boost.PolyCollection in two scenarios:
  656. * Insertion of elements.
  657. * Linear traversal and processing with `std::for_each` and `boost::poly_collection::for_each`
  658. (with and without
  659. [link poly_collection.tutorial.algorithms.type_restitution type restitution]).
  660. As a comparison baseline we used containers and facilities from the
  661. standard library and Boost (details below). Tests were run in the
  662. following environments:
  663. * *_vs2015_x86_*: Visual C++ 2015 in 32-bit (x86) release mode, Windows 7 64-bit, Intel Core i5-2520M @2.5GHz
  664. * *_vs2015_x64_*: Visual C++ 2015 in 64-bit (x64) release mode, same machine
  665. * *_gcc63_x64_*: GCC 6.3 release mode, Xubuntu 17.04 x64, Intel Core i7-5820 @3.3GHz
  666. * *_clang40_x64_*: Clang 4.0 release mode, same machine
  667. [section Container definitions]
  668. [section `boost::base_collection`]
  669. * Baseline container: `ptr_vector` = `boost::ptr_vector<base>`
  670. * Polymorphic collection: `base_collection` = `boost::base_collection<base>`
  671. * Element types: `T1` = `derived1`, `T2` = `derived2`, `T3` = `derived3`
  672. [perf_base_types]
  673. [endsect]
  674. [section `boost::function_collection`]
  675. * Baseline container: `func_vector` = `std::vector<std::function<int(int)>>`
  676. * Polymorphic collection: `function_collection` = `boost::function_collection<int(int)>`
  677. * Element types: `T1` = `concrete1`, `T2` = `concrete2`, `T3` = `concrete3`
  678. [perf_function_types]
  679. [endsect]
  680. [section `boost::any_collection`]
  681. * Baseline container: `any_vector` = `std::vector<boost::type_erasure::any<concept_>>`
  682. * Polymorphic collection: `any_collection` = `boost::any_collection<concept_>`
  683. * Element types: `T1` = `int`, `T2` = `double`, `T3` = `char`
  684. [perf_any_types]
  685. [endsect]
  686. [endsect]
  687. [section Insertion tests]
  688. Tests measure the time taken to insert /n/ elements (/n/ between
  689. 10'''<superscript>2</superscript>''' and 10'''<superscript>7</superscript>''')
  690. from a source of values with types following the cyclic sequence
  691. `T1` `T1` `T2` `T2` `T3` `T1` `T1` `T2` `T2` `T3` ···
  692. No `reserve` operation is done before insertion.
  693. The figures show resulting times in nanoseconds/element.
  694. The horizontal axis is logarithmic.
  695. [section Results for `boost::base_collection`]
  696. [perf_fig Insertion.._vs2015_x86_..insert_base_vs2015_x86]
  697. [perf_fig Insertion.._vs2015_x64_..insert_base_vs2015_x64]
  698. [perf_fig Insertion.._gcc63_x64_..insert_base_gcc63_x64]
  699. [perf_fig Insertion.._clang40_x64_..insert_base_clang40_x64]
  700. [endsect]
  701. [section Results for `boost::function_collection`]
  702. [perf_fig Insertion.._vs2015_x86_..insert_function_vs2015_x86]
  703. [perf_fig Insertion.._vs2015_x64_..insert_function_vs2015_x64]
  704. [perf_fig Insertion.._gcc63_x64_..insert_function_gcc63_x64]
  705. [perf_fig Insertion.._clang40_x64_..insert_function_clang40_x64]
  706. [endsect]
  707. [section Results for `boost::any_collection`]
  708. [perf_fig Insertion.._vs2015_x86_..insert_any_vs2015_x86]
  709. [perf_fig Insertion.._vs2015_x64_..insert_any_vs2015_x64]
  710. [perf_fig Insertion.._gcc63_x64_..insert_any_gcc63_x64]
  711. [perf_fig Insertion.._clang40_x64_..insert_any_clang40_x64]
  712. [endsect]
  713. [endsect]
  714. [section Processing tests]
  715. Tests measure the time taken to traverse a container of size
  716. /n/ (/n/ between
  717. 10'''<superscript>2</superscript>''' and 10'''<superscript>7</superscript>''')
  718. and execute an operation on each of its elements. The operation for
  719. `boost::base_collection` and `boost::function_collection` (and the associated
  720. baseline containers) is defined as
  721. [perf_for_each_callable]
  722. whereas for `boost::any_collection` we use
  723. [perf_for_each_incrementable]
  724. The baseline container is tested with three different setups:
  725. * Directly as initialized by the process described for the
  726. [link poly_collection.performance.insertion_tests insertion tests].
  727. The sequence of types is complex enough that CPU's branch prediction mechanisms
  728. are not able to fully anticipate it
  729. [footnote This has been verified empirically: simpler cycles did indeed
  730. yield better execution times.]. As elements are ordered according to their
  731. construction time, certain degree of memory contiguity is expected.
  732. * With an extra post-insertion stage by which elements are sorted
  733. according to their `typeid()`. This increases branch prediction
  734. efficiency at the expense of having worse cache friendliness.
  735. * With an extra post-insertion stage that randomly
  736. shuffles all the elements in the container. This is the worst possible
  737. scenario both in terms of caching and branch prediction.
  738. As for the polymorphic collection, three variations are measured:
  739. * With `std::for_each` (the same as the baseline container).
  740. * Using `boost::poly_collection::for_each`.
  741. * Using `boost::poly_collection::for_each` with
  742. [link poly_collection.tutorial.algorithms.type_restitution /type restitution/]
  743. of `T1`, `T2` and `T3`.
  744. The figures show resulting times in nanoseconds/element.
  745. The horizontal axis is logarithmic.
  746. [section Results for `boost::base_collection`]
  747. [perf_fig Processing.._vs2015_x86_..for_each_base_vs2015_x86]
  748. [perf_fig Processing.._vs2015_x64_..for_each_base_vs2015_x64]
  749. [perf_fig Processing.._gcc63_x64_..for_each_base_gcc63_x64]
  750. [perf_fig Processing.._clang40_x64_..for_each_base_clang40_x64]
  751. [endsect]
  752. [section Results for `boost::function_collection`]
  753. [perf_fig Processing.._vs2015_x86_..for_each_function_vs2015_x86]
  754. [perf_fig Processing.._vs2015_x64_..for_each_function_vs2015_x64]
  755. [perf_fig Processing.._gcc63_x64_..for_each_function_gcc63_x64]
  756. [perf_fig Processing.._clang40_x64_..for_each_function_clang40_x64]
  757. [endsect]
  758. [section Results for `boost::any_collection`]
  759. [perf_fig Processing.._vs2015_x86_..for_each_any_vs2015_x86]
  760. [perf_fig Processing.._vs2015_x64_..for_each_any_vs2015_x64]
  761. [perf_fig Processing.._gcc63_x64_..for_each_any_gcc63_x64]
  762. [perf_fig Processing.._clang40_x64_..for_each_any_clang40_x64]
  763. [endsect]
  764. [endsect]
  765. [endsect]
  766. [include reference.qbk]
  767. [section Future work]
  768. A number of features asked by reviewers and users of Boost.PolyCollection are
  769. considered for inclusion into future versions of the library.
  770. [section Alternative RTTI systems]
  771. Boost.PolyCollection can be extended to use _Boost.TypeIndex_ in RTTI-challenged
  772. scenarios. Taking this idea further, it is not unusual that some environments
  773. (game engines, for instance) provide their own RTTI framework: an even more
  774. ambitious extension to Boost.PolyCollection would then be to make it configurable
  775. for user-provided RTTI through some sort of traits class specifying replacements for
  776. [@http://en.cppreference.com/w/cpp/types/type_info `std::type_info`]
  777. and [@http://en.cppreference.com/w/cpp/language/typeid `typeid`].
  778. [endsect]
  779. [section Copy traits]
  780. `boost::base_collection` requires that stored objects be _MoveConstructible_ and
  781. _MoveAssignable_; unfortunately, it is customary to restrict copying in OOP
  782. hierarchies to avoid slicing, which would force users to revisit their class
  783. definitions in order to use Boost.PolyCollection. This can be alleviated by
  784. offering a configurable traits class where copy and assignment can be defined
  785. externally
  786. ``
  787. template<typename T>
  788. struct copy_traits
  789. {
  790. void construct(void*,T&&);
  791. void assign(T&,T&&);
  792. };
  793. ``
  794. with default implementations resorting to regular placement `new` and
  795. `T::operator=`.
  796. [endsect]
  797. [section Parallel algorithms]
  798. C++17 introduces
  799. [@http://en.cppreference.com/w/cpp/experimental/parallelism parallel algorithms],
  800. like for instance a parallel version of _std::for_each_; it is only
  801. natural then to provide the corresponding
  802. [link poly_collection.tutorial.algorithms Boost.PolyCollection-specific algorithms].
  803. The segmented nature of polymorphic collections makes them particularly amenable to
  804. parallel processing.
  805. [endsect]
  806. [section `variant_collection`]
  807. /Closed polymorphism/ is a kind of dynamic polymorphism where the set of implementation
  808. types is fixed at definition time: the prime example of this paradigm in C++ is
  809. [@http://en.cppreference.com/w/cpp/utility/variant `std::variant`]. Although
  810. `boost::any_collection<boost::mpl::vector<>>` can act as a sort of replacement for
  811. `std::vector<std::variant<T1,...,TN>>`, this is in fact more similar to a
  812. `std::vector<`[@http://en.cppreference.com/w/cpp/utility/any `std::any`]`>`,
  813. and a collection class `boost::variant_collection<T1,...,TN>` could be designed to
  814. better model closed polymorphism and take further advantage of the fact that
  815. implementation types are fixed (for instance, internal virtual calls can be
  816. completely eliminated). From a conceptual point of view, this would require introducing
  817. a new [* `ClosedPolymorphicCollection`] notion and renaming the current
  818. [link poly_collection.reference.polymorphic_containers.polymorphic_collections [* `PolymorphicCollection`]]
  819. model to [* `OpenPolymorphicCollection`].
  820. [endsect]
  821. [section Ordered polymorphic collections]
  822. Users have expressed interest in polymorphic collections where elements are kept
  823. ordered within their segment and optionally duplicates are excluded, much like
  824. [@http://boost.org/doc/html/container/non_standard_containers.html#container.non_standard_containers.flat_xxx `boost::flat_set`/`boost::flat_multiset`]
  825. do over their internal data vector. The open question remains of whether these
  826. collections should also guarantee some order between segments (current ones don't)
  827. to allow for the definition of container-level `operator<` and related operators.
  828. [endsect]
  829. [endsect]
  830. [section Release notes]
  831. [section Boost 1.72]
  832. * Maintenance work.
  833. [endsect]
  834. [section Boost 1.71]
  835. * Maintenance work.
  836. [endsect]
  837. [section Boost 1.70]
  838. * Improved handling of stateful allocators and allocator propagation traits,
  839. after an error reported by Billy O'Neal ([github_pr poly_collection 9]).
  840. * Fixed a potentially serious bug with an internal cache structure.
  841. [endsect]
  842. [section Boost 1.69]
  843. * Added Boost.PolyCollection-specific versions of algorithms
  844. `std::for_each_n` and `std::sample`.
  845. [endsect]
  846. [section Boost 1.67]
  847. * Maintenance fixes.
  848. [endsect]
  849. [section Boost 1.66]
  850. * Boost.PolyCollection has been backported to GCC 4.8 to 4.9 and Clang 3.3 to 3.6.
  851. The version of libstdc++-v3 shipped with GCC 4.8 (which can also be used by Clang)
  852. has deficiencies that result in the following limitations when using
  853. Boost.PolyCollection:
  854. * Stateful allocators are not properly supported.
  855. * Allocator-extended move construction decays to allocator-extended copy construction.
  856. * Copy construction crashes if an exception is thrown during element copying.
  857. * Maintenance fixes.
  858. [endsect]
  859. [section Boost 1.65]
  860. * Initial release.
  861. [endsect]
  862. [endsect]
  863. [section Acknowledgments]
  864. The library uses [@http://www.pdimov.com/ Peter Dimov]'s
  865. [@http://www.pdimov.com/cpp2/simple_cxx11_metaprogramming.html implementation
  866. of `std::make_index_sequence`].
  867. [@http://manu343726.github.io/ Manu Sánchez] aided immensely with CI setup and
  868. performance testing. [@http://stackoverflow.com/users/85371/sehe Sehe]
  869. contributed performance results for GCC 5.2, and
  870. [@https://www.linkedin.com/in/francisco-jose-tapia-iba%C3%B1ez-4239a07a Francisco
  871. José Tapia] for Clang 3.9.
  872. The Boost acceptance review took place between the 3rd and 17th of May, 2017. Thank you
  873. to Ion Gaztañaga for smoothly managing the process. The following people participated
  874. with full reviews or valuable comments:
  875. Pete Bartlett, Hans Dembinski, Dominique Devienne, Edward Diener, Vinnie Falco,
  876. Ion Gaztañaga, Andrzej Krzemienski, Brook Milligan, Thorsten Ottosen, Steven Watanabe,
  877. Adam Wulkiewicz. Many thanks to all of them. Steven Watanabe gave crucial help in
  878. solving some hard problems related to the usage of _Boost.TypeErasure_.
  879. Boost.PolyCollection was designed and written between rainy
  880. [@https://es.wikipedia.org/wiki/Viav%C3%A9lez Viavélez], noisy Madrid and beautiful
  881. [@https://en.wikipedia.org/wiki/C%C3%A1ceres,_Spain Cáceres], August-November, 2016.
  882. Most of the after-review work in preparation for the official Boost release was done
  883. in the quiet town of [@https://es.wikipedia.org/wiki/Oropesa_(Toledo) Oropesa] during
  884. the spring of 2017.
  885. In memory of Joaquín López Borrella (1939-2015), in memory of Héctor (2004-2017):
  886. may your ghosts keep us company.
  887. [endsect]