Skip to content

About

A reflection-based approach for generating Python/Lua/JS bindings for your C++ code

Resources

Code of conduct

Contributing

Security policy

Stars

87 stars

Watchers

2 watching

Forks

Latest commit

 

History

414 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Mirror Bridge

Write C++ once, bind everywhere — automatically.

Tests Release License

Generate Python, Lua, and JavaScript bindings from C++ using C++26 reflection. Zero boilerplate, zero runtime overhead, zero binding code.

mirror_bridge demo: one command generates Python, Lua and JavaScript modules

C++26 reflection ships in stock GCC 16.1+ (g++ -std=c++26 -freflection); Bloomberg's clang-p2996 also works. The CLI auto-detects whichever is present. Reflection was voted into C++26 in June 2025 — this is standard C++, not a fork-only experiment.

One C++ Class, Three Languages

struct Calculator {
    double value = 0.0;
    double add(double x) { return value += x; }
    double subtract(double x) { return value -= x; }
};
mirror_bridge generate src/ --module calc --lang all

That command is the entire binding process — no .def(), no macros in your class, no wrapper files. The same Calculator is now native in all three languages:

Python

import calc
c = calc.Calculator()
c.add(10)
print(c.value)   # 10.0

Lua

package.cpath = "build/lua/?.so;" .. package.cpath
local calc = require("calc")
local c = calc.Calculator()
c:add(10)
print(c.value)   -- 10.0

JavaScript (Node.js)

const calc = require('./build/calc.node');
const c = new calc.Calculator();
c.add(10);
console.log(c.value);   // 10.0

Proof on a Real Library: Open3D

We ported Open3D's geometry module — the same API surface that takes 25,262 lines of hand-written pybind11 upstream — with 71 generated lines and one command (full case study, runnable example):

cd examples/open3d-port
mirror_bridge generate src/ --module open3d_geometry --lang all -I /usr/include/eigen3

And it's not a slower convenience layer: on the identical C++ compiled with identical flags, ingesting a 1M-point cloud from Python (list of [x,y,z] → std::vector<Eigen::Vector3d>) runs 42x faster than the pybind11 binding (12.2ms vs 509ms), because the reflection-generated converter is specialized at compile time instead of dispatching through generic type casters (fair-comparison methodology).

And on thirty-one libraries we didn't pick

Open3D is a case we worked, so on its own it proves only that it can be done. To make the claim falsifiable instead, CI runs the same one-line command every week against a corpus of unmodified third-party libraries and publishes the result, failures included: the compatibility matrix.

Today that page is mostly a work queue, which is the reason to publish it. The discovery half holds up — reflection reads the headers and reports the real class list without anyone naming a class by hand, 23 in jsoncpp, 41 in yaml-cpp, 108 in spdlog — and the matrix then says, per library, exactly what stops a module coming out, grouped so the most valuable thing to fix is the line with the most libraries on it. pybind11 and nanobind have nothing to put in that first column, because the class list is the thing their user writes.

Try It Now

No installation required — run in your browser or with Docker:

Interactive Playground - Godbolt examples showing C++26 reflection in action

GitHub Codespaces (browser-based, ~2 min):

Open in GitHub Codespaces

Docker (local, one command):

docker run -it --rm -v $(pwd):/workspace ghcr.io/franciscothiesen/mirror_bridge:latest

Then inside the container:

cd examples/01-hello-world
../../tools/mirror_bridge generate src/ --module greeter --lang python
python3 test_greeter.py

Quick Start

# 1. Get the environment
./start_dev_container.sh  # Choose option 1 for pre-built image

# 2. Verify
./tests/run_all_tests.sh

# 3. Try an example
cd examples/01-hello-world
../../tools/mirror_bridge generate src/ --module greeter --lang python
python3 test_greeter.py

Full Quick Start Guide →

Features

Language Support

Language Status Auto-Discovery
Python Stable --lang python
Lua Stable --lang lua
JavaScript (Node.js) Stable --lang js
Rust Prototype Code generation

Feature Matrix

Feature Python Lua JavaScript
Data Members ✅ ✅ ✅
Methods (any arity) ✅ ✅ ✅
Constructors ✅ ✅ ✅
Containers (vector/array) ✅ ✅ ✅
Maps (map/unordered_map) ✅ ✅ ✅
Sets (set/unordered_set) ✅ ✅ ✅
Tuples (tuple/pair) ✅ ✅ ✅
Variants (std::variant) ✅ ✅ ✅
Nested Objects ✅ ✅ ✅
Iteration, len(), in ✅ ❌ ❌
Pickling / copy.deepcopy ✅ ❌ ❌
Enums ✅ ⚠️ ⚠️
Inheritance ✅ ✅ ✅
Method Overloading ✅ ⚠️ ⚠️
Smart Pointers ⚠️ ⚠️ ⚠️
Exception Handling ✅ ✅ ✅
Cross-Module Types ✅ ❌ ❌
Zero-Copy Buffers ✅ ❌ ❌
Async/Await ✅ ❌ ✅
std::optional ✅ ✅ ✅
std::expected ✅ ✅ ✅

✅ Full support ⚠️ Partial support ❌ Not supported

✅ Python data model: a class whose begin()/end() form a range over convertible elements iterates and answers in; add size() and it gains len(), and an empty one is falsy. A default-constructible class whose members are all assignable pickles and deep-copies, with the member list taken from reflection so it cannot fall behind the C++ struct; one that cannot be reconstructed — a const or [[=readonly{}]] member, a private or [[=exclude{}]] one the state cannot see, or a member a base class declares — keeps refusing to pickle rather than silently dropping a field.

