Skip to content

Commit 378debb

Browse files
committed
stl_bind.h: make bind_{vector,map} work for non-copy-constructible types
Most stl_bind modifiers require copying, so if the type isn't copy constructible, we provide a read-only interface instead. In practice, this means that if the type is non-copyable, it will be, for all intents and purposes, read-only from the Python side (but currently it simply fails to compile with such a container). It is still possible for the caller to provide an interface manually (by defining methods on the returned class_ object), but this isn't something stl_bind can handle because the C++ code to construct values is going to be highly dependent on the container value_type.
1 parent 43755c3 commit 378debb

File tree

3 files changed

+287
-99
lines changed

3 files changed

+287
-99
lines changed

include/pybind11/stl_bind.h

Lines changed: 184 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ struct is_comparable<T, enable_if_t<container_traits<T>::is_pair>> {
6363
template <typename, typename, typename... Args> void vector_if_copy_constructible(const Args &...) { }
6464
template <typename, typename, typename... Args> void vector_if_equal_operator(const Args &...) { }
6565
template <typename, typename, typename... Args> void vector_if_insertion_operator(const Args &...) { }
66+
template <typename, typename, typename... Args> void vector_modifiers(const Args &...) { }
6667

6768
template<typename Vector, typename Class_>
6869
void vector_if_copy_constructible(enable_if_t<
70+
std::is_copy_constructible<Vector>::value &&
6971
std::is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
7072

7173
cl.def(pybind11::init<const Vector &>(), "Copy constructor");
@@ -107,71 +109,34 @@ void vector_if_equal_operator(enable_if_t<is_comparable<Vector>::value, Class_>
107109
);
108110
}
109111

110-
template <typename Vector, typename Class_> auto vector_if_insertion_operator(Class_ &cl, std::string const &name)
111-
-> decltype(std::declval<std::ostream&>() << std::declval<typename Vector::value_type>(), void()) {
112-
using size_type = typename Vector::size_type;
113-
114-
cl.def("__repr__",
115-
[name](Vector &v) {
116-
std::ostringstream s;
117-
s << name << '[';
118-
for (size_type i=0; i < v.size(); ++i) {
119-
s << v[i];
120-
if (i != v.size() - 1)
121-
s << ", ";
122-
}
123-
s << ']';
124-
return s.str();
125-
},
126-
"Return the canonical string representation of this list."
127-
);
128-
}
129-
130-
NAMESPACE_END(detail)
131-
132-
//
133-
// std::vector
134-
//
135-
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
136-
pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
112+
// Vector modifiers -- requires a copyable vector_type:
113+
// (Technically, some of these (pop and __delitem__) don't actually require copyability, but it seems
114+
// silly to allow deletion but not insertion, so include them here too.)
115+
template <typename Vector, typename Class_>
116+
void vector_modifiers(enable_if_t<std::is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
137117
using T = typename Vector::value_type;
138118
using SizeType = typename Vector::size_type;
139119
using DiffType = typename Vector::difference_type;
140-
using ItType = typename Vector::iterator;
141-
using Class_ = pybind11::class_<Vector, holder_type>;
142-
143-
Class_ cl(m, name.c_str(), std::forward<Args>(args)...);
144-
145-
cl.def(pybind11::init<>());
146-
147-
// Register copy constructor (if possible)
148-
detail::vector_if_copy_constructible<Vector, Class_>(cl);
149-
150-
// Register comparison-related operators and functions (if possible)
151-
detail::vector_if_equal_operator<Vector, Class_>(cl);
152120

153-
// Register stream insertion operator (if possible)
154-
detail::vector_if_insertion_operator<Vector, Class_>(cl, name);
121+
cl.def("append",
122+
[](Vector &v, const T &value) { v.push_back(value); },
123+
arg("x"),
124+
"Add an item to the end of the list");
155125

156126
cl.def("__init__", [](Vector &v, iterable it) {
157127
new (&v) Vector();
158128
try {
159129
v.reserve(len(it));
160130
for (handle h : it)
161-
v.push_back(h.cast<typename Vector::value_type>());
131+
v.push_back(h.cast<T>());
162132
} catch (...) {
163133
v.~Vector();
164134
throw;
165135
}
166136
});
167137

168-
cl.def("append",
169-
[](Vector &v, const T &value) { v.push_back(value); },
170-
arg("x"),
171-
"Add an item to the end of the list");
172-
173138
cl.def("extend",
174-
[](Vector &v, Vector &src) {
139+
[](Vector &v, const Vector &src) {
175140
v.reserve(v.size() + src.size());
176141
v.insert(v.end(), src.begin(), src.end());
177142
},
@@ -210,21 +175,6 @@ pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::stri
210175
"Remove and return the item at index ``i``"
211176
);
212177

213-
cl.def("__bool__",
214-
[](const Vector &v) -> bool {
215-
return !v.empty();
216-
},
217-
"Check whether the list is nonempty"
218-
);
219-
220-
cl.def("__getitem__",
221-
[](const Vector &v, SizeType i) -> T {
222-
if (i >= v.size())
223-
throw pybind11::index_error();
224-
return v[i];
225-
}
226-
);
227-
228178
cl.def("__setitem__",
229179
[](Vector &v, SizeType i, const T &t) {
230180
if (i >= v.size())
@@ -233,26 +183,6 @@ pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::stri
233183
}
234184
);
235185

236-
cl.def("__delitem__",
237-
[](Vector &v, SizeType i) {
238-
if (i >= v.size())
239-
throw pybind11::index_error();
240-
v.erase(v.begin() + typename Vector::difference_type(i));
241-
},
242-
"Delete list elements using a slice object"
243-
);
244-
245-
cl.def("__len__", &Vector::size);
246-
247-
cl.def("__iter__",
248-
[](Vector &v) {
249-
return pybind11::make_iterator<
250-
return_value_policy::reference_internal, ItType, ItType, T>(
251-
v.begin(), v.end());
252-
},
253-
pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
254-
);
255-
256186
/// Slicing protocol
257187
cl.def("__getitem__",
258188
[](const Vector &v, slice slice) -> Vector * {
@@ -291,6 +221,15 @@ pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::stri
291221
"Assign list elements using a slice object"
292222
);
293223

224+
cl.def("__delitem__",
225+
[](Vector &v, SizeType i) {
226+
if (i >= v.size())
227+
throw pybind11::index_error();
228+
v.erase(v.begin() + DiffType(i));
229+
},
230+
"Delete the list elements at index ``i``"
231+
);
232+
294233
cl.def("__delitem__",
295234
[](Vector &v, slice slice) {
296235
size_t start, stop, step, slicelength;
@@ -310,6 +249,118 @@ pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::stri
310249
"Delete list elements using a slice object"
311250
);
312251

252+
}
253+
254+
// Default __getitem__, when we can copy the value:
255+
template <typename Vector, typename Class_>
256+
void vector_accessor(enable_if_t<std::is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
257+
using T = typename Vector::value_type;
258+
using SizeType = typename Vector::size_type;
259+
using ItType = typename Vector::iterator;
260+
cl.def("__getitem__",
261+
[](const Vector &v, SizeType i) -> T {
262+
if (i >= v.size())
263+
throw pybind11::index_error();
264+
return v[i];
265+
}
266+
);
267+
268+
cl.def("__iter__",
269+
[](Vector &v) {
270+
return pybind11::make_iterator<
271+
return_value_policy::reference_internal, ItType, ItType, T>(
272+
v.begin(), v.end());
273+
},
274+
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
275+
);
276+
}
277+
278+
// When we can't copy, we have to return a reference and use a keepalive:
279+
template <typename Vector, typename Class_>
280+
void vector_accessor(enable_if_t<!std::is_copy_constructible<typename Vector::value_type>::value, Class_> &cl) {
281+
using T = typename Vector::value_type;
282+
using SizeType = typename Vector::size_type;
283+
using ItType = typename Vector::iterator;
284+
285+
cl.def("__getitem__",
286+
[](Vector &v, SizeType i) -> T & {
287+
if (i >= v.size())
288+
throw pybind11::index_error();
289+
return v[i];
290+
},
291+
return_value_policy::reference_internal // ref + keepalive
292+
);
293+
294+
cl.def("__iter__",
295+
[](Vector &v) {
296+
return pybind11::make_iterator<
297+
return_value_policy::reference_internal, ItType, ItType, T&>(
298+
v.begin(), v.end());
299+
},
300+
keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
301+
);
302+
}
303+
304+
template <typename Vector, typename Class_> auto vector_if_insertion_operator(Class_ &cl, std::string const &name)
305+
-> decltype(std::declval<std::ostream&>() << std::declval<typename Vector::value_type>(), void()) {
306+
using size_type = typename Vector::size_type;
307+
308+
cl.def("__repr__",
309+
[name](Vector &v) {
310+
std::ostringstream s;
311+
s << name << '[';
312+
for (size_type i=0; i < v.size(); ++i) {
313+
s << v[i];
314+
if (i != v.size() - 1)
315+
s << ", ";
316+
}
317+
s << ']';
318+
return s.str();
319+
},
320+
"Return the canonical string representation of this list."
321+
);
322+
}
323+
324+
NAMESPACE_END(detail)
325+
326+
//
327+
// std::vector
328+
//
329+
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
330+
pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
331+
using Class_ = pybind11::class_<Vector, holder_type>;
332+
333+
Class_ cl(m, name.c_str(), std::forward<Args>(args)...);
334+
335+
cl.def(pybind11::init<>());
336+
337+
// Register copy constructor (if possible)
338+
detail::vector_if_copy_constructible<Vector, Class_>(cl);
339+
340+
// Register comparison-related operators and functions (if possible)
341+
detail::vector_if_equal_operator<Vector, Class_>(cl);
342+
343+
// Register stream insertion operator (if possible)
344+
detail::vector_if_insertion_operator<Vector, Class_>(cl, name);
345+
346+
// Modifiers require copyable vector value type
347+
detail::vector_modifiers<Vector, Class_>(cl);
348+
349+
// Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive
350+
detail::vector_accessor<Vector, Class_>(cl);
351+
352+
cl.def("__bool__",
353+
[](const Vector &v) -> bool {
354+
return !v.empty();
355+
},
356+
"Check whether the list is nonempty"
357+
);
358+
359+
cl.def("__len__", &Vector::size);
360+
361+
362+
363+
313364
#if 0
314365
// C++ style functions deprecated, leaving it here as an example
315366
cl.def(pybind11::init<size_type>());
@@ -363,7 +414,9 @@ NAMESPACE_BEGIN(detail)
363414

364415
/* Fallback functions */
365416
template <typename, typename, typename... Args> void map_if_insertion_operator(const Args &...) { }
417+
template <typename, typename, typename... Args> void map_assignment(const Args &...) { }
366418

419+
// Map assignment when copy-assignable: just copy the value
367420
template <typename Map, typename Class_>
368421
void map_assignment(enable_if_t<std::is_copy_assignable<typename Map::mapped_type>::value, Class_> &cl) {
369422
using KeyType = typename Map::key_type;
@@ -378,21 +431,23 @@ void map_assignment(enable_if_t<std::is_copy_assignable<typename Map::mapped_typ
378431
);
379432
}
380433

434+
// Not copy-assignable, but still copy-constructible: we can update the value by erasing and reinserting
381435
template<typename Map, typename Class_>
382-
void map_if_copy_assignable(enable_if_t<
383-
!std::is_copy_assignable<typename Map::mapped_type>::value,
436+
void map_assignment(enable_if_t<
437+
!std::is_copy_assignable<typename Map::mapped_type>::value &&
438+
std::is_copy_constructible<typename Map::mapped_type>::value,
384439
Class_> &cl) {
385440
using KeyType = typename Map::key_type;
386441
using MappedType = typename Map::mapped_type;
387442

388443
cl.def("__setitem__",
389444
[](Map &m, const KeyType &k, const MappedType &v) {
390445
// We can't use m[k] = v; because value type might not be default constructable
391-
auto r = m.insert(std::make_pair(k, v));
446+
auto r = m.emplace(k, v);
392447
if (!r.second) {
393-
// value type might be const so the only way to insert it is to erase it first...
448+
// value type is not copy assignable so the only way to insert it is to erase it first...
394449
m.erase(r.first);
395-
m.insert(std::make_pair(k, v));
450+
m.emplace(k, v);
396451
}
397452
}
398453
);
@@ -419,12 +474,48 @@ template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &
419474
"Return the canonical string representation of this map."
420475
);
421476
}
477+
478+
// Default __getitem__, when we can copy the value:
479+
template <typename Map, typename Class_>
480+
void map_accessor(enable_if_t<std::is_copy_constructible<typename Map::mapped_type>::value, Class_> &cl) {
481+
using KeyType = typename Map::key_type;
482+
using MappedType = typename Map::mapped_type;
483+
484+
cl.def("__getitem__",
485+
[](Map &m, const KeyType &k) -> MappedType {
486+
auto it = m.find(k);
487+
if (it == m.end())
488+
throw pybind11::key_error();
489+
return it->second;
490+
}
491+
);
492+
493+
}
494+
495+
// When we can't copy, we have to return a reference and use a keepalive:
496+
template <typename Map, typename Class_>
497+
void map_accessor(enable_if_t<!std::is_copy_constructible<typename Map::mapped_type>::value, Class_> &cl) {
498+
using KeyType = typename Map::key_type;
499+
using MappedType = typename Map::mapped_type;
500+
501+
cl.def("__getitem__",
502+
[](Map &m, const KeyType &k) -> MappedType & {
503+
auto it = m.find(k);
504+
if (it == m.end())
505+
throw pybind11::key_error();
506+
return it->second;
507+
},
508+
return_value_policy::reference_internal // ref + keepalive
509+
);
510+
511+
}
512+
513+
422514
NAMESPACE_END(detail)
423515

424516
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
425517
pybind11::class_<Map, holder_type> bind_map(module &m, const std::string &name, Args&&... args) {
426518
using KeyType = typename Map::key_type;
427-
using MappedType = typename Map::mapped_type;
428519
using Class_ = pybind11::class_<Map, holder_type>;
429520

430521
Class_ cl(m, name.c_str(), std::forward<Args>(args)...);
@@ -449,16 +540,11 @@ pybind11::class_<Map, holder_type> bind_map(module &m, const std::string &name,
449540
pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
450541
);
451542

452-
cl.def("__getitem__",
453-
[](Map &m, const KeyType &k) -> MappedType {
454-
auto it = m.find(k);
455-
if (it == m.end())
456-
throw pybind11::key_error();
457-
return it->second;
458-
}
459-
);
543+
// Accessor and iterator; return by value if copyable, otherwise we return by ref + keep-alive
544+
detail::map_accessor<Map, Class_>(cl);
460545

461-
detail::map_if_copy_assignable<Map, Class_>(cl);
546+
// Assignment provided only if the type is copyable
547+
detail::map_assignment<Map, Class_>(cl);
462548

463549
cl.def("__delitem__",
464550
[](Map &m, const KeyType &k) {

0 commit comments

Comments
 (0)