diff --git a/CMakeLists.txt b/CMakeLists.txt index 38297dbb..3f10bd37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ set(PYBIND11_HEADERS include/pybind11/pybind11.h include/pybind11/pytypes.h include/pybind11/stl.h + include/pybind11/stl_bind.h include/pybind11/typeid.h ) @@ -128,6 +129,7 @@ set(PYBIND11_EXAMPLES example/example14.cpp example/example15.cpp example/example16.cpp + example/example17.cpp example/issues.cpp ) diff --git a/README.md b/README.md index 93e30dc6..376f5629 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Jonas Adler, Sylvain Corlay, Axel Huebl, @hulucc, +Sergey Lyskov Johan Mabille, Tomasz Miąsko, and Ben Pritchard. diff --git a/docs/advanced.rst b/docs/advanced.rst index e83ba273..837450ec 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -779,6 +779,10 @@ exceptions: | | accesses in ``__getitem__``, | | | ``__setitem__``, etc.) | +--------------------------------------+------------------------------+ +| :class:`pybind11::value_error` | ``ValueError`` (used to | +| | indicate wrong value passed | +| | in ``container.remove(...)`` | ++--------------------------------------+------------------------------+ | :class:`pybind11::error_already_set` | Indicates that the Python | | | exception flag has already | | | been initialized | @@ -1531,4 +1535,3 @@ work, it is important that all lines are indented consistently, i.e.: .. [#f4] http://www.sphinx-doc.org .. [#f5] http://github.com/pybind/pbtest - diff --git a/example/example.cpp b/example/example.cpp index b4199e8a..470684a3 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -25,6 +25,7 @@ void init_ex13(py::module &); void init_ex14(py::module &); void init_ex15(py::module &); void init_ex16(py::module &); +void init_ex17(py::module &); void init_issues(py::module &); #if defined(PYBIND11_TEST_EIGEN) @@ -50,6 +51,7 @@ PYBIND11_PLUGIN(example) { init_ex14(m); init_ex15(m); init_ex16(m); + init_ex17(m); init_issues(m); #if defined(PYBIND11_TEST_EIGEN) diff --git a/example/example17.cpp b/example/example17.cpp new file mode 100644 index 00000000..8ae4cad0 --- /dev/null +++ b/example/example17.cpp @@ -0,0 +1,36 @@ +/* + example/example17.cpp -- Usage of stl_binders functions + + Copyright (c) 2016 Sergey Lyskov + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "example.h" + +#include + +class El { +public: + El() = delete; + El(int v) : a(v) { } + + int a; +}; + +std::ostream & operator<<(std::ostream &s, El const&v) { + s << "El{" << v.a << '}'; + return s; +} + +void init_ex17(py::module &m) { + pybind11::class_(m, "El") + .def(pybind11::init()); + + pybind11::bind_vector(m, "VectorInt"); + + pybind11::bind_vector(m, "VectorEl"); + + pybind11::bind_vector>(m, "VectorVectorEl"); +} diff --git a/example/example17.py b/example/example17.py new file mode 100644 index 00000000..65e586bc --- /dev/null +++ b/example/example17.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +from __future__ import print_function + +from example import VectorInt, El, VectorEl, VectorVectorEl + +v_int = VectorInt([0, 0]) +print(len(v_int)) + +print(bool(v_int)) + +v_int2 = VectorInt([0, 0]) +print(v_int == v_int2) + +v_int2[1] = 1 +print(v_int != v_int2) + +v_int2.append(2) +v_int2.append(3) +v_int2.insert(0, 1) +v_int2.insert(0, 2) +v_int2.insert(0, 3) +print(v_int2) + +v_int.append(99) +v_int2[2:-2] = v_int +print(v_int2) +del v_int2[1:3] +print(v_int2) +del v_int2[0] +print(v_int2) + +v_a = VectorEl() +v_a.append(El(1)) +v_a.append(El(2)) +print(v_a) + +vv_a = VectorVectorEl() +vv_a.append(v_a) +vv_b = vv_a[0] +print(vv_b) diff --git a/example/example17.ref b/example/example17.ref new file mode 100644 index 00000000..55e47a68 --- /dev/null +++ b/example/example17.ref @@ -0,0 +1,10 @@ +2 +True +True +True +VectorInt[3, 2, 1, 0, 1, 2, 3] +VectorInt[3, 2, 0, 0, 99, 2, 3] +VectorInt[3, 0, 99, 2, 3] +VectorInt[0, 99, 2, 3] +VectorEl[El{1}, El{2}] +VectorEl[El{1}, El{2}] diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 38f8bc8c..e60684fc 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -305,6 +305,7 @@ NAMESPACE_END(detail) class error_already_set : public std::runtime_error { public: error_already_set() : std::runtime_error(detail::error_string()) {} }; PYBIND11_RUNTIME_EXCEPTION(stop_iteration) PYBIND11_RUNTIME_EXCEPTION(index_error) +PYBIND11_RUNTIME_EXCEPTION(value_error) PYBIND11_RUNTIME_EXCEPTION(cast_error) /// Thrown when pybind11::cast or handle::call fail due to a type casting error [[noreturn]] PYBIND11_NOINLINE inline void pybind11_fail(const char *reason) { throw std::runtime_error(reason); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 38bc5fc4..abbf479c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -396,6 +396,7 @@ protected: } } catch (const error_already_set &) { return nullptr; } catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); return nullptr; + } catch (const value_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr; } catch (const stop_iteration &e) { PyErr_SetString(PyExc_StopIteration, e.what()); return nullptr; } catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return nullptr; } catch (const std::domain_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return nullptr; diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h new file mode 100644 index 00000000..2cf303ad --- /dev/null +++ b/include/pybind11/stl_bind.h @@ -0,0 +1,349 @@ +/* + pybind11/std_bind.h: Binding generators for STL data types + + Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "common.h" +#include "operators.h" + +#include +#include +#include +#include + +NAMESPACE_BEGIN(pybind11) +NAMESPACE_BEGIN(detail) + +/* SFINAE helper class used by 'is_comparable */ +template struct container_traits { + template static std::true_type test_comparable(decltype(std::declval() == std::declval())*); + template static std::false_type test_comparable(...); + template static std::true_type test_value(typename T2::value_type *); + template static std::false_type test_value(...); + template static std::true_type test_pair(typename T2::first_type *, typename T2::second_type *); + template static std::false_type test_pair(...); + + static constexpr const bool is_comparable = std::is_same(nullptr))>::value; + static constexpr const bool is_pair = std::is_same(nullptr, nullptr))>::value; + static constexpr const bool is_vector = std::is_same(nullptr))>::value; + static constexpr const bool is_element = !is_pair && !is_vector; +}; + +/* Default: is_comparable -> std::false_type */ +template +struct is_comparable : std::false_type { }; + +/* For non-map data structures, check whether operator== can be instantiated */ +template +struct is_comparable< + T, typename std::enable_if::is_element && + container_traits::is_comparable>::type> + : std::true_type { }; + +/* For a vector/map data structure, recursively check the value type (which is std::pair for maps) */ +template +struct is_comparable::is_vector>::type> { + static constexpr const bool value = + is_comparable::value; +}; + +/* For pairs, recursively check the two data types */ +template +struct is_comparable::is_pair>::type> { + static constexpr const bool value = + is_comparable::value && + is_comparable::value; +}; + +/* Fallback functions */ +template void vector_if_copy_constructible(const Args&...) { } +template void vector_if_equal_operator(const Args&...) { } +template void vector_if_insertion_operator(const Args&...) { } + +template::value, int>::type = 0> +void vector_if_copy_constructible(Class_ &cl) { + cl.def(pybind11::init(), + "Copy constructor"); +} + +template::value, int>::type = 0> +void vector_if_equal_operator(Class_ &cl) { + using T = typename Vector::value_type; + + cl.def(self == self); + cl.def(self != self); + + cl.def("count", + [](const Vector &v, const T &x) { + return std::count(v.begin(), v.end(), x); + }, + arg("x"), + "Return the number of times ``x`` appears in the list" + ); + + cl.def("remove", [](Vector &v, const T &x) { + auto p = std::find(v.begin(), v.end(), x); + if (p != v.end()) + v.erase(p); + else + throw pybind11::value_error(); + }, + arg("x"), + "Remove the first item from the list whose value is x. " + "It is an error if there is no such item." + ); + + cl.def("__contains__", + [](const Vector &v, const T &x) { + return std::find(v.begin(), v.end(), x) != v.end(); + }, + arg("x"), + "Return true the container contains ``x``" + ); +} + +template auto vector_if_insertion_operator(Class_ &cl, const char *name) + -> decltype(std::declval() << std::declval(), void()) { + using size_type = typename Vector::size_type; + + cl.def("__repr__", + [name](Vector &v) { + std::ostringstream s; + s << name << '['; + for (size_type i=0; i < v.size(); ++i) { + s << v[i]; + if (i != v.size() - 1) + s << ", "; + } + s << ']'; + return s.str(); + }, + "Return the canonical string representation of this list." + ); +} + +NAMESPACE_END(detail) + + +template , typename holder_type = std::unique_ptr>, typename... Args> +pybind11::class_, holder_type> bind_vector(pybind11::module &m, const char *name, Args&&... args) { + using Vector = std::vector; + using SizeType = typename Vector::size_type; + using Class_ = pybind11::class_; + + Class_ cl(m, name, std::forward(args)...); + + cl.def(pybind11::init<>()); + + // Register copy constructor (if possible) + detail::vector_if_copy_constructible(cl); + + // Register comparison-related operators and functions (if possible) + detail::vector_if_equal_operator(cl); + + // Register stream insertion operator (if possible) + detail::vector_if_insertion_operator(cl, name); + + cl.def("__init__", [](Vector &v, iterable it) { + new (&v) Vector(); + try { + v.reserve(len(it)); + for (handle h : it) + v.push_back(h.cast()); + } catch (...) { + v.~Vector(); + throw; + } + }); + cl.def("append", (void (Vector::*) (const T &)) & Vector::push_back, + arg("x"), + "Add an item to the end of the list"); + + cl.def("extend", + [](Vector &v, Vector &src) { + v.reserve(v.size() + src.size()); + v.insert(v.end(), src.begin(), src.end()); + }, + arg("L"), + "Extend the list by appending all the items in the given list" + ); + + cl.def("insert", + [](Vector &v, SizeType i, const T &x) { + v.insert(v.begin() + i, x); + }, + arg("i") , arg("x"), + "Insert an item at a given position." + ); + + cl.def("pop", + [](Vector &v) { + if (v.empty()) + throw pybind11::index_error(); + T t = v.back(); + v.pop_back(); + return t; + }, + "Remove and return the last item" + ); + + cl.def("pop", + [](Vector &v, SizeType i) { + if (i >= v.size()) + throw pybind11::index_error(); + T t = v[i]; + v.erase(v.begin() + i); + return t; + }, + arg("i"), + "Remove and return the item at index ``i``" + ); + + cl.def("__bool__", + [](const Vector &v) -> bool { + return !v.empty(); + }, + "Check whether the list is nonempty" + ); + + cl.def("__getitem__", + [](const Vector &v, SizeType i) { + if (i >= v.size()) + throw pybind11::index_error(); + return v[i]; + } + ); + + cl.def("__setitem__", + [](Vector &v, SizeType i, const T &t) { + if (i >= v.size()) + throw pybind11::index_error(); + v[i] = t; + } + ); + + cl.def("__delitem__", + [](Vector &v, SizeType i) { + if (i >= v.size()) + throw pybind11::index_error(); + v.erase(v.begin() + i); + }, + "Delete list elements using a slice object" + ); + + cl.def("__len__", &Vector::size); + + cl.def("__iter__", + [](Vector &v) { + return pybind11::make_iterator(v.begin(), v.end()); + }, + pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */ + ); + + /// Slicing protocol + cl.def("__getitem__", + [](const Vector &v, slice slice) -> Vector * { + ssize_t start, stop, step, slicelength; + + if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) + throw pybind11::error_already_set(); + + Vector *seq = new Vector(); + seq->reserve((size_t) slicelength); + + for (int i=0; ipush_back(v[start]); + start += step; + } + return seq; + }, + arg("s"), + "Retrieve list elements using a slice object" + ); + + cl.def("__setitem__", + [](Vector &v, slice slice, const Vector &value) { + ssize_t start, stop, step, slicelength; + if (!slice.compute(v.size(), &start, &stop, &step, &slicelength)) + throw pybind11::error_already_set(); + + if ((size_t) slicelength != value.size()) + throw std::runtime_error("Left and right hand size of slice assignment have different sizes!"); + + for (int i=0; i()); + + cl.def("resize", + (void (Vector::*) (size_type count)) & Vector::resize, + "changes the number of elements stored"); + + cl.def("erase", + [](Vector &v, SizeType i) { + if (i >= v.size()) + throw pybind11::index_error(); + v.erase(v.begin() + i); + }, "erases element at index ``i``"); + + cl.def("empty", &Vector::empty, "checks whether the container is empty"); + cl.def("size", &Vector::size, "returns the number of elements"); + cl.def("push_back", (void (Vector::*)(const T&)) &Vector::push_back, "adds an element to the end"); + cl.def("pop_back", &Vector::pop_back, "removes the last element"); + + cl.def("max_size", &Vector::max_size, "returns the maximum possible number of elements"); + cl.def("reserve", &Vector::reserve, "reserves storage"); + cl.def("capacity", &Vector::capacity, "returns the number of elements that can be held in currently allocated storage"); + cl.def("shrink_to_fit", &Vector::shrink_to_fit, "reduces memory usage by freeing unused memory"); + + cl.def("clear", &Vector::clear, "clears the contents"); + cl.def("swap", &Vector::swap, "swaps the contents"); + + cl.def("front", [](Vector &v) { + if (v.size()) return v.front(); + else throw pybind11::index_error(); + }, "access the first element"); + + cl.def("back", [](Vector &v) { + if (v.size()) return v.back(); + else throw pybind11::index_error(); + }, "access the last element "); + +#endif + + return cl; +} + +NAMESPACE_END(pybind11) diff --git a/setup.py b/setup.py index 4c6e156a..07465bb6 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ setup( 'include/pybind11/numpy.h', 'include/pybind11/pybind11.h', 'include/pybind11/stl.h', + 'include/pybind11/stl_bind.h', 'include/pybind11/common.h', 'include/pybind11/functional.h', 'include/pybind11/operators.h',