MultiIndex containers -- offering vector and set access

Solution 1:

Random access index would match the "vector" interface.

An ordered unique index would match the "set" interface.

However, if you have a unique index, this will prevent insertion of duplicates. So, you would get:

Live On Compiler Explorer

#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index_container.hpp>
#include <fmt/ranges.h>

namespace bmi = boost::multi_index;

using Table = bmi::multi_index_container< //
    int,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct asVector>>,
        bmi::ordered_unique<bmi::tag<struct asSet>, bmi::identity<int>>>>;

int main()
{
    Table data{ 6, 6, 5, 5, 4, 4 };

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),    //
               data.get<asSet>());
}

Printing

As vec {6, 5, 4}
As set {4, 5, 6}

Now, I think the "best" you could do with this is to make the order index non-unique (so, mimicking a std::multiset instead of std::set): Live On Compiler Explorer

bmi::ordered_non_unique<bmi::tag<struct asSet>, bmi::identity<int>>

Printing

As vec [6, 6, 5, 5, 4, 4]
As set {4, 4, 5, 5, 6, 6}

If you want to traverse unique elements, using a range adaptor would be minimally costly:

  1. Using Boost Live

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),     //
               data.get<asSet>() | boost::adaptors::uniqued);
    
  2. Using RangeV3 Live

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),     //
               data.get<asSet>() | ranges::views::unique);
    
  3. Using Standard Ranges; I couldn't make this work but it should really be something like std::ranges::unique(data.get<asSet>())

All printing

As vec {6, 6, 5, 5, 4, 4}
As set {4, 5, 6}

Other Ideas

If you don't require random access, then sequenced index might be preferrable for you. And note that this interface comes with the handy unique() and sort() methods (just like std::list).

UPDATE To The Comments

Here's a rolled-in-one response to the comments:

Live On Compiler Explorer

#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <fmt/ranges.h>
#include <random>

namespace bmi = boost::multi_index;

template <typename T>
using ListSet = bmi::multi_index_container< //
    T,
    bmi::indexed_by<
        bmi::sequenced<bmi::tag<struct byInsertion>>,                   //
        bmi::ordered_unique<bmi::tag<struct Ordered>, bmi::identity<T>> //
        >>;

int main()
{
    ListSet<int> data;

    std::mt19937 prng{99}; // "random" seed
    for (std::uniform_int_distribution d(1, 10); data.size() < 5;) {
        int el = d(prng);
        if (auto [it, unique] = data.push_back(el); not unique) {
            fmt::print("Element {} duplicates index #{}\n", el,
                    std::distance(begin(data), it));
        }
    }

    fmt::print("By insertion {}\nOrdered {}\n", data, data.get<Ordered>());
}

Prints

Element 9 duplicates index #3
Element 8 duplicates index #1
By insertion [7, 8, 5, 9, 1]
Ordered {1, 5, 7, 8, 9}