From 44a69f78cfddbe99f47a1d2061f477c9b2d7ba23 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Thu, 3 Nov 2016 12:42:46 +0000 Subject: [PATCH] std::experimental::optional (#475) * Add type caster for std::experimental::optional * Add tests for std::experimental::optional * Support both / * Mention std{::experimental,}::optional in the docs --- docs/advanced/cast/overview.rst | 130 ++++++++++++++++---------------- docs/changelog.rst | 1 + include/pybind11/stl.h | 56 ++++++++++++++ tests/test_python_types.cpp | 14 ++++ tests/test_python_types.py | 15 +++- 5 files changed, 152 insertions(+), 64 deletions(-) diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst index 4df1bd103..e9f43be3b 100644 --- a/docs/advanced/cast/overview.rst +++ b/docs/advanced/cast/overview.rst @@ -75,66 +75,70 @@ The following basic data types are supported out of the box (some may require an additional extension header to be included). To pass other data structures as arguments and return values, refer to the section on binding :ref:`classes`. -+---------------------------------+--------------------------+-------------------------------+ -| Data type | Description | Header file | -+=================================+==========================+===============================+ -| ``int8_t``, ``uint8_t`` | 8-bit integers | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``int16_t``, ``uint16_t`` | 16-bit integers | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``int32_t``, ``uint32_t`` | 32-bit integers | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``int64_t``, ``uint64_t`` | 64-bit integers | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``ssize_t``, ``size_t`` | Platform-dependent size | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``float``, ``double`` | Floating point types | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``bool`` | Two-state Boolean type | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``char`` | Character literal | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``wchar_t`` | Wide character literal | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``const char *`` | UTF-8 string literal | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``const wchar_t *`` | Wide string literal | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::string`` | STL dynamic UTF-8 string | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::pair`` | Pair of two custom types | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::tuple<...>`` | Arbitrary tuple of types | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::reference_wrapper<...>`` | Reference type wrapper | :file:`pybind11/pybind11.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::complex`` | Complex numbers | :file:`pybind11/complex.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::array`` | STL static array | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::vector`` | STL dynamic array | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::list`` | STL linked list | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::map`` | STL ordered map | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::unordered_map`` | STL unordered map | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::set`` | STL ordered set | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::unordered_set`` | STL unordered set | :file:`pybind11/stl.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``std::chrono::time_point<...>``| STL date/time | :file:`pybind11/chrono.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``Eigen::Matrix<...>`` | Eigen: dense matrix | :file:`pybind11/eigen.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``Eigen::Map<...>`` | Eigen: mapped memory | :file:`pybind11/eigen.h` | -+---------------------------------+--------------------------+-------------------------------+ -| ``Eigen::SparseMatrix<...>`` | Eigen: sparse matrix | :file:`pybind11/eigen.h` | -+---------------------------------+--------------------------+-------------------------------+ ++------------------------------------+---------------------------+-------------------------------+ +| Data type | Description | Header file | ++=---================================+===========================+===============================+ +| ``int8_t``, ``uint8_t`` | 8-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``int16_t``, ``uint16_t`` | 16-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``int32_t``, ``uint32_t`` | 32-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``int64_t``, ``uint64_t`` | 64-bit integers | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``ssize_t``, ``size_t`` | Platform-dependent size | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``float``, ``double`` | Floating point types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``bool`` | Two-state Boolean type | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``char`` | Character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``wchar_t`` | Wide character literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``const char *`` | UTF-8 string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``const wchar_t *`` | Wide string literal | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::string`` | STL dynamic UTF-8 string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::pair`` | Pair of two custom types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::tuple<...>`` | Arbitrary tuple of types | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::reference_wrapper<...>`` | Reference type wrapper | :file:`pybind11/pybind11.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::complex`` | Complex numbers | :file:`pybind11/complex.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::array`` | STL static array | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::vector`` | STL dynamic array | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::list`` | STL linked list | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::map`` | STL ordered map | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::unordered_map`` | STL unordered map | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::set`` | STL ordered set | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::unordered_set`` | STL unordered set | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::optional`` | STL optional type (C++17) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::experimental::optional`` | STL optional type (exp.) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``std::chrono::time_point<...>`` | STL date/time | :file:`pybind11/chrono.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``Eigen::Matrix<...>`` | Eigen: dense matrix | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``Eigen::Map<...>`` | Eigen: mapped memory | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-------------------------------+ +| ``Eigen::SparseMatrix<...>`` | Eigen: sparse matrix | :file:`pybind11/eigen.h` | ++------------------------------------+---------------------------+-------------------------------+ diff --git a/docs/changelog.rst b/docs/changelog.rst index e837aa426..87d38d3d8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -59,6 +59,7 @@ Breaking changes queued for v2.0.0 (Not yet released) to do it manually via ``PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr)``. * Default return values policy changes: non-static properties now use ``reference_internal`` and static properties use ``reference`` (previous default was ``automatic``, i.e. ``copy``). +* Support for ``std::experimental::optional`` and ``std::optional`` (C++17). * Various minor improvements of library internals (no user-visible changes) 1.8.1 (July 12, 2016) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index e5c6e3c7e..b3fe15771 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -22,6 +22,21 @@ #pragma warning(disable: 4127) // warning C4127: Conditional expression is constant #endif +#ifdef __has_include +// std::optional +# if __has_include() +# include +# define PYBIND11_HAS_OPTIONAL 1 +# endif +// std::experimental::optional +# if __has_include() +# include +# if __cpp_lib_experimental_optional // just in case +# define PYBIND11_HAS_EXP_OPTIONAL 1 +# endif +# endif +#endif + NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) @@ -183,6 +198,47 @@ template struct template struct type_caster> : map_caster, Key, Value> { }; +// This type caster is intended to be used for std::optional and std::experimental::optional +template struct optional_caster { + using value_type = typename intrinsic_type::type; + using caster_type = type_caster; + + static handle cast(const T& src, return_value_policy policy, handle parent) { + if (!src) + return none(); + return caster_type::cast(*src, policy, parent); + } + + bool load(handle src, bool convert) { + if (!src) { + return false; + } else if (src.is_none()) { + value = {}; // nullopt + return true; + } else if (!inner.load(src, convert)) { + return false; + } else { + value.emplace(static_cast(inner)); + return true; + } + } + + PYBIND11_TYPE_CASTER(T, _("Optional[") + caster_type::name() + _("]")); + +private: + caster_type inner; +}; + +#if PYBIND11_HAS_OPTIONAL +template struct type_caster> + : public optional_caster> {}; +#endif + +#if PYBIND11_HAS_EXP_OPTIONAL +template struct type_caster> + : public optional_caster> {}; +#endif + NAMESPACE_END(detail) inline std::ostream &operator<<(std::ostream &os, const handle &obj) { diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 678a56d15..6a9e3cf5c 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -289,4 +289,18 @@ test_initializer python_types([](py::module &m) { return d; }); + + // this only tests std::experimental::optional for now + bool has_optional = false; +#ifdef PYBIND11_HAS_EXP_OPTIONAL + has_optional = true; + using opt_int = std::experimental::optional; + m.def("double_or_zero", [](const opt_int& x) -> int { + return x.value_or(0) * 2; + }); + m.def("half_or_none", [](int x) -> opt_int { + return x ? opt_int(x / 2) : opt_int(); + }); +#endif + m.attr("has_optional") = py::cast(has_optional); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 9bba8249c..3a8a09ead 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -1,6 +1,6 @@ import pytest -from pybind11_tests import ExamplePythonTypes, ConstructorStats +from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional def test_static(): @@ -295,3 +295,16 @@ def test_accessors(): assert d["set"] == 1 assert d["deferred_set"] == 1 assert d["var"] == 99 + + +@pytest.mark.skipif(not has_optional, reason='no ') +def test_optional(): + from pybind11_tests import double_or_zero, half_or_none + + assert double_or_zero(None) == 0 + assert double_or_zero(42) == 84 + pytest.raises(TypeError, double_or_zero, 'foo') + + assert half_or_none(0) is None + assert half_or_none(42) == 21 + pytest.raises(TypeError, half_or_none, 'foo')