Skip to content

Commit

Permalink
Introduce def_visitor abstraction for objects that provide custom bin…
Browse files Browse the repository at this point in the history
…ding logic when passed to def() (#884)
  • Loading branch information
oremanj authored Jan 28, 2025
1 parent 534fd8c commit 89c40e1
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 18 deletions.
42 changes: 42 additions & 0 deletions docs/api_core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,13 @@ Class binding
where possible. See the discussion of :ref:`customizing object creation
<custom_new>` for more details.

.. cpp:function:: template <typename Visitor, typename... Extra> class_ &def(def_visitor<Visitor> arg, const Extra &... extra)

Dispatch to custom user-provided binding logic implemented by the type
``Visitor``, passing it the binding annotations ``extra...``.
See the documentation of :cpp:struct:`nb::def_visitor\<..\> <def_visitor>`
for details.

.. cpp:function:: template <typename C, typename D, typename... Extra> 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
Expand Down Expand Up @@ -2654,6 +2661,41 @@ Class binding
See the discussion of :ref:`customizing Python object creation <custom_new>`
for more information.

.. cpp:struct:: template <typename Visitor> def_visitor

An empty base object which serves as a tag to allow :cpp:func:`class_::def()`
to dispatch to custom logic implemented by the type ``Visitor``. 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 ``def_visitor``, you would write something like:

.. code-block:: cpp
struct my_ops : nb::def_visitor<my_ops> {
template <typename Class, typename... Extra>
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_<MyType>(m, "MyType")
.def("some_method", &MyType::some_method)
.def(my_ops())
... ;
Any arguments to :cpp:func:`class_::def()` after the ``def_visitor`` object
get passed through as the ``Extra...`` parameters to ``execute()``.
As with any other C++ object, data needed by the ``def_visitor`` 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
--------------
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ Version TBD (not yet released)
not be an instance of the alias/trampoline type.
(PR `#859 <https://github.com/wjakob/nanobind/pull/859>`__)

- Added :cpp:class:`nb::def_visitor\<..\> <def_visitor>`, which can be used to
define your own binding logic that operates on a :cpp:class:`nb::class_\<..\>
<class_>` when an instance of the visitor 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 <https://github.com/wjakob/nanobind/pull/884>`__)

Version 2.4.0 (Dec 6, 2024)
---------------------------

Expand Down
34 changes: 16 additions & 18 deletions include/nanobind/nb_class.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,18 @@ inline void *type_get_slot(handle h, int slot_id) {
#endif
}

template <typename Visitor> struct def_visitor {
protected:
// Ensure def_visitor<T> can only be derived from, not constructed
// directly
def_visitor() {
static_assert(std::is_base_of_v<def_visitor, Visitor>,
"def_visitor uses CRTP: def_visitor<T> should be "
"a base of T");
}
};

template <typename... Args> struct init {
template <typename... Args> struct init : def_visitor<init<Args...>> {
template <typename T, typename... Ts> friend class class_;
NB_INLINE init() {}

Expand All @@ -355,7 +365,7 @@ template <typename... Args> struct init {
}
};

template <typename Arg> struct init_implicit {
template <typename Arg> struct init_implicit : def_visitor<init_implicit<Arg>> {
template <typename T, typename... Ts> friend class class_;
NB_INLINE init_implicit() { }

Expand Down Expand Up @@ -455,7 +465,7 @@ template <typename Func, typename Sig = detail::function_signature_t<Func>>
struct new_;

template <typename Func, typename Return, typename... Args>
struct new_<Func, Return(Args...)> {
struct new_<Func, Return(Args...)> : def_visitor<new_<Func, Return(Args...)>> {
std::remove_reference_t<Func> func;

new_(Func &&f) : func((detail::forward_t<Func>) f) {}
Expand Down Expand Up @@ -614,21 +624,9 @@ class class_ : public object {
return *this;
}

template <typename... Args, typename... Extra>
NB_INLINE class_ &def(init<Args...> &&arg, const Extra &... extra) {
arg.execute(*this, extra...);
return *this;
}

template <typename Arg, typename... Extra>
NB_INLINE class_ &def(init_implicit<Arg> &&arg, const Extra &... extra) {
arg.execute(*this, extra...);
return *this;
}

template <typename Func, typename... Extra>
NB_INLINE class_ &def(new_<Func> &&arg, const Extra &... extra) {
arg.execute(*this, extra...);
template <typename Visitor, typename... Extra>
NB_INLINE class_ &def(def_visitor<Visitor> &&arg, const Extra &... extra) {
static_cast<Visitor&&>(arg).execute(*this, extra...);
return *this;
}

Expand Down

0 comments on commit 89c40e1

Please sign in to comment.