Fix stl_bind to support movable, non-copyable value types (#490)

This commit includes the following changes:

* Don't provide make_copy_constructor for non-copyable container

make_copy_constructor currently fails for various stl containers (e.g.
std::vector, std::unordered_map, std::deque, etc.) when the container's
value type (e.g. the "T" or the std::pair<K,T> for a map) is
non-copyable.  This adds an override that, for types that look like
containers, also requires that the value_type be copyable.

* 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.

* stl_bind: copy only for arithmetic value types

For non-primitive types, we may well be copying some complex type, when
returning by reference is more appropriate.  This commit returns by
internal reference for all but basic arithmetic types.

* Return by reference whenever possible

Only if we definitely can't--i.e. std::vector<bool>--because v[i]
returns something that isn't a T& do we copy; for everything else, we
return by reference.

For the map case, we can always return by reference (at least for the
default stl map/unordered_map).
This commit is contained in:
Jason Rhinelander
2016-11-15 06:30:38 -05:00
committed by Wenzel Jakob
parent 06bd27f536
commit 617fbcfc1e
4 changed files with 299 additions and 116 deletions

View File

@@ -11,6 +11,7 @@
#include <pybind11/stl_bind.h>
#include <map>
#include <deque>
#include <unordered_map>
class El {
@@ -26,6 +27,32 @@ std::ostream & operator<<(std::ostream &s, El const&v) {
return s;
}
/// Issue #487: binding std::vector<E> with E non-copyable
class E_nc {
public:
explicit E_nc(int i) : value{i} {}
E_nc(const E_nc &) = delete;
E_nc &operator=(const E_nc &) = delete;
E_nc(E_nc &&) = default;
E_nc &operator=(E_nc &&) = default;
int value;
};
template <class Container> Container *one_to_n(int n) {
auto v = new Container();
for (int i = 1; i <= n; i++)
v->emplace_back(i);
return v;
}
template <class Map> Map *times_ten(int n) {
auto m = new Map();
for (int i = 1; i <= n; i++)
m->emplace(int(i), E_nc(10*i));
return m;
}
test_initializer stl_binder_vector([](py::module &m) {
py::class_<El>(m, "El")
.def(py::init<int>());
@@ -36,6 +63,7 @@ test_initializer stl_binder_vector([](py::module &m) {
py::bind_vector<std::vector<El>>(m, "VectorEl");
py::bind_vector<std::vector<std::vector<El>>>(m, "VectorVectorEl");
});
test_initializer stl_binder_map([](py::module &m) {
@@ -44,4 +72,24 @@ test_initializer stl_binder_map([](py::module &m) {
py::bind_map<std::map<std::string, double const>>(m, "MapStringDoubleConst");
py::bind_map<std::unordered_map<std::string, double const>>(m, "UnorderedMapStringDoubleConst");
});
test_initializer stl_binder_noncopyable([](py::module &m) {
py::class_<E_nc>(m, "ENC")
.def(py::init<int>())
.def_readwrite("value", &E_nc::value);
py::bind_vector<std::vector<E_nc>>(m, "VectorENC");
m.def("get_vnc", &one_to_n<std::vector<E_nc>>, py::return_value_policy::reference);
py::bind_vector<std::deque<E_nc>>(m, "DequeENC");
m.def("get_dnc", &one_to_n<std::deque<E_nc>>, py::return_value_policy::reference);
py::bind_map<std::map<int, E_nc>>(m, "MapENC");
m.def("get_mnc", &times_ten<std::map<int, E_nc>>, py::return_value_policy::reference);
py::bind_map<std::unordered_map<int, E_nc>>(m, "UmapENC");
m.def("get_umnc", &times_ten<std::unordered_map<int, E_nc>>, py::return_value_policy::reference);
});