From 10a65539d4d7ef0467edd970c413981815757662 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Mon, 7 Sep 2026 13:15:35 -0700 Subject: [PATCH] Bind std::function variables as named Python functions --- DOCS.md | 7 ++++- gtwrap/pybind_wrapper.py | 35 ++++++++++++++++++++- tests/expected/python/namespaces_pybind.cpp | 31 ++++++++++++++++-- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/DOCS.md b/DOCS.md index 2d25ce0..8303045 100644 --- a/DOCS.md +++ b/DOCS.md @@ -128,7 +128,12 @@ The python wrapper supports keyword arguments for functions/methods. Hence, the - Global variables - Similar to global functions, the wrapper supports global variables as well. - - Currently we only support primitive types, such as `double`, `int`, `string`, etc. + - Primitive types such as `double`, `int`, and `string` become Python module attributes. + - Variables whose C++ type is `std::function`, including type aliases, become + named Python functions with signatures that `pybind11-stubgen` can discover. + Declare the alias in an included C++ header and use it as the variable type + in the interface file. Empty `std::function` values become `None`. + MATLAB continues to ignore global variables. - E.g. ```cpp const double kGravity = -9.81; diff --git a/gtwrap/pybind_wrapper.py b/gtwrap/pybind_wrapper.py index 6ee569c..7b6dbf5 100755 --- a/gtwrap/pybind_wrapper.py +++ b/gtwrap/pybind_wrapper.py @@ -43,6 +43,35 @@ class PybindWrapper: return PyArgPolicy::type>::make(name); } +} // namespace internal +} // namespace gtwrap +""" + + VARIABLE_BINDING_SUPPORT = """ +#include + +namespace gtwrap { +namespace internal { + +// Let C++ resolve aliases rather than guessing callable types in the parser. +template +void bind_variable(pybind11::module_& module, const char* name, const T& value) { + module.attr(name) = value; +} + +template +void bind_variable(pybind11::module_& module, const char* name, + const std::function& value) { + if (!value) { + module.attr(name) = pybind11::none(); + } else if (auto target = value.template target()) { + // Preserve pybind11's direct C++ callback path for function pointers. + module.def(name, *target); + } else { + module.def(name, value); + } +} + } // namespace internal } // namespace gtwrap """ @@ -443,7 +472,9 @@ def wrap_variable(self, else: variable_value = variable.default - return '{prefix}{module_var}.attr("{variable_name}") = {namespace}{variable_value};'.format( + return ('{prefix}gtwrap::internal::bind_variable(' + '{module_var}, "{variable_name}", ' + '{namespace}{variable_value});').format( prefix=prefix, module_var=module_var, variable_name=variable.name, @@ -936,6 +967,8 @@ def wrap_file(self, ]) includes += self.ARG_POLICY_SUPPORT + if 'gtwrap::internal::bind_variable(' in wrapped_bindings: + includes += self.VARIABLE_BINDING_SUPPORT return self.module_template.format( module_def=module_def, diff --git a/tests/expected/python/namespaces_pybind.cpp b/tests/expected/python/namespaces_pybind.cpp index 763254a..ee5cecd 100644 --- a/tests/expected/python/namespaces_pybind.cpp +++ b/tests/expected/python/namespaces_pybind.cpp @@ -29,6 +29,33 @@ pybind11::arg py_arg(const char* name) { } // namespace internal } // namespace gtwrap +#include + +namespace gtwrap { +namespace internal { + +// Let C++ resolve aliases rather than guessing callable types in the parser. +template +void bind_variable(pybind11::module_& module, const char* name, const T& value) { + module.attr(name) = value; +} + +template +void bind_variable(pybind11::module_& module, const char* name, + const std::function& value) { + if (!value) { + module.attr(name) = pybind11::none(); + } else if (auto target = value.template target()) { + // Preserve pybind11's direct C++ callback path for function pointers. + module.def(name, *target); + } else { + module.def(name, value); + } +} + +} // namespace internal +} // namespace gtwrap + @@ -98,7 +125,7 @@ void gtwrap_bind_namespaces_py(py::module_ &m_) { gtwrap_class_m_ns2_ClassC .def(py::init<>()); - m_ns2.attr("aNs2Var") = ns2::aNs2Var; + gtwrap::internal::bind_variable(m_ns2, "aNs2Var", ns2::aNs2Var); m_ns2.def("aGlobalFunction",static_cast(&ns2::aGlobalFunction)); m_ns2.def("overloadedGlobalFunction",static_cast(&ns2::overloadedGlobalFunction), gtwrap::internal::py_arg("a")); m_ns2.def("overloadedGlobalFunction",static_cast(&ns2::overloadedGlobalFunction), gtwrap::internal::py_arg("a"), gtwrap::internal::py_arg("b")); @@ -106,7 +133,7 @@ void gtwrap_bind_namespaces_py(py::module_ &m_) { gtwrap_class_m__ClassD .def(py::init<>()); - m_.attr("aGlobalVar") = aGlobalVar; + gtwrap::internal::bind_variable(m_, "aGlobalVar", aGlobalVar); pybind11::module m_gtsam = py::reinterpret_borrow(m_.attr("gtsam")); auto gtwrap_class_m_gtsam_Values = py::reinterpret_borrow>>(m_gtsam.attr("Values"));