C++/Python interop

Modern SLAM research lives in two worlds: performance-critical estimation code is written in C++, while experimentation, deep learning, and evaluation happen in Python. C++/Python interop is the bridge — you wrap a C++ core in Python bindings so that the same tracker or optimizer can be driven from a notebook, combined with PyTorch models, or benchmarked with Python tooling, without rewriting anything.

PyBind11 is the de-facto standard for writing these bindings. It is a header-only C++ library that exposes C++ classes and functions as Python modules with very little boilerplate:

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>   // automatic Eigen <-> NumPy conversion

Eigen::Matrix4d track(const Eigen::Matrix4d& T_prev, const cv::Mat& img);

PYBIND11_MODULE(myslam, m) {
    m.def("track", &track, "Track one frame and return the new pose");
}

The pybind11/eigen.h header converts between Eigen matrices and NumPy arrays automatically, which is exactly what SLAM code needs: poses, point clouds, and Jacobians cross the language boundary as arrays with no manual copying code. Many libraries you will use are wrapped this way — GTSAM ships an official Python wrapper, and projects like COLMAP’s pycolmap follow the same pattern.

Binding a whole system rather than free functions looks like this — note the GIL guard, which is what keeps a multi-threaded SLAM core running while Python waits:

namespace py = pybind11;

py::class_<SlamSystem>(m, "SlamSystem")
    .def(py::init<const std::string&>())              // config file path
    .def("track", &SlamSystem::track,
         py::call_guard<py::gil_scoped_release>())    // release GIL during C++ work
    .def_property_readonly("map_points", &SlamSystem::mapPoints);

nanobind is the successor from the same author, redesigned for lower binding overhead, smaller binaries, and faster compile times. Its API is intentionally close to PyBind11, so knowledge transfers directly; new projects that care about call overhead (e.g., binding small functions called per-feature or per-frame) increasingly choose nanobind.

What to understand when wrapping SLAM code

Common pitfalls

Why it matters for SLAM

The field is converging on hybrid systems: classical C++ back-ends combined with learned front-ends (feature detectors, depth networks, matchers) that live in Python/PyTorch. Being able to bind a C++ optimizer into Python — or call a learned matcher from C++ via exported models — is what makes those combinations practical. It also transforms your own C++ code: once wrapped, it becomes scriptable, unit-testable from pytest, and easy to evaluate against datasets with Python tooling.

Hands-on