user1633272
user1633272

Reputation: 2309

How to update value of boost multi_index_container?

I use multi_index_container to track the insertion order and doing mapping work (like LinkedMap of Java).

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>

#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;

template<typename KeyType, typename MappedType>
struct LinkedMap {
    typedef std::pair<KeyType, MappedType> value_type;
    typedef boost::multi_index_container<
        value_type,
        boost::multi_index::indexed_by<
        boost::multi_index::random_access<>,
        boost::multi_index::ordered_unique<
        boost::multi_index::member<value_type, KeyType, &value_type::first>
        >
        >
    > type;
};

TEST_CLASS(LinkedMapTest) {
public:
    TEST_METHOD(ShouldUpdateEntry) {
        LinkedMap<int, std::wstring>::type userMap;
        userMap.push_back(std::make_pair(1, L"John"));
        userMap.push_back(std::make_pair(1, L"Tom")); // 2nd push_back
        auto& idToNameMap = userMap.get<1>();
        auto& iter = idToNameMap.find(1);
        Assert::AreEqual(std::wstring(L"Tom"), iter->second);
    }
};

The test case fails.

Result Message: Assert failed. Expected:< Tom> Actual:< John>

That means the 2nd push_back doesn't update the value.

What's wrong with my code? How could I implement a LinkedMap?

Upvotes: 1

Views: 1763

Answers (2)

The ordered index you specified is unique:

boost::multi_index::ordered_unique<
    boost::multi_index::member<value_type, KeyType, &value_type::first>
 >

Make it non-unique to allow for duplicate keys:

boost::multi_index::ordered_non_unique<
    boost::multi_index::member<value_type, KeyType, &value_type::first>
>

Alternatively, if your keys need be unique and what you want is to update the existing entry you can do something like (untested):

auto& idToNameMap = userMap.get<1>();
auto& iter = idToNameMap.find(1);
if(iter != idToNameMap.end()) {
   idToNameMap.modify(iter, [](auto& p){p->second = "Tom";});
} 

The following is another way to do it that you might find more patalable (again, untested):

LinkedMap::iterator push_back_or_update(LinkedMap& m,const LinkedMap::value_type& x) {
    auto r=m.push_back(x);
    if(!r.second) m.replace(r.first, x);
    return r.first;
}

Upvotes: 3

Richard Hodges
Richard Hodges

Reputation: 69864

  • fixed bugs
  • replaced windows-specific tests with asserts (so I can test it on my system)
  • added demo of replacement through an index view
  • added tags

 

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <cassert>
#include <string>
#include <utility>

struct by_key {};
struct by_value {};


template<typename KeyType, typename MappedType>
struct LinkedMap {
    using tag_by_key = boost::multi_index::tag<by_key>;
    using tag_by_value = boost::multi_index::tag<by_value>;
    typedef std::pair<KeyType, MappedType> value_type;
    typedef boost::multi_index_container
    <
        value_type,
        boost::multi_index::indexed_by
        <
            boost::multi_index::random_access<tag_by_value>,
            boost::multi_index::ordered_unique
            <
                tag_by_key,
                boost::multi_index::member<value_type, KeyType, &value_type::first>
            >
        >
    > type;
};

int main()
{
    LinkedMap<int, std::wstring>::type userMap;
    userMap.push_back(std::make_pair(1, L"John"));

    // should not overwrite - as per std::map
    auto ib = userMap.push_back(std::make_pair(1, L"Tom"));
    assert(ib.second == false); // 2nd push_back should fail

    auto overwrite = [&](int key, auto&& val)
    {
        auto& idToNameMap = userMap.get<by_key>();
        auto iter = idToNameMap.find(key);
        if (iter == idToNameMap.end())
        {
            auto ib = userMap.push_back(std::make_pair(1, std::forward<decltype(val)>(val)));
            assert(ib.second);
        }
        else
        {
            idToNameMap.replace(iter, 
                std::make_pair(key, std::forward<decltype(val)>(val)));
        }
    };


    overwrite(1, L"Tom");
    auto& idToNameMap = userMap.get<by_key>();
    auto iter = idToNameMap.find(1);
    assert(std::wstring(L"Tom") == iter->second);
}

Upvotes: 1

Related Questions