Skip to content

Commit 89c40e1

Browse files
authored
Introduce def_visitor abstraction for objects that provide custom binding logic when passed to def() (#884)
1 parent 534fd8c commit 89c40e1

File tree

3 files changed

+66
-18
lines changed

3 files changed

+66
-18
lines changed

docs/api_core.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,13 @@ Class binding
22172217
where possible. See the discussion of :ref:`customizing object creation
22182218
<custom_new>` for more details.
22192219

2220+
.. cpp:function:: template <typename Visitor, typename... Extra> class_ &def(def_visitor<Visitor> arg, const Extra &... extra)
2221+
2222+
Dispatch to custom user-provided binding logic implemented by the type
2223+
``Visitor``, passing it the binding annotations ``extra...``.
2224+
See the documentation of :cpp:struct:`nb::def_visitor\<..\> <def_visitor>`
2225+
for details.
2226+
22202227
.. cpp:function:: template <typename C, typename D, typename... Extra> class_ &def_rw(const char * name, D C::* p, const Extra &...extra)
22212228

22222229
Bind the field `p` and assign it to the class member `name`. nanobind
@@ -2654,6 +2661,41 @@ Class binding
26542661
See the discussion of :ref:`customizing Python object creation <custom_new>`
26552662
for more information.
26562663

2664+
.. cpp:struct:: template <typename Visitor> def_visitor
2665+
2666+
An empty base object which serves as a tag to allow :cpp:func:`class_::def()`
2667+
to dispatch to custom logic implemented by the type ``Visitor``. This is the
2668+
same mechanism used by :cpp:class:`init`, :cpp:class:`init_implicit`, and
2669+
:cpp:class:`new_`; it's exposed publicly so that you can create your own
2670+
reusable abstractions for binding logic.
2671+
2672+
To define a ``def_visitor``, you would write something like:
2673+
2674+
.. code-block:: cpp
2675+
2676+
struct my_ops : nb::def_visitor<my_ops> {
2677+
template <typename Class, typename... Extra>
2678+
void execute(Class &cl, const Extra&... extra) {
2679+
/* series of def() statements on `cl`, which is a nb::class_ */
2680+
}
2681+
};
2682+
2683+
Then use it like:
2684+
2685+
.. code-block:: cpp
2686+
2687+
nb::class_<MyType>(m, "MyType")
2688+
.def("some_method", &MyType::some_method)
2689+
.def(my_ops())
2690+
... ;
2691+
2692+
Any arguments to :cpp:func:`class_::def()` after the ``def_visitor`` object
2693+
get passed through as the ``Extra...`` parameters to ``execute()``.
2694+
As with any other C++ object, data needed by the ``def_visitor`` can be passed
2695+
through template arguments or ordinary constructor arguments.
2696+
The ``execute()`` method may be static if it doesn't need to access anything
2697+
in ``*this``.
2698+
26572699

26582700
GIL Management
26592701
--------------

docs/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ Version TBD (not yet released)
6565
not be an instance of the alias/trampoline type.
6666
(PR `#859 <https://github.com/wjakob/nanobind/pull/859>`__)
6767

68+
- Added :cpp:class:`nb::def_visitor\<..\> <def_visitor>`, which can be used to
69+
define your own binding logic that operates on a :cpp:class:`nb::class_\<..\>
70+
<class_>` when an instance of the visitor object is passed to
71+
:cpp:func:`class_::def()`. This generalizes the mechanism used by
72+
:cpp:class:`init`, :cpp:class:`new_`, etc, so that you can create
73+
binding abstractions that "feel like" the built-in ones.
74+
(PR `#884 <https://github.com/wjakob/nanobind/pull/884>`__)
75+
6876
Version 2.4.0 (Dec 6, 2024)
6977
---------------------------
7078

include/nanobind/nb_class.h

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,18 @@ inline void *type_get_slot(handle h, int slot_id) {
329329
#endif
330330
}
331331

332+
template <typename Visitor> struct def_visitor {
333+
protected:
334+
// Ensure def_visitor<T> can only be derived from, not constructed
335+
// directly
336+
def_visitor() {
337+
static_assert(std::is_base_of_v<def_visitor, Visitor>,
338+
"def_visitor uses CRTP: def_visitor<T> should be "
339+
"a base of T");
340+
}
341+
};
332342

333-
template <typename... Args> struct init {
343+
template <typename... Args> struct init : def_visitor<init<Args...>> {
334344
template <typename T, typename... Ts> friend class class_;
335345
NB_INLINE init() {}
336346

@@ -355,7 +365,7 @@ template <typename... Args> struct init {
355365
}
356366
};
357367

358-
template <typename Arg> struct init_implicit {
368+
template <typename Arg> struct init_implicit : def_visitor<init_implicit<Arg>> {
359369
template <typename T, typename... Ts> friend class class_;
360370
NB_INLINE init_implicit() { }
361371

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

457467
template <typename Func, typename Return, typename... Args>
458-
struct new_<Func, Return(Args...)> {
468+
struct new_<Func, Return(Args...)> : def_visitor<new_<Func, Return(Args...)>> {
459469
std::remove_reference_t<Func> func;
460470

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

617-
template <typename... Args, typename... Extra>
618-
NB_INLINE class_ &def(init<Args...> &&arg, const Extra &... extra) {
619-
arg.execute(*this, extra...);
620-
return *this;
621-
}
622-
623-
template <typename Arg, typename... Extra>
624-
NB_INLINE class_ &def(init_implicit<Arg> &&arg, const Extra &... extra) {
625-
arg.execute(*this, extra...);
626-
return *this;
627-
}
628-
629-
template <typename Func, typename... Extra>
630-
NB_INLINE class_ &def(new_<Func> &&arg, const Extra &... extra) {
631-
arg.execute(*this, extra...);
627+
template <typename Visitor, typename... Extra>
628+
NB_INLINE class_ &def(def_visitor<Visitor> &&arg, const Extra &... extra) {
629+
static_cast<Visitor&&>(arg).execute(*this, extra...);
632630
return *this;
633631
}
634632

0 commit comments

Comments
 (0)