# pybind11 pybind11 is a lightweight, header-only C++ library that enables seamless interoperability between C++ and Python. Its primary purpose is to create Python bindings for existing C++ code with minimal boilerplate, using compile-time type introspection via C++11 features such as variadic templates, lambda functions, and tuples. Unlike Boost.Python, which it was inspired by, pybind11 carries no external dependencies beyond the C++ standard library and Python itself (CPython 3.8+, PyPy3 7.3.17+, or GraalPy 24.1+), resulting in binaries that are typically 2–5x smaller and faster to compile. The library supports a comprehensive range of C++ constructs: free functions and class methods with overloading, instance and static attributes, properties, enumerations, inheritance with automatic downcasting, custom exceptions, iterators, smart pointers (`std::unique_ptr`, `std::shared_ptr`, and `py::smart_holder`), STL container conversions, NumPy buffer protocol integration, vectorized array operations, and embedding of the Python interpreter inside C++ applications. All standard headers are prefixed `pybind11/` and the namespace alias `namespace py = pybind11` is used throughout this documentation. --- ## PYBIND11_MODULE — Define an extension module The `PYBIND11_MODULE` macro is the entry point for every pybind11 extension. It defines the function that Python calls when importing the module. The optional `py::mod_gil_not_used()` tag signals that the module is safe to use without the GIL (for free-threaded CPython builds). ```cpp // example.cpp #include namespace py = pybind11; int add(int i, int j) { return i + j; } double multiply(double x, double y = 1.0) { return x * y; } PYBIND11_MODULE(example, m, py::mod_gil_not_used()) { m.doc() = "A minimal pybind11 demonstration module"; // Bind a free function with keyword args and default values m.def("add", &add, "Add two integers", py::arg("i"), py::arg("j")); m.def("multiply", &multiply, "Multiply two numbers", py::arg("x"), py::arg("y") = 1.0); // Export a constant attribute m.attr("PI") = 3.14159265358979; } ``` ```bash # Compile (Linux/macOS) c++ -O3 -Wall -shared -std=c++11 -fPIC \ $(python3 -m pybind11 --includes) \ example.cpp -o example$(python3 -m pybind11 --extension-suffix) ``` ```python import example print(example.add(3, 4)) # 7 print(example.multiply(5.0)) # 5.0 (default y=1.0) print(example.multiply(x=3, y=2)) # 6.0 print(example.PI) # 3.14159265358979 ``` --- ## py::class_ — Bind C++ classes and structs `py::class_` exposes a C++ type to Python, including constructors, methods, and attributes. Methods are registered with `.def()`, read-write fields with `.def_readwrite()`, and computed properties with `.def_property()`. In pybind11 v3 it is recommended to add `py::smart_holder` as a second template argument for safe smart-pointer interop. ```cpp #include namespace py = pybind11; class Pet { public: Pet(const std::string &name, int age) : name(name), age(age) {} std::string repr() const { return ""; } std::string name; int age; private: std::string secret = "treats"; }; PYBIND11_MODULE(pets, m, py::mod_gil_not_used()) { py::class_(m, "Pet") .def(py::init(), py::arg("name"), py::arg("age")) // Direct field access .def_readwrite("name", &Pet::name) // Read-only computed property via getter/setter .def_property("age", [](const Pet &p) { return p.age; }, [](Pet &p, int v) { if (v < 0) throw std::invalid_argument("age must be >= 0"); p.age = v; }) // Lambda bound as __repr__ .def("__repr__", &Pet::repr) // Dynamic attribute support (adds __dict__) // .def(...) with py::dynamic_attr() in constructor instead: ; } ``` ```python import pets p = pets.Pet("Molly", 3) print(repr(p)) # p.name = "Charlie" p.age = 5 print(p.age) # 5 try: p.age = -1 # raises ValueError: age must be >= 0 except Exception as e: print(e) ``` --- ## py::class_ with dynamic attributes Adding `py::dynamic_attr()` to the `py::class_` constructor allows Python-side code to attach arbitrary attributes at runtime, exactly like a native Python class. ```cpp #include namespace py = pybind11; struct Widget { int value = 0; }; PYBIND11_MODULE(widgets, m, py::mod_gil_not_used()) { py::class_(m, "Widget", py::dynamic_attr()) .def(py::init<>()) .def_readwrite("value", &Widget::value); } ``` ```python import widgets w = widgets.Widget() w.value = 42 # C++ field w.label = "button" # dynamic Python attribute w.enabled = True print(w.__dict__) # {'label': 'button', 'enabled': True} ``` --- ## py::init — Constructors and multiple overloads `py::init()` wraps a constructor. Multiple constructors are registered by chaining `.def(py::init<...>())` calls. Use `py::overload_cast` to disambiguate overloaded methods. ```cpp #include #include namespace py = pybind11; struct Point { double x, y; Point() : x(0), y(0) {} Point(double x, double y) : x(x), y(y) {} void set(double x_) { x = x_; } void set(double x_, double y_) { x = x_; y = y_; } double length() const { return std::sqrt(x*x + y*y); } }; PYBIND11_MODULE(geometry, m, py::mod_gil_not_used()) { py::class_(m, "Point") .def(py::init<>()) // default ctor .def(py::init(), // (x, y) ctor py::arg("x"), py::arg("y")) // Disambiguate overloaded set() .def("set", py::overload_cast(&Point::set), "Set x only", py::arg("x")) .def("set", py::overload_cast(&Point::set), "Set x and y", py::arg("x"), py::arg("y")) .def("length", &Point::length) .def("__repr__", [](const Point &p) { return "Point(" + std::to_string(p.x) + ", " + std::to_string(p.y) + ")"; }); } ``` ```python import geometry p1 = geometry.Point() # default: (0, 0) p2 = geometry.Point(3.0, 4.0) print(p2.length()) # 5.0 p2.set(0.0) # set x only p2.set(6.0, 8.0) # set x and y print(p2.length()) # 10.0 ``` --- ## Inheritance and automatic downcasting C++ inheritance hierarchies are mirrored in Python by passing the C++ base type as a template parameter to `py::class_`. For polymorphic types (those with at least one virtual function), pybind11 automatically downcasts base-class pointers to the actual derived type at runtime. ```cpp #include namespace py = pybind11; struct Animal { virtual ~Animal() = default; virtual std::string sound() const = 0; std::string name; Animal(const std::string &n) : name(n) {} }; struct Dog : Animal { using Animal::Animal; std::string sound() const override { return "woof!"; } std::string fetch() const { return name + " fetches the ball!"; } }; struct Cat : Animal { using Animal::Animal; std::string sound() const override { return "meow!"; } }; std::unique_ptr make_animal(bool dog) { if (dog) return std::make_unique("Rex"); return std::make_unique("Whiskers"); } PYBIND11_MODULE(animals, m, py::mod_gil_not_used()) { py::class_(m, "Animal") .def("sound", &Animal::sound) .def_readwrite("name", &Animal::name); py::class_(m, "Dog") .def(py::init()) .def("fetch", &Dog::fetch); py::class_(m, "Cat") .def(py::init()); m.def("make_animal", &make_animal, py::arg("dog")); } ``` ```python import animals a = animals.make_animal(True) print(type(a).__name__) # Dog (auto-downcast from Animal*) print(a.sound()) # woof! print(a.fetch()) # Rex fetches the ball! b = animals.make_animal(False) print(type(b).__name__) # Cat print(b.sound()) # meow! ``` --- ## Trampoline classes — Override virtual functions in Python To allow Python subclasses to override C++ virtual methods, a *trampoline* helper class is required. It uses `PYBIND11_OVERRIDE` / `PYBIND11_OVERRIDE_PURE` macros to forward virtual calls to the Python override. With `py::smart_holder`, trampolines must also inherit `py::trampoline_self_life_support`. ```cpp #include namespace py = pybind11; class Processor { public: virtual ~Processor() = default; virtual std::string process(const std::string &input) = 0; virtual std::string name() const { return "Processor"; } }; // Trampoline class PyProcessor : public Processor, public py::trampoline_self_life_support { public: using Processor::Processor; std::string process(const std::string &input) override { PYBIND11_OVERRIDE_PURE(std::string, Processor, process, input); } std::string name() const override { PYBIND11_OVERRIDE(std::string, Processor, name); } }; std::string run(Processor &p, const std::string &data) { return "[" + p.name() + "] " + p.process(data); } PYBIND11_MODULE(processors, m, py::mod_gil_not_used()) { py::class_(m, "Processor") .def(py::init<>()) .def("process", &Processor::process) .def("name", &Processor::name); m.def("run", &run); } ``` ```python import processors class UpperProcessor(processors.Processor): def process(self, input: str) -> str: return input.upper() def name(self) -> str: return "UpperProcessor" p = UpperProcessor() print(processors.run(p, "hello world")) # [UpperProcessor] HELLO WORLD ``` --- ## py::native_enum — Bind C++ enums to Python native enums `py::native_enum` (introduced in pybind11 v3) binds C++ enum types to Python's standard `enum.Enum` (or `enum.IntEnum`, etc.), producing PEP 435-compatible enumerations. Always call `.finalize()` after listing all values. ```cpp #include #include namespace py = pybind11; enum class Color { Red = 0, Green, Blue }; enum class Direction { North = 1, South, East, West }; struct Canvas { Color bg = Color::White; Canvas(Color c) : bg(c) {} }; PYBIND11_MODULE(graphics, m, py::mod_gil_not_used()) { py::native_enum(m, "Color", "enum.IntEnum", "RGB color values") .value("Red", Color::Red) .value("Green", Color::Green) .value("Blue", Color::Blue) .export_values() .finalize(); py::native_enum(m, "Direction", "enum.Enum") .value("North", Direction::North) .value("South", Direction::South) .value("East", Direction::East) .value("West", Direction::West) .finalize(); } ``` ```python import graphics from graphics import Color, Direction print(Color.Red) # Color.Red print(Color.Red.value) # 0 print(isinstance(Color.Green, int)) # True (IntEnum) d = Direction.North print(d.name) # North # Iterating for c in Color: print(c.name, c.value) # Red 0 / Green 1 / Blue 2 ``` --- ## Return value policies Return value policies control Python object lifetime for values returned from C++ functions, especially raw pointers. The correct policy must be chosen explicitly to avoid crashes or memory leaks. ```cpp #include namespace py = pybind11; struct Config { std::string host = "localhost"; int port = 8080; }; static Config global_config; struct Server { Config config; Server() : config{} {} // Returns pointer to internal config — use reference_internal Config *get_config() { return &config; } // Returns pointer to global — use reference (C++ manages lifetime) static Config *global() { return &global_config; } // Returns a new copy — default (move/copy) policy is fine Config make_config() const { return config; } }; PYBIND11_MODULE(server, m, py::mod_gil_not_used()) { py::class_(m, "Config") .def(py::init<>()) .def_readwrite("host", &Config::host) .def_readwrite("port", &Config::port); py::class_(m, "Server") .def(py::init<>()) // reference_internal: keeps Server alive while Config ref is used .def("get_config", &Server::get_config, py::return_value_policy::reference_internal) // reference: global object, C++ owns it .def_static("global_config", &Server::global, py::return_value_policy::reference) // copy: always safe, creates independent Python-owned copy .def("make_config", &Server::make_config, py::return_value_policy::copy); } ``` ```python import server s = server.Server() cfg = s.get_config() # reference_internal: s kept alive cfg.host = "example.com" print(s.get_config().host) # example.com g = server.Server.global_config() print(g.port) # 8080 copy_cfg = s.make_config() copy_cfg.port = 9090 print(s.get_config().port) # 8080 (original unchanged) ``` --- ## py::keep_alive and py::call_guard — Call policies `py::keep_alive` prevents the *nurse* object from being garbage-collected while the *patient* object is alive. `py::call_guard` wraps a function call with an RAII scope guard, commonly used to release the GIL. ```cpp #include namespace py = pybind11; struct Item { std::string value; Item(std::string v) : value(std::move(v)) {} }; struct Container { std::vector items; void add(Item *item) { items.push_back(item); } size_t size() const { return items.size(); } }; PYBIND11_MODULE(storage, m, py::mod_gil_not_used()) { py::class_(m, "Item") .def(py::init()); py::class_(m, "Container") .def(py::init<>()) // keep_alive<1,2>: keep the Item (arg 2) alive as long as // the Container (arg 1 / `this`) is alive .def("add", &Container::add, py::keep_alive<1, 2>()) .def("size", &Container::size); } ``` ```python import storage c = storage.Container() c.add(storage.Item("apple")) c.add(storage.Item("banana")) print(c.size()) # 2 # Items won't be collected while c is alive ``` --- ## *args and **kwargs — Variadic Python arguments `py::args` (a subclass of `py::tuple`) and `py::kwargs` (a subclass of `py::dict`) allow C++ functions to accept arbitrary positional and keyword arguments mirroring Python's `*args` and `**kwargs`. ```cpp #include namespace py = pybind11; py::object variadic(py::args args, const py::kwargs &kwargs) { py::list result; for (auto item : args) result.append(py::str("arg: ") + py::str(item)); for (auto kv : kwargs) result.append(py::str(kv.first) + py::str("=") + py::str(kv.second)); return result; } // Keyword-only after *args void with_kw_only(int a, py::args rest, int b) { py::print("a =", a, ", b =", b, ", rest =", rest); } PYBIND11_MODULE(variadics, m, py::mod_gil_not_used()) { m.def("variadic", &variadic); m.def("with_kw_only", &with_kw_only, py::arg("a"), py::arg("b")); } ``` ```python import variadics print(variadics.variadic(1, 2, "three", x=10, y=20)) # ['arg: 1', 'arg: 2', 'arg: three', 'x=10', 'y=20'] variadics.with_kw_only(1, 2, 3, b=99) # a = 1 , b = 99 , rest = (2, 3) ``` --- ## py::arg options — noconvert, none, kw_only, pos_only `py::arg` objects carry metadata that refines how function arguments are processed: `.noconvert()` disables implicit conversion, `.none(bool)` controls `None`-to-nullptr mapping, `py::kw_only()` enforces keyword-only arguments, and `py::pos_only()` enforces positional-only arguments. ```cpp #include namespace py = pybind11; struct Dog { std::string name; }; std::string bark(Dog *dog) { if (!dog) return "(no dog)"; return dog->name + " says woof!"; } double strict_double(double v) { return v * 2; } void mixed(int pos_only, int normal, int kw_only) {} PYBIND11_MODULE(args_demo, m, py::mod_gil_not_used()) { py::class_(m, "Dog") .def(py::init<>()) .def_readwrite("name", &Dog::name); // .none(true) — allow Python None → nullptr m.def("bark", &bark, py::arg("dog").none(true)); // .noconvert() — refuse implicit __float__ conversion m.def("strict_double", &strict_double, py::arg("v").noconvert()); // pos_only / kw_only markers m.def("mixed", &mixed, py::arg("pos_only"), py::pos_only(), py::arg("normal"), py::kw_only(), py::arg("kw_only")); } ``` ```python import args_demo d = args_demo.Dog() d.name = "Rex" print(args_demo.bark(d)) # Rex says woof! print(args_demo.bark(None)) # (no dog) print(args_demo.strict_double(3.0)) # 6.0 # args_demo.strict_double(MyFloat(3)) # TypeError args_demo.mixed(1, 2, kw_only=3) # OK # args_demo.mixed(pos_only=1, ...) # TypeError ``` --- ## Exception handling — C++ ↔ Python translation pybind11 automatically translates standard C++ exceptions to Python exceptions. Custom C++ exception classes can be registered with `py::register_exception`. Python exceptions raised inside C++ code arrive as `py::error_already_set`. ```cpp #include namespace py = pybind11; // Custom C++ exception class DatabaseError : public std::runtime_error { public: using std::runtime_error::runtime_error; }; std::string query(const std::string &sql) { if (sql.empty()) throw std::invalid_argument("SQL query must not be empty"); if (sql.find("DROP") != std::string::npos) throw DatabaseError("Destructive queries are forbidden"); return "result_of: " + sql; } void call_python_callback(py::object cb) { try { cb(42); } catch (py::error_already_set &e) { // Inspect and re-raise or handle if (e.matches(PyExc_ValueError)) { py::print("Caught Python ValueError:", e.what()); } else { throw; // re-raise other Python exceptions } } } PYBIND11_MODULE(db, m, py::mod_gil_not_used()) { // Register custom C++ exception → Python class py::register_exception(m, "DatabaseError", PyExc_RuntimeError); m.def("query", &query); m.def("call_python_callback", &call_python_callback); } ``` ```python import db try: db.query("") except ValueError as e: print("ValueError:", e) # SQL query must not be empty try: db.query("DROP TABLE users") except db.DatabaseError as e: print("DatabaseError:", e) # Destructive queries are forbidden def bad_cb(n): raise ValueError("bad value!") db.call_python_callback(bad_cb) # Caught Python ValueError: bad value! ``` --- ## STL containers — pybind11/stl.h Including `pybind11/stl.h` enables automatic copying conversion between C++ `std::vector`, `std::list`, `std::set`, `std::map`, `std::optional`, `std::variant` and their Python equivalents. For pass-by-reference semantics, use `PYBIND11_MAKE_OPAQUE` or `py::bind_vector` / `py::bind_map` from `pybind11/stl_bind.h`. ```cpp #include #include #include namespace py = pybind11; // Make this type opaque so Python sees it as a native-like object PYBIND11_MAKE_OPAQUE(std::vector) std::vector filter_long(const std::vector &words, size_t min_len) { std::vector out; for (auto &w : words) if (w.size() >= min_len) out.push_back(w); return out; } std::map word_lengths(const std::vector &words) { std::map m; for (auto &w : words) m[w] = (int)w.size(); return m; } PYBIND11_MODULE(stl_demo, m, py::mod_gil_not_used()) { // Auto-converted functions m.def("filter_long", &filter_long, py::arg("words"), py::arg("min_len")); m.def("word_lengths", &word_lengths, py::arg("words")); // Opaque std::vector exposed as a Python object with full interface py::bind_vector>(m, "IntVector"); } ``` ```python import stl_demo words = ["apple", "fig", "banana", "kiwi"] print(stl_demo.filter_long(words, 5)) # ['apple', 'banana'] print(stl_demo.word_lengths(words)) # {'apple': 5, 'banana': 6, 'fig': 3, 'kiwi': 4} # Opaque vector — mutations are visible in C++ v = stl_demo.IntVector() v.append(10) v.append(20) print(len(v), list(v)) # 2 [10, 20] ``` --- ## Smart pointers — py::smart_holder and std::shared_ptr `py::smart_holder` (added in pybind11 v3) is the recommended holder type for `py::class_`. It supports bidirectional `std::unique_ptr` and `std::shared_ptr` transfers and is required when using trampoline classes. Use `py::classh` as a shorthand for `py::class_`. ```cpp #include namespace py = pybind11; class Resource { public: Resource(std::string name) : name_(std::move(name)) {} const std::string &name() const { return name_; } ~Resource() { /* cleanup */ } private: std::string name_; }; std::unique_ptr make_resource(const std::string &n) { return std::make_unique(n); } void consume(std::unique_ptr r) { // Takes ownership; Python object is disowned after this call (void)r; } void share(std::shared_ptr r) { // Increments refcount; both Python and C++ share ownership (void)r; } PYBIND11_MODULE(resources, m, py::mod_gil_not_used()) { // py::classh is shorthand for py::class_ py::classh(m, "Resource") .def(py::init()) .def("name", &Resource::name); m.def("make_resource", &make_resource); m.def("consume", &consume); // disowns the Python object m.def("share", &share); // shared ownership } ``` ```python import resources r = resources.make_resource("db_conn") print(r.name()) # db_conn resources.share(r) # still accessible in Python after this print(r.name()) # db_conn resources.consume(r) # Python object is now disowned; don't use r again ``` --- ## NumPy buffer protocol and py::array_t Include `pybind11/numpy.h` to enable zero-copy exchange between C++ data structures and NumPy arrays. Expose a buffer view with `py::buffer_protocol()` and `def_buffer()`, or accept typed arrays with `py::array_t`. ```cpp #include #include namespace py = pybind11; class Matrix { public: Matrix(size_t rows, size_t cols) : rows_(rows), cols_(cols), data_(rows * cols, 0.0f) {} float &operator()(size_t r, size_t c) { return data_[r * cols_ + c]; } float *data() { return data_.data(); } size_t rows() const { return rows_; } size_t cols() const { return cols_; } private: size_t rows_, cols_; std::vector data_; }; // Compute sum of all elements in a typed NumPy array double array_sum(py::array_t arr) { auto buf = arr.request(); auto *ptr = static_cast(buf.ptr); double total = 0.0; for (py::ssize_t i = 0; i < buf.size; ++i) total += ptr[i]; return total; } PYBIND11_MODULE(numpy_demo, m, py::mod_gil_not_used()) { py::class_(m, "Matrix", py::buffer_protocol()) .def(py::init()) .def("__setitem__", [](Matrix &m, std::pair idx, float v) { m(idx.first, idx.second) = v; }) .def_buffer([](Matrix &m) -> py::buffer_info { return py::buffer_info( m.data(), sizeof(float), py::format_descriptor::format(), 2, { m.rows(), m.cols() }, { sizeof(float) * m.cols(), sizeof(float) } ); }); m.def("array_sum", &array_sum, py::arg("array")); } ``` ```python import numpy as np import numpy_demo mat = numpy_demo.Matrix(3, 3) mat[0, 0] = 1.0 mat[1, 1] = 5.0 # Zero-copy conversion to NumPy arr = np.array(mat, copy=False) print(arr.shape, arr.dtype) # (3, 3) float32 print(arr[0, 0], arr[1, 1]) # 1.0 5.0 data = np.array([[1.0, 2.0], [3.0, 4.0]]) print(numpy_demo.array_sum(data)) # 10.0 ``` --- ## Embedding the Python interpreter pybind11 can embed a Python interpreter into a C++ application using `py::scoped_interpreter`. Link against `pybind11::embed` in CMake. Use `py::exec`, `py::eval`, and `py::module_::import` to interact with Python. ```cpp // main.cpp #include #include namespace py = pybind11; using namespace py::literals; int main() { py::scoped_interpreter guard{}; // start interpreter (RAII) // Execute a Python string py::exec(R"( import sys print("Python", sys.version.split()[0], "embedded!") )"); // Call Python from C++ and retrieve results auto math = py::module_::import("math"); double result = math.attr("sqrt")(py::float_(2.0)).cast(); std::cout << "sqrt(2) = " << result << "\n"; // 1.41421... // Build Python objects using C++ API auto kwargs = py::dict("name"_a = "World", "count"_a = 3); py::exec(R"( msg = "Hello, {name}! " * count print(msg.format(name=name)) )", py::globals(), kwargs); // Import and run a local Python module py::module_::import("sys").attr("path").attr("insert")(0, "."); // If "mymodule.py" is present, it can be imported here: // auto mod = py::module_::import("mymodule"); return 0; } ``` ```cmake # CMakeLists.txt cmake_minimum_required(VERSION 3.15) project(embed_example) find_package(pybind11 REQUIRED) add_executable(embed_example main.cpp) target_link_libraries(embed_example PRIVATE pybind11::embed) ``` ``` $ ./embed_example Python 3.11.x embedded! sqrt(2) = 1.41421 Hello, World! Hello, World! Hello, World! ``` --- ## Build systems — CMake, scikit-build-core, setuptools pybind11 supports multiple build systems. The recommended approach for distributing packages on PyPI is `scikit-build-core`, which uses CMake under the hood. For standalone C++ projects, `find_package(pybind11)` + `pybind11_add_module` is the cleanest path. ```cmake # CMakeLists.txt — standalone CMake build cmake_minimum_required(VERSION 3.15...4.2) project(myext LANGUAGES CXX) set(PYBIND11_FINDPYTHON ON) find_package(pybind11 CONFIG REQUIRED) pybind11_add_module(myext myext.cpp) install(TARGETS myext DESTINATION .) ``` ```toml # pyproject.toml — scikit-build-core (recommended for PyPI packages) [build-system] requires = ["scikit-build-core", "pybind11"] build-backend = "scikit_build_core.build" [project] name = "myext" version = "0.1.0" requires-python = ">=3.8" ``` ```meson # meson.build — Meson alternative project('myext', 'cpp', version: '0.1.0', default_options: ['cpp_std=c++11']) py = import('python').find_installation(pure: false) pybind11_dep = dependency('pybind11') py.extension_module('myext', 'myext.cpp', install: true, dependencies: [pybind11_dep]) ``` ```bash # Build and install with pip (scikit-build-core) pip install . # Or compile manually (Linux/macOS) c++ -O3 -Wall -shared -std=c++11 -fPIC \ $(python3 -m pybind11 --includes) \ myext.cpp -o myext$(python3 -m pybind11 --extension-suffix) ``` --- ## Summary pybind11 is the de-facto standard for creating Python bindings for C++ libraries. Its primary use cases span scientific computing (exposing numerical solvers, simulators, and matrix libraries like Eigen with zero-copy NumPy integration), systems programming (wrapping OS-level C++ APIs for Python tooling), and performance-critical Python extensions (replacing pure-Python hot paths with compiled C++ that Python code calls transparently). The library's header-only design means it integrates into any existing C++ build system without linking additional shared libraries, and its `py::smart_holder` mechanism ensures safe memory ownership semantics even for complex class hierarchies with virtual functions. Integration patterns follow a consistent structure: define C++ classes and functions, create a `PYBIND11_MODULE` block, and register everything using the fluent `py::class_` and `m.def()` APIs. For packaging, `scikit-build-core` + `CMakeLists.txt` + `pyproject.toml` is the modern recommended workflow for distributing wheels on PyPI. When embedding Python inside C++ applications, `py::scoped_interpreter` provides automatic lifecycle management, and the full pybind11 binding API remains available for bidirectional C++/Python data exchange. Exception handling, GIL management (`py::gil_scoped_release` / `py::call_guard`), and STL container bridging all follow the same declarative, compile-time-verified pattern that makes pybind11 both safe and ergonomic.