boost multi_index_container, range mutating algorithms and constness

469 views Asked by At

I'm using boost multi_index_container, which is queried by equal_range and the result returned from the function using range::join and boost::any_range
The any_range Reference argument is defined as const reference to the type - must be const because of the multi_index_container nature, not quite sure about the reference. Example:

typedef boost::any_range<TestData, boost::random_access_traversal_tag,
                         const TestData &, std::ptrdiff_t> TestRange;

Now what I need is to use mutating range algorithms like boost::sort, unique, etc., which obviously cannot run on range because of the constness of elements in range.
Is it any workaround except copying elements into new container?

EDIT 1:
struct and MIC example:

struct TestData {
  TestData()
      : m_strMem01("test"), m_intMem02(rand()),
        m_boolMem03(rand() < RAND_MAX / 2) {}
  std::string m_strMem01;
  int m_intMem02;
  bool m_boolMem03;
};

typedef boost::multi_index_container<
    TestData,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct RndKey1>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key1>,
            bmi::composite_key<
                TestData,
                bmi::member<TestData, std::string, &TestData::m_strMem01>,
                bmi::member<TestData, bool, &TestData::m_boolMem03>>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key4>,
            bmi::composite_key<
                TestData,
                bmi::member<TestData, std::string, &TestData::m_strMem01>,
                bmi::member<TestData, bool, &TestData::m_intMem02>>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key2>,
            bmi::member<TestData, int, &TestData::m_intMem02>>,
        bmi::ordered_non_unique<
            bmi::tag<struct Key3>,
            bmi::member<TestData, bool, &TestData::m_boolMem03>>>>
    TestDataContainer;
2

There are 2 answers

7
Joaquín M López Muñoz On BEST ANSWER

OK, once you have your range you really can't sort it or somehow rearrange it because the order of elements is fixed by the index --this is an absolutely fundamental invariant of indices enforced by the constness of elements, much as you'd find with say a std::set. Rather than doing a full copy to an external container you can create a more lightweight view out of pointers or references to the original elements that can later be manipulated as you wish. This is an example with views constructed as std::vectors of std::reference_wrappers to the elements:

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/join.hpp>
#include <functional>
#include <iostream>
#include <vector>

using namespace boost::multi_index;

struct X
{
  int x,y;
};

std::ostream& operator<<(std::ostream& os,const X& a)
{
  return os<<"{"<<a.x<<","<<a.y<<"}";
}

typedef multi_index_container<
  X,
  indexed_by<
    ordered_non_unique<member<X,int,&X::x>>
  >
> multi_t;

struct view:std::vector<std::reference_wrapper<const X>>
{
  using base=std::vector<std::reference_wrapper<const X>>;

  template<typename InputIterator>
  view(InputIterator first,InputIterator last):base(first,last){}

  template<typename InputIterator>
  view(const std::pair<InputIterator,InputIterator> p):base(p.first,p.second){}  
};

int main()
{
  multi_t m={{0,1},{0,0},{0,2},{1,3},{1,1},{2,0},{2,1}};

  view v1=m.equal_range(0); // elements with x==0
  view v2=m.equal_range(2); // elements with x==2
  auto v3=boost::range::join(v1,v2); // join them
  boost::range::sort(v3,[](const X& a,const X& b){return a.y<b.y;}); // sort them by y
  for(const auto& x:v3)std::cout<<x<<" "; // output
}

Output:

{0,0} {2,0} {0,1} {2,1} {0,2} 
8
Richard Hodges On

It sounds as if you don't really want your data in a multi_index container at all. The multi_index container (as it's name hints) is designed to store immutable data while maintaining several indexes (or views with constraints) against that data.

You can make part of your data mutable if you want to be able to update it, but you must ensure that the mutable parts do not participate in index construction.

If what you really want to do is mutate the data and produce new indexes as a result then you have 2 options - either remove and reinsert or (better) don't use boost::multi_index.

One approach would be to use a vector and maintain your own ad-hoc indexes.

for example, an index of unique entries in a vector of string (to keep it simple):

vector<const string*> unique_stable_index(const vector<string>& data)
{
    struct less_ptr {
        bool operator()(const string* l, const string* r) const {
            return *l < *r;
        }
    };

    vector<const string*> result;
    set<const string*, less_ptr> seen;
    for (const auto& s : data) {
        if(seen.insert(&s).second)
            result.push_back(&s);
    }

    return result;
}