From a83d69e78ffe58bbd410aa7503384a312e08963b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Gs=C3=A4nger?= <8004308+sgsaenger@users.noreply.github.com> Date: Thu, 31 Oct 2019 12:38:24 +0100 Subject: [PATCH] test pair-copyability on C++17 upwards (#1886) * test pair-copyability on C++17 upwards The stdlib falsely detects containers like M=std::map as copyable, even when one of T and U is not copyable. Therefore we cannot rely on the stdlib dismissing std::pair by itself, even on C++17. * fix is_copy_assignable bind_map used std::is_copy_assignable which suffers from the same problems as std::is_copy_constructible, therefore the same fix has been applied. * created tests for copyability --- include/pybind11/cast.h | 16 ++++++++++++---- include/pybind11/stl_bind.h | 4 ++-- tests/test_stl_binders.cpp | 22 +++++++++++++++++++++ tests/test_stl_binders.py | 38 +++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index e1bbf03aa..90407eb98 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -797,12 +797,20 @@ template struct is_copy_constructible> >::value>> : is_copy_constructible {}; -#if !defined(PYBIND11_CPP17) -// Likewise for std::pair before C++17 (which mandates that the copy constructor not exist when the -// two types aren't themselves copy constructible). +// Likewise for std::pair +// (after C++17 it is mandatory that the copy constructor not exist when the two types aren't themselves +// copy constructible, but this can not be relied upon when T1 or T2 are themselves containers). template struct is_copy_constructible> : all_of, is_copy_constructible> {}; -#endif + +// The same problems arise with std::is_copy_assignable, so we use the same workaround. +template struct is_copy_assignable : std::is_copy_assignable {}; +template struct is_copy_assignable, + std::is_same + >::value>> : is_copy_assignable {}; +template struct is_copy_assignable> + : all_of, is_copy_assignable> {}; NAMESPACE_END(detail) diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index d3adaed3a..62bd90819 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -512,7 +512,7 @@ template void map_assignment(const Args & // Map assignment when copy-assignable: just copy the value template -void map_assignment(enable_if_t::value, Class_> &cl) { +void map_assignment(enable_if_t::value, Class_> &cl) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; @@ -528,7 +528,7 @@ void map_assignment(enable_if_t void map_assignment(enable_if_t< - !std::is_copy_assignable::value && + !is_copy_assignable::value && is_copy_constructible::value, Class_> &cl) { using KeyType = typename Map::key_type; diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp index a88b589e1..868887409 100644 --- a/tests/test_stl_binders.cpp +++ b/tests/test_stl_binders.cpp @@ -54,6 +54,14 @@ template Map *times_ten(int n) { return m; } +template NestMap *times_hundred(int n) { + auto m = new NestMap(); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + (*m)[i].emplace(int(j*10), E_nc(100*j)); + return m; +} + TEST_SUBMODULE(stl_binders, m) { // test_vector_int py::bind_vector>(m, "VectorInt", py::buffer_protocol()); @@ -85,6 +93,20 @@ TEST_SUBMODULE(stl_binders, m) { m.def("get_mnc", ×_ten>, py::return_value_policy::reference); py::bind_map>(m, "UmapENC"); m.def("get_umnc", ×_ten>, py::return_value_policy::reference); + // Issue #1885: binding nested std::map> with E non-copyable + py::bind_map>>(m, "MapVecENC"); + m.def("get_nvnc", [](int n) + { + auto m = new std::map>(); + for (int i = 1; i <= n; i++) + for (int j = 1; j <= n; j++) + (*m)[i].emplace_back(j); + return m; + }, py::return_value_policy::reference); + py::bind_map>>(m, "MapMapENC"); + m.def("get_nmnc", ×_hundred>>, py::return_value_policy::reference); + py::bind_map>>(m, "UmapUmapENC"); + m.def("get_numnc", ×_hundred>>, py::return_value_policy::reference); // test_vector_buffer py::bind_vector>(m, "VectorUChar", py::buffer_protocol()); diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index 6d5a15983..b83a587f2 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -212,6 +212,44 @@ def test_noncopyable_containers(): assert vsum == 150 + # nested std::map + nvnc = m.get_nvnc(5) + for i in range(1, 6): + for j in range(0, 5): + assert nvnc[i][j].value == j + 1 + + for k, v in nvnc.items(): + for i, j in enumerate(v, start=1): + assert j.value == i + + # nested std::map + nmnc = m.get_nmnc(5) + for i in range(1, 6): + for j in range(10, 60, 10): + assert nmnc[i][j].value == 10 * j + + vsum = 0 + for k_o, v_o in nmnc.items(): + for k_i, v_i in v_o.items(): + assert v_i.value == 10 * k_i + vsum += v_i.value + + assert vsum == 7500 + + # nested std::unordered_map + numnc = m.get_numnc(5) + for i in range(1, 6): + for j in range(10, 60, 10): + assert numnc[i][j].value == 10 * j + + vsum = 0 + for k_o, v_o in numnc.items(): + for k_i, v_i in v_o.items(): + assert v_i.value == 10 * k_i + vsum += v_i.value + + assert vsum == 7500 + def test_map_delitem(): mm = m.MapStringDouble()