c++booststdmapboost-multi-index

Problem using boost::multi_index with composite key member functions


I have the following container:

using KeyValue = mutable_pair<Key, Value>;
using MyContainer = boost::multi_index_container<
    KeyValue,
    boost::multi_index::indexed_by<
        boost::multi_index::hashed_unique<
            boost::multi_index::tag<KeyValueTag>,
            boost::multi_index::composite_key<
                KeyValue,
                boost::multi_index::const_mem_fun<KeyValue::first_type, unsigned int, &KeyValue::first_type::foo>,
                boost::multi_index::const_mem_fun<KeyValue::first_type, unsigned int, &KeyValue::first_type::bar>,
                boost::multi_index::const_mem_fun<KeyValue::first_type, unsigned int, &KeyValue::first_type::baz>,
            >
        >,
        boost::multi_index::hashed_non_unique<
            boost::multi_index::tag<BazTag>,
            boost::multi_index::const_mem_fun<KeyValue::first_type, unsigned int, &KeyValue::first_type::baz>
        >,
        boost::multi_index::hashed_non_unique<
            boost::multi_index::tag<BarTag>,
            boost::multi_index::const_mem_fun<KeyValue::first_type, unsigned int, &KeyValue::first_type::bar>
        >
    >
>;

Where mutable_pair is the boost provided example for maps, and Key is a class that contains const member accessors for foo, bar and baz.

The code compiles fine, however when trying to query by any index ie:

MyContainer c;
const auto& byBaz = c.get<BazTag>();
const auto it = byBaz.find(11);
// or
const auto [beg, end] = byBaz.equal_range(11);

it complains of

<long instantiation template error>
in mem_fun.hpp:58:23: error: no match for ‘operator*’ (operand type is ‘const mutable_pair<Key, Value>’)
   58 |     return operator()(*x);

What am I missing? I've been struggling with this for hours :(


Solution

  • The code compiles fine

    That's because templates members aren't instantiated unless you use them. You don't have valid indexes for your element type.

    Your indexes are trying the equivalent of

    KeyValue pair;
    unsigned Key::(*pfoo)() = &Key::foo;
    
    pair.*pfoo
    

    Instead of

    pair.first.*pfoo;
    

    You need accessors for KeyValue, not Key

    unsigned int getFoo(const KeyValue & pair) {
        return pair.first.foo();
    }
    unsigned int getBar(const KeyValue & pair) {
        return pair.first.bar();
    }
    unsigned int getBaz(const KeyValue & pair) {
        return pair.first.baz();
    }
    
    using MyContainer = boost::multi_index_container<
        KeyValue,
        boost::multi_index::indexed_by<
            boost::multi_index::hashed_unique<
                boost::multi_index::tag<KeyValueTag>,
                boost::multi_index::composite_key<
                    KeyValue,
                    boost::multi_index::global_fun<KeyValue, unsigned int, &getFoo>,
                    boost::multi_index::global_fun<KeyValue, unsigned int, &getBar>,
                    boost::multi_index::global_fun<KeyValue, unsigned int, &getBaz>,
                >
            >,
            boost::multi_index::hashed_non_unique<
                boost::multi_index::tag<BazTag>,
                boost::multi_index::global_fun<KeyValue, unsigned int, &getBaz>
            >,
            boost::multi_index::hashed_non_unique<
                boost::multi_index::tag<BarTag>,
                boost::multi_index::global_fun<KeyValue, unsigned int, &getBar>
            >
        >
    >;
    

    Aside: If you have C++17 and boost 1.69 or later, you can use a much terser syntax for keys:

    boost::multi_index::key<&getFoo, &getBar, &getBaz>
    boost::multi_index::key<&getBar>