Skip to content

Commit 694d996

Browse files
authored
Merge pull request #504 from ManifoldFR/topic/generic-maps
[std-map] add more general visitor for map types
2 parents 9537d8f + 00bbef8 commit 694d996

File tree

5 files changed

+108
-30
lines changed

5 files changed

+108
-30
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
77
## [Unreleased]
88

99
### Added
10+
11+
- 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))
12+
- 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))
1013
- Add type_info helpers ([#502](https://github.com/stack-of-tasks/eigenpy/pull/502))
1114

15+
### Changed
16+
17+
- Move `StdMapPythonVisitor` out of `eigenpy::python` namespace, which was a mistake ([#504](https://github.com/stack-of-tasks/eigenpy/pull/504))
18+
1219
## [3.9.0] - 2024-08-31
1320

1421
### Changed

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ EigenPy — Versatile and efficient Python bindings between Numpy and Eigen
1414
**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.
1515

1616
**EigenPy** provides:
17-
- full memory sharing between Numpy and Eigen, avoiding memory allocation
18-
- full support Eigen::Ref avoiding memory allocation
19-
- full support of the Eigen::Tensor module
20-
- exposition of the Geometry module of Eigen for easy code prototyping
21-
- standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing))
22-
- full support of SWIG objects
23-
- full support of runtime declaration of Numpy scalar types
24-
- extended API to expose std::vector types
25-
- full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory)
17+
18+
- full memory sharing between Numpy and Eigen, avoiding memory allocation
19+
- full support Eigen::Ref avoiding memory allocation
20+
- full support of the Eigen::Tensor module
21+
- exposition of the Geometry module of Eigen for easy code prototyping
22+
- standard matrix decomposion routines of Eigen such as the Cholesky decomposition (SVD and QR decompositions [can be added](#contributing))
23+
- full support of SWIG objects
24+
- full support of runtime declaration of Numpy scalar types
25+
- extended API to expose several STL types and some of their Boost equivalents: `optional` types, `std::pair`, maps, variants...
26+
- full support of vectorization between C++ and Python (all the hold objects are properly aligned in memory)
2627

2728
## Setup
2829

include/eigenpy/std-map.hpp

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ struct overload_base_get_item_for_std_map
7070
// Rohan Budhiraja.
7171
///////////////////////////////////////////////////////////////////////////////
7272

73-
namespace python {
74-
7573
namespace bp = boost::python;
7674

7775
/**
@@ -144,7 +142,7 @@ struct dict_to_map {
144142
bp::throw_error_already_set();
145143
}
146144
typename Container::mapped_type val = valproxy();
147-
map[key] = val;
145+
map.emplace(key, val);
148146
}
149147

150148
// remember the location for later
@@ -161,40 +159,91 @@ struct dict_to_map {
161159
}
162160
};
163161

162+
/// Policies which handle the non-default constructible case
163+
/// and set_item() using emplace().
164+
template <class Container, bool NoProxy>
165+
struct emplace_set_derived_policies
166+
: bp::map_indexing_suite<
167+
Container, NoProxy,
168+
emplace_set_derived_policies<Container, NoProxy> > {
169+
typedef typename Container::key_type index_type;
170+
typedef typename Container::value_type::second_type data_type;
171+
typedef typename Container::value_type value_type;
172+
using DerivedPolicies =
173+
bp::detail::final_map_derived_policies<Container, NoProxy>;
174+
175+
template <class Class>
176+
static void extension_def(Class& cl) {
177+
// Wrap the map's element (value_type)
178+
std::string elem_name = "map_indexing_suite_";
179+
bp::object class_name(cl.attr("__name__"));
180+
bp::extract<std::string> class_name_extractor(class_name);
181+
elem_name += class_name_extractor();
182+
elem_name += "_entry";
183+
namespace mpl = boost::mpl;
184+
185+
typedef typename mpl::if_<
186+
mpl::and_<boost::is_class<data_type>, mpl::bool_<!NoProxy> >,
187+
bp::return_internal_reference<>, bp::default_call_policies>::type
188+
get_data_return_policy;
189+
190+
bp::class_<value_type>(elem_name.c_str(), bp::no_init)
191+
.def("__repr__", &DerivedPolicies::print_elem)
192+
.def("data", &DerivedPolicies::get_data, get_data_return_policy())
193+
.def("key", &DerivedPolicies::get_key);
194+
}
195+
196+
static void set_item(Container& container, index_type i, data_type const& v) {
197+
container.emplace(i, v);
198+
}
199+
};
200+
164201
/**
165-
* @brief Expose an std::map from a type given as template argument.
202+
* @brief Expose the map-like container, e.g. (std::map).
166203
*
167-
* @param[in] T Type to expose as std::map<T>.
168-
* @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>.
169-
* @param[in] Allocator Type for the Allocator in
170-
* std::map<T,Compare,Allocator>.
204+
* @param[in] Container Container to expose.
171205
* @param[in] NoProxy When set to false, the elements will be copied when
172206
* returned to Python.
173207
*/
174-
template <class Key, class T, class Compare = std::less<Key>,
175-
class Allocator = std::allocator<std::pair<const Key, T> >,
176-
bool NoProxy = false>
177-
struct StdMapPythonVisitor
178-
: public bp::map_indexing_suite<
179-
typename std::map<Key, T, Compare, Allocator>, NoProxy>,
180-
public dict_to_map<std::map<Key, T, Compare, Allocator> > {
181-
typedef std::map<Key, T, Compare, Allocator> Container;
208+
template <class Container, bool NoProxy = false>
209+
struct GenericMapVisitor
210+
: public emplace_set_derived_policies<Container, NoProxy>,
211+
public dict_to_map<Container> {
182212
typedef dict_to_map<Container> FromPythonDictConverter;
183213

184214
static void expose(const std::string& class_name,
185215
const std::string& doc_string = "") {
186216
namespace bp = bp;
187217

188218
bp::class_<Container>(class_name.c_str(), doc_string.c_str())
189-
.def(StdMapPythonVisitor())
219+
.def(GenericMapVisitor())
190220
.def("todict", &FromPythonDictConverter::todict, bp::arg("self"),
191-
"Returns the std::map as a Python dictionary.")
221+
"Returns the map type as a Python dictionary.")
192222
.def_pickle(PickleMap<Container>());
193223
// Register conversion
194224
FromPythonDictConverter::register_converter();
195225
}
196226
};
197227

228+
/**
229+
* @brief Expose an std::map from a type given as template argument.
230+
*
231+
* @param[in] T Type to expose as std::map<T>.
232+
* @param[in] Compare Type for the Compare in std::map<T,Compare,Allocator>.
233+
* @param[in] Allocator Type for the Allocator in
234+
* std::map<T,Compare,Allocator>.
235+
* @param[in] NoProxy When set to false, the elements will be copied when
236+
* returned to Python.
237+
*/
238+
template <class Key, class T, class Compare = std::less<Key>,
239+
class Allocator = std::allocator<std::pair<const Key, T> >,
240+
bool NoProxy = false>
241+
struct StdMapPythonVisitor
242+
: GenericMapVisitor<std::map<Key, T, Compare, Allocator>, NoProxy> {};
243+
244+
namespace python {
245+
// fix previous mistake
246+
using ::eigenpy::StdMapPythonVisitor;
198247
} // namespace python
199248
} // namespace eigenpy
200249

unittest/python/test_std_map.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
from std_map import copy, std_map_to_dict
1+
from std_map import copy, copy_boost, std_map_to_dict
22

33
t = {"one": 1.0, "two": 2.0}
4+
t2 = {"one": 1, "two": 2, "three": 3}
45

56
assert std_map_to_dict(t) == t
67
assert std_map_to_dict(copy(t)) == t
8+
m = copy_boost(t2)
9+
assert m.todict() == t2

unittest/std_map.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include <eigenpy/eigenpy.hpp>
55
#include <eigenpy/std-map.hpp>
6-
#include <iostream>
6+
#include <boost/unordered_map.hpp>
77

88
namespace bp = boost::python;
99

@@ -22,14 +22,32 @@ std::map<std::string, T1> copy(const std::map<std::string, T1>& map) {
2222
return out;
2323
}
2424

25+
template <typename T1>
26+
boost::unordered_map<std::string, T1> copy_boost(
27+
const boost::unordered_map<std::string, T1>& obj) {
28+
return obj;
29+
}
30+
31+
struct X {
32+
X() = delete;
33+
X(int x) : val(x) {}
34+
int val;
35+
};
36+
2537
BOOST_PYTHON_MODULE(std_map) {
2638
eigenpy::enableEigenPy();
2739

28-
eigenpy::python::StdMapPythonVisitor<
40+
eigenpy::StdMapPythonVisitor<
2941
std::string, double, std::less<std::string>,
3042
std::allocator<std::pair<const std::string, double> >,
3143
true>::expose("StdMap_Double");
3244

45+
eigenpy::GenericMapVisitor<boost::unordered_map<std::string, int> >::expose(
46+
"boost_map_int");
47+
48+
eigenpy::GenericMapVisitor<std::map<std::string, X> >::expose("StdMap_X");
49+
3350
bp::def("std_map_to_dict", std_map_to_dict<double>);
3451
bp::def("copy", copy<double>);
52+
bp::def("copy_boost", copy_boost<int>);
3553
}

0 commit comments

Comments
 (0)