From b35bef74e475a3735d105d440bd0f5f6f2382c42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Joaqu=C3=ADn=20M=2E=20L=C3=B3pez=20Mu=C3=B1oz?=
+Fernando Cacciola, Darren Cook, Beman Dawes, Jeremy Maitin-Shepard and Daryle
+Walker from the Boost mailing list provided useful suggestions for improvement
+on the first alpha releases of the library. Gang Wang discovered several
+bugs in the code. Thomas Wenisch brought out the idea of "sequence sets"
+from which sequenced indices were designed. Giovanni Bajo, Chris Little and
+Maxim Yegorushkin tested the library on several platforms. Rosa
+Bernárdez proofread the last version of the tutorial.
+
+Pavel Voženílek has been immensely helpful in thoroughly reviewing
+every single bit of the library, and he also suggested several extra
+functionalities, most notably range querying, safe mode, polymorphic key
+extractors and MPL support. Thank you!
+
+The Boost acceptance review took place between March 20th and 30th 2004.
+Pavel Voženílek was the review manager. Thanks to all the people
+who participated and specially to those who submitted reviews:
+Fredrik Blomqvist, Tom Brinkman, Paul A Bristow, Darren Cook, Jeff Garland,
+David B. Held, Brian McNamara, Gary Powell, Rob Stewart, Arkadiy Vertleyb,
+Jörg Walter. Other Boost members also contributed ideas, particularly
+in connection with the library's naming scheme: Pavol Droba,
+Dave Gomboc, Jeremy Maitin-Shepard, Thorsten Ottosen, Matthew Vogt,
+Daryle Walker. My apologies if I inadvertently left somebody out of this
+list.
+
+Boost.MultiIndex could not have been written without Aleksey Gurtovoy
+et al. superb Boost MPL
+Library. Also, Aleksey's techniques for dealing with ETI-related
+problems in MSVC++ 6.0 helped solve some internal issues of the library.
+
+The internal implementation of red-black trees is based on that of SGI STL
+stl_tree.h file:
+
+ Revised May 7th 2004 Copyright © 2003-2004 Joaquín M López Muñoz.
+Use, modification, and distribution are subject to the Boost Software
+License, Version 1.0. (See accompanying file
+LICENSE_1_0.txt or copy at
+www.boost.org/LICENSE_1_0.txt)
+
+In relational databases, composite keys depend on two or more fields of a given table.
+The analogous concept in Boost.MultiIndex is modeled by means of
+
+
+
+Composite keys are sorted by lexicographical order, i.e. sorting is performed
+by the first key, then the second key if the first one is equal, etc. This
+order allows for partial searches where only the first keys are specified:
+
+On the other hand, partial searches without specifying the first keys are not
+allowed.
+
+By default, the corresponding
+See Example 7 in the examples section
+for an application of
+The
+This possibility is fully exploited by predefined key extractors provided
+by Boost.MultiIndex, making it simpler to define
+Note that this is specified in exactly the same manner as a
+In fact, support for pointers is further extended to accept what we call
+chained pointers. Such a chained pointer is defined by induction as a raw or
+smart pointer or iterator to the actual element, to a reference wrapper of the
+element or to another chained pointer; that is, chained pointers are arbitrary
+compositions of pointer-like types ultimately dereferencing
+to the element from where the key is to be extracted. Examples of chained
+pointers to
+
+
+
+Boost.MultiIndex Acknowledgements
+
+
+
+
+Copyright (c) 1996,1997
+Silicon Graphics Computer Systems, Inc.
+
+
+
+Permission to use, copy, modify, distribute and sell this software
+and its documentation for any purpose is hereby granted without fee,
+provided that the above copyright notice appear in all copies and
+that both that copyright notice and this permission notice appear
+in supporting documentation. Silicon Graphics makes no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+
+
+Copyright (c) 1994
+Hewlett-Packard Company
+
+Permission to use, copy, modify, distribute and sell this software
+and its documentation for any purpose is hereby granted without fee,
+provided that the above copyright notice appear in all copies and
+that both that copyright notice and this permission notice appear
+in supporting documentation. Hewlett-Packard Company makes no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+
+I would like to dedicate this piece of work to Rosa Bernárdez, my very first
+C++ teacher, for her unconditional support in many endeavors of which programming is
+by no means the most important. In memory of my cat López (2001-2003): he
+lived too fast, died too young.
+
+
+
+
+
+
+
+
+
+
+
+
+
+Boost.MultiIndex Advanced topics
+
+
+
+Contents
+
+
+
+
+ctor_args_list
multi_index_container
+
+ multi_index_container
+
+
+ Composite keys
+
+composite_key
, as shown in the example:
+
+
+
+struct phonebook_entry
+{
+ std::string family_name;
+ std::string given_name;
+ std::string phone_number;
+
+ phonebook_entry(
+ std::string family_name,
+ std::string given_name,
+ std::string phone_number):
+ family_name(family_name),given_name(given_name),phone_number(phone_number)
+ {}
+};
+
+// define a multi_index_container with a composite key on
+// (family_name,given_name)
+typedef multi_index_container<
+ phonebook_entry,
+ indexed_by<
+ //non-unique as some subscribers might have more than one number
+ ordered_non_unique<
+ composite_key<
+ phonebook_entry,
+ member<phonebook_entry,std::string,&phonebook_entry::family_name>,
+ member<phonebook_entry,std::string,&phonebook_entry::given_name>
+ >
+ >,
+ ordered_unique< // unique as numbers belong to only one subscriber
+ member<phonebook_entry,std::string,&phonebook_entry::phone_number>
+ >
+ >
+> phonebook;
+
composite_key
accepts two or more key extractors on the same
+value (here, phonebook_entry
). Lookup operations on a composite
+key are accomplished by passing tuples with the values searched:
+
+
+
+phonebook pb;
+...
+// search for Dorothea White's number
+phonebook::iterator it=pb.find(
+ boost::make_tuple(std::string("White"),std::string("Dorothea")));
+std::string number=it->phone_number;
+
+
+
+phonebook pb;
+...
+// look for all Whites
+std::pair<phonebook::iterator,phonebook::iterator> p=
+ pb.equal_range(boost::make_tuple(std::string("White")));
+
std::less
predicate is used
+for each subkey of a composite key. Alternate comparison predicates can
+be specified with
+composite_key_compare
:
+
+
+
+// phonebook with given names in reverse order
+
+typedef multi_index_container<
+ phonebook_entry,
+ indexed_by<
+ ordered_non_unique<
+ composite_key<
+ phonebook_entry,
+ member<phonebook_entry,std::string,&phonebook_entry::family_name>,
+ member<phonebook_entry,std::string,&phonebook_entry::given_name>
+ >,
+ composite_key_compare<
+ std::less<std::string>, // family names sorted as by default
+ std::greater<std::string> // given names reversed
+ >
+ >,
+ ordered_unique<
+ member<phonebook_entry,std::string,&phonebook_entry::phone_number>
+ >
+ >
+> phonebook;
+
composite_key
.
+Advanced features of Boost.MultiIndex key
+extractors
+
+Key Extractor
+concept allows the same object to extract keys from several different types,
+possibly through suitably defined overloads of operator()
:
+
+
+
+// example of a name extractor from employee and employee *
+struct name_extractor
+{
+ const std::string& operator()(const employee& e)const{return e.name;}
+ std::string& operator()(employee& e)const{return e.name;}
+ std::string& operator()(employee* e)const{return e->name;}
+};
+
multi_index_container
s
+where elements are pointers or references to the actual objects. The following
+specifies a multi_index_container
of pointers to employees sorted by their
+names.
+
+
+
+typedef multi_index_container<
+ employee *,
+ indexed_by<
+ ordered_non_unique<member<employee,std::string,&employee::name> > >
+> employee_set;
+
multi_index_container
+of actual employee
objects: member
takes care of the
+extra dereferencing needed to gain access to employee::name
. A similar
+functionality is provided for interoperability with reference wrappers from
+Boost.Ref:
+
+
+
+typedef multi_index_container<
+ boost::reference_wrapper<const employee>,
+ indexed_by<
+ ordered_non_unique<member<employee,std::string,&employee::name> > >
+> employee_set;
+
employee
are:
+
+
+In general, chained pointers with dereferencing distance greater than 1 are not
+likely to be used in a normal program, but they can arise in frameworks
+which construct "views" as employee *
,const employee *
,std::auto_ptr<employee>
,std::list<boost::reference_wrapper<employee> >::iterator
,employee **
,boost::shared_ptr<const employee *>
.multi_index_container
s from preexisting
+multi_index_container
s.
+
+In order to present a short summary of the different usages of Boost.MultiIndex +key extractors in the presence of reference wrappers and pointers, consider the +following final type: +
+ ++ ++struct T +{ + int i; + const int j; + int f()const; + int g(); +}; +
+The table below lists the appropriate key extractors to be used for
+different pointer and reference wrapper types based on T
, for
+each of its members.
+
+
element type | +sorted by | +key extractor | +applicable toconst elements? |
+ read/write? | +
---|---|---|---|---|
T |
+ i |
+ member<T,int,&T::i> |
+ yes | +yes | +
j |
+ member<T,const int,&T::j> |
+ yes | +no | +|
f() |
+ const_mem_fun<T,int,&T::f> |
+ yes | +no | +|
g() |
+ mem_fun<T,int,&T::g> |
+ no | +no | +|
reference_wrapper<T> |
+ i |
+ member<T,int,&T::i> |
+ yes | +yes | +
j |
+ member<T,const int,&T::j> |
+ yes | +no | +|
f() |
+ const_mem_fun<T,int,&T::f> |
+ yes | +no | +|
g() |
+ mem_fun<T,int,&T::g> |
+ yes | +no | +|
reference_wrapper<const T> |
+ i |
+ member<T,const int,&T::i> |
+ yes | +no | +
j |
+ member<T,const int,&T::j> |
+ yes | +no | +|
f() |
+ const_mem_fun<T,int,&T::f> |
+ yes | +no | +|
g() |
+ + | |||
chained pointer to T + or to reference_wrapper<T> |
+ i |
+ member<T,int,&T::i> |
+ yes | +yes | +
j |
+ member<T,const int,&T::j> |
+ yes | +no | +|
f() |
+ const_mem_fun<T,int,&T::f> |
+ yes | +no | +|
g() |
+ mem_fun<T,int,&T::g> |
+ yes | +no | +|
chained pointer to const T + or to reference_wrapper<const T> |
+ i |
+ member<T,const int,&T::i> |
+ yes | +no | +
j |
+ member<T,const int,&T::j> |
+ yes | +no | +|
f() |
+ const_mem_fun<T,int,&T::f> |
+ yes | +no | +|
g() |
+ + |
+The column "applicable to const
elements?" states whether the
+corresponding key extractor can be used when passed constant elements (this
+relates to the elements specified in the first column, not the referenced
+T
objects). The only negative case is for T::g
when
+the elements are raw T
objects, which make sense as we are dealing
+with a non-constant member function: this also implies that multi_index_container
s
+of elements of T
cannot be sorted by T::g
, because
+elements contained within a multi_index_container
are treated as constant.
+
+A key extractor is called read/write if it returns a non-constant reference
+to the key when passed a non-constant element, and it is called read-only
+otherwise. In order to use multi_index_container::modify_key
, the associated
+key extractor must be read/write. The column "read/write?" shows that most
+combinations yield read-only extractors.
+
+Some care has to be taken to preserve const
-correctness in the
+specification of the key extractors: in some sense, the const
+qualifier is carried along to the member part, even if that particular
+member is not defined as const
. For instance, if the elements
+are of type const T *
, sorting by T::i
is not
+specified as member<const T,int,&T::i>
, but rather as
+member<T,const int,&T::i>
.
+
+For practical demonstrations of use of these key extractors, refer to +example 2 and +example 6 in the examples section. +
+ +member_offset
+The member
key extractor poses some problems in compilers
+that do not properly support pointers to members as non-type
+template arguments. The following compilers have been confirmed not
+to work correctly with member
:
+
member_offset
+has been provided that does the work of member
at the
+expense of less convenient notation and the possibility of
+non-conformance with the standard. Please consult
+the reference for further information on member_offset
.
++ +
+The following test program can help determine if your compiler +properly supports pointers to members as non-type template parameters: +
+ ++ ++#include <boost/multi_index_container/member.hpp> +#include <utility> +#include <iostream> + +using namespace std; +using boost::multi_index::member; + +typedef std::pair<int,int> pair_of_ints; + +int main() +{ + pair_of_ints p(0,1); + int i=member<pair_of_ints,int,&pair_of_ints::first>()(p); + int j=member<pair_of_ints,int,&pair_of_ints::second>()(p); + if(i!=0||j!=1){ + cout<<"WARNING: compiler does not properly support\n" + "pointers as non-type template parameters"<<endl; + return 1; + } + + cout<<"test succesful"<<endl; + return 0; +} + +
+If you find a compiler not listed here that does not pass the test, +please report to the maintainer of the library. As an example of use, +given the class
+ ++ ++class A +{ + int x; +} +
+the instantiation member<A,int,&A::x>
can be simulated then
+as member_offset<A,int,offsetof(A,x)>
.
+
+For those writing portable code, Boost.MultiIndex provides the ternary macro
+BOOST_MULTI_INDEX_MEMBER
. Continuing with the example above, the
+expression
+
+ ++BOOST_MULTI_INDEX_MEMBER(A,int,x) +
+expands to either +
+ ++ ++member<A,int,&A::x> +
+or alternatively to +
+ ++ ++member_offset<A,int,offsetof(A,x)> +
+depending on whether the current compiler supports pointer to members as
+non-type template arguments or not. The alternative expansion is driven by
+the defect macro BOOST_NO_POINTER_TO_MEMBER_TEMPLATE_PARAMETERS
,
+which has been proposed for inclusion in the
+Boost Configuration Library.
+Until the defect macro is accepted, Boost.MultiIndex treats it as if defined for
+
BOOST_MULTI_INDEX_MEMBER
by manually defining
+BOOST_NO_POINTER_TO_MEMBER_TEMPLATE_PARAMETERS
prior to the
+inclusion of Boost.MultiIndex headers.
+
+
+const_mem_fun_explicit
and
+mem_fun_explicit
+MSVC++ 6.0 has problems with const
member functions as non-type
+template parameters, and thus does not accept the const_mem_fun
+key extractor. A simple workaround, fortunately, has been found, consisting
+in specifying the type of these pointers as an additional template
+parameter. The alternative const_mem_fun_explicit
extractor
+adopts this solution; for instance, given the type
+
+ ++struct A +{ + int f()const; +}; +
+the extractor const_mem_fun<A,int,&A::f>
can be replaced by
+const_mem_fun_explicit<A,int,int (A::*)()const,&A::f>
. A similar
+mem_fun_explicit
class template is provided for non-constant
+member functions.
+
+If you are writing cross-platform code, the selection of either key extractor
+is transparently handled by the macro BOOST_MULTI_INDEX_CONST_MEM_FUN
,
+so that
+
+ ++BOOST_MULTI_INDEX_CONST_MEM_FUN(A,int,f) +
+expands by default to +
+ ++ ++const_mem_fun<A,int,&A::f> +
+but resolves to +
+ ++ ++const_mem_fun_explicit<A,int,int (A::*)()const,&A::f> +
+in MSVC++ 6.0. An analogous macro BOOST_MULTI_INDEX_MEM_FUN
is
+provided as well.
+
composite_key
in compilers
+without partial template specialization
+Much of the power of composite_key
derives from the ability
+to perform searches when only the first elements of the compound key are
+given. In order to enable this functionality, std::less
and
+std::greater
are specialized for
+
+composite_key_result
instantiations to provide
+overloads accepting tuples of values.
+
+In those compilers that do not support partial template specialization,
+tuple-based comparisons are not available by default. In this case,
+multi_index_container
instantiations using composite keys
+will work as expected (elements are sorted lexicographically on the
+results of the combined keys), except that lookup operations will not
+accept tuples as an argument. The most obvious workaround
+to this deficiency involves explicitly specifying the comparison
+predicate with composite_key_compare
: this is tedious as
+the comparison predicates for all the element key extractors must be
+explicitly typed. For this reason, Boost.MultiIndex provides the replacement
+class template
+
+composite_key_result_less
, that
+acts as the missing specialization of std::less
for
+composite_key_result
s:
+
+ ++typedef composite_key< + phonebook_entry, + member<phonebook_entry,std::string,&phonebook_entry::family_name>, + member<phonebook_entry,std::string,&phonebook_entry::given_name> +> ckey_t; + +typedef multi_index_container< + phonebook_entry, + indexed_by< + ordered_non_unique< + ckey_t, + // composite_key_result_less plays the role of + // std::less<ckey_t::result_type> + composite_key_result_less<ckey_t::result_type> + >, + ordered_unique< + member<phonebook_entry,std::string,&phonebook_entry::phone_number> + > + > +> phonebook; +
+There is also an analogous
+
+composite_key_result_greater
class to substitute for
+specializations of std::greater
.
+
ctor_args_list
+Although in most cases multi_index_container
s will be default constructed
+(or copied from a preexisting multi_index_container
), sometimes it is
+necessary to specify particular values for the internal objects used (key extractors,
+comparison predicates, allocator), for instance if some of these objects do not have
+a default constructor. The same situation can arise with standard STL containers,
+which allow for the optional specification of such objects:
+
+ ++// example of non-default constructed std::set +template<typename IntegralType> +struct modulo_less +{ + modulo_less(IntegralType m):modulo(m){} + + bool operator()(IntegralType x,IntegralType y)const + { + return (x%modulo)<(y%modulo); + } + +private: + IntegralType modulo; +}; + +typedef std::set<unsigned int,modulo_less<unsigned int> > modulo_set; + +modulo_set m(modulo_less<unsigned int>(10)); +
+multi_index_container
does also provide this functionality, though in a
+considerably more complex fashion, due to the fact that the constructor
+of a multi_index_container
has to accept values for all the internal
+objects of its indices. The full form of multi_index_container
constructor
+is
+
+ ++explicit multi_index_container( + const ctor_args_list& args_list=ctor_args_list(), + const allocator_type& al=allocator_type()); +
+The specification of the allocator object poses no particular problems;
+as for the ctor_args_list
, this object is designed so as to hold
+the necessary construction values for every index in the multi_index_container
.
+From the point of view of the user, ctor_args_list
is equivalent
+to the type
+
+ ++boost::tuple<C0,...,CI-1> +
+where I
is the number of indices, and Ci
is
+
+ ++nth_index<i>::type::ctor_args +
+that is, the nested type ctor_args
of the i
-th index. Each
+ctor_args
type is in turn a tuple holding values for constructor
+arguments of the associated index: so, ordered indices demand a key extractor object
+and a comparison predicate, while sequenced indices do not need any construction
+argument. For instance, given the definition
+
+ ++typedef multi_index_container< + unsigned int, + indexed_by< + ordered_unique<identity<unsigned int> >, + ordered_non_unique<identity<unsigned int>, modulo_less<unsigned int> >, + sequenced<> + > +> modulo_indexed_set; +
+the corresponding ctor_args_list
type is equivalent to
+
+ ++boost::tuple< + // ctr_args of index #0 + boost::tuple<identity<unsigned int>,std::less<unsigned int> >, + + // ctr_args of index #1 + boost::tuple<identity<unsigned int>,modulo_less<unsigned int> >, + + // sequenced indices do not have any construction argument + boost::tuple<> +> +
+Such a modulo_indexed_set
cannot be default constructed, because
+modulo_less
does not provide a default constructor. The following shows
+how the construction can be done:
+
+ ++modulo_indexed_set::ctor_args_list args_list= + boost::make_tuple( + // ctor_args for index #0 is default constructible + modulo_indexed_set::nth_index<0>::type::ctor_args(), + + boost::make_tuple(identity<unsigned int>(),modulo_less<unsigned int>(10)), + + // this is also default constructible (actually, an empty tuple) + modulo_indexed_set::nth_index<2>::type::ctor_args(), + ); + +modulo_indexed_set m(args_list); +
+A program is provided in the examples section that +puts in practise these concepts. +
+ +
+The concept of Design by Contract, originally developed as part
+of Bertrand Meyer's Eiffel language,
+revolves around the formulation of a contract between the user
+of a library and the implementor, by which the first is required to
+respect some preconditions on the values passed when invoking
+methods of the library, and the implementor guarantees in return
+that certain constraints on the results are met (postconditions),
+as well as the honoring of specified internal consistency rules, called
+invariants. Eiffel natively supports the three parts of the
+contract just described by means of constructs require
,
+ensure
and invariant
, respectively.
+
+C++ does not enjoy direct support for Design by Contract techniques: these +are customarily implemented as assertion code, often turned off in +release mode for performance reasons. Following this approach, +Boost.MultiIndex provides two distinct debugging modes: +
+The idea of adding precondition checking facilities to STL as a debugging aid +was first introduced by Cay S. Horstmann in his +Safe STL library and later +adopted by STLport Debug +Mode. Similarly, Boost.MultiIndex features the so-called safe mode +in which all sorts of preconditions are checked when dealing with iterators +and functions of the library. +
+ +
+Boost.MultiIndex safe mode is set by globally defining the macro
+BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
. Error conditions
+are checked via the macro BOOST_MULTI_INDEX_SAFE_MODE_ASSERT
, which
+by default resolves to a call to
+BOOST_ASSERT
.
+
+If the user decides to define her own version of
+BOOST_MULTI_INDEX_SAFE_MODE_ASSERT
, it has to take the form
+
+ ++BOOST_MULTI_INDEX_SAFE_MODE_ASSERT(expr,error_code) +
+where expr
is the condition checked and error_code
+is one value of the safe_mode::error_code
enumeration:
+
+ ++namespace boost{ + +namespace multi_index{ + +namespace safe_mode{ + +enum error_code +{ + invalid_iterator, // default initialized iterator + not_dereferenceable_iterator, // iterator is not dereferenceable + not_incrementable_iterator, // iterator points to end of sequence + not_decrementable_iterator, // iterator points to beginning of sequence + not_owner, // iterator does not belong to the container + not_same_owner, // iterators belong to different containers + invalid_range, // last not reachable from first + inside_range, // iterator lies within a range (and it mustn't) + same_container // containers ought to be different +}; + +} // namespace multi_index::safe_mode + +} // namespace multi_index + +} // namespace boost +
+For instance, the following replacement of
+BOOST_MULTI_INDEX_SAFE_MODE_ASSERT
throws an exception instead of
+asserting:
+
+ ++#include <boost/multi_index_container/safe_mode_errors.hpp> + +struct safe_mode_exception +{ + safe_mode_exception(boost::multi_index::safe_mode::error_code error_code): + error_code(error_code) + {} + + boost::multi_index::safe_mode::error_code error_code; +}; + +#define BOOST_MULTI_INDEX_SAFE_MODE_ASSERT(expr,error_code) \ +if(!(expr)){throw safe_mode_exception(error_code);} + +// This has to go before the inclusion of any header from Boost.MultiIndex, +// except possibly safe_error_codes.hpp. +
+Other possibilites, like outputting to a log or firing some kind of alert, are +also implementable. +
+ +
+Warning: Safe mode adds a very important overhead to the program
+both in terms of space and time used, so in general it should not be set for
+NDEBUG
builds. Also, this mode is intended solely as a debugging aid,
+and programs must not rely on it as part of their normal execution flow: in
+particular, no guarantee is made that all possible precondition errors are diagnosed,
+or that the checks remain stable across different versions of the library.
+
+The so called invariant-checking mode of Boost.MultiIndex can be
+set by globally defining the macro
+BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
.
+When this mode is in effect, all public functions of Boost.MultiIndex
+will perform post-execution tests aimed at ensuring that the basic
+internal invariants of the data structures managed are preserved.
+
+If an invariant test fails, Boost.MultiIndex will indicate the failure
+by means of the unary macro BOOST_MULTI_INDEX_INVARIANT_ASSERT
.
+Unless the user provides a definition for this macro, it defaults to
+
+BOOST_ASSERT
. Any assertion of this kind should
+be regarded in principle as a bug in the library. Please report such
+problems, along with as much contextual information as possible, to the
+maintainer of the library.
+
+It is recommended that users of Boost.MultiIndex always set the +invariant-checking mode in debug builds. +
+ +multi_index_container
+Academic movitations aside, there is a practical interest in simulating standard
+associative containers by means of multi_index_container
, namely to take
+advantage of extended functionalities provided by multi_index_container
for
+lookup, range querying and updating.
+
+In order to simulate a std::set
one can follow the substitution
+rule:
+
+ ++std::set<Key,Compare,Allocator> -> + multi_index_container< + Key, + indexed_by<ordered_unique<identity<Key>,Compare> >, + Allocator + > +
+In the default case where Compare=std::less<Key>
and
+Allocator=std::allocator<Key>
, the substitution rule is
+simplified as
+
+ ++std::set<Key> -> multi_index_container<Key> +
+The substitution of multi_index_container
for std::set
keeps
+the whole set of functionality provided by std::set
, so in
+principle it is a drop-in replacement needing no further adjustments.
+
+std::multiset
can be simulated in a similar manner, according to the
+following rule:
+
+ ++std::multiset<Key,Compare,Allocator> -> + multi_index_container< + Key, + indexed_by<ordered_non_unique<identity<Key>,Compare> >, + Allocator + > +
+When default values are taken into consideration, the rule takes the form +
+ ++ ++std::multiset<Key> -> + multi_index_container< + Key, + indexed_by<ordered_non_unique<identity<Key> > > + > +
+The simulation of std::multiset
s with multi_index_container
+results in a slight difference with respect to the interface offered: the member
+function insert(const value_type&)
does not return an
+iterator
as in std::multiset
s, but rather a
+std::pair<iterator,bool>
in the spirit of std::set
s.
+In this particular case, however, the bool
member of the returned
+pair is always true
.
+
+The case of std::map
s and std::multimap
s does not lend
+itself to such a direct simulation by means of multi_index_container
. The main
+problem lies in the fact that elements of a multi_index_container
are treated
+as constant, while the std::map
and std::multimap
handle
+objects of type std::pair<const Key,T>
, thus allowing for free
+modification of the value part. To overcome this difficulty we need to create an ad
+hoc pair class:
+
+ ++template <typename T1,typename T2> +struct mutable_pair +{ + typedef T1 first_type; + typedef T2 second_type; + + mutable_pair():first(T1()),second(T2()){} + mutable_pair(const T1& f,const T2& s):first(f),second(s){} + mutable_pair(const std::pair<T1,T2>& p):first(p.first),second(p.second){} + + T1 first; + mutable T2 second; +}; +
+and so the substitution rules are: +
+ ++ ++std::map<Key,T,Compare,Allocator> -> + multi_index_container< + Element, + indexed_by< + ordered_unique<member<Element,Key,&Element::first>,Compare> + >, + typename Allocator::template rebind<Element>::other + > + +std::multimap<Key,T,Compare,Allocator> -> + multi_index_container< + Element, + indexed_by< + ordered_non_unique<member<Element,Key,&Element::first>,Compare> + >, + typename Allocator::template rebind<Element>::other + > + +(with Element=mutable_pair<Key,T>) +
+If default values are considered, the rules take the form: +
+ ++ ++std::map<Key,T> -> + multi_index_container< + Element, + indexed_by<ordered_unique<member<Element,Key,&Element::first> > > + > + +std::multimap<Key,T> -> + multi_index_container< + Element, + indexed_by<ordered_non_unique<member<Element,Key,&Element::first> > > + > + +(with Element=mutable_pair<Key,T>) +
+Unlike as with standard sets, the interface of these multi_index_container
-simulated
+maps does not exactly conform to that of std::map
s and
+std::multimap
s. The most obvious difference is the lack of
+operator []
, either in read or write mode; this, however, can be
+simulated with appropriate use of find
and insert
.
+
+These simulations of standard associative containers with multi_index_container
+are comparable to the original constructs in terms of space and time efficiency.
+See the performance section for further details.
+
std::list
+Unlike the case of associative containers, simulating std::list
+in Boost.MultiIndex does not add any significant functionality, so the following
+is presented merely for completeness sake.
+
+Much as with standard maps, the main difficulty to overcome when simulating
+std::list
derives from the constant nature of elements of an
+multi_index_container
. Again, some sort of adaption class is needed, like
+for instance the following:
+
+ ++template <typename T> +struct mutable_value +{ + mutable_value(const T& t):t(t){} + operator T&()const{return t;} + +private: + mutable T t; +}; +
+which allows us to use the substitution rule: +
+ ++ ++std::list<T,Allocator> -> + multi_index_container< + Element, + indexed_by<sequenced<> >, + typename Allocator::template rebind<Element>::other + > + +(with Element=mutable_value<T>) +
+or, if the default value Allocator=std::allocator<T>
is used:
+
+ ++std::list<T> -> + multi_index_container<mutable_value<T>,indexed_by<sequenced<> > > +
multi_index_container
+Boost.MultiIndex provides a number of facilities intended to allow the analysis and
+synthesis of multi_index_container
instantiations by
+MPL metaprograms.
+
+Given a multi_index_container
instantiation, the following nested types are
+provided for compile-time inspection of the various types occurring in the
+definition of the multi_index_container
:
+
index_specifier_type_list
,index_type_list
,iterator_type_list
,const_iterator_type_list
.MPL Forward Sequence
with as many elements as indices
+comprise the multi_index_container
: for instance, the n
-nth
+element of iterator_type_list
is the same as
+nth_index_iterator<n>::type
.
+
+
+
+A subtle but important distinction exists between
+index_specifier_type_list
and index_type_list
:
+the former typelist holds the index specifiers
+with which the multi_index_container
instantiation was defined,
+while the latter gives access to the actual implementation classes
+corresponding to each specifier. An example will help to clarify
+this distinction. Given the instantiation:
+
+ ++typedef multi_index_container< + int, + indexed_by< + ordered_unique<identity<int> >, + sequenced<> + > +> indexed_t; +
+indexed_t::index_specifier_type_list
is a type list with
+elements
+
+ ++ordered_unique<identity<int> > +sequenced<> +
+while indexed_t::index_type_list
holds the types
+
+ ++multi_index_container::nth_type<0>::type +multi_index_container::nth_type<1>::type +
+so the typelists are radically different. +
+ +
+Although typically indices are specified by means of the
+indexed_by
construct, actually any MPL sequence of
+index specifiers can be provided instead:
+
+ ++typedef mpl::vector<ordered_unique<identity<int> >,sequenced<> > index_list_t; + +typedef multi_index_container< + int, + index_list_t +> indexed_t; +
+This possibility enables the synthesis of instantiations of
+multi_index_container
through MPL metaprograms, as the following
+example shows:
+
+ ++// original multi_index_container instantiation +typedef multi_index_container< + int, + indexed_by< + ordered_unique<identity<int> > + > +> indexed_t1; + +// we take its index list and add an index +typedef boost::mpl::push_front< + indexed_t1::index_specifier_type_list, + sequenced<> +>::type index_list_t; + +// augmented multi_index_container +typedef multi_index_container< + int, + index_list_t +> indexed_t2; +
Revised May 7th 2004
+ +Copyright © 2003-2004 Joaquín M López Muñoz. +Use, modification, and distribution are subject to the Boost Software +License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt) +
+ + + diff --git a/doc/compiler_specifics.html b/doc/compiler_specifics.html new file mode 100644 index 0000000..bbfc475 --- /dev/null +++ b/doc/compiler_specifics.html @@ -0,0 +1,314 @@ + + + + + ++Boost.MultiIndex has been tried in different compilers, with +various degrees of success. We list the limitations encountered, +along with suitable workarounds when available. Up to date information +on compatibility of Boost.MultiIndex with several compilers can +be found at the +Boost Compiler Status Summary. +
+ ++Currently, Boost.MultiIndex cannot be used with BCB 6.4. The +number of problems encountered during the tests makes it unlikely that +future versions of the library can be made to work under +this compiler. +
+ ++No problems have been detected with this compiler. The tests were +performed under Cygwin 1.5.7. Most likely Boost.MultiIndex will work seamlessly +for GNU GCC 3.3 or later under any platform. +
+ +
+member
not supported,
+replace with
+member_offset
or
+use the cross-platform macro
+
+BOOST_MULTI_INDEX_MEMBER
.
+
+Altough Koenig lookup seems to be officially supported by this compiler,
+some issues have arisen seemingly due to bugs related to this facility.
+In these cases you might need to explicitly qualify global names with
+::boost::multi_index
.
+
+No problems have been detected with this compiler. +
+ ++No problems have been detected with this compiler. +
+ + +
+member
not supported,
+replace with
+member_offset
or
+use the cross-platform macro
+
+BOOST_MULTI_INDEX_MEMBER
.
+
+const_mem_fun
not
+supported, replace with
+
+const_mem_fun_explicit
+or use the cross-platform macro
+
+BOOST_MULTI_INDEX_CONST_MEM_FUN
.
+
+mem_fun
is not
+supported, replace with
+
+mem_fun_explicit
or
+use the cross-platform macro
+
+BOOST_MULTI_INDEX_MEM_FUN
.
+
+No support for index retrieval +and projection +nested types and member functions: +
nth_index
,index
,nth_index_iterator
,nth_index_const_iterator
,index_iterator
,index_const_iterator
,get
,project
.::boost::multi_index
.
+
+
+
+The lack of partial template specialization support in MSVC++ 6.0
+results in some inconveniences when using composite_key
that
+can be remedied as explained in
+"composite_key
+in compilers without partial template specialization" on the advanced
+topics section.
+
+MSVC++ 6.0 presents serious limitations for the maximum length of
+symbol names generated by the compiler, which might result in the
+linker error
+LNK1179:
+invalid or corrupt file: duplicate comdat
+comdat
. To overcome this problem, you can restrict the maximum
+number of elements accepted by
+indexed_by
,
+tag
and
+composite_key
+by globally setting the values of the macros
+
BOOST_MULTI_INDEX_LIMIT_INDEXED_BY_SIZE
+ (default in MSVC++ 6.0: 5),BOOST_MULTI_INDEX_LIMIT_TAG_SIZE
+ (default in MSVC++ 6.0: 3),BOOST_MULTI_INDEX_LIMIT_COMPOSITE_KEY_SIZE
+ (default in MSVC++ 6.0: 5).
+Under some circumstances, the compiler emits the error
+
+C2587
: '_U' : illegal use of local variable as
+default parameter
, inside the MSVC internal header
+<xlocnum>
.
+This problem is a recurrent bug of the compiler, and has been reported in
+other unrelated libraries, like the
+Boost Graph Library,
+Boost.MultiArray,
+Boost.Regex,
+CGAL and
+MySQL++.
+The error is triggered, though not in a systematic manner, by the use
+of multi_index_container
iterator constructor. Two workarounds exist:
+the first consists of avoiding this constructor and replacing
+code like:
+
+ ++multi_index_container<...> s(c.begin(),c.end()); +
+with equivalent operations: +
+ ++ ++multi_index_container<...> s; +s.insert(c.begin(),c.end()); +
+The second workaround has not been confirmed by the author, but it is given
+on the Internet in connection with this error appearing in other libraries.
+Replace line 84 of <xlocnum>
+
+
+ ++ #define _VIRTUAL virtual +
+with the following: +
+ ++ + ++ #define _VIRTUAL +
+Warning: it is not known whether this
+replacement can result in unexpected side effects in code implicitly
+using <xlocnum>
.
+
+In general, the extensive use of templates by Boost.MultiIndex puts this compiler +under severe stress, so that several internal limitations may be reached. +The following measures can help alleviate these problems: +
/Zm
(Specify Memory Allocation Limit)
+ to increase the amount of memory available for compilation. Usual values for
+ this option range from 300 to 800./ZI
(Program Database for
+ Edit and Continue) to a less demanding type of debugging information
+ (/Zi
, /Z7
or Zd
.)C1055
: compiler limit : out of keys
, try
+ disabling the option /Gm
(Enable Minimal Rebuild.) In these
+ cases, it is also beneficial to split the project into smaller
+ subprojects.+Boost.MultiIndex works for this configuration. The same limitations apply as +in MSVC++ 6.0 with its original Dinkumware standard library. +
+ +
+Problems have been reported when compiling the library with the /Gm
+option (Enable Minimal Rebuild.) Seemingly, this is due to an
+internal defect of the compiler (see for instance
+
+this mention of a similar issue in the Boost Users mailing list.)
+If /Gm
is turned off, Boost.MultiIndex compiles and runs
+without further problems.
+
Revised May 7th 2004
+ +Copyright © 2003-2004 Joaquín M López Muñoz. +Use, modification, and distribution are subject to the Boost Software +License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt) +
+ + + diff --git a/doc/examples.html b/doc/examples.html new file mode 100644 index 0000000..87e581b --- /dev/null +++ b/doc/examples.html @@ -0,0 +1,319 @@ + + + + + +multi_index_container
s
+ with ctor_args_list
+See source code. +
+ +
+Basic program showing the multi-indexing capabilities of Boost.MultiIndex
+with an admittedly boring set of employee
records.
+
+See source code. +
+ +
+Usually keys assigned to an index are based on a member variable of the
+element, but key extractors can be defined which take their value from
+a member function. This has some similarity with the concept of
+calculated keys supported by some relational database engines.
+The example shows how to use the predefined const_mem_fun
+key extractor to deal with this situation.
+
+Keys based on member functions usually will not be actual references,
+but rather the temporary values resulting from the invocation of the
+member function used. This implies that modify_key
cannot be
+applied to this type of extractors, which is a perfectly logical
+constraint anyway.
+
multi_index_container
s
+with ctor_args_list
+See source code. +
+ +
+We show a practical example of usage of multi_index_container::ctor_arg_list
,
+whose definition and purpose are explained in the
+Advanced topics section. The
+program groups a sorted collection of numbers based on identification through
+modulo arithmetics, by which x
and y
are equivalent
+if (x%n)==(y%n)
, for some fixed n
.
+
+See source code. +
+ +
+This example shows how to construct a bidirectional map with
+multi_index_container
. By a bidirectional map we mean
+a container of elements of std::pair<const FromType,const ToType>
+such that no two elements exists with the same first
+or second
value (std::map
only
+guarantees uniqueness of the first member). Fast lookup is provided
+for both keys. The program features a tiny Spanish-English
+dictionary with online query of words in both languages.
+
+See source code. +
+ +
+The combination of a sequenced index with an index of type ordered_non_unique
+yields a list
-like structure with fast lookup capabilities. The
+example performs some operations on a given text, like word counting and
+selective deletion of some words.
+
+See source code. +
+ +
+This program illustrates some advanced techniques that can be applied
+for complex data structures using multi_index_container
.
+Consider a car_model
class for storing information
+about automobiles. On a fist approach, car_model
can
+be defined as:
+
+ ++struct car_model +{ + std::string model; + std:string manufacturer; + int price; +}; +
+This definition has a design flaw that any reader acquainted with
+relational databases can easily spot: The manufacturer
+member is duplicated among all cars having the same manufacturer.
+This is a waste of space and poses difficulties when, for instance,
+the name of a manufacturer has to be changed. Following the usual
+principles in relational database design, the appropriate design
+involves having the manufactures stored in a separate
+multi_index_container
and store pointers to these in
+car_model
:
+
+ ++struct car_manufacturer +{ + std::string name; +}; + +struct car_model +{ + std::string model; + car_manufacturer* manufacturer; + int price; +}; +
+Although predefined Boost.MultiIndex key extractors can handle many +situations involving pointers (see +advanced features +of Boost.MultiIndex key extractors in the Advanced topics section), this case +is complex enough that a suitable key extractor has to be defined. The following +utility cascades two key extractors: +
+ ++ ++template<class KeyExtractor1,class KeyExtractor2> +struct key_from_key +{ +public: + typedef typename KeyExtractor1::result_type result_type; + + key_from_key( + const KeyExtractor1& key1_=KeyExtractor1(), + const KeyExtractor2& key2_=KeyExtractor2()): + key1(key1_),key2(key2_) + {} + + template<typename Arg> + result_type operator()(Arg& arg)const + { + return key1(key2(arg)); + } + +private: + KeyExtractor1 key1; + KeyExtractor2 key2; +}; +
+so that access from a car_model
to the name
field
+of its associated car_manufacturer
can be accomplished with
+
+ ++key_from_key< + member<car_manufacturer,const std::string,&car_manufacturer::name>, + member<car_model,const car_manufacturer *,car_model::manufacturer> +> +
+The program asks the user for a car manufacturer and a range of prices +and returns the car models satisfying these requirements. This is a complex +search that cannot be performed on a single operation. Broadly sketched, +one procedure for executing the selection is: +
equal_range
,
+ multi_index_container
sorted
+ by price,
+ lower_bound
and
+ upper_bound
;
+lower_bound
and upper_bound
,
+ multi_index_container
sorted
+ by manufacturer,
+ equal_range
.
+multi_index_container
.
+In order to avoid object copying, appropriate view types
+are defined with multi_index_container
s having as elements
+pointers to car_model
s instead of actual objects.
+These views have to be supplemented with appropriate
+dereferencing key extractors.
+
+
++See source code. +
+ +
+Boost.MultiIndex
+composite_key
construct provides a flexible tool for
+creating indices with non-trivial sorting criteria.
+The program features a rudimentary simulation of a file system
+along with an interactive Unix-like shell. A file entry is represented by
+the following structure:
+
+ ++struct file_entry +{ + std::string name; + unsigned size; + bool is_dir; // true if the entry is a directory + const file_entry* dir; // directory this entry belongs in +}; +
+Entries are kept in a multi_index_container
maintaining two indices
+with composite keys:
+
ls
. The shell simulation only has three
+commands:
+cd [.|..|<directory>]
ls [-s]
(-s
orders the output by size)mkdir <directory>
+The reader is challenged to add more functionality to the program (for
+instance, implementation of the cp
command and handling of
+absolute paths.)
+
Revised May 7th 2004
+ +Copyright © 2003-2004 Joaquín M López Muñoz. +Use, modification, and distribution are subject to the Boost Software +License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt) +
+ + + diff --git a/doc/future_work.html b/doc/future_work.html new file mode 100644 index 0000000..dc709e3 --- /dev/null +++ b/doc/future_work.html @@ -0,0 +1,273 @@ + + + + + +
+A number of new functionalities are considered for inclusion into
+future releases of Boost.MultiIndex. Some of them depend on the
+potential for extensibility of the library, which has been a guiding
+principle driving the current internal design of multi_index_container
.
+
+Several STL implementations feature hashed sets as a natural
+counterpart to std::set
and std::multiset
.
+multi_index_container
can also benefit from the inclusion of hashed
+indices. As the exact details of the interfaces of hashed sets differ among
+library vendors, a good starting point seems Matt Austern's paper
+A
+Proposal to Add Hash Tables to the Standard Library (revision 4), which
+has been submitted for acceptance into the next revision of the
+C++ standard.
+
+Ordered indices are implemented using red-black trees; these trees
+can be augmented with additional information to obtain a type
+of data structure called
+order-statistics
+trees, allowing for logarithmic search of the n-th element. It
+has been proposed that order-statistics trees be used to devise a new type of
+ranked indices that support operator[]
while retaining
+the functionality of ordered indices.
+
+Notifying indices can be implemented as decorators over +preexistent index types, with the added functionality that internal +events of the index (insertion, erasing, modifying of elements) are +signalled to an external entity --for instance, by means of the +Boost.Signals +library. This functionality can have applications for: +
+The following is a sketch of a possible realization of notifying +indices: +
+ ++ ++struct insert_log +{ + void operator()(int x) + { + std::clog<<"insert: "<<x<<std::endl; + } +}; + +int main() +{ + typedef multi_index_container< + int, + indexed_by< + notifying<ordered_unique<identity<int> > >, // notifying index + ordered_non_unique<identity<int> > + > + > indexed_t; + + indexed_t t; + + // on_insert is the signal associated to insertions + t.on_insert.connect(insert_log()); + + t.insert(0); + t.insert(1); + + return 0; +} + +// output: +// insert: 0 +// insert: 1 +
+The notifying indices functionality described above exploits a powerful +design pattern based on index adaptors, decorators over preexistent +indices which add some functionality or somehow change the semantics of +the underlying index. This pattern can be used for the implementation +of constraints, adaptors that restrict the elements accepted by an +index according to some validation predicate. The following is a possible +realization of how constraints syntax may look like: +
+ ++ ++struct is_even +{ + bool operator()(int x)const{return x%2==0;} +}; + +typedef multi_index_container< + int, + indexed_by< + constrained<ordered_unique<identity<int> >,is_even> + > +> indexed_t; +
+The mechanisms by which Boost.MultiIndex orchestrates the
+operations of the indices held by a multi_index_container
are
+simple enough to make them worth documenting so that the (bold)
+user can write implementations for her own indices.
+
+Example 4 in the examples section
+features a bidirectional map, implemented as an
+multi_index_container
with two unique ordered indices. This particular
+structure is deemed important enough as to provide it as a separate
+class template, relying internally in multi_index_container
. As
+feedback is collected from the users of Boost.MultiIndex, other singular
+instantiations of multi_index_container
might be encapsulated
+to form a component library of ready to use containers.
+
+multi_index_container
is rich enough to provide the basis
+for implementation of indexed maps, i.e. maps which
+can be looked upon several different keys. The motivation for having
+such a container is mainly aesthetic convenience, since it
+would not provide any additional feature to similar constructs
+based directly on multi_index_container
.
+
+The main challenge in writing an indexed map lies in the design of a
+reasonable interface that resembles that of std::map
as
+much as possible. There seem to be fundamental difficulties in extending
+the syntax of a std::map
to multiple keys. For one example,
+consider the situation:
+
+ ++indexed_map<int,string,double> m; +// keys are int and string, double is the mapped to value + +... + +cout<<m[0]<<endl; // OK +cout<<m["zero"]<<endl; // OK +m[1]=1.0; // !! +
+In the last sentence of the example, the user has no way of
+providing the string
key mapping to the same value
+as m[1]
. This and similar problems have to be devoted
+a careful study when designing the interface of a potential
+indexed map.
+
+Once Robert Ramey's
+serialization library gets accepted into Boost, support for
+archiving/retrieving multi_index_container
s should be added.
+
+Andrei Alexandrescu introduced a technique for simulating move
+constructors called Mojo (see his article in C/C++ User Journal
+
+"Generic<Programming>: Move Constructors".) Move semantics
+alleviates the computational load involved in the creation and copying
+of temporary objects, specially for heavy classes as
+multi_index_container
s are. David Abrahams and Gary Powell provide
+an alternative implementation of move semantics in their paper
+
+"Clarification of Initialization of Class Objects by rvalues" for
+the C++ Evolution Working Group.
+
+Adding move semantics to multi_index_container
is particularly
+beneficial when the container is used as an internal building block in other
+libraries (vg. relational database frameworks), enabling the efficient
+development of functions returning multi_index_container
s. Without support
+for move semantics, this scheme is impractical and less elegant syntaxes
+should be resorted to.
+
Revised May 7th 2004
+ +Copyright © 2003-2004 Joaquín M López Muñoz. +Use, modification, and distribution are subject to the Boost Software +License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt) +
+ + + diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..5147aa6 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,74 @@ + + + + + +
+The Boost Multi-index Containers Library provides a class template named
+multi_index_container
which enables the construction of containers
+maintaining one or more indices with different sorting and access semantics.
+Indices provide interfaces similar to those of STL containers, making using them
+familiar. The concept of multi-indexing over the same collection of elements is
+borrowed from relational database terminology and allows for the specification of
+complex data structures in the spirit of multiply indexed relational tables where
+simple sets and maps are not enough.
+
+Boost.MultiIndex features additional functionalities, like subobject searching,
+range querying and in-place updating of elements, which make it a convenient replacement
+for std::set
and set::multiset
even when no multi-indexing
+capabilities are needed.
+
Revised May 7th 2004
+ +Copyright © 2003-2004 Joaquín M López Muñoz. +Use, modification, and distribution are subject to the Boost Software +License, Version 1.0. (See accompanying file +LICENSE_1_0.txt or copy at +www.boost.org/LICENSE_1_0.txt) +
+ + + diff --git a/doc/lopez.jpg b/doc/lopez.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac37b4a94eb4303cfab9af0394509252dcd46bca GIT binary patch literal 15167 zcmeHOc|6qJzyHn*#y*D1QZe>Q_UsYKmVMXAgBin&!C1!LVvi8fDzapWP-+rNC3`Dl zO_oR=5>Eb%;`Kuxl?$4&MlVZR?`{_lJ0=<5
zFS5%rI!E$l%z15u^&5Zx3N=K_zO*e&-oVb`uy0cW(_d8_3$=?5vBdCb6qQRSS)9L~
zn0s;Nt3J~C5%ZqH5A9uMf_0Hewsj>YM)`y><)Udeud0tr(`U~I d{X)@WmGnI(O?Pf~HvHG{
z+}zyT0-h&D2>V^?i@me#+ae}M#f`q)1oaaI%L3N2>TYy!`%7mr(yW>ezq9eYF~x0B
zE84W2L*_fsuiJ|JM8rfA5fm?-Ti)S5G-l16w e=VeW2uOh&G@p2xfSQSkU-OR*
z#)RQ^)t?J3Ei_07yjye?NlXx_uSWytk(j{>%i1Ajgg{iB-+F3=F^hVP7Fam}Qf%cy
zU-i=I&CH^~+}(tWYbh(Mu8}h*hi)B$K87r@L!gC*YL;ndr>jy;hb=des^LGF?C3SD
zCvFgW$3iSNg41Yn{xDp6XvyS?h{%2DMb0=AD)$jQ*R)xz3rK$XS`@go?_nRF92oiR
zR_uAe^!+0?>nvuac-Znp-gERXl%7-7uYd4Ql~ABW=4oTSS!ln517 POQCo$UUMP6Q{aG)e36#FAO0(eb~+WEMKMuikJ(cN#^+fLTW^J`@w2
z+o6B^-H;t8c~+SK}jhmJ<{D%=VM4U#DtEgjDE!E&KZJRa|$YrSbF_Y
zsE=n_Mv#gq9HhtitZ`^p`Y3Js&&zvKYBRogRM(O6pYLw2c?JBgezm|zG~=7lZy!7T
zI(0ZeDfB(rpBe!Y);s@D7W>`09*DzdohFIk`Mj1m+(&;x#F1laXzU(JQu5?t{jjuzV6{PD_3Zp=m&8-w*`KiY&fzIxtW0g<
zSy4olMDYK8QGGx}`&shCiXqiuI59i#E!og!3&poAq;I;H;MYc}NwZv~g-8jbnf{cg
zONuibLQ!Fq9K#uODr7%w%J8u^q#nF($z4f3a0!1QZbKNDI4>HtV$c1qg@xeJPli8{
zwPl7^>rghP&P9{(8G?zG@1wtza;ncoR&h^t_9c_zn^ROVK1*qa%gjtoku7yB%+E8u
z=Shlqse}*SuRju75xB&!s?7tAmg>gFw2+$;5B;xBcA^;(>;mCJ@a_ii?5?X9-h9IU
z@`)4
Bw*BFm5^g&cVodHki7l8Lv@!KG
z_FqC#lLU0BSrc)W3$0a^LMy*&$$UjE`uRH4W0E=}FqGkysfNu}pL$L$Q^e53=#|}V
z6pC~bKnwq=HgiC2PeRU~q(h1nS70H2W7?_Db#_j(g$7b5G9^>=!d=1cXCYl*a7WN#
z@kd!<_T2Z&+($<;B?sXk8K{@de~A>0IJs$Cob#{f-U3+hGD#WL6z*d`p)j$W{0n;~
zzgF(uT*CGdfSa$|`sj44rE;1z4T;>D=Y0b6r=-
2*zcbQ!&
zP5m*{Cs~0wgqf-l^4SCnOTSq|(A3QES%mYc+P5SGwBht)p?JYfr`_kPQkjk}9fiD*
z?P)z6&QUvDeV80b0C@?&KjqS*PoMIQeReB|u;KEkm`ln*$Q=A)HVs9*
z9xz>PyC$5V6GNReqG*iT>gpz>$B>nkl15)B#5v>=)Y17vkDFaAuXXv?M2@KsaWP^#
zCC
S^8f5&4$&(zh^117Gvfd&JvtYKjJ00;^(Yz)}F
z3a9T>;;Awhlbn=oa{L>iWfJ}2=eQ%tXy${_tXxaYWA@)=ZSo+t7RT=Bp|~VQ0^Lum
z83xmrTXzr26UL8ys;a+gX@0^RD*H-BO-{8Y<8d<#cy9x-_22DY5!bXB9*+jP=eO2w
zwWzh&`&t_}dEXvnZx5{pIIa3GK#oRxC^H_(1_YpnY8F^ekJUJraDP@;2~A5z2`x9n
z(8OZLd#*Q!w#)v6uk~ie-;S4X4*`wg!zI|sQdI$~oEc(n#X-DJVwIfFTFZ?%YN`G6
zYDFDk@J0<2JTE}L17Dp!vZ+v6OJ1(A)$Fk1=v!lReS7j@1J!QPGtDWmJF*+0HV|Yb
zLnyNpGN2XoxZz|KmvrSkz}v^Q`>F}G3a+p_!3yszi!$Qd(E@wj>1*1~{+7!~rUH~9
zU(butMVkge