welder 0.1.0
Bindings for annotated C++ types, from C++26 reflection
Loading...
Searching...
No Matches
bindable.hpp
Go to the documentation of this file.
1#pragma once
2#include <array>
3#include <concepts>
4#include <cstddef>
5#include <meta>
6#include <type_traits>
7#include <utility>
8
9// The STL templates named in the element-wise wrapper table below must be
10// complete/visible so `^^std::vector` & friends resolve.
11#include <deque>
12#include <list>
13#include <map>
14#include <memory>
15#include <expected>
16#include <optional>
17#include <set>
18#include <span>
19#include <string>
20#include <tuple>
21#include <unordered_map>
22#include <unordered_set>
23#include <variant>
24#include <vector>
25
26#include <welder/bind_traits.hpp> // param_types (for signature asserts)
27#include <welder/concepts.hpp> // caster_oracle (the backend leaf test)
28#include <welder/reflect.hpp> // welded_for
29
56
57namespace welder::inline v0 {
58
71 static consteval bool counts_as_registered(std::meta::info type, lang L) {
72 return welder::welded_for(type, L);
73 }
74};
75
76namespace detail {
77
79template <caster_oracle B, class T, lang L, class Reg>
80consteval bool bindable();
81
100 std::meta::info tmpl;
101 std::size_t values;
102};
103
105consteval std::array<wrapper_spec, 18> stl_wrappers() {
106 return {{
107 {^^std::vector, 1}, {^^std::list, 1},
108 {^^std::deque, 1}, {^^std::set, 1},
109 {^^std::multiset, 1}, {^^std::unordered_set, 1},
110 {^^std::unordered_multiset, 1},
111 {^^std::map, 2}, {^^std::multimap, 2},
112 {^^std::unordered_map, 2}, {^^std::unordered_multimap, 2},
113 {^^std::optional, 1}, {^^std::shared_ptr, 1},
114 {^^std::unique_ptr, 1}, {^^std::pair, 2},
115 {^^std::array, 1}, {^^std::tuple, 0},
116 {^^std::variant, 0},
117 }};
118}
119
126consteval long wrapper_value_count(std::meta::info type) {
127 if (!std::meta::has_template_arguments(type))
128 return -1;
129 const std::meta::info tmpl{std::meta::template_of(type)};
130 for (const wrapper_spec& w : stl_wrappers())
131 if (w.tmpl == tmpl)
132 return w.values != 0 ? static_cast<long>(w.values)
133 : static_cast<long>(
134 std::meta::template_arguments_of(type).size());
135 return -1;
136}
137
142template <std::meta::info Type, std::size_t N>
143consteval std::array<std::meta::info, N> leading_args() {
144 std::array<std::meta::info, N> out{};
145 std::size_t i{0};
146 for (auto arg : std::meta::template_arguments_of(Type)) {
147 if (i == N)
148 break;
149 out[i++] = arg;
150 }
151 return out;
152}
153
161consteval bool is_expected_specialization(std::meta::info type) {
162 return std::meta::has_template_arguments(type) &&
163 std::meta::template_of(type) == ^^std::expected;
164}
165
168consteval std::meta::info expected_value_arg(std::meta::info type) {
169 return std::meta::template_arguments_of(type)[0];
170}
171
182consteval bool is_span_specialization(std::meta::info type) {
183 return std::meta::has_template_arguments(type) &&
184 std::meta::template_of(type) == ^^std::span;
185}
186
189consteval std::meta::info span_element_arg(std::meta::info type) {
190 return std::meta::template_arguments_of(type)[0];
191}
192
200template <caster_oracle B, lang L, class Reg, auto Args, std::size_t... I>
201consteval bool args_bindable(std::index_sequence<I...>) {
202 return (bindable<B, typename [:Args[I]:], L, Reg>() && ...);
203}
204
217template <caster_oracle B, class T, lang L, class Reg>
218consteval bool bindable() {
219 // Strip cv/ref/pointer so the STL-wrapper table below sees the bare
220 // specialization (a parameter may arrive as `const std::vector<Foo>&`).
221 // dealias because remove_* are alias templates: their reflection is an alias
222 // carrying neither template arguments nor annotations — the underlying type
223 // does. remove_cvref handles the ref+cv combo, then remove_pointer, then a
224 // final remove_cv for a pointee's constness (`const Foo*` -> `Foo`).
225 constexpr std::meta::info u{std::meta::dealias(
226 ^^std::remove_cv_t<std::remove_pointer_t<std::remove_cvref_t<T>>>)};
228 // Type-level escape hatch: the user vouches this type is registered /
229 // convertible outside welder's view. Checked first, and on every recursion
230 // level, so trusting `Foo` also clears `Foo*`, `const Foo&` and
231 // `std::vector<Foo>` (whose element recurses back to a trusted `Foo`).
232 return true;
233 } else {
234 constexpr long values{wrapper_value_count(u)};
235 if constexpr (values >= 0) {
236 constexpr auto args{leading_args<u, static_cast<std::size_t>(values)>()};
238 std::make_index_sequence<args.size()>{});
239 } else if constexpr (B::template has_native_caster<typename [:u:]>) {
240 return true;
241 } else if constexpr (is_expected_specialization(u)) {
242 // A rod that maps the error branch onto the target language's
243 // exception channel never converts E at all, so only the VALUE
244 // argument has to bind. Placed AFTER the native-caster check on
245 // purpose: a rod whose framework converts the whole expected
246 // wholesale — a hand-written pybind11/nanobind caster for a
247 // project's `Result<T>` — still wins, so this widening cannot
248 // change what the Python rods already accept.
249 //
250 // `std::expected<void, E>` carries no value at all: it is a pure
251 // effect that can fail, so there is nothing to convert and it always
252 // binds. (The gate never sees a bare `void` return — only this
253 // wrapper can put one in a value position.)
254 if constexpr (std::meta::dealias(expected_value_arg(u)) == ^^void)
255 return true;
256 else
258 } else if constexpr (is_span_specialization(u)) {
259 // Element-wise, but checked here rather than from the table so a rod
260 // whose framework casts the whole span keeps winning (see
261 // is_span_specialization). Whether a span may appear in RETURN
262 // position at all is the rod's call, not the gate's.
264 } else if constexpr (std::meta::is_union_type(u)) {
265 // Unions never bind — not even welded ones (no sweep registers
266 // them; see assert_bindable's union diagnostic for why and for
267 // the remedies). This branch sits AFTER the trust and
268 // native-caster checks on purpose: a user who hand-registers a
269 // union with the backend (pybind11/nanobind both allow it) can
270 // still vouch for it via the trust hatches or a self-contained
271 // caster.
272 return false;
273 } else {
274 return Reg::counts_as_registered(u, L);
275 }
276 }
277}
278
280template <class T>
281consteval bool mentions_union();
282
286template <auto Args, std::size_t... I>
287consteval bool args_mention_union(std::index_sequence<I...>) {
288 return (mentions_union<typename [:Args[I]:]>() || ...);
289}
290
300template <class T>
301consteval bool mentions_union() {
302 constexpr std::meta::info u{std::meta::dealias(
303 ^^std::remove_cv_t<std::remove_pointer_t<std::remove_cvref_t<T>>>)};
304 if constexpr (std::meta::is_union_type(u)) {
305 return true;
306 } else {
307 constexpr long values{wrapper_value_count(u)};
308 if constexpr (values >= 0) {
309 constexpr auto args{leading_args<u, static_cast<std::size_t>(values)>()};
311 std::make_index_sequence<args.size()>{});
312 } else {
313 return false;
314 }
315 }
316}
317
318} // namespace detail
319
329template <caster_oracle B, class T, lang L, class Reg = welded_registration>
330consteval bool bindable() {
332}
333
343template <caster_oracle B, class T, lang L, class Reg = welded_registration>
344consteval void assert_bindable() {
345 constexpr bool ok{bindable<B, T, L, Reg>()};
346 // Two mutually exclusive asserts so a union-caused failure names the real
347 // remedy (std::variant) instead of the generic "weld it" — welding a
348 // union is itself a hard error.
349 static_assert(
351 "welder: this surface names a UNION, and unions do not bind — C++ has "
352 "no way to observe which member is active, so generated accessors "
353 "would read inactive members (undefined behavior). Use std::variant "
354 "instead (converted natively by every rod), or expose safe accessor "
355 "functions; otherwise mark::exclude the member that uses it. To "
356 "hand-register the union with the backend yourself, vouch for it via "
357 "welder::trust_bindable. The offending type is the template argument "
358 "of this assert_bindable<B, T, L> instantiation.");
359 static_assert(
361 "welder: cannot bind this C++ type to the target language. Weld it with "
362 "[[=welder::weld(...)]], or register a backend type converter for it; "
363 "otherwise mark::exclude the member that uses it. The offending type is "
364 "the template argument of this assert_bindable<B, T, L> instantiation.");
365}
366
367namespace detail {
368
375template <caster_oracle B, std::meta::info Fn, lang L, class Reg, std::size_t... I>
376consteval void assert_params_bindable(std::index_sequence<I...>) {
377 static constexpr auto params{param_types<Fn>()};
379}
380
381} // namespace detail
382
391template <caster_oracle B, std::meta::info Fn, lang L,
392 class Reg = welded_registration>
394 // Guard n != 0: param_types<Fn> materializes a std::array<info, n>, and
395 // std::array<info, 0>::operator[] is not consteval (it must not be
396 // instantiated for a parameterless function).
397 constexpr std::size_t n{std::meta::parameters_of(Fn).size()};
398 if constexpr (n != 0)
399 detail::assert_params_bindable<B, Fn, L, Reg>(std::make_index_sequence<n>{});
400 if constexpr (!std::meta::is_constructor(Fn)) {
401 using R = [:std::meta::return_type_of(Fn):];
402 if constexpr (!std::is_void_v<R>)
404 }
405}
406
407// --- member-aware asserts (honor the trust_bindable member mark) -------------
408//
409// The driver uses these at each emission site: they run the bindability gate on a
410// member's type / a callable's signature *unless* the member carries a
411// [[=welder::mark::trust_bindable]] mark for L, in which case the user has vouched
412// for the type and the gate is skipped. (The type-level trust_bindable<T> point is
413// folded into bindable() itself, above, so it needs no per-site handling.)
414
420template <caster_oracle B, std::meta::info Member, lang L,
421 class Reg = welded_registration>
422consteval void assert_member_bindable() {
423 if constexpr (!welder::trusted_for(Member, L))
424 assert_bindable<B, typename [:std::meta::type_of(Member):], L, Reg>();
425}
426
432template <caster_oracle B, std::meta::info Fn, lang L,
433 class Reg = welded_registration>
434consteval void assert_callable_bindable() {
435 if constexpr (!welder::trusted_for(Fn, L))
437}
438
451template <caster_oracle B, std::meta::info Fn, lang L,
452 class Reg = welded_registration>
453consteval void assert_setter_bindable() {
454 if constexpr (!welder::trusted_for(Fn, L)) {
455 constexpr std::size_t n{std::meta::parameters_of(Fn).size()};
456 if constexpr (n != 0)
458 std::make_index_sequence<n>{});
459 }
460}
461
462namespace detail {
463
468template <caster_oracle B, std::meta::info Fn, std::meta::info Type, lang L,
469 class Reg, std::size_t... I>
470consteval void assert_operands(std::index_sequence<I...>) {
471 static constexpr auto params{param_types<Fn>()};
472 (
473 [] {
474 if constexpr (std::meta::dealias(std::meta::remove_cvref(
475 params[I])) != std::meta::dealias(Type))
476 assert_bindable<B, typename [:params[I]:], L, Reg>();
477 }(),
478 ...);
479}
480
481} // namespace detail
482
495template <caster_oracle B, std::meta::info Fn, std::meta::info Type, lang L,
496 class Reg = welded_registration>
497consteval void assert_operands_bindable() {
498 if constexpr (!welder::trusted_for(Fn, L)) {
499 constexpr std::size_t n{std::meta::parameters_of(Fn).size()};
500 if constexpr (n != 0)
502 std::make_index_sequence<n>{});
503 }
504}
505
506} // namespace welder
Backend-agnostic selection layer: the reflection predicates and selectors that decide what participat...
The core interface concepts welder's static polymorphism rests on, gathered in one catalogue: the cus...
The one bindability fact a backend must provide: can it natively convert a type without welder regist...
Definition concepts.hpp:87
consteval std::meta::info expected_value_arg(std::meta::info type)
The VALUE argument of the std::expected type (its first).
Definition bindable.hpp:168
consteval std::array< wrapper_spec, 18 > stl_wrappers()
The element-wise STL-wrapper table welder recurses into.
Definition bindable.hpp:105
consteval bool mentions_union()
Forward declaration: mentions_union() recurses like bindable() does.
Definition bindable.hpp:301
consteval bool args_mention_union(std::index_sequence< I... >)
Whether every one of a wrapper's value arguments mentions a union.
Definition bindable.hpp:287
consteval void assert_params_bindable(std::index_sequence< I... >)
Assert every parameter type of Fn binds.
Definition bindable.hpp:376
consteval bool args_bindable(std::index_sequence< I... >)
Whether every one of a wrapper's value arguments (spliced back to types) binds.
Definition bindable.hpp:201
consteval void assert_operands(std::index_sequence< I... >)
The parameter walk behind assert_operands_bindable — gate each parameter that is not the anchor type ...
Definition bindable.hpp:470
consteval auto param_types()
A function's parameter types, as a static array of reflections.
consteval bool is_span_specialization(std::meta::info type)
Is type a std::span specialization — the non-owning sequence view?
Definition bindable.hpp:182
consteval std::meta::info span_element_arg(std::meta::info type)
The ELEMENT argument of the std::span type (its first).
Definition bindable.hpp:189
consteval long wrapper_value_count(std::meta::info type)
How many leading arguments of type to recurse if it is a listed wrapper.
Definition bindable.hpp:126
consteval bool is_expected_specialization(std::meta::info type)
Is type a std::expected specialization — the fallible-result family?
Definition bindable.hpp:161
consteval bool bindable()
Forward declaration: bindable() recurses through a container's element types.
Definition bindable.hpp:218
consteval std::array< std::meta::info, N > leading_args()
The first N template arguments of Type, as a splice-ready static array.
Definition bindable.hpp:143
consteval bool welded_for(std::meta::info type, lang L)
Is type welded for language L — i.e.
Definition reflect.hpp:40
consteval void assert_callable_bindable()
Assert a function/method/operator/constructor signature binds, unless trusted.
Definition bindable.hpp:434
consteval void assert_bindable()
Hard error the instant welder would bind an unbindable type.
Definition bindable.hpp:344
consteval bool bindable()
Is T bindable to language L under backend B?
Definition bindable.hpp:330
consteval bool trusted_for(std::meta::info member, lang L)
Does member carry a trust_bindable mark covering language L?
Definition reflect.hpp:224
lang
The target languages welder ships rods for — but not the whole value space.
Definition lang.hpp:42
consteval void assert_setter_bindable()
Assert a property setter's parameter binds, unless trusted — the write half of the getter/setter mach...
Definition bindable.hpp:453
consteval void assert_member_bindable()
Assert the type of a data member / namespace variable binds, unless trusted.
Definition bindable.hpp:422
consteval void assert_signature_bindable()
Assert every parameter type and the (non-void) return type of Fn binds.
Definition bindable.hpp:393
constexpr bool trust_bindable
The type-level trust customization point: specialize to true to trust T wherever it appears (member,...
consteval void assert_operands_bindable()
Assert the operand types of an operator<=> overload bind, unless the overload is trusted — the compar...
Definition bindable.hpp:497
Language-agnostic resolution: given a reflected type/member and a target language,...
One row of the element-wise STL-wrapper table.
Definition bindable.hpp:99
std::size_t values
Leading arguments to recurse; 0 = all.
Definition bindable.hpp:101
std::meta::info tmpl
The class template, e.g.
Definition bindable.hpp:100
The default registration oracle of the bindability gate: a program-defined class/enum type counts as ...
Definition bindable.hpp:69
static consteval bool counts_as_registered(std::meta::info type, lang L)
Does welding under this policy provide a registration for type?
Definition bindable.hpp:71