C++ Boost


  • Description: Boost in modern C++ — what's been standardized, variant/visitor, geometry (distance/predicates/boolean ops/R-tree), Asio, Program Options, Multi-Index
  • My Notion Note ID: K2A-B2-4
  • Created: 2020-01-17
  • Updated: 2026-04-30
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. Two Roles in Modern C++

A peer-reviewed collection of mostly header-only C++ libraries. Many components — shared_ptr, function, bind, array, tuple, regex, chrono, thread, filesystem, optional, variant, any — incubated in Boost and were later adopted into the standard library.

Two reasons to use Boost today:

  1. Polyfill for older standards: boost::optional on a C++14 codebase, boost::filesystem before C++17.
  2. Production libraries with no std equivalent: Asio, Geometry, Multi-Index, Spirit, Beast, Graph, Program Options.

For new C++17/20/23 code, default to std:: for what std:: provides; use Boost for what it doesn't.

2. The Boost-to-std Story

Boost component Standardized as Year
boost::shared_ptr, weak_ptr std::shared_ptr, weak_ptr C++11
boost::scoped_ptr (≈ unique ownership) std::unique_ptr C++11
boost::function, boost::bind std::function, std::bind C++11
boost::array std::array C++11
boost::tuple std::tuple C++11
boost::regex std::regex C++11
boost::chrono std::chrono C++11
boost::thread, mutex, future std::thread, std::mutex, std::future C++11
boost::unordered_map/set std::unordered_map/set C++11
boost::filesystem std::filesystem C++17
boost::optional std::optional C++17
boost::variant std::variant C++17
boost::any std::any C++17
boost::string_view std::string_view C++17
boost::format std::format (loosely) C++20

For new code:

  • Default to std:: for the row-by-row migration above.
  • Reach for the Boost version when you need something std:: doesn't have: boost::variant supports recursive variants more cleanly via boost::recursive_wrapper, boost::filesystem has features that haven't reached std::filesystem, boost::optional accepts references (std::optional<T&> is not allowed), boost::asio has no std:: equivalent at all.

3. boost::variant and apply_visitor

boost::variant<T1, T2, ...> holds exactly one of its alternatives at a time. To handle whichever it holds without an if/switch ladder, use a visitor: a function object whose call operator is overloaded for each alternative.

#include <iostream>
#include <string>
#include <vector>
#include <boost/variant.hpp>

using Cell = boost::variant<double, std::string>;

struct Print : public boost::static_visitor<bool> {
    bool operator()(double d) const {
        std::cout << d << '\n';
        return true;
    }
    bool operator()(const std::string& s) const {
        std::cout << s << '\n';
        return true;
    }
};

int main() {
    std::vector<Cell> cells;
    cells.emplace_back(1.4234);
    cells.emplace_back(std::string("hello"));

    Print printer;
    for (const auto& c : cells) {
        boost::apply_visitor(printer, c);
    }
}

Key points:

  • static_visitor<R> declares the visitor's return type. All overloads must return R (or a type convertible to it). Use static_visitor<> for void.
  • The visitor must have an overload for every alternative. Compile fails otherwise — that exhaustiveness check is the whole point.
  • apply_visitor(visitor, variant) returns the visitor's return value.
  • For two variants together, pass both — the visitor needs operator()(A, B) overloads.

In modern C++, std::variant + std::visit provide the same pattern with cleaner ergonomics; you can also use a type-erased lambda overload set:

#include <variant>
template <class... Fs> struct overload : Fs... { using Fs::operator()...; };
template <class... Fs> overload(Fs...) -> overload<Fs...>;

std::variant<double, std::string> v = std::string("hi");
std::visit(overload{
    [](double d)             { std::cout << d; },
    [](const std::string& s) { std::cout << s; }
}, v);

Use Boost's version when you're stuck pre-C++17, or when you need recursive variants via boost::recursive_wrapper<T>.

4. boost::geometry

Boost.Geometry implements OGC-style 2D/3D geometry: points, segments, polylines (linestrings), polygons, multi-geometries, predicates, boolean operations, and a high-quality R-tree spatial index. It supports both Cartesian and spherical/geographic coordinate systems via coordinate strategies — you can compute haversine distance over lat/lon directly.

