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 <typename Vector, rv_policy Policy = rv_policy::automatic_reference, typename... Args>
33class_<Vector> bind_safe_vector(handle scope, const char* name, Args&&... args)
34{
35 using ValueRef = typename detail::iterator_access<typename Vector::iterator>::result_type;
36 using Value = std::decay_t<ValueRef>;
37 using ValueType = typename Value::element_type;
38
39 static_assert(
40 std::is_same_v<std::shared_ptr<ValueType>, Value>,
41 "bind_safe_vector(): Value type must be a std::shared_ptr<>");
42
43 static_assert(
44 !detail::is_base_caster_v<detail::make_caster<Value>> ||
45 detail::is_copy_constructible_v<Value> ||
46 (Policy != rv_policy::automatic_reference && Policy != rv_policy::copy),
47 "bind_safe_vector(): the generated __getitem__ would copy elements, so the "
48 "element type must be copy-constructible");
49
50 handle cl_cur = type<Vector>();
51 if (cl_cur.is_valid()) {
52 // Binding already exists, don't re-create
53 return borrow<class_<Vector>>(cl_cur);
54 }
55
56 auto cl = class_<Vector>(scope, name, std::forward<Args>(args)...)
57 .def(init<>(), "Default constructor")
58
59 .def("__len__", [](const Vector& v) { return v.size(); })
60
61 .def(
62 "__bool__",
63 [](const Vector& v) { return !v.empty(); },
64 "Check whether the vector is nonempty")
65
66 .def(
67 "__repr__",
68 [](handle_t<Vector> h) { return steal<str>(detail::repr_list(h.ptr())); })
69
70 .def(
71 "__iter__",
72 [](Vector& v) {
73 return make_iterator<Policy>(
74 type<Vector>(),
75 "Iterator",
76 v.Vector::Super::begin(),
77 v.Vector::Super::end());
78 },
79 keep_alive<0, 1>())
80
81 .def(
82 "__getitem__",
83 [](Vector& v, Py_ssize_t i) -> ValueRef {
84 return v.Vector::Super::operator[](detail::wrap(i, v.size()));
85 },
86 Policy)
87
88 .def("clear", [](Vector& v) { v.clear(); }, "Remove all items from list.");
89
90 if constexpr (detail::is_copy_constructible_v<Value>) {
91 cl.def(init<const Vector&>(), "Copy constructor");
92
93 cl.def(
94 "__init__",
95 [](Vector* v, typed<iterable, Value> seq) {
96 new (v) Vector();
97 v->reserve(len_hint(seq));
98 for (handle h : seq) v->Vector::Super::push_back(cast<Value>(h));
99 },
100 "Construct from an iterable object");
101
102 implicitly_convertible<iterable, Vector>();
103
104 cl.def(
105 "append",
106 [](Vector& v, const Value& value) { v.Vector::Super::push_back(value); },
107 "Append `arg` to the end of the list.")
108
109 .def(
110 "insert",
111 [](Vector& v, Py_ssize_t i, const Value& x) {
112 if (i < 0) i += (Py_ssize_t)v.size();
113 if (i < 0 || (size_t)i > v.size()) throw index_error();
114 v.insert(v.Vector::Super::begin() + i, x);
115 },
116 "Insert object `arg1` before index `arg0`.")
117
118 .def(
119 "pop",
120 [](Vector& v, Py_ssize_t i) {
121 size_t index = detail::wrap(i, v.size());
122 Value result = std::move(v.Vector::Super::operator[](index));
123 v.erase(v.Vector::Super::begin() + index);
124 return result;
125 },
126 arg("index") = -1,
127 "Remove and return item at `index` (default last).")
128
129 .def(
130 "extend",
131 [](Vector& v, const Vector& src) {
132 v.insert(
133 v.Vector::Super::end(),
134 src.Vector::Super::begin(),
135 src.Vector::Super::end());
136 },
137 "Extend `self` by appending elements from `arg`.")
138
139 .def(
140 "__setitem__",
141 [](Vector& v, Py_ssize_t i, const Value& value) {
142 v.Vector::Super::operator[](detail::wrap(i, v.size())) = value;
143 })
144
145 .def(
146 "__delitem__",
147 [](Vector& v, Py_ssize_t i) {
148 v.erase(v.Vector::Super::begin() + detail::wrap(i, v.size()));
149 })
150
151 .def(
152 "__getitem__",
153 [](const Vector& v, const slice& slice) -> Vector* {
154 auto [start, stop, step, length] = slice.compute(v.size());
155 auto* seq = new Vector();
156 seq->reserve(length);
157
158 for (size_t i = 0; i < length; ++i) {
159 seq->Vector::Super::push_back(v.Vector::Super::operator[](start));
160 start += step;
161 }
162
163 return seq;
164 })
165
166 .def(
167 "__setitem__",
168 [](Vector& v, const slice& slice, const Vector& value) {
169 auto [start, stop, step, length] = slice.compute(v.size());
170
171 if (length != value.size())
172 throw index_error(
173 "The left and right hand side of the slice "
174 "assignment have mismatched sizes!");
175
176 for (size_t i = 0; i < length; ++i) {
177 v.Vector::Super::operator[](start) = value.Vector::Super::operator[](i);
178 start += step;
179 }
180 })
181
182 .def("__delitem__", [](Vector& v, const slice& slice) {
183 auto [start, stop, step, length] = slice.compute(v.size());
184 if (length == 0) return;
185
186 stop = start + (length - 1) * step;
187 if (start > stop) {
188 std::swap(start, stop);
189 step = -step;
190 }
191
192 if (step == 1) {
193 v.erase(v.Vector::Super::begin() + start, v.Vector::Super::begin() + stop + 1);
194 } else {
195 for (size_t i = 0; i < length; ++i) {
196 v.erase(v.Vector::Super::begin() + stop);
197 stop -= step;
198 }
199 }
200 });
201 }
202
203 if constexpr (detail::is_equality_comparable_v<Value>) {
204 cl.def(self == self, sig("def __eq__(self, arg: object, /) -> bool"))
205 .def(self != self, sig("def __ne__(self, arg: object, /) -> bool"))
206
207 .def(
208 "__contains__",
209 [](const Vector& v, const Value& x) {
210 return std::find(v.Vector::Super::begin(), v.Vector::Super::end(), x) !=
211 v.Vector::Super::end();
212 })
213
214 .def(
215 "__contains__", // fallback for incompatible types
216 [](const Vector&, handle) { return false; })
217
218 .def(
219 "count",
220 [](const Vector& v, const Value& x) {
221 return std::count(v.Vector::Super::begin(), v.Vector::Super::end(), x);
222 },
223 "Return number of occurrences of `arg`.")
224
225 .def(
226 "remove",
227 [](Vector& v, const Value& x) {
228 auto p = std::find(v.Vector::Super::begin(), v.Vector::Super::end(), x);
229 if (p != v.Vector::Super::end())
230 v.erase(p);
231 else
232 throw value_error();
233 },
234 "Remove first occurrence of `arg`.");
235 }
236
237 return cl;
238}
239
240NAMESPACE_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