Adding bind_map
diff --git a/docs/advanced.rst b/docs/advanced.rst
index 3018600..979e0bf 100644
--- a/docs/advanced.rst
+++ b/docs/advanced.rst
@@ -551,6 +551,18 @@
enabled. The types ``std::pair<>`` and ``std::tuple<>`` are already supported
out of the box with just the core :file:`pybind11/pybind11.h` header.
+Alternatively it might be desirable to bind STL containers as native C++ classes,
+eliminating the need of converting back and forth between C++ representation
+and Python one. The downside of this approach in this case users will have to
+deal with C++ containers directly instead of using already familiar Python lists
+or dicts.
+
+Pybind11 provide set of binder functions to bind various STL containers like vectors,
+maps etc. All binder functions are designed to return instances of pybind11::class_
+objects so developers can bind extra functions if needed. For complete set of
+available functions please see :file:`pybind11/stl_bind.h`. For an example on using
+this feature, please see :file:`tests/test_stl_binders.cpp`.
+
.. note::
Arbitrary nesting of any of these types is supported.
diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h
index 9434510..2612b51 100644
--- a/include/pybind11/stl_bind.h
+++ b/include/pybind11/stl_bind.h
@@ -12,6 +12,7 @@
#include "common.h"
#include "operators.h"
+#include <map>
#include <type_traits>
#include <utility>
#include <algorithm>
@@ -130,10 +131,12 @@
NAMESPACE_END(detail)
-
-template <typename T, typename Allocator = std::allocator<T>, typename holder_type = std::unique_ptr<std::vector<T, Allocator>>, typename... Args>
-pybind11::class_<std::vector<T, Allocator>, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
- using Vector = std::vector<T, Allocator>;
+//
+// std::vector
+//
+template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
+pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
+ using T = typename Vector::value_type;
using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator;
@@ -350,4 +353,118 @@
return cl;
}
+
+
+//
+// std::map
+//
+
+NAMESPACE_BEGIN(detail)
+
+/* Fallback functions */
+template <typename, typename, typename... Args> void map_if_insertion_operator(const Args&...) { }
+
+template <typename Map, typename Class_, typename... Args> void map_if_copy_assignable(Class_ &cl, const Args&...) {
+ using KeyType = typename Map::key_type;
+ using MappedType = typename Map::mapped_type;
+
+ cl.def("__setitem__",
+ [](Map &m, const KeyType &k, const MappedType &v) {
+ auto it = m.find(k);
+ if (it != m.end()) it->second = v;
+ else m.emplace(k, v);
+ });
+
+}
+
+template<typename Map, typename Class_, typename std::enable_if<!std::is_copy_assignable<typename Map::mapped_type>::value, int>::type = 0>
+void map_if_copy_assignable(Class_ &cl) {
+ using KeyType = typename Map::key_type;
+ using MappedType = typename Map::mapped_type;
+
+ cl.def("__setitem__",
+ [](Map &m, const KeyType &k, const MappedType &v) {
+ auto r = m.insert( std::make_pair(k, v) ); // We can't use m[k] = v; because value type might not be default constructable
+ if (!r.second) { // value type might be const so the only way to insert it is to errase it first...
+ m.erase(r.first);
+ m.insert( std::make_pair(k, v) );
+ }
+ });
+}
+
+
+template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &cl, std::string const &name)
+-> decltype(std::declval<std::ostream&>() << std::declval<typename Map::key_type>() << std::declval<typename Map::mapped_type>(), void()) {
+
+ cl.def("__repr__",
+ [name](Map &m) {
+ std::ostringstream s;
+ s << name << '{';
+ bool f = false;
+ for (auto const & kv : m) {
+ if (f) s << ", ";
+ s << kv.first << ": " << kv.second;
+ f = true;
+ }
+ s << '}';
+ return s.str();
+ },
+ "Return the canonical string representation of this map."
+ );
+}
+NAMESPACE_END(detail)
+
+template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
+pybind11::class_<Map, holder_type> bind_map(module &m, const std::string &name, Args&&... args) {
+ using KeyType = typename Map::key_type;
+ using MappedType = typename Map::mapped_type;
+ using Class_ = pybind11::class_<Map, holder_type>;
+
+ Class_ cl(m, name.c_str(), std::forward<Args>(args)...);
+
+ cl.def(pybind11::init<>());
+
+ // Register stream insertion operator (if possible)
+ detail::map_if_insertion_operator<Map, Class_>(cl, name);
+
+ cl.def("__bool__",
+ [](const Map &m) -> bool {
+ return !m.empty();
+ },
+ "Check whether the map is nonempty"
+ );
+
+ cl.def("__iter__",
+ [](Map &m) {
+ return pybind11::make_key_iterator(m.begin(), m.end());
+ },
+ pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
+ );
+
+ cl.def("items",
+ [](Map &m) { return pybind11::make_iterator(m.begin(), m.end()); },
+ pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
+ );
+
+ cl.def("__getitem__",
+ [](Map &m, const KeyType &k) -> MappedType {
+ auto it = m.find(k);
+ if (it != m.end()) return it->second;
+ else throw pybind11::key_error(); // it is not always possible to convert key to string // pybind11::key_error(k)
+ });
+
+ detail::map_if_copy_assignable<Map, Class_>(cl);
+
+ cl.def("__delitem__",
+ [](Map &m, const KeyType &k) {
+ auto it = m.find(k);
+ if (it != m.end()) return m.erase(it);
+ else throw pybind11::key_error(); // it is not always possible to convert key to string // pybind11::key_error(k)
+ });
+
+ cl.def("__len__", &Map::size);
+
+ return cl;
+}
+
NAMESPACE_END(pybind11)
diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp
index a6ed6bf..dabcaf0 100644
--- a/tests/test_stl_binders.cpp
+++ b/tests/test_stl_binders.cpp
@@ -28,10 +28,18 @@
py::class_<El>(m, "El")
.def(py::init<int>());
- py::bind_vector<unsigned int>(m, "VectorInt");
- py::bind_vector<bool>(m, "VectorBool");
+ py::bind_vector< std::vector<unsigned int> >(m, "VectorInt");
+ py::bind_vector< std::vector<bool> >(m, "VectorBool");
- py::bind_vector<El>(m, "VectorEl");
+ py::bind_vector< std::vector<El> >(m, "VectorEl");
- py::bind_vector<std::vector<El>>(m, "VectorVectorEl");
+ py::bind_vector< std::vector< std::vector<El> > >(m, "VectorVectorEl");
+});
+
+test_initializer stl_binder_map([](py::module &m) {
+ py::bind_map< std::map<std::string, double> >(m, "MapStringDouble");
+ py::bind_map< std::unordered_map<std::string, double> >(m, "UnorderedMapStringDouble");
+
+ py::bind_map< std::map<std::string, double const> >(m, "MapStringDoubleConst");
+ py::bind_map< std::unordered_map<std::string, double const> >(m, "UnorderedMapStringDoubleConst");
});
diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py
index 2aaaf3a..3026357 100644
--- a/tests/test_stl_binders.py
+++ b/tests/test_stl_binders.py
@@ -1,5 +1,3 @@
-
-
def test_vector_int():
from pybind11_tests import VectorInt
@@ -51,3 +49,51 @@
for i in range(10):
assert vv_c[i] == (i % 2 == 0)
assert str(vv_c) == "VectorBool[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]"
+
+
+def test_map_string_double():
+ from pybind11_tests import MapStringDouble, UnorderedMapStringDouble
+
+ m = MapStringDouble()
+ m['a'] = 1
+ m['b'] = 2.5
+
+ keys = []
+ for k in m: keys.append(k)
+ assert keys == ['a', 'b']
+
+ key_values = []
+ for k, v in m.items(): key_values.append( (k, v) )
+ assert key_values == [('a', 1), ('b', 2.5) ]
+
+ assert str(m) == "MapStringDouble{a: 1, b: 2.5}"
+
+
+ um = UnorderedMapStringDouble()
+ um['ua'] = 1.1
+ um['ub'] = 2.6
+
+ keys = []
+ for k in um: keys.append(k)
+ assert sorted(keys) == ['ua', 'ub']
+
+ key_values = []
+ for k, v in um.items(): key_values.append( (k, v) )
+ assert sorted(key_values) == [('ua', 1.1), ('ub', 2.6) ]
+
+ str(um)
+
+
+def test_map_string_double_const():
+ from pybind11_tests import MapStringDoubleConst, UnorderedMapStringDoubleConst
+
+ mc = MapStringDoubleConst()
+ mc['a'] = 10
+ mc['b'] = 20.5
+ assert str(mc) == "MapStringDoubleConst{a: 10, b: 20.5}"
+
+ umc = UnorderedMapStringDoubleConst()
+ umc['a'] = 11
+ umc['b'] = 21.5
+
+ str(umc)