4.1 Geometries and Setup

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>

namespace bg = boost::geometry;

using Point      = bg::model::d2::point_xy<double>;
using Segment    = bg::model::segment<Point>;
using Linestring = bg::model::linestring<Point>;        // open polyline
using Polygon    = bg::model::polygon<Point>;            // outer + 0..N inner rings
using MultiPoly  = bg::model::multi_polygon<Polygon>;
using Box        = bg::model::box<Point>;

// WKT (Well-Known Text) is the easiest construction path
Polygon poly;
bg::read_wkt("POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))", poly);

Linestring path;
bg::read_wkt("LINESTRING(0 0, 1 1, 2 0, 3 1)", path);

// Polygons must be CLOSED (last vertex == first) and have valid orientation.
// Use bg::correct() if your input may not be normalized:
bg::correct(poly);

// For lat/lon, swap the point type:
using GeoPoint = bg::model::point<double, 2, bg::cs::geographic<bg::degree>>;

For Cartesian work, point_xy<double> (or point<double, N, bg::cs::cartesian> for higher dims) is the default. For Earth-scale work, the geographic coordinate system computes distances and predicates on the WGS-84 ellipsoid.

4.2 Distance and Length

Point a(0, 0), b(3, 4);
double d_pp = bg::distance(a, b);                    // 5.0  -- point to point

Segment seg(Point(0, 0), Point(10, 0));
Point   q(5, 3);
double d_ps = bg::distance(q, seg);                  // 3.0  -- point to segment

double d_pl = bg::distance(q, path);                 // point to linestring
double d_pp_poly = bg::distance(q, poly);            // point to polygon (0 if inside)
double d_ll = bg::distance(path, poly);              // linestring to polygon

double len  = bg::length(path);                      // polyline arc length
double per  = bg::perimeter(poly);                   // polygon perimeter
double area = bg::area(poly);

// Geographic distance (haversine on spheroid)
GeoPoint sf(-122.4194, 37.7749);
GeoPoint ny(-74.0060,  40.7128);
double d_geo = bg::distance(sf, ny);                 // metres on WGS-84

// Centroid (works for points/linestrings/polygons)
Point c;
bg::centroid(poly, c);

distance always returns 0 when the geometries touch or overlap. To get a "signed distance" (positive outside, negative inside) you need to combine distance with within.

4.3 Predicates

Point p(5, 5);

bool within_p   = bg::within(p, poly);        // strict interior
bool covered_p  = bg::covered_by(p, poly);    // interior OR boundary
bool disjoint_p = bg::disjoint(p, poly);      // no point in common
bool intersects = bg::intersects(p, poly);    // !disjoint

// Polygon vs polygon
Polygon other; bg::read_wkt("POLYGON((5 5, 5 15, 15 15, 15 5, 5 5))", other);

bool a_within_b   = bg::within(poly, other);   // strictly inside (no boundary contact)
bool a_covered_b  = bg::covered_by(poly, other);
bool b_contains_a = bg::within(poly, other) || bg::equals(poly, other);  // OGC contains
bool overlap      = bg::overlaps(poly, other); // partial overlap, neither contains the other
bool touches      = bg::touches(poly, other);  // share boundary, no interior overlap
bool equal        = bg::equals(poly, other);
bool crosses      = bg::crosses(path, poly);   // for line/polygon intersections that cut through

The distinction between within (strict interior) and covered_by (interior or boundary) catches a surprising number of bugs. The OGC predicate names also have subtle definitions — intersects is the union of all "they meet at all" cases; overlaps requires interior overlap and neither shape contains the other; touches requires only boundary contact.

4.4 Boolean Operations and Polygon Algebra

MultiPoly out;
bg::intersection(poly, other, out);      // A ∩ B
bg::union_(poly, other, out);            // A ∪ B  (union_ — `union` is reserved)
bg::difference(poly, other, out);        // A \ B
bg::sym_difference(poly, other, out);    // (A \ B) ∪ (B \ A)

