diff --git a/CHANGELOG.md b/CHANGELOG.md index 264fe6f7c427835ebe76b1b6358f3d4c28c8ee4c..14daba352c16a917b5c4de492e5608afc21c7585 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<std::string, int>` ([#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 53d120ce14f8d050e6b34eaa7ef02b3fc0b16f9c..ce57bc0bbc544de06a10bd7fcdacab5110aa420e 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 0d7123577b1a337f5aefe7fd372b6c8ec8209385..6640450f686fdf8bacd227545bc2c8e65aba174b 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 <class Container, bool NoProxy> +struct emplace_set_derived_policies + : bp::map_indexing_suite< + Container, NoProxy, + emplace_set_derived_policies<Container, NoProxy> > { + 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<Container, NoProxy>; + + template <class Class> + 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<std::string> class_name_extractor(class_name); + elem_name += class_name_extractor(); + elem_name += "_entry"; + namespace mpl = boost::mpl; + + typedef typename mpl::if_< + mpl::and_<boost::is_class<data_type>, mpl::bool_<!NoProxy> >, + bp::return_internal_reference<>, bp::default_call_policies>::type + get_data_return_policy; + + bp::class_<value_type>(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<T>. - * @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>. - * @param[in] Allocator Type for the Allocator in - * std::map<T,Compare,Allocator>. + * @param[in] Container Container to expose. * @param[in] NoProxy When set to false, the elements will be copied when * returned to Python. */ -template <class Key, class T, class Compare = std::less<Key>, - class Allocator = std::allocator<std::pair<const Key, T> >, - bool NoProxy = false> -struct StdMapPythonVisitor - : public bp::map_indexing_suite< - typename std::map<Key, T, Compare, Allocator>, NoProxy>, - public dict_to_map<std::map<Key, T, Compare, Allocator> > { - typedef std::map<Key, T, Compare, Allocator> Container; +template <class Container, bool NoProxy = false> +struct GenericMapVisitor + : public emplace_set_derived_policies<Container, NoProxy>, + public dict_to_map<Container> { typedef dict_to_map<Container> FromPythonDictConverter; static void expose(const std::string& class_name, @@ -186,15 +216,34 @@ struct StdMapPythonVisitor namespace bp = bp; bp::class_<Container>(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<Container>()); // 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<T>. + * @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>. + * @param[in] Allocator Type for the Allocator in + * std::map<T,Compare,Allocator>. + * @param[in] NoProxy When set to false, the elements will be copied when + * returned to Python. + */ +template <class Key, class T, class Compare = std::less<Key>, + class Allocator = std::allocator<std::pair<const Key, T> >, + bool NoProxy = false> +struct StdMapPythonVisitor + : GenericMapVisitor<std::map<Key, T, Compare, Allocator>, 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 091027b21b063eb3cfe0e33659a5fc7e96096943..ea57111952e3ed7c84027217250a64b902966298 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 5997e39cc82df6deb68ceb355e3613c2cb122b83..ef641aad861c92d796d385f5f60d7cedc0017af9 100644 --- a/unittest/std_map.cpp +++ b/unittest/std_map.cpp @@ -3,7 +3,7 @@ #include <eigenpy/eigenpy.hpp> #include <eigenpy/std-map.hpp> -#include <iostream> +#include <boost/unordered_map.hpp> namespace bp = boost::python; @@ -22,14 +22,32 @@ std::map<std::string, T1> copy(const std::map<std::string, T1>& map) { return out; } +template <typename T1> +boost::unordered_map<std::string, T1> copy_boost( + const boost::unordered_map<std::string, T1>& 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::string>, std::allocator<std::pair<const std::string, double> >, true>::expose("StdMap_Double"); + eigenpy::GenericMapVisitor<boost::unordered_map<std::string, int> >::expose( + "boost_map_int"); + + eigenpy::GenericMapVisitor<std::map<std::string, X> >::expose("StdMap_X"); + bp::def("std_map_to_dict", std_map_to_dict<double>); bp::def("copy", copy<double>); + bp::def("copy_boost", copy_boost<int>); }