Modern C++ meets Multiple Languages: Automatic bindings using C++26 reflection - zero boilerplate, pure compile-time magic.
⚠️ EXPERIMENTAL: This project requires C++26 reflection (P2996), which is not yet standardized. It only works with Bloomberg's clang-p2996 fork. Not recommended for production use until P2996 lands in standard C++26.
// Write your C++ code once
struct Calculator {
double value = 0.0;
double add(double x) { return value += x; }
double subtract(double x) { return value -= x; }
};Python:
import cpp_calc
calc = cpp_calc.Calculator()
calc.add(10)
calc.subtract(3)
print(calc.value) # 7.0JavaScript (Node.js):
const calc = new addon.Calculator();
calc.add(10);
calc.subtract(3);
console.log(calc.x); // 7.0Lua:
local calc = cpp_calc.Calculator()
calc:add(10)
calc:subtract(3)
print(calc.value) -- 7.0No manual binding code. No wrapper macros. Just pure C++26 reflection. 🎉
| Language | Status | API | Example |
|---|---|---|---|
| Python | ✅ Stable | Python C API | import my_module |
| JavaScript | ✅ Stable | Node.js N-API | const mod = require('my_module') |
| Lua | ✅ Stable | Lua C API | local mod = require("my_module") |
Mirror Bridge is a header-only library that uses C++26 reflection (P2996) to automatically introspect your C++ classes at compile-time and generate bindings for Python, JavaScript, and Lua. It discovers:
- ✅ Data members - automatic getters/setters with type safety
- ✅ Methods (any number of parameters) - variadic parameter support
- ✅ Constructors - including parameterized constructors
- ✅ Method overloading - automatic name mangling for overloads
- ✅ Smart pointers -
std::unique_ptr,std::shared_ptrwith automatic conversion - ✅ Nested classes - recursive handling, cross-file dependencies
- ✅ Containers -
std::vector,std::arraywith bidirectional conversion - ✅ Exception handling - C++ exceptions → Python exceptions
- ✅ Enums - automatic conversion to/from Python int
- ✅ Object representation - automatic
__repr__implementation - ✅ Inheritance - reflection automatically discovers inherited members
Zero overhead: All binding code is generated at compile-time through template metaprogramming and reflection - no runtime costs.
# 1. Get the environment
./start_dev_container.sh
# Choose option 1 to pull pre-built image (~2 min)
# 2. Inside container - verify it works
cd /workspace && ./tests/run_all_tests.sh
# 3. Try an example
cd examples/option2
../../mirror_bridge_auto src/ --module math_module
python3 test_option2.pyThat's it! See QUICKSTART.md for a detailed walkthrough.
Mirror Bridge offers two workflows optimized for different use cases:
Just point at a directory - bindings are auto-generated for all classes.
Python:
mirror_bridge_auto src/ --module my_moduleLua:
mirror_bridge_auto_lua src/ --module my_moduleJavaScript (Node.js):
mirror_bridge_auto_js src/ --module my_module- ✅ Zero configuration - discovers all classes automatically
- ✅ Perfect for prototyping and small projects
- ✅ Opt-out via comments - mark classes to skip
- ✅ Works for all three languages - Python, Lua, JavaScript
Example:
// src/calculator.hpp
struct Calculator {
double value;
double add(double x);
};
// src/vector3.hpp
struct Vector3 {
double x, y, z;
double length();
};# One command binds BOTH classes
mirror_bridge_auto src/ --module mylibimport mylib
calc = mylib.Calculator()
vec = mylib.Vector3()See examples/option2/ for full example.
Declarative config for explicit control over what gets bound.
Create my_module.mirror:
module: my_module
include_dirs: src/, include/
Calculator: calculator.hpp
Vector3: vector3.hpp
mirror_bridge_generate my_module.mirror- ✅ Explicit control - only bind what you specify
- ✅ Version control friendly - declarative config
- ✅ Class renaming -
Foo::Bar: foo.hpp as Bar
See examples/option3/ for full example.
Full comparison: examples/README.md
For easier integration, Mirror Bridge provides single-header amalgamated versions for each language. Just copy one file to your project!
Generate single-headers:
This creates:
single_header/mirror_bridge_python.hpp(~1771 lines, 65KB)single_header/mirror_bridge_lua.hpp(~859 lines, 32KB)single_header/mirror_bridge_javascript.hpp(~875 lines, 33KB)
Usage:
// Instead of: #include "python/mirror_bridge_python.hpp"
// Just:
#include "mirror_bridge_python.hpp" // Single self-contained header!
MIRROR_BRIDGE_MODULE(my_module,
mirror_bridge::bind_class<MyClass>(m, "MyClass");
)See SINGLE_HEADER_GUIDE.md for complete documentation.
struct Point { double x, y; };p = my_module.Point()
p.x = 3.0 # Automatic getter/setter
p.y = 4.0struct MathOps {
double add3(double a, double b, double c) { return a + b + c; }
double sum5(double a, double b, double c, double d, double e) {
return a + b + c + d + e;
}
void reset() { value = 0; } // Zero parameters work too
};ops = my_module.MathOps()
ops.add3(1.0, 2.0, 3.0) # 3 parameters ✓
ops.sum5(1, 2, 3, 4, 5) # 5 parameters ✓
ops.reset() # 0 parameters ✓
# ANY number of parameters supported through variadic templatesstruct Rectangle {
Rectangle() : width(0), height(0) {}
Rectangle(double w, double h) : width(w), height(h) {}
Rectangle(double w, double h, std::string name)
: width(w), height(h), name(name) {}
double width, height;
std::string name;
};r1 = my_module.Rectangle() # Default constructor
r2 = my_module.Rectangle(10.0, 5.0) # 2-parameter constructor
r3 = my_module.Rectangle(10, 5, "box") # 3-parameter constructor
# Automatic constructor discovery and parameter matchingstruct Printer {
void print(int value) { /* ... */ }
void print(double value) { /* ... */ }
void print(std::string value) { /* ... */ }
};p = my_module.Printer()
p.print_int(42) # int overload
p.print_double(3.14) # double overload
p.print_std__string("hello") # string overload
# Automatic name mangling distinguishes overloadsstruct Data {
std::string name;
int value;
};
struct ResourceManager {
std::unique_ptr<Data> unique_data;
std::shared_ptr<Data> shared_data;
std::unique_ptr<Data> create_unique(std::string n, int v);
};rm = my_module.ResourceManager()
# Smart pointers convert to/from Python dicts
result = rm.create_unique("test", 42)
print(result) # {'name': 'test', 'value': 42}
# Set from dict - creates managed pointer automatically
rm.unique_data = {'name': 'data', 'value': 123}
# None handling for null pointers
rm.unique_data = None # Sets to nullptrstruct Address {
std::string city;
};
struct Person {
std::string name;
Address addr; // Nested!
};p = my_module.Person()
p.addr = {'city': 'Boston'} # Dict conversionstruct Data {
std::vector<double> values;
std::array<int, 3> coords;
};d.values = [1.0, 2.0, 3.0] # List → vector
d.coords = [1, 2, 3] # List → arraydouble divide(double x) {
if (x == 0) throw std::runtime_error("Division by zero");
return value / x;
}try:
calc.divide(0)
except RuntimeError as e:
print(e) # "Division by zero"mirror_bridge/
├── mirror_bridge.hpp # Single-header library (core reflection logic)
├── mirror_bridge_pch.hpp # Precompiled header wrapper (optional)
├── mirror_bridge_auto # Auto-discovery script
├── mirror_bridge_generate # Config file script
├── mirror_bridge_build # Direct compilation script
├── mirror_bridge_build_pch # PCH builder script (optional)
├── start_dev_container.sh # Docker setup (persistent container)
├── examples/
│ ├── README.md # Detailed usage guide
│ ├── option2/ # Auto-discovery example
│ └── option3/ # Config file example
└── tests/
├── run_all_tests.sh # Automated test suite
├── test_pch.sh # PCH functionality test
└── e2e/ # End-to-end tests
├── basic/ # Point2D, Vector3
├── containers/ # std::vector, std::array
├── nesting/ # Nested classes, cross-file
└── methods/ # Method binding (Calculator)
Mirror Bridge leverages C++26 reflection at compile-time:
- Discovery: Uses
std::meta::nonstatic_data_members_of(^^T)to find all class members - Method Introspection: Uses
std::meta::members_of+std::meta::is_functionto find methods - Type Extraction: Uses
std::meta::type_ofandstd::meta::identifier_offor names - Code Generation: Generates Python C API bindings via template metaprogramming
- Compilation: Compiles to
.somodule with reflection-enabled clang
All binding logic is resolved at compile-time - zero runtime overhead.
See CONTRIBUTING.md for technical details.
- Compiler: Bloomberg clang-p2996 (P2996 reflection support)
- Python: 3.7+
- Platform: Linux (or macOS with Docker)
# Inside Docker container:
# Run all automated tests
./tests/run_all_tests.sh
# Output:
# ✓ Built: 12 bindings
# ✓ Passed: 12 tests
# ✓ ALL TESTS PASSED!
# Test coverage:
# - Basic data members (Point2D, Vector3)
# - Containers (std::vector, std::array)
# - Nested classes (2-level, 3-level, cross-file)
# - Methods (Calculator with various signatures)
# - Variadic parameters (3, 4, 5, 6 parameter methods)
# - Constructors with parameters (0, 2, 3 parameters)
# - Method overloading (int/double/string overloads)
# - Smart pointers (unique_ptr, shared_ptr conversion)Advanced feature tests (tests/e2e/advanced/):
- Variadic: Methods with 3-6 parameters, weighted sums, format functions
- Constructors: Default, 2-param, 3-param constructor matching
- Overloading: Type-based name mangling for overloaded methods
- Smart Pointers: Bidirectional dict conversion, nullptr handling, return values
- Passing bound class instances as parameters (requires reference/pointer handling)
- Template classes (must be explicitly instantiated before binding)
- Const method overloads (treated as same method currently)
- Advanced smart pointers (
weak_ptr, custom deleters)
Recently Completed:
- ✅ Variadic parameter support (any number of parameters)
- ✅ Constructor parameter binding
- ✅ Method overloading via name mangling
- ✅ Smart pointer support (
unique_ptr,shared_ptr)
Next:
Mirror Bridge delivers significant performance improvements over pybind11:
- Simple project (1 class): 816ms vs 1,938ms pybind11 (2.4x faster)
- Medium project (10 classes): 1,543ms vs 3,637ms pybind11 (2.4x faster)
- Why: Reflection eliminates template metaprogramming overhead
- Function calls: 35ns vs 127ns pybind11 (3.6x faster)
- Object construction: 47ns vs 256ns pybind11 (5.4x faster)
- Why: Direct Python C API calls, no template dispatch
- Auto-discovery:
mirror_bridge_auto src/ --module name - No binding code required vs 18-103 lines for pybind11
- Instant: Add members/methods → automatically bound
Methodology: 5 runs per test, median ± stddev reported, identical optimization flags (-O3 -DNDEBUG)
For even faster builds, use precompiled headers to cache the Mirror Bridge infrastructure:
# One-time: Build PCH (takes ~600ms, reuse forever)
./mirror_bridge_build_pch -o build -t release
# Every build: Use PCH for 3-6x faster compilation
mirror_bridge_auto src/ --module my_module --use-pch build/mirror_bridge_pch.hpp.gchPerformance with PCH:
- Simple project: 567ms → 194ms (66% faster, 2.9x speedup)
- Medium project: 1580ms → 252ms (84% faster, 6.3x speedup)
- One-time cost: ~600ms to build PCH (amortized across all builds)
Key benefits:
- ✅ Shared across projects - build PCH once, use everywhere
- ✅ Debug/Release PCH - separate PCH for different build configurations
- ✅ Zero code changes - just add
--use-pchflag - ✅ Automatic detection -
mirror_bridge_autofinds PCH automatically
Complete guide: See PCH_GUIDE.md and WORKFLOW_GUIDE.md
Test suite: Run ./tests/test_pch.sh to verify PCH infrastructure
Run comprehensive tests yourself:
See benchmarks/FINAL_RESULTS.md for complete results and analysis.
This is an experimental project exploring C++26 reflection. Contributions welcome!
Areas needing work:
- Extended parameter support for methods
- Template class handling
- Additional backends (Rust, Lua)
Apache License 2.0 - See LICENSE for details.
Status: Experimental - C++26 reflection is not yet supported on all C++ compilers. This project uses Bloomberg's clang-p2996 implementation.
Yes, method binding works! See calculator tests for full examples.