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++
- 2. The Boost-to-
stdStory - 3.
boost::variantandapply_visitor - 4.
boost::geometry - 5.
boost::asio - 6.
boost::program_options - 7.
boost::multi_index - 8. Other Useful Libraries at a Glance
- 9. Build Integration
- 10. References
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:
- Polyfill for older standards:
boost::optionalon a C++14 codebase,boost::filesystembefore C++17. - Production libraries with no
stdequivalent: 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::variantsupports recursive variants more cleanly viaboost::recursive_wrapper,boost::filesystemhas features that haven't reachedstd::filesystem,boost::optionalaccepts references (std::optional<T&>is not allowed),boost::asiohas nostd::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 returnR(or a type convertible to it). Usestatic_visitor<>forvoid.- 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 union — union 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
- Boost main site — download and documentation index.
- Boost.Variant docs —
apply_visitor,static_visitor, recursive variants. - Boost.Geometry docs — geometries, predicates, R-tree.
- Boost.Asio docs — sockets, timers, coroutines.
- Boost.MultiIndex docs — index types and tutorials.
- Boost.ProgramOptions docs.
- cppreference:
std::variantandstd::visit— the standard alternative.