// Buffer (offset polygon outward/inward by a distance)
MultiPoly buffered;
bg::buffer(poly, buffered,
    bg::strategy::buffer::distance_symmetric<double>(0.5),
    bg::strategy::buffer::side_straight(),
    bg::strategy::buffer::join_round(),
    bg::strategy::buffer::end_flat(),
    bg::strategy::buffer::point_circle(8));

// Simplify a polyline or polygon (Douglas-Peucker)
Linestring simple;
bg::simplify(path, simple, /*max_distance=*/0.1);

// Convex hull of any geometry
Polygon hull;
bg::convex_hull(poly, hull);

// Envelope (axis-aligned bounding box)
Box bbox;
bg::envelope(poly, bbox);

// Reverse a linestring or polygon ring orientation
bg::reverse(path);

union_, not unionunion is a C++ keyword. Same for or_/and_-like overloads in some Boost components.

For correctness, polygons fed to boolean ops must be valid: closed rings, correct orientation (outer CCW, holes CW under the OGC convention), no self-intersections. Use bg::is_valid(poly, msg) to check; bg::correct(poly) fixes the easy cases (closure, orientation).

4.5 R-Tree Spatial Index

For "find me everything near here" queries over a static or slowly-changing set of geometries, an R-tree is the right structure. Boost.Geometry ships one.

#include <boost/geometry/index/rtree.hpp>
namespace bgi = bg::index;

using Value = std::pair<Box, std::size_t>;   // (bbox, payload-id)

bgi::rtree<Value, bgi::quadratic<16>> rtree;
for (std::size_t i = 0; i < things.size(); ++i) {
    rtree.insert({things[i].bbox(), i});
}

// or bulk-load from a range -- much faster than insert-one-by-one
std::vector<Value> bulk = ...;
bgi::rtree<Value, bgi::quadratic<16>> rtree2(bulk.begin(), bulk.end());

Splitting algorithm template parameter:

  • quadratic<N> — fast inserts, decent queries. Good default.
  • linear<N> — fastest inserts, poorest query quality.
  • rstar<N> — slowest inserts, best query quality. Pick if you build once and query a lot.

Query predicates

std::vector<Value> hits;
Box q({4, 4}, {6, 6});

// Spatial predicates -- all match against each value's bbox
rtree.query(bgi::intersects(q),    std::back_inserter(hits));
rtree.query(bgi::within(q),        std::back_inserter(hits));   // bbox strictly inside q
rtree.query(bgi::covered_by(q),    std::back_inserter(hits));
rtree.query(bgi::contains(q),      std::back_inserter(hits));   // bbox contains q
rtree.query(bgi::disjoint(q),      std::back_inserter(hits));
rtree.query(bgi::overlaps(q),      std::back_inserter(hits));

// Nearest-neighbor: k closest values to a query point
std::vector<Value> nn;
rtree.query(bgi::nearest(Point(5, 5), 5), std::back_inserter(nn));

// Custom filter via satisfies()
rtree.query(bgi::intersects(q) &&
            bgi::satisfies([](const Value& v) {
                return v.second % 2 == 0;   // application-level predicate
            }),
            std::back_inserter(hits));

Predicates compose with !, &&, ||:

rtree.query(bgi::intersects(q) && !bgi::within(small_box),
            std::back_inserter(hits));

For streaming results (avoid materialising into a vector), use the query iterators:

for (auto it = rtree.qbegin(bgi::intersects(q));
     it != rtree.qend();
     ++it) {
    use(*it);
    if (done) break;     // early exit; nearest() ranks results lazily
}

The R-tree indexes bounding boxes, not arbitrary shapes. For polygon containment queries, store (bbox, polygon-id) pairs and use the R-tree to filter candidates, then run the precise predicate on the polygon itself. This two-stage pattern (broad-phase → narrow-phase) is the standard way to make polygon-containment queries scale.

5. boost::asio

Asio is the de facto C++ async I/O library — the basis for the Networking TS proposal that has been circling C++ standardization for years. It covers TCP/UDP sockets, timers, signal handling, file descriptors, and serial ports, on top of an event-loop abstraction (io_context).

#include <boost/asio.hpp>
namespace asio = boost::asio;

asio::io_context io;
asio::steady_timer timer(io, std::chrono::seconds(1));
timer.async_wait([](const boost::system::error_code& ec) {
    if (!ec) std::cout << "tick\n";
});
io.run();   // blocks until all work completes

