Ram Ram - 1 month ago 11x
C++ Question

How to use/create boost::multi_index?

Can someone explain to me in detail how to create a multi index map using

? I saw many examples online and also the boost page, but I could not understand it. I would want to map class object pointer by multiple int/longs as keys. Can someone please help me with understanding this?

I have a class
and multiple properties of the class which are
long long
. I want to store properties
long long
as keys to map to -> <pointer to X>.

I want to be able to look up the pointer given any property. Some of the properties are unique to each object of
and some are not unique.


Boost.Multi-index offers an extremely customisable interface, at the cost of offering an extremely complex interface, so it's easy to understand why you're stuck.

I'll present a commented example which should match your use case.

First, our data:

struct X
  long long l; // assume unique
  int i1; // assume unique
  int i2; // assume non-unique
  // plus any ohter data you have in your class X

Next, we'll prepare one tag for each index which we want the contain to have. Tags aren't strictly necessary (indexes can be accessed by their order), but it's more convenient to provide a name for each index:

struct IndexByL {};
struct IndexByI1 {};
struct IndexByI2 {};

Now, we have what we need to put together the type of the container:

using Container = boost::multi_index_container<
  X*, // the data type stored
  boost::multi_index::indexed_by< // list of indexes
    boost::multi_index::hashed_unique<  //hashed index over 'l'
      boost::multi_index::tag<IndexByL>, // give that index a name
      boost::multi_index::member<X, long long, &X::l> // what will be the index's key
    boost::multi_index::ordered_unique<  //ordered index over 'i1'
      boost::multi_index::tag<IndexByI1>, // give that index a name
      boost::multi_index::member<X, int, &X::i1> // what will be the index's key
    boost::multi_index::hashed_non_unique<  //hashed non-unique index over 'i2'
      boost::multi_index::tag<IndexByI2>, // give that index a name
      boost::multi_index::member<X, int, &X::i2> // what will be the index's key

That's it, we have a container. Next, here's how we can use it:

Container c;  // empty container
X x1{...}, x2{...}, x3{...};  // some data

// Insert some elements
auto& indexByL = c.get<IndexByL>(); // here's where index tags (=names) come in handy

// Look up by i1
auto& indexByI1 = c.get<IndexByI1>();
auto itFound = indexByI1.find(42);
if (itFound != indexByI1.end())
  X *x = *itFound;

// Look up by i2
auto& indexByI2 = c.get<IndexByI2>();
size_t numberOfHundreds = indexByI2.count(100);

And now, some prose about how the beast works in general.

You define a multi-index container by specifying the type of objects it will store (X* in your case), and one or more indexes which can be used to access the stored objects. Think of indexes as interfaces for accessing the data.

Indexes can be of different kinds:

  • Indexes based on ordering by a key (think std::set or std::map)
  • Indexes based on ranking by a key (think the same plus easy access to nth element)
  • Indexes based on hashing a key (think std::unordered_set or std::unordered_map)
  • Indexes based on accessing in stable order (think std::list)
  • Indexes based on random access in stable order (think std::vector)

The key-based indexes can also be either unique (like std::map), or non-unique (like std::multimap).

When defining the container, you pass each index you want to have as one template argument to boost::multi_index::indexed_by. (In our example above, I added three indexes).

For indexes which do not use a key (stable order & random access), nothing needs to be specified; you just say "I want such an index."

For key-based indexes, you also need to specify how the key is obtained from the data. That's where key extractors come into play. (These are the three uses of boost::multi_index::member in the example). Basically, for each index, you provide a recipe (or algorithm) for deriving the key from the data stored in the container. Currently available key extractors are:

  • Use the element itself: identity
  • Use a data member of the element: member
  • Use a (constant) member function of the element: [const_]mem_fun
  • Use a global function: global_fun
  • Combine multiple key extractors into one: composite_key

Note that key extractors are able to dereference pointers transparently. That is, if your data element is a pointer to class C, you can specify key extractors over class C and the dereference will happen automagically. (This property is also used in the example).

This way a container with indexes is defined. To access an index, you call the get member function template on the container. You can refer to an index by its sequential number in the list of template arguments of indexed_by. For more readable manipulation, you can however introduce a tag for each index (or just some of them). A tag is an arbitrary type (usually an empty structure with a suitable name) which allows you to use that type as template argument for get instead of the index's sequential number. (This is also used in the example).

Once you retrieve a reference to an index from the container, you can use it just like the data structure to which the index corresponds (map, hashset, vector, ...). Changes done through that index will affect the entire container.