welder 0.1.0
Bindings for annotated C++ types, from C++26 reflection
Loading...
Searching...
No Matches
rod.hpp
Go to the documentation of this file.
1#pragma once
35#include <array>
36#include <cstddef>
37#include <cstdint>
38#include <meta>
39#include <string>
40#include <type_traits>
41#include <utility>
42
43#include <welder/welder.hpp> // welder::welder + the rod contract + driver
44#include <welder/rods/python/doc_style.hpp> // welder::rods::python::google_style
45#include <welder/rods/python/operators.hpp> // welder::rods::python::operator_dunder
46#include <welder/rods/python/trampoline.hpp> // trampoline_for / gate / coverage
47#include <welder/bind_traits.hpp>// param_types / param_names / aggregate_fields
48#include <welder/doc.hpp> // function_docstring
49
50#include <nanobind/nanobind.h>
51#include <nanobind/ndarray.h> // nb::ndarray (zero-copy numpy views)
52#include <nanobind/stl/bind_vector.h> // nb::bind_vector (opaque sequences)
53#include <nanobind/stl/optional.h> // lazy NSDMI defaults (Optional[F] = None)
54#include <nanobind/stl/bind_map.h> // nb::bind_map (opaque maps)
55
56#include <welder/containers.hpp> // container_kind_of (bind_vector vs bind_map)
57#include <welder/rods/python/array_interface.hpp> // numpy __array_interface__ for POD vectors
58
67#ifndef WELDER_OPAQUE
68#define WELDER_OPAQUE(...) NB_MAKE_OPAQUE(__VA_ARGS__)
69#endif
70
71namespace welder::inline v0::rods::nanobind {
72
73// Inside `welder::rods::nanobind`, the unqualified name `nanobind` resolves to *this*
74// namespace, not the library. Alias the real one once — the way nanobind's own
75// docs spell it — and use `nb::` for every library reference below.
76namespace nb = ::nanobind;
77
96template <::welder::doc_style DocStyle = ::welder::rods::python::google_style>
97struct rod {
98 static constexpr lang language{lang::py};
99 using module_type = nb::module_;
100
105 template <class E> using enum_handle_type = nb::enum_<E>;
106
107 protected:
108 // --- implementation helpers (not part of the welder::rod contract) --
109
136 template <class T>
137 static constexpr bool _needs_registration =
138 std::is_enum_v<std::remove_cvref_t<T>> ||
139 nb::detail::is_base_caster_v<nb::detail::make_caster<T>>;
140
146 static consteval nb::rv_policy _rv_policy(::welder::rv_kind k) {
147 switch (k) {
148 case ::welder::rv_kind::automatic: return nb::rv_policy::automatic;
149 case ::welder::rv_kind::automatic_reference: return nb::rv_policy::automatic_reference;
150 case ::welder::rv_kind::take_ownership: return nb::rv_policy::take_ownership;
151 case ::welder::rv_kind::copy: return nb::rv_policy::copy;
152 case ::welder::rv_kind::move: return nb::rv_policy::move;
153 case ::welder::rv_kind::reference: return nb::rv_policy::reference;
154 case ::welder::rv_kind::reference_internal: return nb::rv_policy::reference_internal;
155 case ::welder::rv_kind::none: return nb::rv_policy::none;
156 }
157 return nb::rv_policy::automatic;
158 }
159
184 template <std::meta::info Fn, class Style>
185 static consteval auto _styled_param_names() {
186 constexpr std::size_t n{std::meta::parameters_of(Fn).size()};
187 std::array<const char*, n> names{};
188 std::size_t i{0};
189 for (auto p : std::meta::parameters_of(Fn))
190 names[i++] = std::meta::has_identifier(p)
191 ? std::define_static_string(Style::transform_field(p))
192 : nullptr;
193 return names;
194 }
195
196 template <std::meta::info Fn, class Style, class Def, std::size_t... I, std::size_t... K>
197 static void _def_function(const char* name, Def def_into,
198 std::index_sequence<I...>, std::index_sequence<K...>) {
199 static constexpr auto names{_styled_param_names<Fn, Style>()};
200 static constexpr auto ka{::welder::detail::keep_alive_pairs<Fn>()};
202 constexpr nb::rv_policy rvp{_rv_policy(::welder::return_policy_of(Fn, language))};
205 if (doc.empty())
206 def_into(name, &[:Fn:], nb::arg(names[I])..., rvp,
207 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
208 else
209 def_into(name, &[:Fn:], doc.c_str(), nb::arg(names[I])..., rvp,
210 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
211 } else {
212 if (doc.empty())
213 def_into(name, &[:Fn:], rvp,
214 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
215 else
216 def_into(name, &[:Fn:], doc.c_str(), rvp,
217 nb::keep_alive<ka[K].nurse, ka[K].patient>()...);
218 }
219 }
220
235 template <std::meta::info Fn, class Self, class Style, class Def, std::size_t... I>
236 static void _def_truncated(const char* name, Def def_into,
237 std::index_sequence<I...>) {
238 // maybe_unused: a zero-arity truncation has an empty I pack, so the
239 // splices below never reference params and gcc flags it set-but-unused.
240 [[maybe_unused]] static constexpr auto params{
242 static constexpr auto names{_styled_param_names<Fn, Style>()};
243 constexpr nb::rv_policy rvp{
245 if constexpr (std::is_void_v<Self>) {
246 auto wrapper = [](typename [:params[I]:]... a) -> decltype(auto) {
247 return [:Fn:](std::forward<typename [:params[I]:]>(a)...);
248 };
250 def_into(name, wrapper, nb::arg(names[I])..., rvp);
251 else
252 def_into(name, wrapper, rvp);
253 } else {
254 auto wrapper = [](Self& self,
255 typename [:params[I]:]... a) -> decltype(auto) {
256 return self.[:Fn:](std::forward<typename [:params[I]:]>(a)...);
257 };
259 def_into(name, wrapper, nb::arg(names[I])..., rvp);
260 else
261 def_into(name, wrapper, rvp);
262 }
263 }
264
268 template <std::meta::info Fn, class Self, class Style, class Def, std::size_t... K>
269 static void _def_default_truncations(const char* name, Def def_into,
270 std::index_sequence<K...>) {
271 constexpr std::size_t P{std::meta::parameters_of(Fn).size()};
272 constexpr std::size_t D{sizeof...(K)};
273 (_def_truncated<Fn, Self, Style>(name, def_into,
274 std::make_index_sequence<P - D + K>{}),
275 ...);
276 }
277
280 template <std::meta::info Fn, class Style, class Def>
281 static void _def_function(const char* name, Def def_into) {
283 name, def_into,
284 std::make_index_sequence<std::meta::parameters_of(Fn).size()>{},
285 std::make_index_sequence<
287 }
288
304 template <std::meta::info Ctor, std::size_t D, class Style, std::size_t... K>
305 static void _def_init_truncations(auto& cls, std::index_sequence<K...>) {
306 constexpr std::size_t P{std::meta::parameters_of(Ctor).size()};
307 (_def_init<Ctor, Style>(cls, std::make_index_sequence<P - D + K>{}), ...);
308 }
309
310 template <std::meta::info Ctor, class Style, std::size_t... I>
311 static void _def_init(auto& cls, std::index_sequence<I...>) {
312 static constexpr auto params{::welder::detail::param_types<Ctor>()};
313 static constexpr auto names{_styled_param_names<Ctor, Style>()};
315 cls.def(nb::init<typename [:params[I]:]...>(), nb::arg(names[I])...);
316 else
317 cls.def(nb::init<typename [:params[I]:]...>());
318 }
319
342 template <class T>
343 static nb::object _copy_instance(nb::handle self, nb::object* memo) {
344 nb::object cls{
345 nb::borrow(reinterpret_cast<PyObject*>(Py_TYPE(self.ptr())))};
346 nb::object out{cls.attr("__new__")(cls)};
347 if (memo)
348 (*memo)[nb::int_(reinterpret_cast<std::uintptr_t>(self.ptr()))] =
349 out;
350 // Copy-construct the C++ payload into the fresh shell in place — the
351 // alias-aware work `nb::init<const T&>` would do, done here so the copy
352 // needs no Python-visible `T(other)` constructor. A Python-derived shell
353 // gets the trampoline so C++ virtual calls keep dispatching into the
354 // Python override; a plain instance gets T. nanobind sizes every
355 // instance for the alias, so either payload fits; inst_mark_ready then
356 // flags it constructed and owned, as nanobind's own init path does.
357 const T& src{nb::cast<const T&>(self)};
358 if (nb::detail::nb_inst_python_derived(out.ptr()))
359 new (nb::inst_ptr<void>(out)) construction_type<T>(src);
360 else
361 new (nb::inst_ptr<void>(out)) T(src);
362 nb::inst_mark_ready(out);
363 if (nb::hasattr(self, "__dict__")) {
364 nb::object d{self.attr("__dict__")};
365 if (memo)
366 d = nb::module_::import_("copy").attr("deepcopy")(d, *memo);
367 out.attr("__dict__").attr("update")(d);
368 }
369 for (nb::handle name :
370 nb::module_::import_("copyreg").attr("_slotnames")(cls)) {
371 if (!nb::hasattr(self, name))
372 continue; // an unassigned slot stays unassigned on the copy
373 nb::object v{nb::getattr(self, name)};
374 if (memo)
375 v = nb::module_::import_("copy").attr("deepcopy")(v, *memo);
376 nb::setattr(out, name, v);
377 }
378 return out;
379 }
380
392 template <class T, std::size_t I>
393 static consteval bool _lazy_default() {
395 using field_type = std::remove_const_t<
396 typename [:std::meta::type_of(::welder::detail::aggregate_fields<T>()[I]):]>;
398 } else {
399 return false;
400 }
401 }
402
406 template <class T, std::size_t I>
407 using _init_param = std::conditional_t<
409 std::optional<std::remove_const_t<
410 typename [:std::meta::type_of(::welder::detail::aggregate_fields<T>()[I]):]>>,
411 std::remove_const_t<
412 typename [:std::meta::type_of(::welder::detail::aggregate_fields<T>()[I]):]>>;
413
420 template <class T, std::size_t I>
421 static auto _init_value(_init_param<T, I>&& arg) {
422 if constexpr (_lazy_default<T, I>()) {
423 constexpr auto field =
425 if (arg)
426 return typename _init_param<T, I>::value_type{std::move(*arg)};
427 return typename _init_param<T, I>::value_type{T{}.[:field:]};
428 } else {
429 return std::move(arg);
430 }
431 }
432
445 template <class T, std::size_t I, class Style>
446 static auto _aggregate_arg([[maybe_unused]] const T& probe) {
447 static constexpr auto fields{::welder::detail::aggregate_fields<T>()};
448 constexpr const char* name{::welder::name_of<fields[I], language, Style,
450 if constexpr (_lazy_default<T, I>()) {
451 // A registration-needed default (a welded class/enum instance)
452 // has no expression-shaped repr anyway — the signature spells it
453 // `...`; None (or omission) selects the NSDMI value in C++.
454 return (nb::arg(name).sig("...") = nb::none());
455 } else if constexpr (I >= ::welder::detail::aggregate_defaults_from<T>()) {
456 return nb::arg(name) = probe.[:fields[I]:];
457 } else {
458 return nb::arg(name);
459 }
460 }
461
475 template <class T, class Style, std::size_t... I>
476 static void _def_aggregate_init(auto& cls, std::index_sequence<I...>) {
477 static constexpr auto fields{::welder::detail::aggregate_fields<T>()};
479 fields.size()) {
480 // The probe exists only when a default is extractable
481 // (aggregate_defaults_from guarantees T{} is well-formed then) —
482 // and T{} in the lambda body reuses the same guarantee.
483 const T probe{};
484 cls.def(
485 "__init__",
486 [](T* self, _init_param<T, I>... args) {
487 new (self) T{_init_value<T, I>(std::move(args))...};
488 },
490 } else {
491 cls.def(
492 "__init__",
493 [](T* self, typename [:std::meta::type_of(fields[I]):]... args) {
494 new (self) T{std::move(args)...};
495 },
496 nb::arg(::welder::name_of<fields[I], language, Style,
498 }
499 }
500
515 static void _install_live_properties(nb::module_& m, nb::dict props) {
516 auto builtins{nb::module_::import_("builtins")};
517 auto subclass{builtins.attr("type")(
518 nb::str("welder_live_module"),
519 nb::make_tuple(m.attr("__class__")), props)};
520 // Stamp the module's own name onto the dynamically-created class, which would
521 // otherwise carry `__module__ == "_frozen_importlib"` (the module-init frame).
522 // Keeps functions welded onto `m` after this swap (e.g. a `weld_function`
523 // following a mutable `weld_variable`) correctly attributed in stubs.
524 subclass.attr("__module__") = m.attr("__name__");
525 m.attr("__class__") = subclass;
526 }
527
543 template <class T, class Trampoline, auto Bases, std::size_t... I>
544 static auto _make_class(nb::handle scope, const char* name, const char* doc,
545 std::index_sequence<I...>) {
546 if constexpr (std::is_void_v<Trampoline>) {
547 if (doc)
548 return nb::class_<T, typename [:Bases[I]:]...>(scope, name, doc);
549 return nb::class_<T, typename [:Bases[I]:]...>(scope, name);
550 } else {
551 // The trampoline is an extra `nb::class_` template argument (nanobind
552 // accepts base and trampoline in either order); Python subclasses then
553 // instantiate it, so their overrides capture C++ virtual calls.
554 if (doc)
555 return nb::class_<T, Trampoline, typename [:Bases[I]:]...>(scope, name, doc);
556 return nb::class_<T, Trampoline, typename [:Bases[I]:]...>(scope, name);
557 }
558 }
559
564 template <class T, auto Bases, std::size_t... I>
565 static auto _make_class_at(nb::handle scope, const char* name,
566 const char* doc, std::index_sequence<I...> seq) {
567 namespace py = ::welder::rods::python;
568 if constexpr (::welder::has_virtual_methods(^^T)) {
569 // Resolve the trampoline: an explicit `trampoline_for<T>` wins; otherwise
570 // scan T's namespace for a `[[=trampoline]]`-annotated subclass.
571 constexpr auto scanned{py::scanned_trampoline_of(^^T)};
572 static_assert(
573 py::trampoline_for<T> != std::meta::info{} || !scanned.ambiguous,
574 "welder: more than one [[=welder::rods::python::trampoline]] class in "
575 "this namespace derives from T; disambiguate by specializing "
576 "welder::rods::python::trampoline_for<T>.");
577 constexpr std::meta::info tramp{py::trampoline_for<T> != std::meta::info{}
578 ? py::trampoline_for<T>
579 : scanned.type};
580 if constexpr (tramp != std::meta::info{}) {
581 using Trampoline = [:tramp:];
582 static_assert(
583 py::trampoline_covers(^^T, ^^Trampoline),
584 "welder: the trampoline registered for this type does not "
585 "override all of its virtual methods; every virtual needs an "
586 "override forwarding to Python (see WELDER_PY_OVERRIDE).");
587 return _make_class<T, Trampoline, Bases>(scope, name, doc, seq);
588 } else {
589 static_assert(
591 "welder: this welded type has virtual methods but no trampoline "
592 "is registered, so a Python subclass could not override them. "
593 "Register one — a [[=welder::rods::python::trampoline]] subclass "
594 "in T's namespace, or a welder::rods::python::trampoline_for<T> "
595 "specialization — or annotate T with "
596 "[[=welder::bind_flat]] to bind it non-overridably.");
597 return _make_class<T, void, Bases>(scope, name, doc, seq);
598 }
599 } else {
600 return _make_class<T, void, Bases>(scope, name, doc, seq);
601 }
602 }
603
604 public:
605 // --- caster oracle + emission primitives (the welder::rod contract) --
606
610 template <class T>
612
615 static consteval const char* special_method_name(std::meta::info op_fn) {
616 return ::welder::rods::python::operator_dunder(op_fn);
617 }
618
619 // --- class binding ------------------------------------------------------
620
625 template <class T>
628
638 template <class T, auto Bases, std::size_t... I>
639 static auto make_class(module_type& m, const char* name, const char* doc,
640 std::index_sequence<I...> seq) {
641 return _make_class_at<T, Bases>(m, name, doc, seq);
642 }
643
649 template <class T, auto Bases, std::size_t... I>
650 static auto make_nested_class(module_type&, auto& outer_cls, const char* name,
651 const char* doc, std::index_sequence<I...> seq) {
652 return _make_class_at<T, Bases>(outer_cls, name, doc, seq);
653 }
654
661 template <class T>
663 std::declval<module_type&>(), nullptr, nullptr, std::index_sequence<>{}));
664
674 template <class T>
675 static class_handle_type<T> reopen_class(module_type& scope, const char* name) {
676 return nb::borrow<class_handle_type<T>>(scope.attr(name));
677 }
678
681 template <class T>
683 const char* name) {
684 return nb::borrow<class_handle_type<T>>(outer.attr(name));
685 }
686
705 template <class T, auto Ctors, bool HasDefault, bool Aggregate, bool Copyable,
706 class Style = ::welder::naming::none>
707 static void add_constructors(auto& cls) {
708 if constexpr (HasDefault)
709 cls.def(nb::init<>());
710 if constexpr (Copyable) {
711 // A trampolined type must keep the copy faithful for Python
712 // subclasses: _copy_instance constructs the ALIAS payload on a
713 // subclass shell only if it is constructible from const T&.
714 static_assert(
715 std::is_constructible_v<construction_type<T>, const T&>,
716 "welder: this type's trampoline lacks a copy-from-base "
717 "constructor, so a copied Python-subclass instance would hold a "
718 "plain base payload and silently stop dispatching virtuals into "
719 "Python. The WELDER_PY_TRAMPOLINE(TRAMP, BASE) macro declares "
720 "it; a hand-rolled trampoline needs 'Tramp(const Base&)' — or "
721 "mark::exclude the copy constructor.");
722 // The copy constructor is exposed ONLY as Python's copy protocol —
723 // `copy.copy`/`copy.deepcopy` — never as a `T(other)` init overload
724 // (that C++-ism is unidiomatic in Python, and would collide with a
725 // one-arg user constructor). _copy_instance owns the in-place copy.
726 cls.def("__copy__", [](nb::handle self) {
727 return _copy_instance<T>(self, nullptr);
728 });
729 cls.def(
730 "__deepcopy__",
731 [](nb::handle self, nb::object memo) {
732 return _copy_instance<T>(self, &memo);
733 },
734 nb::arg("memo"));
735 }
736 template for (constexpr auto ctor : std::define_static_array(Ctors)) {
737 _def_init<ctor, Style>(cls, std::make_index_sequence<
738 std::meta::parameters_of(ctor).size()>{});
739 // C++ default arguments on constructors: _def_init already takes
740 // the parameter index sequence, so each omissible arity is just a
741 // shorter sequence — nb::init<first-N> calls the constructor with
742 // N arguments and the language applies the real defaults.
743 constexpr std::size_t d{::welder::detail::trailing_default_count<ctor>()};
744 if constexpr (d > 0)
746 std::make_index_sequence<d>{});
747 }
748 if constexpr (Aggregate) {
749 constexpr auto fields{::welder::detail::aggregate_fields<T>()};
750 _def_aggregate_init<T, Style>(cls, std::make_index_sequence<fields.size()>{});
751 }
752 }
753
766 template <std::meta::info Mem, class Bound>
767 static constexpr bool _erasable_field{[] {
768 if (!std::meta::is_public(Mem) || std::meta::is_bit_field(Mem))
769 return false;
770 if (std::meta::parent_of(Mem) != ^^Bound)
771 return false;
772 using F = [:std::meta::remove_cv(std::meta::type_of(Mem)):];
773 if (std::meta::is_volatile_type(std::meta::type_of(Mem)))
774 return false;
775 return std::is_arithmetic_v<F> || std::is_enum_v<F> ||
776 std::is_same_v<F, std::string>;
777 }()};
778
809 template <class D>
810 static void _def_erased_field(nb::handle cls, const char* name,
811 std::size_t offset, bool read_only,
812 const char* doc) {
813 auto get = [offset](nb::handle self) -> D {
814 return *reinterpret_cast<const D*>(
815 static_cast<const char*>(nb::inst_ptr<void>(self)) + offset);
816 };
817 nb::object get_p{doc ? nb::cpp_function(get, nb::is_method(),
818 nb::is_getter(), doc)
819 : nb::cpp_function(get, nb::is_method(),
820 nb::is_getter())};
821 nb::object set_p{};
822 if (!read_only) {
823 auto set = [offset](nb::handle self, D value) {
824 *reinterpret_cast<D*>(
825 static_cast<char*>(nb::inst_ptr<void>(self)) + offset) =
826 std::move(value);
827 };
828 set_p = nb::cpp_function(set, nb::is_method());
829 }
830 nb::detail::property_install(cls.ptr(), name, get_p.ptr(), set_p.ptr());
831 }
832
848 template <std::meta::info Mem, class Style = ::welder::naming::none>
849 static void add_field(auto& cls) {
850 constexpr const char* name{
852 constexpr const char* doc{::welder::doc_of<Mem>()};
853 // Read-only either because the member's type is const (def_rw's setter
854 // would not compile) or because a `no_reassign` mark forces it on an
855 // otherwise-mutable member (a whole-object rebind is rejected; in-place
856 // mutation still writes through the reference_internal getter).
857 constexpr bool read_only{std::meta::is_const_type(std::meta::type_of(Mem)) ||
859 if constexpr (!std::meta::is_public(Mem)) {
860 // A protected member (admitted under policy::weld_protected) binds
861 // as a property over welder::detail::field_access — gcc-16 rejects
862 // the dependent `&[:Mem:]` for protected data (see field_access).
863 // Same semantics as def_rw: reference_internal getter.
865 if constexpr (read_only) {
866 if constexpr (doc)
867 cls.def_prop_ro(name, &fa::get,
868 nb::rv_policy::reference_internal, doc);
869 else
870 cls.def_prop_ro(name, &fa::get,
871 nb::rv_policy::reference_internal);
872 } else {
873 if constexpr (doc)
874 cls.def_prop_rw(name, &fa::get, &fa::set,
875 nb::rv_policy::reference_internal, doc);
876 else
877 cls.def_prop_rw(name, &fa::get, &fa::set,
878 nb::rv_policy::reference_internal);
879 }
880 } else if constexpr (_erasable_field<
881 Mem, typename std::remove_reference_t<
882 decltype(cls)>::Type>) {
883 using F = [:std::meta::remove_cv(std::meta::type_of(Mem)):];
884 // .bytes is ptrdiff_t (P2996's member_offset); a real member's
885 // offset is never negative, so the cast is value-preserving.
887 cls, name,
888 static_cast<std::size_t>(std::meta::offset_of(Mem).bytes),
889 read_only, doc);
890 } else if constexpr (read_only) {
891 if constexpr (doc)
892 cls.def_ro(name, &[:Mem:], doc);
893 else
894 cls.def_ro(name, &[:Mem:]);
895 } else {
896 if constexpr (doc)
897 cls.def_rw(name, &[:Mem:], doc);
898 else
899 cls.def_rw(name, &[:Mem:]);
900 }
901 }
902
915 template <class T, std::meta::info Getter, std::meta::info Setter>
916 static void add_property(auto& cls, const char* name) {
918 constexpr ::welder::rv_kind rvk{::welder::return_policy_of(Getter, language)};
919 constexpr const char* doc{::welder::doc_of<Getter>()};
920 auto def{[&](auto&&... extra) {
921 if constexpr (Setter == std::meta::info{}) {
922 cls.def_prop_ro(name, &[:Getter:],
923 std::forward<decltype(extra)>(extra)...);
924 } else if constexpr (std::is_void_v<
925 typename [:std::meta::return_type_of(Setter):]>) {
926 cls.def_prop_rw(name, &[:Getter:], &[:Setter:],
927 std::forward<decltype(extra)>(extra)...);
928 } else {
929 // A value-returning setter (a fluent T& set_x(…)): discard the
930 // return — the property protocol has no slot for it, and the
931 // gate deliberately never checked it.
932 using Arg = typename
933 [:std::meta::type_of(std::meta::parameters_of(Setter)[0]):];
934 static constexpr auto sp{&[:Setter:]};
935 cls.def_prop_rw(
936 name, &[:Getter:],
937 [](T& self, Arg v) { (self.*sp)(std::forward<Arg>(v)); },
938 std::forward<decltype(extra)>(extra)...);
939 }
940 }};
941 constexpr nb::rv_policy rvp{[] {
942 if constexpr (rvk != ::welder::rv_kind::automatic) {
943 return _rv_policy(rvk);
944 } else {
945 constexpr auto rt{std::meta::return_type_of(Getter)};
946 return std::meta::is_pointer_type(rt) ||
947 std::meta::is_lvalue_reference_type(rt)
948 ? nb::rv_policy::reference_internal
949 : nb::rv_policy::automatic;
950 }
951 }()};
952 if constexpr (doc)
953 def(rvp, doc);
954 else
955 def(rvp);
956 }
957
960 template <auto Fns, class Style = ::welder::naming::none>
961 static void add_method(auto& cls) {
962 constexpr const char* name{
964 template for (constexpr auto fn : std::define_static_array(Fns)) {
965 _def_function<fn, Style>(name, [&cls](auto&&... a) {
966 cls.def(std::forward<decltype(a)>(a)...);
967 });
968 // C++ default arguments: one truncated overload per omissible
969 // arity (see _def_truncated for why the value cannot be restated).
970 constexpr std::size_t d{::welder::detail::trailing_default_count<fn>()};
971 if constexpr (d > 0)
973 fn, typename std::remove_reference_t<decltype(cls)>::Type,
974 Style>(
975 name,
976 [&cls](auto&&... a) {
977 cls.def(std::forward<decltype(a)>(a)...);
978 },
979 std::make_index_sequence<d>{});
980 }
981 }
982
984 template <auto Fns, class Style = ::welder::naming::none>
985 static void add_static_method(auto& cls) {
986 constexpr const char* name{
987 ::welder::name_of<Fns[0], language, Style,
989 template for (constexpr auto fn : std::define_static_array(Fns)) {
990 _def_function<fn, Style>(name, [&cls](auto&&... a) {
991 cls.def_static(std::forward<decltype(a)>(a)...);
992 });
993 constexpr std::size_t d{::welder::detail::trailing_default_count<fn>()};
994 if constexpr (d > 0)
996 name,
997 [&cls](auto&&... a) {
998 cls.def_static(std::forward<decltype(a)>(a)...);
999 },
1000 std::make_index_sequence<d>{});
1001 }
1002 }
1003
1013 template <class T, auto Fns>
1014 static void add_operator(auto& cls) {
1015 template for (constexpr auto fn : std::define_static_array(Fns)) {
1016 if constexpr (::welder::detail::free_operator_reflected(fn, ^^T)) {
1018 } else {
1022 std::make_index_sequence<
1024 }
1025 }
1026 }
1027
1033 template <class T, auto Fns, auto Covered>
1034 static void add_comparisons(auto& cls) {
1036 [&cls](const char* name, auto fp) {
1037 cls.def(name, fp, nb::is_operator{});
1038 });
1039 }
1040
1043 template <class T, std::meta::info Fn>
1044 static void add_stringifier(auto& cls) {
1045 cls.def("__str__", &::welder::detail::stringify<T, Fn>);
1046 }
1047
1048 private:
1055 template <std::meta::info Fn, bool NotImpl, class Cls, std::size_t... K>
1056 static void _def_operator(const char* name, Cls& cls,
1057 std::index_sequence<K...>) {
1058 static constexpr auto ka{::welder::detail::keep_alive_pairs<Fn>()};
1060 constexpr nb::rv_policy rvp{
1063 auto def{[&](auto&&... extra) {
1064 if (doc.empty())
1065 cls.def(name, &[:Fn:], rvp,
1066 nb::keep_alive<ka[K].nurse, ka[K].patient>()...,
1067 std::forward<decltype(extra)>(extra)...);
1068 else
1069 cls.def(name, &[:Fn:], doc.c_str(), rvp,
1070 nb::keep_alive<ka[K].nurse, ka[K].patient>()...,
1071 std::forward<decltype(extra)>(extra)...);
1072 }};
1073 if constexpr (NotImpl)
1074 def(nb::is_operator{});
1075 else
1076 def();
1077 }
1078
1082 template <class T, std::meta::info Fn>
1083 static void _def_reflected_operator(auto& cls) {
1084 constexpr const char* name{::welder::rods::python::reflected_dunder(Fn)};
1085 if constexpr (name != nullptr) {
1087 using Lhs = typename
1088 [:std::meta::type_of(std::meta::parameters_of(Fn)[0]):];
1089 cls.def(
1090 name,
1091 [](const T& self, Lhs lhs) {
1092 return [:Fn:](static_cast<Lhs&&>(lhs), self);
1093 },
1094 nb::is_operator{});
1095 }
1096 }
1097
1098 public:
1099
1100 // --- enum binding -------------------------------------------------------
1101
1108 template <class E>
1109 static auto make_enum(module_type& m, const char* name,
1110 const ::welder::detail::enum_doc& ed) {
1111 // ed's summary + documented enumerators fold into the class docstring under
1112 // DocStyle (an Attributes section) — the one place nanobind's stub generator
1113 // carries an enumerator's doc into the .pyi. Empty (wholly undocumented) is
1114 // branched out rather than passed. nb::enum_ builds the type in its ctor and
1115 // copies the doc, so the transient string here is safe.
1116 const std::string doc{DocStyle::format_enum(ed)};
1117 if (!doc.empty())
1118 return nb::enum_<E>(m, name, doc.c_str(), nb::is_arithmetic());
1119 return nb::enum_<E>(m, name, nb::is_arithmetic());
1120 }
1121
1127 template <class E>
1128 static auto make_nested_enum(module_type&, auto& outer_cls, const char* name,
1129 const ::welder::detail::enum_doc& ed) {
1130 const std::string doc{DocStyle::format_enum(ed)};
1131 if (!doc.empty())
1132 return nb::enum_<E>(outer_cls, name, doc.c_str(), nb::is_arithmetic());
1133 return nb::enum_<E>(outer_cls, name, nb::is_arithmetic());
1134 }
1135
1137 template <std::meta::info Enum, class Style = ::welder::naming::none>
1138 static void add_enumerator(auto& e) {
1139 e.value(
1141 [:Enum:]);
1142 }
1143
1146 template <class E>
1147 static void finish_enum(auto& e) {
1148 // Mirror C++ scope semantics: an unscoped enum's enumerators are visible
1149 // unqualified, so export them into the enclosing scope; a scoped enum's
1150 // are reached as E.Value, so leave them scoped.
1151 if constexpr (!std::is_scoped_enum_v<E>)
1152 e.export_values();
1153 }
1154
1155 // --- namespace / module binding -----------------------------------------
1156
1160 static nb::dict open_module(module_type&) { return nb::dict{}; }
1161
1166 static void set_module_doc(module_type& m, const char* doc) {
1167 m.attr("__doc__") = doc;
1168 }
1169
1177 template <auto Fns, class Style = ::welder::naming::none>
1178 static nb::object add_function(module_type& m, const char* name = nullptr) {
1179 const char* fn_name{::welder::name_of_or<Fns[0], language, Style,
1181 template for (constexpr auto fn : std::define_static_array(Fns)) {
1182 _def_function<fn, Style>(fn_name, [&m](auto&&... a) {
1183 m.def(std::forward<decltype(a)>(a)...);
1184 });
1185 constexpr std::size_t d{::welder::detail::trailing_default_count<fn>()};
1186 if constexpr (d > 0)
1188 fn_name,
1189 [&m](auto&&... a) { m.def(std::forward<decltype(a)>(a)...); },
1190 std::make_index_sequence<d>{});
1191 }
1192 return m.attr(fn_name);
1193 }
1194
1201 template <std::meta::info Var, class Style = ::welder::naming::none>
1202 static void add_variable(module_type& m, nb::dict& live,
1203 const char* name_override = nullptr) {
1204 const char* name{
1206 name_override)};
1207 if constexpr (std::meta::is_const_type(std::meta::type_of(Var))) {
1208 m.attr(name) = [:Var:]; // immutable: a value snapshot at bind time
1209 } else {
1210 // Mutable: a live property over the C++ global. The descriptors take a
1211 // leading `self` (the module) and ignore it.
1212 auto property{nb::module_::import_("builtins").attr("property")};
1213 live[name] = property(
1214 nb::cpp_function([](nb::object) { return [:Var:]; }),
1215 nb::cpp_function([](nb::object,
1216 typename [:std::meta::type_of(Var):] v) {
1217 [:Var:] = v;
1218 }));
1219 }
1220 }
1221
1223 static module_type add_submodule(module_type& m, const char* name) {
1224 return m.def_submodule(name);
1225 }
1226
1248 template <class Container, class Style = ::welder::naming::none>
1249 static void bind_container(module_type& m, const char* name) {
1250 constexpr ::welder::container_kind kind{
1251 ::welder::container_kind_of(^^Container)};
1252 if constexpr (kind == ::welder::container_kind::sequence) {
1253 // reference_internal (not nanobind's automatic_reference default,
1254 // which downgrades an lvalue-reference return to a *copy*) so
1255 // __getitem__/__iter__ hand out a live reference aliasing the C++
1256 // element — v[i].field = x writes through, matching pybind11's own
1257 // bind_vector default. A scalar element ignores the policy and casts
1258 // by value (a copy, as intended).
1259 auto cls{
1260 nb::bind_vector<Container, nb::rv_policy::reference_internal>(
1261 m, name)};
1262 using Elem = typename Container::value_type;
1263 using Size = typename Container::size_type;
1264 // `reserve(n)`: pre-grow capacity so a following run of append()/new()
1265 // does not reallocate — the efficient way to bulk-populate, and the way
1266 // to keep element references valid across that run (no reallocation ⇒ no
1267 // move). Only where the container actually has reserve (std::vector;
1268 // std::deque has none).
1269 if constexpr (requires(Container& c, Size n) { c.reserve(n); })
1270 cls.def(
1271 "reserve", [](Container& v, Size n) { v.reserve(n); },
1272 nb::arg("n"),
1273 "Pre-allocate capacity for at least n elements (no-op if capacity "
1274 "already exceeds n). Prevents reallocation — and reference "
1275 "invalidation — across a following run of append()/new().");
1276 // `resize(n)`: grow/shrink to exactly n elements, value-initializing any
1277 // new tail elements — allocate-then-fill-by-index bulk population. Needs a
1278 // default-constructible element (the requires-expression gates it).
1279 if constexpr (requires(Container& c, Size n) { c.resize(n); })
1280 cls.def(
1281 "resize", [](Container& v, Size n) { v.resize(n); },
1282 nb::arg("n"),
1283 "Resize to exactly n elements, value-initializing any new tail "
1284 "elements. Shrinking or reallocating invalidates references.");
1285 // `new()`: default-construct an element in place at the back and hand
1286 // back a **live reference** to it (reference_internal, kept alive to the
1287 // container) — so generic Python code can grow a container of welded
1288 // structs without importing the element type just to construct one
1289 // (`e = v.new(); e.field = x`). Class elements only: a scalar would be
1290 // returned by value (a dead copy, mutations lost), which is a footgun, so
1291 // it's omitted there — use `append(value)`. Standard bind_vector caveat:
1292 // a later append/clear may reallocate and invalidate the reference.
1293 if constexpr (std::is_class_v<Elem> &&
1294 std::is_default_constructible_v<Elem>) {
1295 cls.def(
1296 "new",
1297 [](Container& v) -> Elem& { return v.emplace_back(); },
1298 nb::rv_policy::reference_internal,
1299 "Default-construct a new element in place at the end and return "
1300 "a live reference to it.");
1301 }
1303 } else if constexpr (kind == ::welder::container_kind::fixed_sequence) {
1304 // std::array has no nb::bind_array; hand-write the vector protocol
1305 // minus the size-changing ops.
1306 _bind_array<Container>(m, name);
1307 } else {
1308 // reference_internal for __getitem__ (see the sequence branch): the
1309 // mapped value is handed out as a live reference, so m[k].field = x
1310 // writes through — a copy for a scalar value type.
1311 nb::bind_map<Container, nb::rv_policy::reference_internal>(m, name);
1312 }
1313 }
1314
1330 template <class Container>
1331 static void _bind_array(module_type& m, const char* name) {
1332 using Elem = typename Container::value_type;
1333 constexpr std::size_t N{std::tuple_size_v<Container>};
1334 constexpr nb::rv_policy P{nb::rv_policy::reference_internal};
1335 auto cls{nb::class_<Container>(m, name)};
1336 cls.def(nb::init<>(), "Default constructor")
1337 .def(nb::init<const Container&>(), "Copy constructor")
1338 .def("__len__", [](const Container&) { return N; })
1339 .def("__bool__", [](const Container&) { return N != 0; })
1340 .def(
1341 "__iter__",
1342 [](Container& a) {
1343 return nb::make_iterator<P>(nb::type<Container>(), "Iterator",
1344 a.begin(), a.end());
1345 },
1346 nb::keep_alive<0, 1>())
1347 .def(
1348 "__getitem__",
1349 [](Container& a, Py_ssize_t i) -> Elem& {
1350 return a[_wrap_index(i, N)];
1351 },
1352 P)
1353 .def("__setitem__", [](Container& a, Py_ssize_t i, const Elem& v) {
1354 a[_wrap_index(i, N)] = v;
1355 });
1356 // Whole-attribute assignment: construct from a length-N iterable, registered
1357 // as an implicit conversion so `def_rw`'s setter accepts a plain list/tuple.
1358 cls.def(
1359 "__init__",
1360 [](Container* self, nb::typed<nb::iterable, Elem> seq) {
1361 // Mirror bind_vector's exception safety: destruct the in-place object
1362 // if the fill fails (a wrong length, an element that won't cast), so a
1363 // welded-class element leaves no default-constructed leftovers behind.
1364 Container* a{new (self) Container{}};
1365 try {
1366 std::size_t i{0};
1367 for (nb::handle h : seq) {
1368 if (i >= N)
1369 throw nb::value_error(
1370 "expected a sequence of the array's fixed length");
1371 (*a)[i++] = nb::cast<Elem>(h);
1372 }
1373 if (i != N)
1374 throw nb::value_error(
1375 "expected a sequence of the array's fixed length");
1376 } catch (...) {
1377 a->~Container();
1378 throw;
1379 }
1380 },
1381 "Construct from a length-N iterable");
1382 nb::implicitly_convertible<nb::iterable, Container>();
1384 }
1385
1392 template <class Container, class Cls>
1393 static void _numpy_view(Cls& cls) {
1394 using Elem = typename Container::value_type;
1395 if constexpr (::welder::container_is_contiguous(^^Container) &&
1396 std::is_arithmetic_v<Elem> && !std::is_same_v<Elem, bool>) {
1397 // numpy's __array__ protocol: numpy 2.x calls it with dtype/copy
1398 // (positional or keyword); accept and ignore both and always hand
1399 // back a live view — copy=True is honored by numpy copying the view.
1400 cls.def(
1401 "__array__",
1402 [](Container& v, nb::handle, nb::handle) {
1403 return nb::ndarray<nb::numpy, Elem, nb::ndim<1>, nb::c_contig>(
1404 v.data(), {v.size()}, nb::find(&v));
1405 },
1406 nb::arg("dtype") = nb::none(), nb::arg("copy") = nb::none());
1407 } else if constexpr (::welder::container_is_contiguous(^^Container) &&
1409 // Contiguous POD-struct element: expose the numpy array-interface
1410 // protocol (structured, zero-copy, numpy-free view of data()).
1412 }
1413 }
1414
1420 static std::size_t _wrap_index(Py_ssize_t i, std::size_t n) {
1421 if (i < 0)
1422 i += static_cast<Py_ssize_t>(n);
1423 if (i < 0 || static_cast<std::size_t>(i) >= n)
1424 throw nb::index_error("array index out of range");
1425 return static_cast<std::size_t>(i);
1426 }
1427
1433 template <class Container, class Elem, class Cls>
1434 static void _array_interface(Cls& cls) {
1435 namespace pyai = ::welder::rods::python;
1436 cls.def_prop_ro("__array_interface__", [](Container& v) {
1437 static constexpr auto d{pyai::ai_descr<^^Elem>()};
1438 nb::list descr{};
1439 for (auto [nm, ts] : d)
1440 descr.append(nb::make_tuple(nm, ts));
1441 nb::dict out{};
1442 out["shape"] = nb::make_tuple(v.size());
1443 out["typestr"] = pyai::ai_typestr<Elem>();
1444 out["descr"] = descr;
1445 out["data"] = nb::make_tuple(
1446 reinterpret_cast<std::uintptr_t>(v.data()), false);
1447 out["version"] = 3;
1448 return out;
1449 });
1450 }
1451
1453 static void close_module(module_type& m, nb::dict& live) {
1454 if (live.size() != 0)
1455 _install_live_properties(m, live);
1456 }
1457};
1458
1459static_assert(::welder::rod<rod<>>,
1460 "welder::rods::nanobind::rod<> must satisfy welder::rod");
1461
1462} // namespace welder::rods::nanobind
The NumPy array-interface descriptor for an opaque std::vector<T> whose element T is a plain-old-data...
Backend-agnostic selection layer: the reflection predicates and selectors that decide what participat...
The contract a rod (a welder backend, welder::rods::…::rod) must satisfy to plug into the generic dri...
Definition concepts.hpp:263
The reference-semantic container table: which STL containers welder can bind opaquely* (by reference)...
Language-agnostic documentation layer: read [[=welder::doc(...)]] annotations off reflected entities ...
Docstring styles shared by welder's Python backends.
The stored forms of the annotation vocabulary.
consteval auto aggregate_fields()
The fields an aggregate is initialized from: its non-static data members in declaration order (all pu...
consteval std::size_t trailing_default_count()
How many TRAILING parameters of Fn carry a C++ default argument.
consteval auto param_types()
A function's parameter types, as a static array of reflections.
consteval bool free_operator_reflected(std::meta::info f, std::meta::info type)
Whether anchored free operator f binds reflected for type: type is the right operand and the left is ...
consteval std::size_t aggregate_defaults_from()
The field index from which a value-extracting backend (the Python rods) can attach the NSDMI defaults...
consteval bool all_params_named()
Whether every parameter of Fn carries an identifier.
std::string stringify(const T &self)
The stringifier wrapper every runtime rod binds for a swept ostream inserter (see is_stringifier_for)...
consteval auto keep_alive_pairs()
The keep_alive dependencies declared on Fn, in declaration order.
consteval const char * operator_dunder(std::meta::info f)
The Python special-method ("dunder") name for an operator (member or anchored free),...
Definition operators.hpp:41
consteval const char * reflected_dunder(std::meta::info f)
The reflected ("swapped-operand") dunder for a free operator whose anchor type is the right operand —...
Definition operators.hpp:79
consteval bool dunder_uses_not_implemented(std::meta::info f)
Whether f's slot participates in Python's NotImplemented protocol — every binary arithmetic / bitwise...
consteval bool pod_array_eligible()
Is std::vector<E> viewable as a NumPy structured array — i.e.
void synthesize_comparisons(Def def)
The comparison-synthesis walk shared by both Python backends: for each operator<=> overload in Fns,...
consteval std::meta::info construction_type_of()
The type welder constructs when binding T: its registered/annotated trampoline if one exists,...
consteval bool has_virtual_methods(std::meta::info type)
Does type declare or inherit any overridable virtual method?
Definition virtuals.hpp:239
consteval bool member_no_reassign(std::meta::info member, lang L)
Is data member member bound read-only for L by a no_reassign mark?
Definition reflect.hpp:243
consteval detail::doc_spec< N > doc(const char(&s)[N])
Attach a docstring to a namespace, class, function, or function parameter.
consteval bool container_is_contiguous(std::meta::info type)
Is type a contiguous sequence — one whose elements live in a single block reachable via ....
consteval container_kind container_kind_of(std::meta::info type)
The opaque-binder kind of type.
lang
The target languages welder ships rods for — but not the whole value space.
Definition lang.hpp:42
@ py
Python (via the pybind11 and nanobind backends).
Definition lang.hpp:43
rv_kind
How a bound callable's returned object is owned/converted in the target language — welder's backend-n...
@ automatic
The rod default — emit no explicit policy.
@ field
a data member → transform_field.
Definition naming.hpp:327
@ static_method
a static member function → transform_static_method.
Definition naming.hpp:325
@ function
a free function → transform_function.
Definition naming.hpp:326
@ fixed_sequence
A fixed-size sequence: std::array<T, N>.
@ sequence
A bind_vector container: std::vector.
consteval void validate_return_policy()
Reject a return_policy on Fn (for language L) that contradicts Fn's return type.
Definition reflect.hpp:361
consteval bool bound_flat(std::meta::info entity)
Does entity (a type or a member function) carry a bind_flat mark?
Definition virtuals.hpp:63
constexpr const char * name_of_or(const char *override_)
Resolve a bound name with a call-site override: override_ wins verbatim, nullptr falls back to name_o...
Definition naming.hpp:424
std::string function_docstring()
The complete docstring for function Fn under Style.
Definition doc.hpp:300
consteval const char * name_of()
The final bound name of Ent (a K-kind entity) for language L under name style Style.
Definition naming.hpp:375
consteval const char * doc_of()
The doc text on Ent (a class, namespace, function, or parameter), or nullptr.
Definition doc.hpp:138
consteval rv_kind return_policy_of(std::meta::info fn, lang L)
The return-value policy declared on callable fn for language L.
Definition reflect.hpp:337
The C++-operator → Python special-method ("dunder") map shared by welder's Python backends.
Splice-based accessors for data member Mem — the pointer-to-member-free route the rods bind a protect...
The identity style: bind every C++ identifier unchanged.
Definition naming.hpp:259
nb::module_ module_type
nanobind's module handle.
Definition rod.hpp:99
static void _def_aggregate_init(auto &cls, std::index_sequence< I... >)
Synthesize a field constructor for a baseless aggregate T.
Definition rod.hpp:476
static void _def_init(auto &cls, std::index_sequence< I... >)
Definition rod.hpp:311
static auto _aggregate_arg(const T &probe)
The nb::arg for field I of aggregate T: named after the field and, for the defaultable NSDMI suffix (...
Definition rod.hpp:446
static consteval nb::rv_policy _rv_policy(::welder::rv_kind k)
Map welder's neutral welder::rv_kind to nanobind's rv_policy.
Definition rod.hpp:146
static consteval const char * special_method_name(std::meta::info op_fn)
Map a member operator to its Python dunder (nullptr = not exposed).
Definition rod.hpp:615
static void _def_function(const char *name, Def def_into)
Convenience overload: derive the parameter and keep_alive index sequences from Fn.
Definition rod.hpp:281
static nb::object add_function(module_type &m, const char *name=nullptr)
Bind free-function overload group Fns as one module-level function (name from Fns[0]; one chained ....
Definition rod.hpp:1178
nb::enum_< E > enum_handle_type
The enum handle make_enum yields — exactly its return type.
Definition rod.hpp:105
static std::size_t _wrap_index(Py_ssize_t i, std::size_t n)
Normalize a Python index i (allowing one level of negative wrap-around) against length n,...
Definition rod.hpp:1420
static auto make_nested_class(module_type &, auto &outer_cls, const char *name, const char *doc, std::index_sequence< I... > seq)
Create the nb::class_ for a nested member type T, registered under its enclosing type's class handle ...
Definition rod.hpp:650
static constexpr lang language
welder::lang::py.
Definition rod.hpp:98
static void _numpy_view(Cls &cls)
Give contiguous sequence class cls the zero-copy NumPy view its element type supports: an __array__ r...
Definition rod.hpp:1393
static class_handle_type< T > reopen_nested_class(module_type &, auto &outer, const char *name)
The nested-scope form of reopen_class — retrieve T from its enclosing type's class handle (outer....
Definition rod.hpp:682
static auto make_enum(module_type &m, const char *name, const ::welder::detail::enum_doc &ed)
Create the nb::enum_<E> handle (a non-null doc becomes its docstring).
Definition rod.hpp:1109
static constexpr bool _erasable_field
Whether Mem can bind through the class-ERASED field path (see _def_erased_field): a public,...
Definition rod.hpp:767
static auto _init_value(_init_param< T, I > &&arg)
The value brace-initializing field I of T from constructor argument arg: the argument itself for a pl...
Definition rod.hpp:421
static void set_module_doc(module_type &m, const char *doc)
Set the (sub)module docstring.
Definition rod.hpp:1166
static void add_enumerator(auto &e)
Add enumerator Enum to the enum handle.
Definition rod.hpp:1138
static void add_comparisons(auto &cls)
Synthesize the relational dunders from operator<=> group Fns via rewritten expressions (a < b,...
Definition rod.hpp:1034
static void add_operator(auto &cls)
Bind operator slot group Fns — one (operator, arity) slot whole, member and anchored free entries mix...
Definition rod.hpp:1014
static void _def_operator(const char *name, Cls &cls, std::index_sequence< K... >)
Def operator Fn (member, or free with the anchor on the left) under dunder name.
Definition rod.hpp:1056
static auto _make_class(nb::handle scope, const char *name, const char *doc, std::index_sequence< I... >)
Construct nb::class_<T, NativeBases...> from a reflected base-type array.
Definition rod.hpp:544
static void add_constructors(auto &cls)
Bind T's whole constructor set (a chained-def framework just loops it): the default constructor when ...
Definition rod.hpp:707
static void _def_reflected_operator(auto &cls)
Bind reflected free operator Fn (T is its right operand) under its reflected dunder,...
Definition rod.hpp:1083
static void bind_container(module_type &m, const char *name)
Bind STL Container opaquely — by reference, with live mutation — under name, the driver's route for a...
Definition rod.hpp:1249
static void add_method(auto &cls)
Bind method overload group Fns (name from Fns[0]; nanobind chains one .def per overload and dispatche...
Definition rod.hpp:961
static void add_variable(module_type &m, nb::dict &live, const char *name_override=nullptr)
Bind namespace variable Var as a module attribute.
Definition rod.hpp:1202
static void close_module(module_type &m, nb::dict &live)
Close the session: apply any accumulated live properties.
Definition rod.hpp:1453
static void _install_live_properties(nb::module_ &m, nb::dict props)
Give module m live get/set semantics for the names in props.
Definition rod.hpp:515
static void add_stringifier(auto &cls)
Bind the swept free ostream inserter Fn as __str__ (via welder::detail::stringify).
Definition rod.hpp:1044
static consteval auto _styled_param_names()
Register the function/method reflected by Fn onto a nanobind target.
Definition rod.hpp:185
static void _def_function(const char *name, Def def_into, std::index_sequence< I... >, std::index_sequence< K... >)
Definition rod.hpp:197
static auto _make_class_at(nb::handle scope, const char *name, const char *doc, std::index_sequence< I... > seq)
The trampoline-aware class factory over an arbitrary registration scope — the shared body of make_cla...
Definition rod.hpp:565
static void add_static_method(auto &cls)
Bind static-method overload group Fns.
Definition rod.hpp:985
static class_handle_type< T > reopen_class(module_type &scope, const char *name)
Retrieve the ALREADY-registered class T as a fillable handle — the two-phase binding hook (optional; ...
Definition rod.hpp:675
static void finish_enum(auto &e)
Finalize enum E: export an unscoped enum's values into the enclosing scope.
Definition rod.hpp:1147
static void add_property(auto &cls, const char *name)
Bind the resolved property (Getter + optional Setter) as a Python property named name (driver-resolve...
Definition rod.hpp:916
static constexpr bool has_native_caster
caster_oracle: T is convertible without welder registering a class for it iff nanobind does not fall ...
Definition rod.hpp:611
decltype(make_class< T, std::array< std::meta::info, 0 >{}>( std::declval< module_type & >(), nullptr, nullptr, std::index_sequence<>{})) class_handle_type
The class handle make_class yields for T — exactly its return type for a base-less T (so it captures ...
Definition rod.hpp:662
static module_type add_submodule(module_type &m, const char *name)
Create a submodule named name under m.
Definition rod.hpp:1223
static void add_field(auto &cls)
Bind data member Mem as an attribute.
Definition rod.hpp:849
static void _array_interface(Cls &cls)
Give the opaque std::vector<Elem> class cls a __array_interface__ property — the numpy array-interfac...
Definition rod.hpp:1434
static nb::object _copy_instance(nb::handle self, nb::object *memo)
The subclass-faithful engine behind __copy__/__deepcopy__.
Definition rod.hpp:343
static auto make_nested_enum(module_type &, auto &outer_cls, const char *name, const ::welder::detail::enum_doc &ed)
Create the nb::enum_<E> for a nested member enum, scoped to its enclosing type's class handle — Pytho...
Definition rod.hpp:1128
static nb::dict open_module(module_type &)
Open a per-module session: a dict accumulating live (mutable-variable) properties; _install_live_prop...
Definition rod.hpp:1160
std::conditional_t< _lazy_default< T, I >(), std::optional< std::remove_const_t< typename[:std::meta::type_of(::welder::detail::aggregate_fields< T >()[I]):]> >, std::remove_const_t< typename[:std::meta::type_of(::welder::detail::aggregate_fields< T >()[I]):]> > _init_param
The synthesized field constructor's parameter type for field I of T: the field type itself,...
Definition rod.hpp:407
static consteval bool _lazy_default()
Whether field I of aggregate T binds its NSDMI default LAZILY: true for a defaultable field whose typ...
Definition rod.hpp:393
static void _def_truncated(const char *name, Def def_into, std::index_sequence< I... >)
Bind ONE truncated overload of Fn taking its first sizeof...(I) parameters: the wrapper calls the C++...
Definition rod.hpp:236
static void _def_erased_field(nb::handle cls, const char *name, std::size_t offset, bool read_only, const char *doc)
Bind one field as a property through CLASS-ERASED accessors: the closures capture the member's byte o...
Definition rod.hpp:810
static void _def_init_truncations(auto &cls, std::index_sequence< K... >)
Register nb::init<P0, P1, …>() for constructor Ctor.
Definition rod.hpp:305
static auto make_class(module_type &m, const char *name, const char *doc, std::index_sequence< I... > seq)
Create the nb::class_<T, Bases…> handle, weaving in a trampoline when T is a welded virtual type with...
Definition rod.hpp:639
static constexpr bool _needs_registration
Whether nanobind can only convert T via runtime class registration.
Definition rod.hpp:137
[:::welder::rods::python::construction_type_of< T >() :] construction_type
The type welder constructs when binding T — its registered trampoline if one exists,...
Definition rod.hpp:626
static void _bind_array(module_type &m, const char *name)
Bind fixed-size sequence Container (std::array<T, N>) opaquely — by reference, with element write-thr...
Definition rod.hpp:1331
static void _def_default_truncations(const char *name, Def def_into, std::index_sequence< K... >)
Bind every omissible arity of Fn (one per trailing defaulted parameter): arities P-D .
Definition rod.hpp:269
Virtual-function overriding support shared by welder's Python backends.
welder's binding entry point: the welder::welder struct.