Boost logo

Boost Users :

Subject: Re: [Boost-users] Boost.MultiIndex. Using several keys together.
From: Joaquin M Lopez Munoz (joaquin_at_[hidden])
Date: 2014-02-05 10:49:12


syvyi <alexander.svk <at> gmail.com> writes:

>
> What if the composite key should be parameterized from a file?

OK, this can be done, but you need to move some of the container
definition components from compile time to run time. The following

typedef std::function<boost::any(const person&)> any_person_extractor;

defines a polymorhpic key extractor on person: something that takes
a person and return something (which we wrap in a boost::any object,
since we won't know what this something is until run time.) Similarly,

typedef std::function<
  bool(const boost::any&,const boost::any&)> any_compare;

is a polymorphic wraper for compare objects taking boost::any's: each
compare objects we feed into any_compare will have to know what's inside
these boost::any's to do the proper comparison, which we can do with

template<typename T>
struct any_less
{
  bool operator()(const boost::any& x,const boost::any& y)const
  {
    return boost::any_cast<T>(x)<boost::any_cast<T>(y);
  }
};

As composite_keys have to be defined with a predefined number of slots,
we must have dummy extractors and compare objects to fill those up
when the number of keys we specify at tun time is less than the number of
slots:

struct void_person_extractor
{
  typedef boost::any result_type;
  result_type operator()(const person&)const{return boost::any();}
};

template<>
struct any_less<void>
{
  bool operator()(const boost::any& x,const boost::any& y)const
  {return false;}
};

Now, let's define the multi_index_container instantiation as:

typedef multi_index_container<
  person,
  indexed_by<
    ordered_non_unique<
      composite_key<
        person,
        any_person_extractor,
        any_person_extractor,
        any_person_extractor,
        any_person_extractor
>,
      composite_key_compare<
        any_compare,
        any_compare,
        any_compare,
        any_compare
>
>
>
> person_container;

What we are doing here is reserve 4 slots (we can have more,
of course) to fill at run time with specific key extractors (and their
corresponding compare objects.) For instance, the following

  person_container c(
    boost::make_tuple(
      boost::make_tuple(
        boost::make_tuple(
          any_person_extractor(member<person,int,&person::birth_year>()),
          any_person_extractor(member<person,std::string,&person::name>()),
          any_person_extractor(
            member<person,std::string,&person::surname>()),
          void_person_extractor()
        ),
        boost::make_tuple(
          any_less<int>(),
          any_less<std::string>(),
          any_less<std::string>(),
          any_less<void>()
        )
      )
    ));

instantiates a person_container with a composite key having three
extractors on birth year, name and surname (a more elaborate example
would fetch the info on which extractors toprovide from a configuration
file or something); the remainng slot we fill with our dummy key
extractor and compa object. Once instantiated, c can be used as a normal
(compile-time defined) container. for instance, the following retrieves
the persons born in 1959:

  std::pair<person_container::iterator,person_container::iterator> p=
    c.equal_range(1959);

If the first key extractor weren't compatible with the int we've
provided, for instance if we'd passed a string, a boost:any exception
would be thrown --that's the price we pay for having this run-time
flexibility, lack of compile time type checkhing.

And that's it. You can play with a full example at

http://coliru.stacked-crooked.com/a/4fa76b314298516d

Joaquín M López Muñoz
Telefónica Digital


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net