hash() is not generated, and what that leaves differs by class. A class with operator== is unhashable: CPython inherits tp_hash from object only when it also inherits tp_richcompare, so defining comparison and no hash makes set, dict and in over a set raise TypeError: unhashable type: 'mod.Point'. Use a list, or key on a member. A class without operator== keeps the identity hash, so two equal-looking objects remain two distinct keys. Hashing the member values instead would be worse: every non-const member is assignable from Python, and a hash that moves corrupts any set or dict already holding the object.

⚠️ Enums, outside Python: Python gets the enum type as an enum.IntEnum carrying the C++ enumerator names and values, so Color.Red exists and list(Color) works. Nothing about the value conversion changed, because an IntEnum member is an int. Lua and JavaScript have no enum object to map onto and their values still cross as bare numbers; generate says which backend does which. An out-of-range integer is still accepted where an enum is expected, in every backend.

⚠️ Smart pointers: a class holding a std::shared_ptr binds, and a shared_ptr return gives you a usable object. What does not cross is the shared ownership: the return is a copy of the pointee, and a shared_ptr parameter is refused. Tracked in #54.

Additional Features

  • Precompiled headers: 3-6x faster builds
  • Auto-discovery: Zero binding code required
  • Templates without hints (Python): generate binds every specialization the headers name (using Vec3f = Vector3<float>; is the declaration), instantiates function templates over the types the headers use, and lets the compiler reject what does not exist. Python sees families: geom.Vector3[float], geom.Vector3(1.0, 2.0, 3.0), geom.clamp(1.5, 0.0, 1.0), v.cast[int](). The plan is written next to the module — details
  • Type stubs: .pyi generation for Python IDE support

Documentation

Topic Description
Getting Started Quick start, installation, first binding
Guides Workflow, PCH, multi-language, independent teams, contributing
Reference CLI, API, configuration, type conversion
Architecture System design and internals
Examples Progressive examples from hello-world to production

CLI Usage

# Auto-discover and compile
mirror_bridge generate src/ --module my_mod --lang python

# With precompiled header (faster)
mirror_bridge pch --output build/ --type release
mirror_bridge generate src/ --module my_mod --lang python --pch

# Multiple languages
mirror_bridge generate src/ --module my_mod --lang all

Full CLI Reference →

Performance

From the CI-regenerated benchmark table (same C++ class, same compiler, -O3 for every framework; only the binding layer differs — absolute times shift with runner hardware, compare the ratios):

Operation Mirror Bridge pybind11 nanobind vs pybind11
Method dispatch (null call) 73ns 190ns 70ns 2.6x faster
Object construction 89ns 376ns 111ns 4.2x faster
Vector return 316ns 914ns 2,650ns 2.9x faster (8.4x vs nanobind)
Point-cloud ingest (1M × [x,y,z] → vector<Eigen::Vector3d>) 12.2ms 509ms — 42x faster
Binding code 0 lines 18+ lines/class 18+ lines/class Zero boilerplate

Honest summary: 2-4x faster than pybind11 across the board, parity with nanobind on scalar dispatch (nanobind wins some by ~10%), and far ahead of both on container traffic. Compile time is ~3.4x faster than pybind11 (567ms vs 1,938ms, no PCH for either); nanobind compiles faster still — Mirror Bridge only matches it with --pch (194ms). The full table is regenerated monthly by CI so it tracks the current code, with methodology and SWIG numbers included. The same run now gates the ratios against committed floors, so a regression fails the build instead of sitting in a table for a month — and it reports every operation where a competitor is still faster rather than rounding that away.

For AI Agents

mirror_bridge is built to be driven by coding agents as well as humans:

  • AGENTS.md: operating manual for agents working in this repo
  • --json everywhere: generate, diff, and doctor emit exactly one JSON object on stdout, with actionable suggestions on failure
  • MCP server: gives any MCP-capable agent generate_bindings, doctor, and check_binding_drift as tool calls. Until the PyPI release lands, build it locally (cd packaging/pip && ./build_wheel.sh, then pip install "$(ls dist/*.whl)[mcp]" and claude mcp add mirror_bridge -- mirror-bridge-mcp)
  • Claude Code skill: drop-in bind-cpp skill teaching the full generate → fix → verify loop
  • llms.txt / llms-full.txt: docs index and full corpus for LLM consumption

Project Structure

mirror_bridge/
├── core/                    # Language-agnostic reflection
├── python/                  # Python C API bindings
├── lua/                     # Lua C API bindings
├── javascript/              # Node.js N-API bindings
├── tools/mirror_bridge      # Unified CLI
├── docs/                    # Documentation
├── examples/                # Progressive examples
└── tests/                   # Test suite

Requirements

  • Compiler: a C++26 reflection compiler — stock GCC 16+ (-freflection) or Bloomberg clang-p2996. Both are provided via Docker; the CLI auto-detects which is installed (override with MB_CXX).
  • Python: 3.7+ | Lua: 5.4 | Node.js: 14+

Note: P3394 field annotations ([[=exclude{}]], [[=readonly{}]]) work under both compilers, in the Python backend. The Lua and JavaScript backends do not read them yet: they bind every named member, so an annotated field is still exposed there.

Contributing

See Contributing Guide for development setup and guidelines.

License

Apache License 2.0 — See LICENSE


Documentation · Examples · Issues

About

A reflection-based approach for generating Python/Lua/JS bindings for your C++ code

Resources

Code of conduct

Contributing

Security policy

Stars

87 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages