From 4cb196d1b2f63480b78646788aaa6e312b44cfc3 Mon Sep 17 00:00:00 2001 From: Vadim Mishanin Date: Sat, 20 Jun 2026 22:00:19 +0800 Subject: [PATCH] refact --- README.md | 6 +- include/rebind/function_invoker.hpp | 5 +- include/rebind/reflect.hpp | 113 ++++++++++++++++------------ 3 files changed, 70 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 2cf8254..92d16ee 100644 --- a/README.md +++ b/README.md @@ -119,4 +119,8 @@ cmake -S . -B build-ubsan -DENABLE_UNDEFINED_SANITIZER=ON - [ ] Support access to public member variables - [ ] Handle types mismatch - [ ] Handle exception translation (C++ → Python) -- [ ] And more +- [ ] Try replace std::array with std::inplace_vector +- [ ] Try to use concepts +- [ ] Use std::meta::parameters_of instead of function_trats struct. +- [ ] Use ranges as much as possible. +- [ ] And more \ No newline at end of file diff --git a/include/rebind/function_invoker.hpp b/include/rebind/function_invoker.hpp index c9173f6..e7f44d0 100644 --- a/include/rebind/function_invoker.hpp +++ b/include/rebind/function_invoker.hpp @@ -65,8 +65,7 @@ struct MethodInvoker { return invokePythonCallable< args_tuple, return_type>(args, [wrapper](auto&&... converted_args) -> decltype(auto) { - return (wrapper->store.cpp_class.value().*Method)(std::forward(converted_args)... - ); + return (wrapper->cpp_class.value().*Method)(std::forward(converted_args)...); }); } }; @@ -78,7 +77,7 @@ struct ConstructorInvoker { static int invoke(PyObject* self, PyObject* args, PyObject*) { auto* wrapper = reinterpret_cast(self); invokePythonCallable(args, [wrapper](auto&&... converted_args) -> void { - std::ignore = wrapper->store.cpp_class.emplace(std::forward(converted_args)...); + std::ignore = wrapper->cpp_class.emplace(std::forward(converted_args)...); }); return 0; } diff --git a/include/rebind/reflect.hpp b/include/rebind/reflect.hpp index 24bb510..2c80987 100644 --- a/include/rebind/reflect.hpp +++ b/include/rebind/reflect.hpp @@ -81,7 +81,7 @@ inline consteval auto getFunction() noexcept { if constexpr (std::meta::is_function(members[I])) { constexpr std::string_view name = std::meta::identifier_of(members[I]); - return std::make_tuple(CallableInfo{name, [:members[I]:]}); + return std::make_tuple(CallableInfo{name, &[:members[I]:]}); } else { return std::tuple<>(); } @@ -103,46 +103,70 @@ template template struct PyClassWrapper { - struct Storage; - consteval { - std::vector mems; - constexpr std::meta::info optional = std::meta::substitute( - ^^std::optional, - { - ^^T - } - ); - mems.push_back(std::meta::data_member_spec(optional, {.name = "cpp_class"})); - std::meta::define_aggregate(^^Storage, mems); - } - PyObject_HEAD Storage store{}; + PyObject_HEAD // new-line + std::optional + cpp_class; - static PyObject* create(PyTypeObject* o, PyObject* args, PyObject* kwds) { return o->tp_alloc(o, 0); } + static PyObject* allocate(PyTypeObject* o, PyObject* args, PyObject* kwds) { + auto* self = reinterpret_cast*>(o->tp_alloc(o, 0)); + + if (!self) { + return nullptr; + } - static void dealloc(PyObject* o) { Py_TYPE(o)->tp_free(o); } + std::construct_at(&self->cpp_class); + + return reinterpret_cast(self); + } + + static void deallocate(PyObject* o) { + auto* self = reinterpret_cast*>(o); + std::destroy_at(&self->cpp_class); + Py_TYPE(o)->tp_free(o); + } }; template struct ClassDescriptor { - PyTypeObject type; + PyTypeObject type{}; std::array methods{}; //< +1 for sentinel object. }; +template +inline consteval auto makeClassDescriptor(T&& methodsTuple) { + constexpr size_t methodsCount = std::tuple_size_v>; + ClassDescriptor desc{}; + + // Assign methods from the tuple. + std::apply( + [&desc](auto... method_def) { + size_t method_id{}; + ((desc.methods[method_id++] = method_def), ...); + }, + methodsTuple + ); + + // Fill sentinel method + desc.methods[methodsCount] = PyMethodDef{.ml_name = nullptr, .ml_meth = nullptr, .ml_flags = 0, .ml_doc = nullptr}; + + return desc; +} + template inline consteval auto getMethodDef() noexcept { static constexpr auto ctx = std::meta::access_context::unprivileged(); constexpr auto members = std::define_static_array(std::meta::members_of(R, ctx)); - if constexpr (std::meta::is_function(members[I]) && !std::meta::is_special_member_function(members[I]) && + if constexpr (std::meta::is_function(members[I]) && std::meta::is_public(members[I]) && + !std::meta::is_static_member(members[I]) && !std::meta::is_special_member_function(members[I]) && !std::meta::is_constructor(members[I])) { - std::meta::parameters_of(members[I]); return std::make_tuple( PyMethodDef{ .ml_name = std::meta::identifier_of(members[I]).data(), .ml_meth = MethodInvoker<&[:members[I]:], Wrapper>::invoke, .ml_flags = METH_VARARGS, - .ml_doc = "doc", + .ml_doc = "doc", //< TODO: meaningful doc. } ); } else { @@ -155,13 +179,14 @@ inline consteval auto collectMethodDefsImpl(std::index_sequence) noexcept return std::tuple_cat(getMethodDef()...); } +// Returns tuple of PyMethodDef. TODO: replace with std::array/std::inplace_vector? template inline consteval auto collectMethodDefs() noexcept { return collectMethodDefsImpl(std::make_index_sequence()>{}); } template -consteval auto getTypesOfFunctionArgs() { +consteval auto getTypesOfParameters() { constexpr auto params = std::define_static_array(std::meta::parameters_of(R)); return [params](std::index_sequence) { @@ -173,11 +198,12 @@ template inline consteval auto getConstructorInvoker() { static constexpr auto ctx = std::meta::access_context::unprivileged(); + // TODO: use ranges template for (constexpr auto m : std::define_static_array(std::meta::members_of(C, ctx))) { - if constexpr (std::meta::is_public(m) && std::meta::is_constructor(m) && !is_copy_constructor(m) && - !is_move_constructor(m)) + if constexpr (std::meta::is_public(m) && std::meta::is_constructor(m) && !std::meta::is_copy_constructor(m) && + !std::meta::is_move_constructor(m)) { - auto argTypes = getTypesOfFunctionArgs(); + auto argTypes = getTypesOfParameters(); return &ConstructorInvoker::invoke; } } @@ -185,40 +211,27 @@ inline consteval auto getConstructorInvoker() { template inline consteval auto reflect_class() noexcept { - constexpr std::meta::info t = std::meta::substitute( - ^^PyClassWrapper, - { - C - } - ); - const auto class_name = std::meta::identifier_of(C); - using type_t = typename[:t:]; - constexpr auto methodDefs = collectMethodDefs(); - ClassDescriptor> desc{}; - desc.type = PyTypeObject{}; + using pywrapper_t = PyClassWrapper; + + const std::string_view class_name = std::meta::identifier_of(C); + + constexpr auto methodsDefs = collectMethodDefs(); + auto desc = makeClassDescriptor(methodsDefs); + // TODO: init ob_base via Python macros? desc.type.ob_base.ob_base.ob_refcnt = 1; desc.type.ob_base.ob_base.ob_type = nullptr; desc.type.ob_base.ob_size = 0; + desc.type.tp_name = class_name.data(); //< TODO: include module name. - desc.type.tp_basicsize = sizeof(type_t); + desc.type.tp_basicsize = sizeof(pywrapper_t); desc.type.tp_itemsize = 0; - desc.type.tp_dealloc = type_t::dealloc; desc.type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; desc.type.tp_doc = PyDoc_STR(class_name.data()); - desc.type.tp_init = getConstructorInvoker(); - desc.type.tp_new = type_t::create; - desc.type.tp_methods = nullptr; //< Note: Filled at runtime - std::apply( - [&desc](auto... method_def) { - size_t method_id{}; - ((desc.methods[method_id++] = method_def), ...); - }, - methodDefs - ); - // Fill sentinel method - desc.methods[std::tuple_size_v] = PyMethodDef{.ml_name = nullptr, .ml_meth = nullptr, .ml_flags = 0, .ml_doc = nullptr}; + desc.type.tp_new = pywrapper_t::allocate; //< Note: allocates a new Python wrapper object/ + desc.type.tp_init = getConstructorInvoker(); //< Note: can be invoked multiple times. + desc.type.tp_dealloc = pywrapper_t::deallocate; + desc.type.tp_methods = nullptr; //< Note: assigned at runtime due to consteval rules. return desc; }