diff --git a/CHANGELOG.md b/CHANGELOG.md index 264fe6f7..14daba35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,15 @@ 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 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 + +- 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 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 diff --git a/include/eigenpy/std-map.hpp b/include/eigenpy/std-map.hpp index 0d712357..6640450f 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; /** @@ -144,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 @@ -161,24 +159,56 @@ 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 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 emplace_set_derived_policies, + public dict_to_map { typedef dict_to_map FromPythonDictConverter; static void expose(const std::string& class_name, @@ -186,15 +216,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..ef641aad 100644 --- a/unittest/std_map.cpp +++ b/unittest/std_map.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace bp = boost::python; @@ -22,14 +22,32 @@ std::map copy(const std::map& map) { return out; } +template +boost::unordered_map copy_boost( + const boost::unordered_map& obj) { + return obj; +} + +struct X { + X() = delete; + X(int x) : val(x) {} + int val; +}; + BOOST_PYTHON_MODULE(std_map) { eigenpy::enableEigenPy(); - eigenpy::python::StdMapPythonVisitor< + eigenpy::StdMapPythonVisitor< std::string, double, std::less, std::allocator >, true>::expose("StdMap_Double"); + 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); }