|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
Introduction
Sometimes it is desirable to have unicity of values, as well as fast lookups for either type. This kind of container can be modeled as a bidirectional map which is entirely symmetric with respect to the two types that compose pairs. In what follows, we'll refer to these types as
NamespaceAs a small tribute to this superb site, and in order to avoid name clashing, Basic usageUsing #pragma warning(disable:4503) #include "bimap.h" #include <iostream> #include <string> using codeproject::bimap; int main(void) { bimap<int,std::string> bm; bm[1]="Monday"; bm[2]="Tuesday"; bm[3]="Wednesday"; bm[4]="Thursday"; bm[5]="Friday"; bm[6]="Saturday"; bm[7]="Sunday"; std::cout<<"Thursday occupies place #"<<bm["Thursday"]<< " in the week (european style)"<<std::endl; return 0; } OutputThursday occupies place #4 in the week (european style)
This looks as if a common The symmetry of bimap<string,int> ranking; ranking["Tom"]=2; ranking["Bob"]=3; ranking["Joe"]=2; // throws bimap_base::duplicate_value // throws throws bimap_base::value_not_found cout<<"First scorer: "<<ranking[1]<<endl;
Memberspaces from and toConsider this example: bimap <int,int> bm; bm[1]=5; // which side of the bimap are we referring to? Since in this case bimap <int,int> bm; bm.from[1]=5; // assigns 5 to the "from" value 1 bm.to[4]=2; // assigns 4 to the "to" value 2 Memberspaces bimap <int,int> bm; bimap <int,int>::from::const_iterator it; for(it=bm.from.begin();it!=bm.from.end();++it){ // lists pairs in (from,to) order cout<<it->first<<"="<<it->second;<<endl; } It doesn't hurt to use the memberspaces even when there's no ambiguity. By default, all types and methods with no explicit memberspace declaration are assumed to belong to the bimap<int,string> bm; bm.find("Hello"); // equivalent to bm.to.find("Hello") Another interesting aspect of memberspaces template <typename container> void dump(const container& c) { for(container::const_iterator it=c.begin();it!=c.end();++it){ cout<<it->first<<""<<it->last<<endl; } } bimap<string,string> bm; dump(bm.from); dump(bm.to); // reverse order Test suiteAlong with bimap.h, a test suite is provided that thoroughly checks the correctness of implementation. This suite also serves as a brief guide to the various uses of Compiler status
A good deal of effort has been put into making the code as standard-conformant as possible, so chances are it will work for other compilers with no modifications. I'd be most grateful if you try the test suite in your favorite environment and report the results. If only minor tweaks are needed to make IssuesAllocator supportAllocator treatment is not as smooth as it should be. Two difficulties arise:
When using Explicit reference to types from and toThe following sentence does not compile: codeproject::bimap<int,int>::from* f; This is not a compiler bug, but a subtlety stemming from the way memberspaces class codeproject::bimap<int,int>::from* f; The problem appears only when referring to the types codeproject::bimap<int,int>::from::iterator it; // OK, no 'class' needed Hint-driven insertionFor The problem is corrected in the STL implementation for VC++ 7.0. Here, In other compilers, the standard rule is followed that the hint should precede the insertion point. Global swap functionsThis problem only applies to VC++ 6.0 and 7.0. Global bimap<int,int> bm1,bm2; swap(bm1,bm2); // should call codeproject::swap() Unfortunately, VC++ does not implement Koenig lookup for non-operator functions, so it is bimap<int,int> bm1,bm2; codeproject::swap(bm1,bm2); ReferenceTypesbimap< typename from_type, typename to_type, typename from_compare=std::less<from_type>, typename to_compare=std::less<to_type>, typename allocator_type= std::allocator<direct_pair<const from_type,const to_type> > > Specifies a bidirectional map of types key_type, from::key_type Synonyms of to::key_type Synonym of mapped_type, from::mapped_type, referent_type,
from::referent_type, data_type, from::data_type
Synonyms of to::mapped_type, to::referent_type, to::data_type Synonyms of key_compare, from::key_compare Synonyms of to::key_compare Synonym of allocator_type, from::allocator_type, to::allocator_type Synonyms of value_type, from::value_type Implementation-specific type convertible to to::value_type Implementation-specific type convertible to value_compare, from::value_compare Implementation-specific functor type that lexicographically compares to::value_compare Implementation-specific functor type that lexicographically compares size_type, from::size_type, to::size_type Synonyms of difference_type, from::difference_type, to::difference_type Synonyms of pointer, from::pointer Synonyms of to::pointer Synonym of const_pointer, from::const_pointer Synonyms of to::const_pointer Synonym of reference, from::reference Synonyms of to::reference Synonym of const_reference, from::const_reference Synonyms of to::const_reference Synonym of iterator, from::iterator Bidirectional iterator to elements of type to::iterator Bidirectional iterator to elements of type const_iterator, from::const_iterator Bidirectional iterator to elements of type to::const_iterator Bidirectional iterator to elements of type reverse_iterator, from::reverse_iterator Bidirectional reverse iterator to elements of type to::reverse_iterator Bidirectional reverse iterator to elements of type const_reverse_iterator, from::const_reverse_iterator Bidirectional reverse iterator to elements of type to::const_reverse_iterator Bidirectional reverse iterator to elements of type inv_bimap This member VC++ 6.0: Due to limitations in allocator support in VC++ 6.0, Exceptionscodeproject::bimap_base::duplicate_value: public std::logic_error
Thrown by certain methods when an insertion is attempted that violates the unicity of codeproject::bimap_base::value_not_found: public std::logic_error
Thrown by the various forms of Constructors and assignmentexplicit bimap( const from_compare& from_comp=from_compare(), const to_compare& to_comp=to_compare(), const allocator_type& al=allocator_type()) Constructs an empty Complexity constant time. bimap(const bimap& r)
Constructs a copy of a given Complexity O(n log n) in general, O(n) if explicit bimap(const inv_bimap& r) Constructs a map from a given Complexity O(n log n) in general, O(n) if template <typename it_type> bimap( it_type first,it_type last, const from_compare& from_comp=from_compare(), const to_compare& to_comp=to_compare(), const allocator_type& al=allocator_type()) Constructs a map and fills it with the values in the range [ Throws
Complexity O(n log n) in general, half the time if [ ~bimap() Destructs the Complexity O(n) bimap& operator=(const bimap& r)
Assigns the given Complexity O(n log n) in general, O(n) if class from& from.operator=(const from& r) This is equivalent to calling r is monotonous (i.e. x<y implies r[x]<r[y] for every (x, r[x]), (y, r[y]) in r).
class to& to.operator=(const to& r) This is equivalent to calling r is monotonous (i.e. x<y implies r[x]<r[y] for every (x, r[x]), (y, r[y]) in r).
Comparisonbool operator==(const bimap& r) const bool operator!=(const bimap& r) const bool operator< (const bimap& r) const bool operator> (const bimap& r) const bool operator<=(const bimap& r) const bool operator>=(const bimap& r) const bool from.operator==(const from& r) const bool from.operator!=(const from& r) const bool from.operator< (const from& r) const bool from.operator> (const from& r) const bool from.operator<=(const from& r) const bool from.operator>=(const from& r) const Comparison of Complexity O(n), where n is the minimum of the lengths of the bool to.operator==(const to& r) const bool to.operator!=(const to& r) const bool to.operator< (const to& r) const bool to.operator> (const to& r) const bool to.operator<=(const to& r) const bool to.operator>=(const to& r) const Comparison of Complexity O(n), where n is the minimum of the lengths of the Iterator retrievaliterator begin() from::iterator from.begin() Return an Complexity constant time to::iterator to.begin() Returns a Complexity constant time iterator end() from::iterator from.end() Return an Complexity constant time to::iterator to.end() Returns a Complexity constant time reverse_iterator rbegin() from::reverse_iterator from.rbegin() Returns a Complexity constant time to::reverse_iterator to.rbegin() Returns a Complexity constant time reverse_iterator rend() from::reverse_iterator from.rend() Returns a Complexity constant time to::reverse_iterator to.rend() Returns a Complexity constant time Utility member functionssize_type size() const from::size_type from.size() const to::size_type to.size() const Returns the number of elements in the map. Complexity constant time size_type max_size() const from::size_type from.max_size() const to::size_type to.max_size() const Return the length of the longest Complexity constant time bool empty() const bool from.empty() const bool to.empty() const Indicate whether the map is empty. Complexity constant time allocator_type get_allocator() const from::allocator_type from.get_allocator() const to::allocator_type to.get_allocator() const Returns a copy of the Complexity constant time Insertion and erasing(implementation specific return type) operator[](const key_type& key) (implementation specific return type) from.operator[](const from::key_type& key)
bimap<int,string> bm; bm[1]="Hello"; //assignment string s=bm[1]; // retrieval of a string&
In some situations, the compiler is unable to automatically perform the conversion to bimap<int,string> bm; string s=bm[1].get(); Throws
Complexity of lookup O(log n) Complexity of assignment O(log n) (twice the time of lookup) (implementation specific return type) operator[](const to::key_type& key)* (implementation specific return type) to.operator[](const to::key_type& key) *Only available if
bimap<int,string> bm; bm["Hello"]=1; // assignment int i=bm["Hello"]; // retrieval of an int&
In some situations, the compiler is unable to automatically perform the conversion to bimap<int,string> bm; int i=bm["Hello"].get(); Throws
Complexity of lookup O(log n) Complexity of assignment O(log n) (twice the time of lookup) std::pair<iterator, bool> insert(const value_type& x) std::pair<from::iterator, bool> from.insert(const from::value_type& x) Insert the given Throws
Complexity O(log n) std::pair<to::iterator, bool> insert(const to::value_type& x) std::pair<to::iterator, bool> to.insert(const to::value_type& x) Insert the given Throws
Complexity O(log n) iterator insert(iterator it, const value_type& x) from::iterator from.insert(from::iterator it, const from::value_type& x) Insert Throws
Complexity O(log n), but half the time if Complexity for VC++ 6.0 O(log n), but half the time if Complexity for VC++ 7.0 O(log n), but half the time if to::iterator insert(to::iterator it, const to::value_type& x) to::iterator to.insert(to::iterator it, const to::value_type& x) Insert Throws
Complexity O(log n), but half the time if Complexity for VC++ 6.0 O(log n), but half the time if Complexity for VC++ 7.0 O(log n), but half the time if std::pair<from::iterator,to::iterator> insert(from::iterator fit,to::iterator tit, const value_type& x) Insert Throws
Complexity O(log n) in general, half the time if one of the hints immediately precedes the insertion point, amortized constant time if both hints immediately precede the corresponding insertion points. Complexity for VC++ 6.0 O(log n) in general, half the time if one of the hints immediately follows the insertion point, amortized constant time if both hints immediately follow the corresponding insertion points. Complexity for VC++ 7.0 O(log n) in general, half the time if one of the hints immediately precedes or follows the insertion point, amortized constant time if both hints immediately precede or follow the corresponding insertion points. template<typename it_type> void insert(it_type first, it_type last) template<typename it_type> void from.insert(it_type first, it_type last) Insert the Throws
Complexity O(m log m+n) where m is the length of [ void insert(const to::value_type * first,const to::value_type * last) template<typename it_type> void to.insert(it_type first,it_type last) Insert the Throws
Complexity O(m log m+n) where m is the length of [ void erase(iterator it) void from.erase(from::iterator it) Replaced in VC++ foriterator erase(iterator it) from::iterator from.erase(from::iterator it) Erase the element pointed to by VC++: The return Complexity O(log n) void erase(to::iterator it) void to.erase(to::iterator it) Replaced in VC++ foriterator erase(to::iterator it) to::iterator to.erase(to::iterator it) Erase the element pointed to by VC++: The return Complexity O(log n) void erase(iterator first,iterator last) void from.erase(from::iterator first,from::iterator last) Erase the elements in the range [ Complexity O(m log n) where m is the length of [ void erase(to::iterator first,to::iterator last) void to.erase(to::iterator first,to::iterator last) Erase the elements in the range [ Complexity O(m log n) where m is the length of [ size_type erase(const key_type& key) from::size_type from.erase(const from::key_type& key) Erase the element whose Complexity O(log n) (twice the time of an insertion based on an iterator). size_type erase(const to::key_type& key)* to::size_type to.erase(const to::key_type& key) *Only available if Erase the element whose Complexity O(log n) (twice the time of an insertion based on an iterator). void clear() void from.clear() void to.clear() These member functions resolve to a call to Complexity O(n log n) void swap(bimap& x) void from.swap(bimap::from x) void to.swap(bimap::to x) Swap the elements of the Complexity constant time void codeproject::swap(bimap& x,bimap& y) void codeproject::swap(bimap::from& x,bimap::from& y) void codeproject::swap(bimap::from& x,bimap::from& y) These are global functions. They swap the elements of Complexity constant time Searchkey_compare key_comp() const from::key_compare from.key_comp() const Return a copy of the internal Complexity constant time to::key_compare to.key_comp() const
Returns a copy of the internal Complexity constant time value_compare value_comp() const from::value_compare from.value_comp() const Return an object of type Complexity constant time to::value_compare to.value_comp() const
Returns an object of type Complexity constant time iterator find(const key_type& key) from::iterator from.find(const from::key_type& key) Return an iterator pointing to the element whose Complexity O(log n) to::iterator find(const to::key_type& key)* to::iterator to.find(const to::key_type& key) *Only available if Return an iterator pointing to the element whose Complexity O(log n) size_type count(const key_type& key) const from::size_type from.count(const from::key_type& key) const Return 1 if the Complexity O(log n) size_type count(const to::key_type& key) const* to::size_type to.count(const to::key_type& key) const *Only available if Return 1 if the Complexity O(log n) iterator lower_bound(const key_type& key) from::iterator from.lower_bound(const from::key_type& key) Return an iterator to the first element of the Complexity O(log n) to::iterator lower_bound(const to::key_type& key)* to::iterator to.lower_bound(const to::key_type& key) *Only available if Return an iterator to the first element of the Complexity O(log n) iterator upper_bound(const key_type& key) from::iterator from.upper_bound(const from::key_type& key) Return an iterator to the first element of the Complexity O(log n) to::iterator upper_bound(const to::key_type& key)* to::iterator to.upper_bound(const to::key_type& key) *Only available if Return an iterator to the first element of the Complexity O(log n) std::pair<iterator, iterator> equal_range(const key_type& key) std::pair<from::iterator, from::iterator> from.equal_range(const from::key_type& key Return a pair of iterators with its first member being Complexity O(log n) std::pair<to::iterator, to::iterator> equal_range(const to::key_type& key)* std::pair<to::iterator, to::iterator> to.equal_range(const to::key_type& key) *Only available if Return a pair of iterators with its first member being Complexity O(log n) Annex 1: Memberspaces
Suppose we are defining a two-way queue holding integers. Such a queue presents two ends, say class twoway_queue { public: void head_push(int n); int head_pull(); void tail_push(int n); int tail_pull(); ... }; This simple solution suffers from several limitations:
Memberspaces solve all these problems by introducing an additional level of scope into the class being defined: class twoway_queue { public: memberspace head //warning, "memberspace" is not C++ { public: void push(int n); int pull(); } memberspace tail { public: void push(int n); int pull(); } ... }; The use of memberspaces is straightforward: twoway_queue q; q.head.push(1); cout<<q.tail.pull()<<endl; fill_n(q.head,100); The additional level of scope brought by member spaces is not restricted to members alone (called with // class to convert between ints and their // string representations and viceversa class int2string_queue { public: memberspace int_end { public: typedef int type; void push(int n); int pull(); } memberspace str_end { public: typedef string type; void push(string str); string pull(); } ... }; int2string_queue q; int2string_queue::str_end::type a; // a string a=q.str_end.pull(); cout<<a<<endl; So, memberspaces can be viewed as dual constructs playing the role of both an embedded object and an in-class namespace. ImplementationAlthough memberspaces are not directly supported by C++, they can be efficiently simulated to a little-known syntax rule dating back from the C language, which allows an object and a class to be given the same name: class A { ... }; class A A; // legal C++! object A is of type A We can take advantage of this rule to lay the grounds for an implementation of memberspaces: class twoway_queue { public: class head { public: void push(int n); int pull(); }head; // head is both a nested // class and an embedded object ... }; Such an artifact imposes no or minimum penalty on the memory layout of the nesting class. To grant inter-accessibility between class twoway_queue { public: class head { friend twoway_queue; public: void push(int n); int pull(); }head; friend class head; ... }; There is only one brick left to complete this building: when the code inside the member space needs to access some member of the enclosing class, it has to find out the object where it is embedded. This can be solved at compile-time if we note that it is known in advance the exact location inside the enclosing class of the member space object, so that we can just force the "upcasting" to the whole class like this: class twoway_queue { class head { public: void push(int n) { ... owner().length++; // access private member // length of twoway_queue } ... private: twoway_queue& owner() { return *reinterpret_cast<twoway_queue*>( reinterpret_cast<char*>(this)-offsetof( twoway_queue,head)); } }head; ... } There are still some uninteresting details to round up, like defining a Annex 2: Data structureIn order to provide
One problem remains, though: STL associative containers are supposed to hold pairs of (key, value) elements. From the point of view of the
Acknowledgements
BibliographyThe implementation code is rather messy and takes advantage of several smart techniques to simulate partial template specialization. Ideas are taken from the following sources:
Version history
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||