mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-14 01:23:53 +00:00
Add type caster for std::variant and other variant-like classes
This commit is contained in:
parent
a01b6b805c
commit
4ffa76ec56
@ -144,6 +144,8 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
|
|||||||
+------------------------------------+---------------------------+-------------------------------+
|
+------------------------------------+---------------------------+-------------------------------+
|
||||||
| ``std::experimental::optional<T>`` | STL optional type (exp.) | :file:`pybind11/stl.h` |
|
| ``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::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` |
|
||||||
+------------------------------------+---------------------------+-------------------------------+
|
+------------------------------------+---------------------------+-------------------------------+
|
||||||
| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` |
|
| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` |
|
||||||
|
@ -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
|
The file :file:`tests/test_python_types.cpp` contains a complete
|
||||||
example that demonstrates how to pass STL data types in more detail.
|
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:
|
.. _opaque:
|
||||||
|
|
||||||
Making opaque types
|
Making opaque types
|
||||||
|
@ -36,6 +36,11 @@
|
|||||||
# define PYBIND11_HAS_EXP_OPTIONAL 1
|
# define PYBIND11_HAS_EXP_OPTIONAL 1
|
||||||
# endif
|
# endif
|
||||||
# endif
|
# endif
|
||||||
|
// std::variant
|
||||||
|
# if defined(PYBIND11_CPP17) && __has_include(<variant>)
|
||||||
|
# include <variant>
|
||||||
|
# define PYBIND11_HAS_VARIANT 1
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NAMESPACE_BEGIN(pybind11)
|
NAMESPACE_BEGIN(pybind11)
|
||||||
@ -262,6 +267,66 @@ template<> struct type_caster<std::experimental::nullopt_t>
|
|||||||
: public void_caster<std::experimental::nullopt_t> {};
|
: public void_caster<std::experimental::nullopt_t> {};
|
||||||
#endif
|
#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)
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
|
inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
|
||||||
|
@ -354,6 +354,23 @@ test_initializer python_types([](py::module &m) {
|
|||||||
m.attr("has_optional") = has_optional;
|
m.attr("has_optional") = has_optional;
|
||||||
m.attr("has_exp_optional") = has_exp_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", []() {
|
m.def("test_default_constructors", []() {
|
||||||
return py::dict(
|
return py::dict(
|
||||||
"str"_a=py::str(),
|
"str"_a=py::str(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# Python < 3 needs this: coding=utf-8
|
# Python < 3 needs this: coding=utf-8
|
||||||
import pytest
|
import pytest
|
||||||
|
import pybind11_tests
|
||||||
|
|
||||||
from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional
|
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
|
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():
|
def test_constructors():
|
||||||
"""C++ default and converting constructors are equivalent to type calls in Python"""
|
"""C++ default and converting constructors are equivalent to type calls in Python"""
|
||||||
from pybind11_tests import (test_default_constructors, test_converting_constructors,
|
from pybind11_tests import (test_default_constructors, test_converting_constructors,
|
||||||
|
Loading…
Reference in New Issue
Block a user