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++
- Peer-reviewed collection of mostly header-only C++ libs.
- Many components —
shared_ptr,function,bind,array,tuple,regex,chrono,thread,filesystem,optional,variant,any— incubated in Boost, later adopted into stdlib.
2 reasons to use Boost today:
- Polyfill for older standards —
boost::optionalon C++14,boost::filesystempre-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 it 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 Boost when
std::lacks:boost::variantsupports recursive variants more cleanly viaboost::recursive_wrapper;boost::filesystemhas featuresstd::filesystemlacks;boost::optionalaccepts references (std::optional<T&>is not allowed);boost::asiohas nostd::equivalent.
3. boost::variant and apply_visitor
boost::variant<T1, T2, ...>holds exactly one alternative.- To handle without
if/switchladder → use visitor: function object withoperator()overloaded per 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 return type. All overloads must returnR(or convertible).static_visitor<>forvoid. -
Visitor must overload for every alternative. Compile fails otherwise — exhaustiveness check is the point.
-
apply_visitor(visitor, variant)— returns visitor's return value. -
2 variants together → pass both; visitor needs
operator()(A, B)overloads. -
Modern C++:
std::variant+std::visitgive same pattern, cleaner ergonomics. 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 pre-C++17, or for recursive variants via
boost::recursive_wrapper<T>.
4. boost::geometry
- OGC-style 2D/3D geometry: points, segments, polylines (linestrings), polygons, multi-geometries, predicates, boolean ops, high-quality R-tree.
- Supports Cartesian + spherical/geographic via coordinate strategies — 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>>;
- Cartesian:
point_xy<double>(orpoint<double, N, bg::cs::cartesian>). - Earth-scale: geographic coordinate system computes distances/predicates on 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);
distancereturns 0 when geometries touch/overlap. For "signed distance" (positive outside, negative inside) → combinedistancewithwithin.
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
within(strict interior) vscovered_by(interior or boundary) — catches many bugs.- OGC predicate distinctions:
intersects— union of all "they meet at all" cases.overlaps— interior overlap AND neither shape contains the other.touches— boundary contact only.
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_, notunion—unionis a C++ keyword. Same foror_/and_-like overloads.- Boolean ops require valid polygons: closed rings, correct orientation (outer CCW, holes CW per OGC), no self-intersections.
bg::is_valid(poly, msg)to check;bg::correct(poly)fixes easy cases (closure, orientation).
4.5 R-Tree Spatial Index
- For "find me everything near here" queries over static/slow-changing set → R-tree.
#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 param:
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));
- Streaming results (no materialize) → 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
}
- R-tree indexes bounding boxes, not arbitrary shapes.
- Polygon containment queries → store
(bbox, polygon-id)pairs; R-tree filters candidates, then run precise predicate on polygon. - 2-stage pattern (broad-phase → narrow-phase) — standard way to scale polygon-containment queries.
5. boost::asio
- De facto C++ async I/O lib. Basis for Networking TS proposal.
- Covers TCP/UDP sockets, timers, signal handling, file descriptors, serial ports — on top of 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 → 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);
}
}
- HTTP/WebSocket on Asio → see Boost.Beast.
6. boost::program_options
- Typed CLI + 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→ unnamed args map to a specific option.parse_config_filereads INI-style files using same descriptions.
7. boost::multi_index
- Container with multiple simultaneous indices over same elements. Like in-memory table with several keys.
- Each index can be: ordered, hashed, sequenced (insertion-order), 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 */ }
- Right tool when you'd otherwise maintain several
std::maps with parallel state, hand-syncing them.
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 mostly header-only, but a handful still build to
.a/.so(filesystem,program_options,iostreams,regex,thread,chrono,serialization, ...). - Of libs used in this note, only Program Options requires linking.
boost::system— header-only since Boost 1.69 (Boost::systemstub kept for backcompat through 1.88, removed in 1.89; on recent Boost dropsystemfromCOMPONENTS).boost::geometry(including R-tree) — 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, ...)
)
Boost::imported targets carry includes + deps. Never use bare${Boost_LIBRARIES}in modern CMake.
10. References
- Boost main site — download + 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 + tutorials.
- Boost.ProgramOptions docs.
- cppreference:
std::variantandstd::visit— standard alternative.