展示 HN:Mirror_bridge – C++ 反射驱动的 Python 绑定生成
Show HN: Mirror_bridge – C++ Reflection powered Python binding generation

原始链接: https://github.com/FranciscoThiesen/mirror_bridge

## 镜像桥:使用 C++26 反射的自动绑定 镜像桥是一个实验性的头文件库,利用 C++26 反射(需要 Bloomberg 的 clang-p2996)从 C++ 代码自动生成 Python、JavaScript 和 Lua 的绑定——**无需任何样板代码**。它在编译时内省 C++ 类,处理数据成员、方法(包括可变参数和重载)、构造函数、智能指针、嵌套类、容器和异常处理。 **主要特性:** * **零代码绑定:** 无需手动编写包装代码或宏。 * **编译时魔法:** 所有绑定逻辑都在编译期间生成,从而实现零运行时开销。 * **多语言支持:** 生成 Python、JavaScript (Node.js) 和 Lua 的绑定。 * **两种工作流程:** “自动发现”用于快速原型设计,而“配置文件”方法则用于生产级控制。 * **性能:** 在编译和运行时均显示出比 pybind11 显著的性能提升。 **入门:** 该项目提供了一个开发容器以方便设置。绑定使用命令行工具(如 `mirror_bridge_auto` 或 `mirror_bridge_generate`)生成。还提供单头文件发行版,以简化集成。 **状态:** 实验性。需要 C++26 反射,尚未推荐用于生产环境。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 展示 HN: Mirror_bridge – C++ 反射驱动的 Python 绑定生成 (github.com/franciscothiesen) 13 分,作者 fthiesen 3 小时前 | 隐藏 | 过去 | 收藏 | 2 评论 sheepscreek 59 分钟前 | 下一个 [–] 这很酷。如果它能可靠地工作,即使是提供的例子,也可以舒适地包装很多 C++ 方法,并大大减少摩擦。2026 规范现在还是草案阶段吗?更新:不用了 - 刚刚读到它只适用于 Bloomberg 的 clang 分支。希望它能尽快合并到上游 clang 中。回复 feb 48 分钟前 | 上一个 [–] 关于这个项目的另一个 HN 帖子:https://news.ycombinator.com/item?id=46144199 回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系方式 搜索:
相关文章

原文

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.

One C++ Class, Three Languages

// 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.0

JavaScript (Node.js):

const calc = new addon.Calculator();
calc.add(10);
calc.subtract(3);
console.log(calc.x);  // 7.0

Lua:

local calc = cpp_calc.Calculator()
calc:add(10)
calc:subtract(3)
print(calc.value)  -- 7.0

No 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_ptr with automatic conversion
  • Nested classes - recursive handling, cross-file dependencies
  • Containers - std::vector, std::array with 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.py

That's it! See QUICKSTART.md for a detailed walkthrough.

Two Approaches to Auto-Generation

Mirror Bridge offers two workflows optimized for different use cases:

Option 1: Auto-Discovery (Minimal Friction)

Just point at a directory - bindings are auto-generated for all classes.

Python:

mirror_bridge_auto src/ --module my_module

Lua:

mirror_bridge_auto_lua src/ --module my_module

JavaScript (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 mylib
import mylib
calc = mylib.Calculator()
vec = mylib.Vector3()

See examples/option2/ for full example.

Option 2: Config File (Production)

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

Single-Header Distribution

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.

What Gets Auto-Generated?

struct Point { double x, y; };
p = my_module.Point()
p.x = 3.0  # Automatic getter/setter
p.y = 4.0

Methods (Variadic Parameters)

struct 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 templates

Constructors with Parameters

struct 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 matching
struct 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 overloads
struct 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 nullptr
struct Address {
    std::string city;
};

struct Person {
    std::string name;
    Address addr;  // Nested!
};
p = my_module.Person()
p.addr = {'city': 'Boston'}  # Dict conversion
struct 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 → array
double 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:

  1. Discovery: Uses std::meta::nonstatic_data_members_of(^^T) to find all class members
  2. Method Introspection: Uses std::meta::members_of + std::meta::is_function to find methods
  3. Type Extraction: Uses std::meta::type_of and std::meta::identifier_of for names
  4. Code Generation: Generates Python C API bindings via template metaprogramming
  5. Compilation: Compiles to .so module 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:

Compile-Time: 2-3x faster

  • 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

Developer Experience: Zero boilerplate

  • 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)

Precompiled Headers (PCH): 3-6x faster compilation

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.gch

Performance 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-pch flag
  • Automatic detection - mirror_bridge_auto finds 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.

联系我们 contact @ memedata.com