Skip to main content

Updated Sep 11, 2026

Boost.MultiIndex

Boost.MultiIndex lets you build a single container with multiple simultaneous indexes over the same set of elements. Instead of maintaining separate std::maps and std::unordered_maps that you must keep in sync manually, you declare all your access patterns up front and the library handles the bookkeeping. Think of it as an in-memory database table with multiple columns indexed.

The problem it solves

You have a collection of records and you need to look them up by name (ordered), by ID (hashed), and iterate them in insertion order. The naive approach — three separate containers pointing at the same data — is error-prone: every insert, update, and erase must touch all three, and a missed update means a corrupted index. multi_index_container eliminates this entire class of bugs.

How it works​

Each element exists once in memory. The indexes are lightweight node structures that point into the shared element storage. Inserting an element updates all indexes atomically; erasing removes it from all indexes at once.

Defining a multi-indexed container​

employee_index.cpp
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/member.hpp>
#include <string>
#include <iostream>

namespace bmi = boost::multi_index;

struct Employee {
int id;
std::string name;
int department;
};

using EmployeeTable = boost::multi_index_container<
Employee,
bmi::indexed_by<
bmi::ordered_unique<bmi::member<Employee, int, &Employee::id>>,
bmi::hashed_non_unique<bmi::member<Employee, std::string, &Employee::name>>,
bmi::ordered_non_unique<bmi::member<Employee, int, &Employee::department>>
>
>;

int main() {
EmployeeTable table;
table.insert({1, "Alice", 10});
table.insert({2, "Bob", 20});
table.insert({3, "Carol", 10});

// Lookup by id (index 0 — ordered_unique)
auto& by_id = table.get<0>();
auto it = by_id.find(2);
if (it != by_id.end())
std::cout << "ID 2: " << it->name << "\n";

// Lookup by name (index 1 — hashed)
auto& by_name = table.get<1>();
auto range = by_name.equal_range("Alice");
for (auto r = range.first; r != range.second; ++r)
std::cout << "Alice -> dept " << r->department << "\n";

// All employees in department 10 (index 2 — ordered by dept)
auto& by_dept = table.get<2>();
auto dept_range = by_dept.equal_range(10);
for (auto r = dept_range.first; r != dept_range.second; ++r)
std::cout << "Dept 10: " << r->name << "\n";
}

Index types​

Index typeEquivalent toUse case
ordered_uniquestd::setprimary key, sorted access
ordered_non_uniquestd::multisetsorted grouping (e.g. by department)
hashed_uniquestd::unordered_setfast exact lookup by unique key
hashed_non_uniquestd::unordered_multisetfast exact lookup, duplicates allowed
sequencedstd::listpreserves insertion order
random_accessstd::vectorO(1)O(1) positional access by index

Modifying elements​

Elements in a multi_index_container are conceptually immutable through iterators — you cannot write through an iterator because a change to a key field would silently corrupt other indexes. Instead, use modify:

modify.cpp
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <string>

namespace bmi = boost::multi_index;

struct Record { int id; std::string label; };

using Table = boost::multi_index_container<
Record,
bmi::indexed_by<bmi::ordered_unique<bmi::member<Record, int, &Record::id>>>
>;

int main() {
Table t;
t.insert({1, "old"});

auto& idx = t.get<0>();
auto it = idx.find(1);
idx.modify(it, [](Record& r) { r.label = "new"; });
// all indexes stay consistent
}
Never cast away const to modify elements

Modifying an element through a const-cast on the iterator bypasses the index update logic. The container's internal invariants break silently — lookups on other indexes will return wrong results or crash. Always use the modify member function.

Composite keys​

When you need to index by multiple fields together (like a compound database index), use composite_key:

composite.cpp
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <string>

namespace bmi = boost::multi_index;

struct LogEntry { std::string host; int severity; std::string message; };

using LogTable = boost::multi_index_container<
LogEntry,
bmi::indexed_by<
bmi::ordered_non_unique<
bmi::composite_key<
LogEntry,
bmi::member<LogEntry, std::string, &LogEntry::host>,
bmi::member<LogEntry, int, &LogEntry::severity>
>
>
>
>;
Think of it as a database

If you find yourself maintaining parallel containers and writing synchronisation logic, stop and consider multi_index_container. Declare the indexes you need, let the library maintain them, and focus on your domain logic instead.

See also​