From 344f9c79b8816eb1872c01e3b68cdb455e9f7b61 Mon Sep 17 00:00:00 2001 From: Joshua Oreman Date: Mon, 27 Jan 2025 10:55:53 -0800 Subject: [PATCH] Introduce class_filler abstraction for objects that provide custom binding logic when passed to def() --- docs/api_core.rst | 42 +++++++++++++++++++++++++++++++++++++ docs/changelog.rst | 8 +++++++ include/nanobind/nb_class.h | 34 ++++++++++++++---------------- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/docs/api_core.rst b/docs/api_core.rst index 10b46ebc..ffce3c15 100644 --- a/docs/api_core.rst +++ b/docs/api_core.rst @@ -2217,6 +2217,13 @@ Class binding where possible. See the discussion of :ref:`customizing object creation ` for more details. + .. cpp:function:: template class_ &def(class_filler arg, const Extra &... extra) + + Dispatch to custom user-provided binding logic implemented by the type + ``Filler``, passing it the binding annotations ``extra...``. + See the documentation of :cpp:struct:`nb::class_filler\<..\> ` + for details. + .. cpp:function:: template class_ &def_rw(const char * name, D C::* p, const Extra &...extra) Bind the field `p` and assign it to the class member `name`. nanobind @@ -2654,6 +2661,41 @@ Class binding See the discussion of :ref:`customizing Python object creation ` for more information. +.. cpp:struct:: template class_filler + + An empty base object which serves as a tag to allow :cpp:func:`class_::def()` + to dispatch to custom logic implemented by the type ``Filler``. This is the + same mechanism used by :cpp:class:`init`, :cpp:class:`init_implicit`, and + :cpp:class:`new_`; it's exposed publicly so that you can create your own + reusable abstractions for binding logic. + + To define a class filler, you would write something like: + + .. code-block:: cpp + + struct my_ops : nb::class_filler { + template + void execute(Class &cl, const Extra&... extra) { + /* series of def() statements on `cl`, which is a nb::class_ */ + } + }; + + Then use it like: + + .. code-block:: cpp + + nb::class_(m, "MyType") + .def("some_method", &MyType::some_method) + .def(my_ops()) + ... ; + + Any arguments to :cpp:func:`class_::def()` after the class filler object + get passed through as the ``Extra...`` parameters to ``execute()``. + As with any other C++ object, data needed by the class filler can be passed + through template arguments or ordinary constructor arguments. + The ``execute()`` method may be static if it doesn't need to access anything + in ``*this``. + GIL Management -------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 7e207490..061273d5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -65,6 +65,14 @@ Version TBD (not yet released) not be an instance of the alias/trampoline type. (PR `#859 `__) +- Added :cpp:class:`nb::class_filler\<..\> `, which can be used to + define your own binding logic that operates on a :cpp:class:`nb::class_\<..\> + ` when an instance of the filler object is passed to + :cpp:func:`class_::def()`. This generalizes the mechanism used by + :cpp:class:`init`, :cpp:class:`new_`, etc, so that you can create + binding abstractions that "feel like" the built-in ones. + (PR `#884 `__) + Version 2.4.0 (Dec 6, 2024) --------------------------- diff --git a/include/nanobind/nb_class.h b/include/nanobind/nb_class.h index 237ec355..fd4fd7dd 100644 --- a/include/nanobind/nb_class.h +++ b/include/nanobind/nb_class.h @@ -329,8 +329,18 @@ inline void *type_get_slot(handle h, int slot_id) { #endif } +template struct class_filler { + protected: + // Ensure class_filler can only be derived from, not constructed + // directly + class_filler() { + static_assert(std::is_base_of_v, + "class_filler uses CRTP: class_filler should be " + "a base of T"); + } +}; -template struct init { +template struct init : class_filler> { template friend class class_; NB_INLINE init() {} @@ -355,7 +365,7 @@ template struct init { } }; -template struct init_implicit { +template struct init_implicit : class_filler> { template friend class class_; NB_INLINE init_implicit() { } @@ -455,7 +465,7 @@ template > struct new_; template -struct new_ { +struct new_ : class_filler> { std::remove_reference_t func; new_(Func &&f) : func((detail::forward_t) f) {} @@ -614,21 +624,9 @@ class class_ : public object { return *this; } - template - NB_INLINE class_ &def(init &&arg, const Extra &... extra) { - arg.execute(*this, extra...); - return *this; - } - - template - NB_INLINE class_ &def(init_implicit &&arg, const Extra &... extra) { - arg.execute(*this, extra...); - return *this; - } - - template - NB_INLINE class_ &def(new_ &&arg, const Extra &... extra) { - arg.execute(*this, extra...); + template + NB_INLINE class_ &def(class_filler &&arg, const Extra &... extra) { + static_cast(arg).execute(*this, extra...); return *this; }