Lagrange
Loading...
Searching...
No Matches
bind_safe_vector.h
1/*
2 * Copyright 2025 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/python/binding.h>
15
16#include <type_traits>
17
18// `def_rw` extra that widens a `bind_safe_vector` member's setter stub. nanobind
19// types the generated setter as the exact bound list type, which rejects a plain
20// Python sequence (even though `bind_safe_vector` registers an `iterable`
21// conversion, so it works at runtime). Pass as an extra to `def_rw`:
22//
23// .def_rw("materials", &T::materials, doc,
24// LA_SAFE_VECTOR_SETTER("materials", "collections.abc.Sequence[int]"))
25//
26// `prop` must match the property name and `arg_type` is the widened argument type.
27#define LA_SAFE_VECTOR_SETTER(prop, arg_type) \
28 nanobind::for_setter(nanobind::sig("def " prop "(self, arg: " arg_type ", /) -> None"))
29
30NAMESPACE_BEGIN(NB_NAMESPACE)
31
32template <
33 typename Vector,
34 rv_policy::value Policy = rv_policy::automatic_reference_v,
35 typename... Args>
36class_<Vector> bind_safe_vector(handle scope, const char* name, Args&&... args)
37{
38 using ValueRef = typename detail::iterator_access<typename Vector::iterator>::result_type;
39 using Value = std::decay_t<ValueRef>;
40 using ValueType = typename Value::element_type;
41
42 static_assert(
43 std::is_same_v<std::shared_ptr<ValueType>, Value>,
44 "bind_safe_vector(): Value type must be a std::shared_ptr<>");
45
46 static_assert(
47 !detail::is_base_caster_v<detail::make_caster<Value>> ||
48 detail::is_copy_constructible_v<Value> ||
49 (Policy != rv_policy::automatic_reference_v && Policy != rv_policy::copy_v),
50 "bind_safe_vector(): the generated __getitem__ would copy elements, so the "
51 "element type must be copy-constructible");
52
53 handle cl_cur = type<Vector>();
54 if (cl_cur.is_valid()) {
55 // Binding already exists, don't re-create
56 return borrow<class_<Vector>>(cl_cur);
57 }
58
59 auto cl = class_<Vector>(scope, name, std::forward<Args>(args)...)
60 .def(init<>(), "Default constructor")
61
62 .def("__len__", [](const Vector& v) { return v.size(); })
63
64 .def(
65 "__bool__",
66 [](const Vector& v) { return !v.empty(); },
67 "Check whether the vector is nonempty")
68
69 .def(
70 "__repr__",
71 [](handle_t<Vector> h) { return steal<str>(detail::repr_list(h.ptr())); })
72
73 .def(
74 "__iter__",
75 [](Vector& v) {
76 return make_iterator<Policy>(
77 type<Vector>(),
78 "Iterator",
79 v.Vector::Super::begin(),
80 v.Vector::Super::end());
81 },
82 keep_alive<0, 1>())
83
84 .def(
85 "__getitem__",
86 [](Vector& v, Py_ssize_t i) -> ValueRef {
87 return v.Vector::Super::operator[](detail::wrap(i, v.size()));
88 },
89 rv_policy::policy_tag<Policy>{})
90
91 .def("clear", [](Vector& v) { v.clear(); }, "Remove all items from list.");
92
93 if constexpr (detail::is_copy_constructible_v<Value>) {
94 cl.def(init<const Vector&>(), "Copy constructor");
95
96 cl.def(
97 "__init__",
98 [](Vector* v, typed<iterable, Value> seq) {
99 new (v) Vector();
100 v->reserve(len_hint(seq));
101 for (handle h : seq) v->Vector::Super::push_back(cast<Value>(h));
102 },
103 "Construct from an iterable object");
104
105 implicitly_convertible<iterable, Vector>();
106
107 cl.def(
108 "append",
109 [](Vector& v, const Value& value) { v.Vector::Super::push_back(value); },
110 "Append `arg` to the end of the list.")
111
112 .def(
113 "insert",
114 [](Vector& v, Py_ssize_t i, const Value& x) {
115 if (i < 0) i += (Py_ssize_t)v.size();
116 if (i < 0 || (size_t)i > v.size()) throw index_error();
117 v.insert(v.Vector::Super::begin() + i, x);
118 },
119 "Insert object `arg1` before index `arg0`.")
120
121 .def(
122 "pop",
123 [](Vector& v, Py_ssize_t i) {
124 size_t index = detail::wrap(i, v.size());
125 Value result = std::move(v.Vector::Super::operator[](index));
126 v.erase(v.Vector::Super::begin() + index);
127 return result;
128 },
129 arg("index") = -1,
130 "Remove and return item at `index` (default last).")
131
132 .def(
133 "extend",
134 [](Vector& v, const Vector& src) {
135 v.insert(
136 v.Vector::Super::end(),
137 src.Vector::Super::begin(),
138 src.Vector::Super::end());
139 },
140 "Extend `self` by appending elements from `arg`.")
141
142 .def(
143 "__setitem__",
144 [](Vector& v, Py_ssize_t i, const Value& value) {
145 v.Vector::Super::operator[](detail::wrap(i, v.size())) = value;
146 })
147
148 .def(
149 "__delitem__",
150 [](Vector& v, Py_ssize_t i) {
151 v.erase(v.Vector::Super::begin() + detail::wrap(i, v.size()));
152 })
153
154 .def(
155 "__getitem__",
156 [](const Vector& v, const slice& slice) -> Vector* {
157 auto [start, stop, step, length] = slice.compute(v.size());
158 auto* seq = new Vector();
159 seq->reserve(length);
160
161 for (size_t i = 0; i < length; ++i) {
162 seq->Vector::Super::push_back(v.Vector::Super::operator[](start));
163 start += step;
164 }
165
166 return seq;
167 })
168
169 .def(
170 "__setitem__",
171 [](Vector& v, const slice& slice, const Vector& value) {
172 auto [start, stop, step, length] = slice.compute(v.size());
173
174 if (length != value.size())
175 throw index_error(
176 "The left and right hand side of the slice "
177 "assignment have mismatched sizes!");
178
179 for (size_t i = 0; i < length; ++i) {
180 v.Vector::Super::operator[](start) = value.Vector::Super::operator[](i);
181 start += step;
182 }
183 })
184
185 .def("__delitem__", [](Vector& v, const slice& slice) {
186 auto [start, stop, step, length] = slice.compute(v.size());
187 if (length == 0) return;
188
189 stop = start + (length - 1) * step;
190 if (start > stop) {
191 std::swap(start, stop);
192 step = -step;
193 }
194
195 if (step == 1) {
196 v.erase(v.Vector::Super::begin() + start, v.Vector::Super::begin() + stop + 1);
197 } else {
198 for (size_t i = 0; i < length; ++i) {
199 v.erase(v.Vector::Super::begin() + stop);
200 stop -= step;
201 }
202 }
203 });
204 }
205
206 if constexpr (detail::is_equality_comparable_v<Value>) {
207 cl.def(self == self, sig("def __eq__(self, arg: object, /) -> bool"))
208 .def(self != self, sig("def __ne__(self, arg: object, /) -> bool"))
209
210 .def(
211 "__contains__",
212 [](const Vector& v, const Value& x) {
213 return std::find(v.Vector::Super::begin(), v.Vector::Super::end(), x) !=
214 v.Vector::Super::end();
215 })
216
217 .def(
218 "__contains__", // fallback for incompatible types
219 [](const Vector&, handle) { return false; })
220
221 .def(
222 "count",
223 [](const Vector& v, const Value& x) {
224 return std::count(v.Vector::Super::begin(), v.Vector::Super::end(), x);
225 },
226 "Return number of occurrences of `arg`.")
227
228 .def(
229 "remove",
230 [](Vector& v, const Value& x) {
231 auto p = std::find(v.Vector::Super::begin(), v.Vector::Super::end(), x);
232 if (p != v.Vector::Super::end())
233 v.erase(p);
234 else
235 throw value_error();
236 },
237 "Remove first occurrence of `arg`.");
238 }
239
240 return cl;
241}
242
243NAMESPACE_END(NB_NAMESPACE)
@ Value
Values that are not attached to a specific element.
Definition AttributeFwd.h:42
Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > Vector
Type alias for one-dimensional column Eigen vectors.
Definition views.h:79
Definition project.cpp:27