From this question:
Sorted vector (aka flat_set) for objects (pointers) with custom embedded key (functor used)
This is an example of a multi-index container. Note: this is not a container as defined by the standard (a lot of methods and types required for that are still missing). It is a container in that you can add objects and you can get iterators to iterate over the container in particular orders. But adding the required methods and types should not be that hard.
The interface to this class is:
MI<TypeToBeStored, ListOfIndexingTypes...>
For each type in <ListOfIndexingTypes...> it creates a separate index into the container. This allows you to iterate through container in different orders.
See example at the end for usage info.
template<typename T, typename... O>
class MI
{
typedef std::vector<T> Storage;
typedef std::pair<Storage*, std::size_t> IndexItem;
template<typename Order>
struct StorageOrder
{
bool operator()(IndexItem const& lhs, IndexItem const& rhs) const
{
Order order;
return order(lhs.first->operator[](lhs.second), rhs.first->operator[](rhs.second));
}
};
template<typename Order>
class Index: public std::set<IndexItem, StorageOrder<Order>>
{};
template<typename Order>
struct Ordering
{
Index<Order>& index;
Storage& storage;
Ordering(Index<Order>& index, Storage& storage): index(index), storage(storage) {}
struct iterator: public std::iterator<std::bidirectional_iterator_tag, T>
{
typedef typename Index<Order>::iterator ExtIterator;;
Storage& store;
ExtIterator i;
iterator(Storage& store, ExtIterator i): store(store), i(i) {}
T& operator*() {return store[i->second];}
T* operator->() {return &store[i->second];}
iterator& operator++ () {++i;return *this;}
iterator operator++ (int) {iterator result(*this);++i;return result;}
bool operator!=(iterator const& rhs) const {return i != rhs.i;}
};
};
typedef std::tuple<Index<O>...> Indexes;
Storage storage;
Indexes index;
template<typename Order>
bool addIndex(IndexItem const& item)
{
auto& anIndex = std::get<Index<Order>>(index);
anIndex.insert(item);
return true;
}
public:
void add(T const& value)
{
std::size_t id = storage.size();
storage.push_back(value);
auto item = IndexItem(&storage, id);
std::make_tuple(addIndex<O>(item)...);
}
template<typename... A>
void emplace(A&&... param)
{
std::size_t id = storage.size();
storage.emplace_back(std::forward<A>(param)...);
auto item = IndexItem(&storage, id);
std::make_tuple(addIndex<O>(item)...);
}
template<typename Order>
Ordering<Order> order()
{
auto& x = std::get<Index<Order>>(index);
return Ordering<Order>(x, storage);
}
};
So if now look at usage examples:
int main()
{
// Define a container for User.
// It has two index into the container (one on Name the other on Age).
MI<User, NameOrder, AgeOrder> index;
// Add some items (could be better as
index.insert({"John", 52});
index.insert({"June", 22});
index.emplace("Steve", 76);
// Loop over the container by Name order
for(auto loop: index.order<NameOrder>())
{
std::cout << loop << "\n";
}
std::cout << "\n\n\n";
// Loop over the container by Age order
for(auto loop: index.order<AgeOrder>())
{
std::cout << loop << "\n";
}
}
Then to implement User
class and orderings on the user.
class User
{
std::string name;
int age;
public:
User(std::string const& name, int age) : name(name), age(age) {}
friend std::ostream& operator<<(std::ostream& str, User const& data)
{
return str << data.name << " : " << data.age;
}
friend class NameOrder;
friend class AgeOrder;
};
struct NameOrder
{
bool operator()(User const& lhs, User const& rhs)
{
return lhs.name < rhs.name;
}
};
struct AgeOrder
{
bool operator()(User const& lhs, User const& rhs)
{
return lhs.age < rhs.age;
}
};