3

I'm trying to update a range of elements in a multi-index map, but it's seems a little more complicated than I expected it to be..

Given the following declarations:

struct Information {

    int number() const {
        return number_;
    }

    int number_;
};


typedef boost::multi_index_container<
        Information, 
        boost::multi_index::indexed_by<
                boost::multi_index::hashed_non_unique<
                        boost::multi_index::tag<int>,
                        boost::multi_index::const_mem_fun<
                                Information, 
                                int, 
                                &Information::number
                        >
                >
        >
    > Information_Map;

Information_Map information_map_;

The following is a summary of what's declared above:

  • A struct Information, which contains an arbitrary number, which is not unique
  • A multi-index map with the following properties
    • Contains elements of type Information
    • Elements are indexed using a non-unique hash
    • The hash's keys are constructed using the member function Information::number()

Now, I also have declared something like this:

struct Plus_One_Modifier {
    void operator()(Information& obj) const {
        obj.number_ += 1;
    }
};

The previous struct is a modifier, which is expected to be used when calling the modify function:

// from boost/multi_index/hashed_index.hpp
template<typename Modifier> bool modify(iterator position,Modifier mod);

Everything works as expected when this function is used to modify only one element:

// Updates the first element
Information_Map::index<int> index = information_map_.get<int>();
index.modify(index.begin(), Plus_One_Modifier());

The problem is that, in one special case, I have to update the whole container, something like this:

// Update the whole container the first element
Information_Map::index<int> index = information_map_.get<int>();
for (Information_Map::index<int>::iterator it = index.begin();
     it != index.end();
     ++it) {
    index.modify(it, Plus_One_Modifier());
}

The previous codes fails to transverse the whole container in most cases, at some point the iterator is modified in a way that ++it is equal to end.

I found that using a third variable seems to alleviate the problem, but I'm not convinced it's correct because it is making more iterations than elements in the container..

// Update the whole container the first element
Information_Map::index<int> index = information_map_.get<int>();
Information_Map::index<int>::iterator begin = index.begin();
Information_Map::index<int>::iterator end = index.end();
while (begin != end) {
    Information_Map::index<int>::iterator it = begin++;
    index.modify(it, Plus_One_Modifier());
}

So, the problem is:

  • I want to modify all elements in the container
  • Modifying an element affects the way the element is indexed in the container, i.e: modifies the key
  • Modifying the key affects the way elements are stored in the container, which means iterators can get a little messy

And I'm looking for a safe way of updating a range of elements in the container.

The only solution that has come to my mind is the following:

  • List all elements in the container
  • Navigate through the list and modify elements one by one

This will work OK, but the performance impact is too big

Any help will be appreciated

ichramm
  • 6,437
  • 19
  • 30

1 Answers1

5

You can not reliably iterate an hashed container under mutation. This is not specific to Boost Multi Index:

You can use an auxiliary index to iterate though:

Live On Coliru

auto& aux = information_map_.get<idx_auxiliary>();
auto& idx = information_map_.get<idx_main>();

size_t iterations = 0;
for(auto rait = aux.begin(); rait != aux.end(); ++rait, ++iterations) {
    auto it = bmi::project<idx_main>(information_map_, rait);
    idx.modify(it, Plus_One_Modifier());
}

// iterations is equal to `information_map_.size()`

Of course, be sure that the auxiliary index has stable iterators under the modifications you are doing in the loop. A sequenced index might also be good for the purpose.

Full Demo

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/mem_fun.hpp>

struct Information {
    Information(int n = 0) : number_(n) {}

    int number_;
    int number() const { return number_; }
};

static std::ostream& operator<<(std::ostream& os, Information const& i) {
    return os << i.number();
}

namespace bmi = boost::multi_index;

typedef boost::multi_index_container<Information, 
    bmi::indexed_by<
        bmi::hashed_non_unique<
            bmi::tag<struct idx_main>,
            bmi::const_mem_fun<Information, int, &Information::number>
        >,
        bmi::random_access<bmi::tag<struct idx_auxiliary> >
    >
> Information_Map;

struct Plus_One_Modifier {
    void operator()(Information& obj) const { obj.number_ += 1; }
};

#include <iostream>

int main() {
    Information_Map information_map_;
    std::generate_n(std::inserter(information_map_, information_map_.end()), 50, [] { return rand()%20; });

    // Updates the first element
    auto& aux = information_map_.get<idx_auxiliary>();
    auto& idx = information_map_.get<idx_main>();

    std::copy(aux.begin(), aux.end(), std::ostream_iterator<Information>(std::cout, " "));

    size_t iterations = 0;
    for(auto rait = aux.begin(); rait != aux.end(); ++rait, ++iterations) {
        auto it = bmi::project<idx_main>(information_map_, rait);
        idx.modify(it, Plus_One_Modifier());
    }

    std::cout << "\nIterations done: " << iterations << "\n";
    std::copy(aux.begin(), aux.end(), std::ostream_iterator<Information>(std::cout, " "));
}

Output:

3 6 17 15 13 15 6 12 9 1 2 7 10 19 3 6 0 6 12 16 11 8 7 9 2 10 2 3 7 15 9 2 2 18 9 7 13 16 11 2 9 13 1 19 4 17 18 4 15 10 
Iterations done: 50
4 7 18 16 14 16 7 13 10 2 3 8 11 20 4 7 1 7 13 17 12 9 8 10 3 11 3 4 8 16 10 3 3 19 10 8 14 17 12 3 10 14 2 20 5 18 19 5 16 11 
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • males sense, using an auxiliary iterator also crossed my mind, what if the range is the result of a search? i mean, the result of calling `equal_range`? – ichramm Mar 12 '15 at 16:35
  • You could transform all iterators in the range by projecting to the auxiliary index – sehe Mar 12 '15 at 16:36
  • 1
    Have a look at `modify_unstable_range` in http://lists.boost.org/boost-users/2006/03/18048.php for an example of in-index range modification. – Joaquín M López Muñoz Mar 14 '15 at 10:51