Add type caster for std::variant and other variant-like classes
diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst
index 54c11a9..49781dc 100644
--- a/docs/advanced/cast/overview.rst
+++ b/docs/advanced/cast/overview.rst
@@ -144,6 +144,8 @@
+------------------------------------+---------------------------+-------------------------------+
| ``std::experimental::optional<T>`` | 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 c76da5c..47c2a96 100644
--- a/docs/advanced/cast/stl.rst
+++ b/docs/advanced/cast/stl.rst
@@ -26,6 +26,51 @@
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 <typename T>
+ struct type_caster<boost::optional<T>> : optional_caster<boost::optional<T>> {};
+ }}
+
+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 <typename... Ts>
+ struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
+
+ // Specifies the function used to visit the variant -- `apply_visitor` instead of `visit`
+ template <>
+ struct visit_helper<boost::variant> {
+ template <typename... Args>
+ static auto call(Args &&...args)
+ -> decltype(boost::apply_visitor(std::forward<Args>(args)...)) {
+ return boost::apply_visitor(std::forward<Args>(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 4b557bd..329ff64 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(<variant>)
+# include <variant>
+# define PYBIND11_HAS_VARIANT 1
+# endif
#endif
NAMESPACE_BEGIN(pybind11)
@@ -262,6 +267,66 @@
: public void_caster<std::experimental::nullopt_t> {};
#endif
+/// Visit a variant and cast any found type to Python
+struct variant_caster_visitor {
+ return_value_policy policy;
+ handle parent;
+
+ template <typename T>
+ handle operator()(T &&src) const {
+ return make_caster<T>::cast(std::forward<T>(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 <template<typename...> class Variant>
+struct visit_helper {
+ template <typename... Args>
+ static auto call(Args &&...args) -> decltype(visit(std::forward<Args>(args)...)) {
+ return visit(std::forward<Args>(args)...);
+ }
+};
+
+/// Generic variant caster
+template <typename Variant> struct variant_caster;
+
+template <template<typename...> class V, typename... Ts>
+struct variant_caster<V<Ts...>> {
+ static_assert(sizeof...(Ts) > 0, "Variant must consist of at least one alternative.");
+
+ template <typename U, typename... Us>
+ bool load_alternative(handle src, bool convert, type_list<U, Us...>) {
+ auto caster = make_caster<U>();
+ if (caster.load(src, convert)) {
+ value = cast_op<U>(caster);
+ return true;
+ }
+ return load_alternative(src, convert, type_list<Us...>{});
+ }
+
+ bool load_alternative(handle, bool, type_list<>) { return false; }
+
+ bool load(handle src, bool convert) {
+ return load_alternative(src, convert, type_list<Ts...>{});
+ }
+
+ template <typename Variant>
+ static handle cast(Variant &&src, return_value_policy policy, handle parent) {
+ return visit_helper<V>::call(variant_caster_visitor{policy, parent},
+ std::forward<Variant>(src));
+ }
+
+ using Type = V<Ts...>;
+ PYBIND11_TYPE_CASTER(Type, _("Union[") + detail::concat(make_caster<Ts>::name()...) + _("]"));
+};
+
+#if PYBIND11_HAS_VARIANT
+template <typename... Ts>
+struct type_caster<std::variant<Ts...>> : variant_caster<std::variant<Ts...>> { };
+#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 18aa87a..776c4ce 100644
--- a/tests/test_python_types.cpp
+++ b/tests/test_python_types.cpp
@@ -354,6 +354,23 @@
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<int, std::string, double> v) {
+ return std::visit(visitor(), v);
+ });
+
+ m.def("cast_variant", []() {
+ using V = std::variant<int, std::string>;
+ 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 9849bc8..45cf3e8 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 @@
assert test_nullopt_exp(43) == 43
+@pytest.mark.skipif(not hasattr(pybind11_tests, "load_variant"), reason='no <variant>')
+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,