Modern Asio supports C++20 coroutines, which let you write linear-looking async code:

asio::awaitable<void> echo(asio::ip::tcp::socket s) {
    char buf[1024];
    for (;;) {
        std::size_t n = co_await s.async_read_some(asio::buffer(buf), asio::use_awaitable);
        co_await asio::async_write(s, asio::buffer(buf, n), asio::use_awaitable);
    }
}

For HTTP/WebSocket on top of Asio, see Boost.Beast.

6. boost::program_options

A typed command-line and config-file parser.

#include <boost/program_options.hpp>
namespace po = boost::program_options;

po::options_description desc("Allowed options");
desc.add_options()
    ("help",          "show help")
    ("threads",       po::value<int>()->default_value(4), "worker threads")
    ("input",         po::value<std::string>()->required(), "input path");

po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);

if (vm.count("help")) { std::cout << desc << '\n'; return 0; }
po::notify(vm);     // throws if required options are missing

int threads = vm["threads"].as<int>();
auto input  = vm["input"].as<std::string>();

positional_options_description lets unnamed arguments map to a specific option. parse_config_file reads INI-style files using the same descriptions.

7. boost::multi_index

A container with multiple simultaneous indices over the same set of elements — like an in-memory table with several keys. Each index can be ordered, hashed, sequenced (insertion-order), or random-access.

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

namespace mi = boost::multi_index;

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

using Table = mi::multi_index_container<
    Employee,
    mi::indexed_by<
        mi::hashed_unique <mi::member<Employee, int,         &Employee::id>>,
        mi::ordered_non_unique<mi::member<Employee, std::string, &Employee::name>>,
        mi::ordered_non_unique<mi::member<Employee, int,         &Employee::salary>>
    >
>;

Table t;
t.insert({1, "alice", 100000});
t.insert({2, "bob",   90000});

auto& by_id      = t.get<0>();
auto& by_name    = t.get<1>();
auto& by_salary  = t.get<2>();

auto it = by_id.find(1);
for (auto& e : by_salary) { /* iterate by salary */ }

This is the right tool when you would otherwise maintain several std::maps with parallel state and have to keep them in sync by hand.

8. Other Useful Libraries at a Glance

Library Purpose std alternative?
boost::beast HTTP, WebSocket on Asio None
boost::graph (BGL) Graph algorithms (BFS/DFS, Dijkstra, A*, ...) None
boost::spirit Recursive-descent parsers via expression templates None
boost::lexical_cast lexical_cast<int>("42") std::from_chars (faster, no exceptions)
boost::tokenizer Split strings std::ranges::split_view (C++20; redesigned in C++23, original kept as lazy_split_view)
boost::iostreams Filtered streams (gzip, bzip2, counted, tee, ...) None
boost::interprocess Shared memory, named mutexes None
boost::test Unit-test framework None (in std)
boost::pool Object pools, fixed-size allocators None
boost::circular_buffer Fixed-capacity ring buffer None (use std::deque or roll your own)
boost::date_time Calendar/time arithmetic std::chrono (C++20 calendar/timezone)
boost::process Cross-platform process spawning None

9. Build Integration

Boost is mostly header-only, but a handful of libraries still build to .a/.so (filesystem, program_options, iostreams, regex, thread, chrono, serialization, ...). Of the libraries used in this note, only Program Options requires linking — boost::system has been header-only since Boost 1.69 (the Boost::system stub library was kept for backward compatibility through 1.88 and removed in Boost 1.89, so on recent Boost you must drop system from COMPONENTS), and boost::geometry (including the R-tree) is fully header-only.

CMake:

find_package(Boost 1.81 REQUIRED COMPONENTS program_options)
# On Boost < 1.89 you can also list `system` in COMPONENTS and link
# Boost::system, but it is no longer required and is removed in 1.89+.

target_link_libraries(myapp PRIVATE
    Boost::program_options
    Boost::headers          # for header-only libs (geometry, multi_index, ...)
)

The Boost:: imported targets carry includes and dependencies; never use the bare ${Boost_LIBRARIES} variable in modern CMake.

10. References