diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst index 54c11a90a..49781dc75 100644 --- a/docs/advanced/cast/overview.rst +++ b/docs/advanced/cast/overview.rst @@ -144,6 +144,8 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-------------------------------+ | ``std::experimental::optional`` | STL optional type (exp.) | :file:`pybind11/stl.h` | +------------------------------------+---------------------------+-------------------------------+ +| ``std::variant<...>`` | Type-safe union (C++17) | :file:`pybind11/stl.h` | ++------------------------------------+---------------------------+-------------------------------+ | ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | +------------------------------------+---------------------------+-------------------------------+ | ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` | diff --git a/docs/advanced/cast/stl.rst b/docs/advanced/cast/stl.rst index c76da5ca1..47c2a9670 100644 --- a/docs/advanced/cast/stl.rst +++ b/docs/advanced/cast/stl.rst @@ -26,6 +26,51 @@ next sections for more details and alternative approaches that avoid this. The file :file:`tests/test_python_types.cpp` contains a complete example that demonstrates how to pass STL data types in more detail. +C++17 library containers +======================== + +The :file:`pybind11/stl.h` header also includes support for ``std::optional<>`` +and ``std::variant<>``. These require a C++17 compiler and standard library. +In C++14 mode, ``std::experimental::optional<>`` is supported if available. + +Various versions of these containers also exist for C++11 (e.g. in Boost). +pybind11 provides an easy way to specialize the ``type_caster`` for such +types: + +.. code-block:: cpp + + // `boost::optional` as an example -- can be any `std::optional`-like container + namespace pybind11 { namespace detail { + template + struct type_caster> : optional_caster> {}; + }} + +The above should be placed in a header file and included in all translation units +where automatic conversion is needed. Similarly, a specialization can be provided +for custom variant types: + +.. code-block:: cpp + + // `boost::variant` as an example -- can be any `std::variant`-like container + namespace pybind11 { namespace detail { + template + struct type_caster> : variant_caster> {}; + + // Specifies the function used to visit the variant -- `apply_visitor` instead of `visit` + template <> + struct visit_helper { + template + static auto call(Args &&...args) + -> decltype(boost::apply_visitor(std::forward(args)...)) { + return boost::apply_visitor(std::forward(args)...); + } + }; + }} // namespace pybind11::detail + +The ``visit_helper`` specialization is not required if your ``name::variant`` provides +a ``name::visit()`` function. For any other function name, the specialization must be +included to tell pybind11 how to visit the variant. + .. _opaque: Making opaque types diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 4b557bd16..329ff645a 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -36,6 +36,11 @@ # define PYBIND11_HAS_EXP_OPTIONAL 1 # endif # endif +// std::variant +# if defined(PYBIND11_CPP17) && __has_include() +# include +# define PYBIND11_HAS_VARIANT 1 +# endif #endif NAMESPACE_BEGIN(pybind11) @@ -262,6 +267,66 @@ template<> struct type_caster : public void_caster {}; #endif +/// Visit a variant and cast any found type to Python +struct variant_caster_visitor { + return_value_policy policy; + handle parent; + + template + handle operator()(T &&src) const { + return make_caster::cast(std::forward(src), policy, parent); + } +}; + +/// Helper class which abstracts away variant's `visit` function. `std::variant` and similar +/// `namespace::variant` types which provide a `namespace::visit()` function are handled here +/// automatically using argument-dependent lookup. Users can provide specializations for other +/// variant-like classes, e.g. `boost::variant` and `boost::apply_visitor`. +template class Variant> +struct visit_helper { + template + static auto call(Args &&...args) -> decltype(visit(std::forward(args)...)) { + return visit(std::forward(args)...); + } +}; + +/// Generic variant caster +template struct variant_caster; + +template class V, typename... Ts> +struct variant_caster> { + static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative."); + + template + bool load_alternative(handle src, bool convert, type_list) { + auto caster = make_caster(); + if (caster.load(src, convert)) { + value = cast_op(caster); + return true; + } + return load_alternative(src, convert, type_list{}); + } + + bool load_alternative(handle, bool, type_list<>) { return false; } + + bool load(handle src, bool convert) { + return load_alternative(src, convert, type_list{}); + } + + template + static handle cast(Variant &&src, return_value_policy policy, handle parent) { + return visit_helper::call(variant_caster_visitor{policy, parent}, + std::forward(src)); + } + + using Type = V; + PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster::name()...) + _("]")); +}; + +#if PYBIND11_HAS_VARIANT +template +struct type_caster> : variant_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 18aa87a6f..776c4ce38 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -354,6 +354,23 @@ test_initializer python_types([](py::module &m) { m.attr("has_optional") = has_optional; m.attr("has_exp_optional") = has_exp_optional; +#ifdef PYBIND11_HAS_VARIANT + struct visitor { + const char *operator()(int) { return "int"; } + const char *operator()(std::string) { return "std::string"; } + const char *operator()(double) { return "double"; } + }; + + m.def("load_variant", [](std::variant v) { + return std::visit(visitor(), v); + }); + + m.def("cast_variant", []() { + using V = std::variant; + return py::make_tuple(V(5), V("Hello")); + }); +#endif + m.def("test_default_constructors", []() { return py::dict( "str"_a=py::str(), diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 9849bc849..45cf3e887 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -1,5 +1,6 @@ # Python < 3 needs this: coding=utf-8 import pytest +import pybind11_tests from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional @@ -370,6 +371,18 @@ def test_exp_optional(): assert test_nullopt_exp(43) == 43 +@pytest.mark.skipif(not hasattr(pybind11_tests, "load_variant"), reason='no ') +def test_variant(doc): + from pybind11_tests import load_variant, cast_variant + + assert load_variant(1) == "int" + assert load_variant("1") == "std::string" + assert load_variant(1.0) == "double" + assert cast_variant() == (5, "Hello") + + assert doc(load_variant) == "load_variant(arg0: Union[int, str, float]) -> str" + + def test_constructors(): """C++ default and converting constructors are equivalent to type calls in Python""" from pybind11_tests import (test_default_constructors, test_converting_constructors,