From 51de65c98b75de3caca613277dfb8426c9a0ae9a Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Tue, 17 Sep 2024 17:48:24 +0200 Subject: [PATCH 1/6] [std-map] add more general visitor for map types + test boost::unordered_map + pull StdMapPythonVisitor out of eigenpy::python namespace (shouldn't exist, add using-decl for backwards compatibility) --- include/eigenpy/std-map.hpp | 43 ++++++++++++++++++++------------- unittest/python/test_std_map.py | 5 +++- unittest/std_map.cpp | 12 ++++++++- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp index 0d712357..70a18b99 100644 --- a/include/eigenpy/std-map.hpp +++ b/include/eigenpy/std-map.hpp @@ -70,8 +70,6 @@ struct overload_base_get_item_for_std_map // Rohan Budhiraja. /////////////////////////////////////////////////////////////////////////////// -namespace python { - namespace bp = boost::python; /** @@ -162,23 +160,15 @@ struct dict_to_map { }; /** - * @brief Expose an std::map from a type given as template argument. + * @brief Expose the map-like container, e.g. (std::map). * - * @param[in] T Type to expose as std::map. - * @param[in] Compare Type for the Compare in std::map. - * @param[in] Allocator Type for the Allocator in - * std::map. + * @param[in] Container Container to expose. * @param[in] NoProxy When set to false, the elements will be copied when * returned to Python. */ -template , - class Allocator = std::allocator >, - bool NoProxy = false> -struct StdMapPythonVisitor - : public bp::map_indexing_suite< - typename std::map, NoProxy>, - public dict_to_map > { - typedef std::map Container; +template +struct GenericMapVisitor : public bp::map_indexing_suite, + public dict_to_map { typedef dict_to_map FromPythonDictConverter; static void expose(const std::string& class_name, @@ -186,15 +176,34 @@ struct StdMapPythonVisitor namespace bp = bp; bp::class_(class_name.c_str(), doc_string.c_str()) - .def(StdMapPythonVisitor()) + .def(GenericMapVisitor()) .def("todict", &FromPythonDictConverter::todict, bp::arg("self"), - "Returns the std::map as a Python dictionary.") + "Returns the map type as a Python dictionary.") .def_pickle(PickleMap()); // Register conversion FromPythonDictConverter::register_converter(); } }; +/** + * @brief Expose an std::map from a type given as template argument. + * + * @param[in] T Type to expose as std::map. + * @param[in] Compare Type for the Compare in std::map. + * @param[in] Allocator Type for the Allocator in + * std::map. + * @param[in] NoProxy When set to false, the elements will be copied when + * returned to Python. + */ +template , + class Allocator = std::allocator >, + bool NoProxy = false> +struct StdMapPythonVisitor + : GenericMapVisitor, NoProxy> {}; + +namespace python { +// fix previous mistake +using ::eigenpy::StdMapPythonVisitor; } // namespace python } // namespace eigenpy diff --git a/unittest/python/test_std_map.py b/unittest/python/test_std_map.py index 091027b2..ea571119 100644 --- a/unittest/python/test_std_map.py +++ b/unittest/python/test_std_map.py @@ -1,6 +1,9 @@ -from std_map import copy, std_map_to_dict +from std_map import copy, copy_boost, std_map_to_dict t = {"one": 1.0, "two": 2.0} +t2 = {"one": 1, "two": 2, "three": 3} assert std_map_to_dict(t) == t assert std_map_to_dict(copy(t)) == t +m = copy_boost(t2) +assert m.todict() == t2 diff --git a/unittest/std_map.cpp b/unittest/std_map.cpp index 5997e39c..539dec06 100644 --- a/unittest/std_map.cpp +++ b/unittest/std_map.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace bp = boost::python; @@ -22,6 +22,12 @@ std::map copy(const std::map& map) { return out; } +template +boost::unordered_map copy_boost( + const boost::unordered_map& obj) { + return obj; +} + BOOST_PYTHON_MODULE(std_map) { eigenpy::enableEigenPy(); @@ -30,6 +36,10 @@ BOOST_PYTHON_MODULE(std_map) { std::allocator >, true>::expose("StdMap_Double"); + eigenpy::GenericMapVisitor >::expose( + "boost_map_int"); + bp::def("std_map_to_dict", std_map_to_dict); bp::def("copy", copy); + bp::def("copy_boost", copy_boost); } From c59e1b58eb70d7976e8b588f220276747e50132b Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Tue, 17 Sep 2024 17:49:48 +0200 Subject: [PATCH 2/6] update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 264fe6f7..0249e8c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Added + +- Add more general visitor `GenericMapPythonVisitor` for map types ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504)), test `boost::unordered_map` - Add type_info helpers ([#502](https://github.com/stack-of-tasks/eigenpy/pull/502)) +### Changed + +- Move `StdMapPythonVisitor` out of `eigenpy::python` namespace, which was a mistake ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504)) + ## [3.9.0] - 2024-08-31 ### Changed From a91e57cb48e1ce3ed4b87f25ffa7eb95d3791ef6 Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Tue, 17 Sep 2024 18:08:52 +0200 Subject: [PATCH 3/6] [std-map] do not use operator[], but emplace() to deal with non-default-constructible case --- include/eigenpy/std-map.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp index 70a18b99..f7ea6c6b 100644 --- a/include/eigenpy/std-map.hpp +++ b/include/eigenpy/std-map.hpp @@ -142,7 +142,7 @@ struct dict_to_map { bp::throw_error_already_set(); } typename Container::mapped_type val = valproxy(); - map[key] = val; + map.emplace(key, val); } // remember the location for later From 826798a2a1bc8933c808f6500f85dc893e836b18 Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Tue, 17 Sep 2024 18:34:56 +0200 Subject: [PATCH 4/6] use parent namespace --- unittest/std_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittest/std_map.cpp b/unittest/std_map.cpp index 539dec06..03bc58b0 100644 --- a/unittest/std_map.cpp +++ b/unittest/std_map.cpp @@ -31,7 +31,7 @@ boost::unordered_map copy_boost( BOOST_PYTHON_MODULE(std_map) { eigenpy::enableEigenPy(); - eigenpy::python::StdMapPythonVisitor< + eigenpy::StdMapPythonVisitor< std::string, double, std::less, std::allocator >, true>::expose("StdMap_Double"); From 91d5fcbc9203c795e098c588dd5f7d7b75e3ded7 Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Tue, 17 Sep 2024 18:44:59 +0200 Subject: [PATCH 5/6] [std-map] fix for non-default constructible types --- include/eigenpy/std-map.hpp | 44 +++++++++++++++++++++++++++++++++++-- unittest/std_map.cpp | 8 +++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp index f7ea6c6b..6640450f 100644 --- a/include/eigenpy/std-map.hpp +++ b/include/eigenpy/std-map.hpp @@ -159,6 +159,45 @@ struct dict_to_map { } }; +/// Policies which handle the non-default constructible case +/// and set_item() using emplace(). +template +struct emplace_set_derived_policies + : bp::map_indexing_suite< + Container, NoProxy, + emplace_set_derived_policies > { + typedef typename Container::key_type index_type; + typedef typename Container::value_type::second_type data_type; + typedef typename Container::value_type value_type; + using DerivedPolicies = + bp::detail::final_map_derived_policies; + + template + static void extension_def(Class& cl) { + // Wrap the map's element (value_type) + std::string elem_name = "map_indexing_suite_"; + bp::object class_name(cl.attr("__name__")); + bp::extract class_name_extractor(class_name); + elem_name += class_name_extractor(); + elem_name += "_entry"; + namespace mpl = boost::mpl; + + typedef typename mpl::if_< + mpl::and_, mpl::bool_ >, + bp::return_internal_reference<>, bp::default_call_policies>::type + get_data_return_policy; + + bp::class_(elem_name.c_str(), bp::no_init) + .def("__repr__", &DerivedPolicies::print_elem) + .def("data", &DerivedPolicies::get_data, get_data_return_policy()) + .def("key", &DerivedPolicies::get_key); + } + + static void set_item(Container& container, index_type i, data_type const& v) { + container.emplace(i, v); + } +}; + /** * @brief Expose the map-like container, e.g. (std::map). * @@ -167,8 +206,9 @@ struct dict_to_map { * returned to Python. */ template -struct GenericMapVisitor : public bp::map_indexing_suite, - public dict_to_map { +struct GenericMapVisitor + : public emplace_set_derived_policies, + public dict_to_map { typedef dict_to_map FromPythonDictConverter; static void expose(const std::string& class_name, diff --git a/unittest/std_map.cpp b/unittest/std_map.cpp index 03bc58b0..ef641aad 100644 --- a/unittest/std_map.cpp +++ b/unittest/std_map.cpp @@ -28,6 +28,12 @@ boost::unordered_map copy_boost( return obj; } +struct X { + X() = delete; + X(int x) : val(x) {} + int val; +}; + BOOST_PYTHON_MODULE(std_map) { eigenpy::enableEigenPy(); @@ -39,6 +45,8 @@ BOOST_PYTHON_MODULE(std_map) { eigenpy::GenericMapVisitor >::expose( "boost_map_int"); + eigenpy::GenericMapVisitor >::expose("StdMap_X"); + bp::def("std_map_to_dict", std_map_to_dict); bp::def("copy", copy); bp::def("copy_boost", copy_boost); From 00bbef893b234b90c903ee060bac4517c3957242 Mon Sep 17 00:00:00 2001 From: ManifoldFR Date: Wed, 18 Sep 2024 11:41:43 +0200 Subject: [PATCH 6/6] Update CHANGELOG and README --- CHANGELOG.md | 3 ++- README.md | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0249e8c3..14daba35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added -- Add more general visitor `GenericMapPythonVisitor` for map types ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504)), test `boost::unordered_map` +- Add more general visitor `GenericMapPythonVisitor` for map types test `boost::unordered_map` ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504)) +- Support for non-[default-contructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) types in map types ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504)) - Add type_info helpers ([#502](https://github.com/stack-of-tasks/eigenpy/pull/502)) ### Changed diff --git a/README.md b/README.md index 53d120ce..ce57bc0b 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,16 @@ EigenPy — Versatile and efficient Python bindings between Numpy and Eigen **EigenPy** is an open-source framework that allows the binding of the famous [Eigen](http://eigen.tuxfamily.org) C++ library in Python via Boost.Python. **EigenPy** provides: - - full memory sharing between Numpy and Eigen, avoiding memory allocation - - full support Eigen::Ref avoiding memory allocation - - full support of the Eigen::Tensor module - - exposition of the Geometry module of Eigen for easy code prototyping - - standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing)) - - full support of SWIG objects - - full support of runtime declaration of Numpy scalar types - - extended API to expose std::vector types - - full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory) + +- full memory sharing between Numpy and Eigen, avoiding memory allocation +- full support Eigen::Ref avoiding memory allocation +- full support of the Eigen::Tensor module +- exposition of the Geometry module of Eigen for easy code prototyping +- standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing)) +- full support of SWIG objects +- full support of runtime declaration of Numpy scalar types +- extended API to expose several STL types and some of their Boost equivalents: `optional` types, `std::pair`, maps, variants... +- full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory) ## Setup