From 8ddc41acda4fc1ec5c364fef6b8ea98b1100b76a Mon Sep 17 00:00:00 2001 From: Joris Vaillant <joris.vaillant@inria.fr> Date: Mon, 29 Jan 2024 17:12:29 +0100 Subject: [PATCH] =?UTF-8?q?core:=C2=A0Add=20BoostVariantConvertor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/eigenpy/boost-variant.hpp | 136 ++++++++++++++++++++++++++ unittest/CMakeLists.txt | 5 + unittest/boost_variant.cpp | 41 ++++++++ unittest/python/test_boost_variant.py | 26 +++++ 4 files changed, 208 insertions(+) create mode 100644 include/eigenpy/boost-variant.hpp create mode 100644 unittest/boost_variant.cpp create mode 100644 unittest/python/test_boost_variant.py diff --git a/include/eigenpy/boost-variant.hpp b/include/eigenpy/boost-variant.hpp new file mode 100644 index 00000000..f3735a95 --- /dev/null +++ b/include/eigenpy/boost-variant.hpp @@ -0,0 +1,136 @@ +// +// Copyright (c) 2024 INRIA +// + +#ifndef __eigenpy_utils_boost_variant_hpp__ +#define __eigenpy_utils_boost_variant_hpp__ + +#include <boost/python.hpp> +#include <boost/variant.hpp> +#include <boost/mpl/for_each.hpp> + +namespace eigenpy { + +namespace details { + +/// Convert boost::variant<class...> alternative to a Python object. +/// This converter copy the alternative. +template <typename Variant> +struct BoostVariantValueToObject : boost::static_visitor<PyObject*> { + typedef Variant variant_type; + + static result_type convert(const variant_type& gm) { + return apply_visitor(BoostVariantValueToObject(), gm); + } + + template <typename T> + result_type operator()(T& t) const { + return boost::python::incref(boost::python::object(t).ptr()); + } +}; + +/// Convert boost::variant<class...> alternative reference to a Python object. +/// This converter return the alternative reference. +/// The code that create the reference holder is taken from +/// \see boost::python::to_python_indirect. +template <typename Variant> +struct BoostVariantRefToObject : boost::static_visitor<PyObject*> { + typedef Variant variant_type; + + static result_type convert(const variant_type& gm) { + return apply_visitor(BoostVariantRefToObject(), gm); + } + + template <typename T> + result_type operator()(T& t) const { + return boost::python::detail::make_reference_holder::execute(&t); + } +}; + +/// Converter used in \see ReturnInternalBoostVariant. +/// This is inspired by \see boost::python::reference_existing_object. +/// It will call \see BoostVariantRefToObject to extract the alternative +/// reference. +template <typename Variant> +struct BoostVariantConverter { + typedef Variant variant_type; + + template <class T> + struct apply { + struct type { + PyObject* operator()(const variant_type& gm) const { + return BoostVariantRefToObject<variant_type>::convert(gm); + } + +#ifndef BOOST_PYTHON_NO_PY_SIGNATURES + PyTypeObject const* get_pytype() const { + return boost::python::converter::registered_pytype< + variant_type>::get_pytype(); + } +#endif + }; + }; +}; + +/// Declare a variant alternative implicitly convertible to the variant +template <typename Variant> +struct BoostVariantImplicitlyConvertible { + typedef Variant variant_type; + + template <class T> + void operator()(T) { + boost::python::implicitly_convertible<T, variant_type>(); + } +}; + +} // namespace details + +/// Variant of \see boost::python::return_internal_reference that +/// extract boost::variant<class...> alternative reference before +/// converting it into a PyObject +template <typename Variant> +struct ReturnInternalBoostVariant : boost::python::return_internal_reference<> { + typedef Variant variant_type; + + typedef details::BoostVariantConverter<variant_type> result_converter; +}; + +/// Define a defaults converter to convert a boost::variant alternative to a +/// Python object by copy and to convert implicitly an alternative to a +/// boost::variant. +/// +/// Example: +/// +/// typedef boost::variant<Struct1, Struct2> MyVariant; +/// struct VariantHolder { +/// MyVariant variant; +/// }; +/// ... +/// void expose() { +/// boost::python::class_<Struct1>("Struct1", bp::init<>()); +/// boost::python::class_<Struct2>("Struct1", bp::init<>()) +/// typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor; +/// Convertor::registration(); +/// +/// boost::python::class_<VariantHolder>("VariantHolder", bp::init<>()) +/// .add_property("variant", +/// bp::make_getter(&VariantHolder::variant, +/// Convertor::return_internal_reference()), +/// bp::make_setter(&VariantHolder::variant)); +/// } +template <typename Variant> +struct BoostVariantConvertor { + typedef Variant variant_type; + typedef ReturnInternalBoostVariant<variant_type> return_internal_reference; + + static void registration() { + typedef details::BoostVariantValueToObject<variant_type> variant_to_value; + boost::python::to_python_converter<variant_type, variant_to_value>(); + boost::mpl::for_each<typename variant_type::types>( + details::BoostVariantImplicitlyConvertible<variant_type>()); + } +}; + +} // namespace eigenpy + +#endif // ifndef __eigenpy_utils_boost_variant_hpp__ diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index dab2bfba..c65c6cf1 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -45,6 +45,7 @@ add_lib_unit_test(std_vector) add_lib_unit_test(std_array) add_lib_unit_test(std_pair) add_lib_unit_test(user_struct) +add_lib_unit_test(boost_variant) function(config_bind_optional tagname opttype) set(MODNAME bind_optional_${tagname}) @@ -132,6 +133,10 @@ add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py" "python;unittest") set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP}) +add_python_unit_test("py-boost-variant" "unittest/python/test_boost_variant.py" + "python;unittest") +set_tests_properties("py-boost-variant" PROPERTIES DEPENDS ${PYWRAP}) + add_python_unit_test("py-bind-virtual" "unittest/python/test_bind_virtual.py" "python;unittest") set_tests_properties("py-bind-virtual" PROPERTIES DEPENDS ${PYWRAP}) diff --git a/unittest/boost_variant.cpp b/unittest/boost_variant.cpp new file mode 100644 index 00000000..096b69a4 --- /dev/null +++ b/unittest/boost_variant.cpp @@ -0,0 +1,41 @@ +/// @file +/// @copyright Copyright 2024 CNRS INRIA + +#include <eigenpy/eigenpy.hpp> +#include <eigenpy/boost-variant.hpp> + +namespace bp = boost::python; + +struct V1 { + int v; +}; +struct V2 { + char v; +}; +typedef boost::variant<V1, V2> MyVariant; + +MyVariant make_variant() { return V1(); } + +struct VariantHolder { + MyVariant variant; +}; + +BOOST_PYTHON_MODULE(boost_variant) { + using namespace eigenpy; + + enableEigenPy(); + + bp::class_<V1>("V1", bp::init<>()).def_readwrite("v", &V1::v); + bp::class_<V2>("V2", bp::init<>()).def_readwrite("v", &V2::v); + + typedef eigenpy::BoostVariantConvertor<MyVariant> Convertor; + Convertor::registration(); + + bp::def("make_variant", make_variant); + + boost::python::class_<VariantHolder>("VariantHolder", bp::init<>()) + .add_property("variant", + bp::make_getter(&VariantHolder::variant, + Convertor::return_internal_reference()), + bp::make_setter(&VariantHolder::variant)); +} diff --git a/unittest/python/test_boost_variant.py b/unittest/python/test_boost_variant.py new file mode 100644 index 00000000..0e1d283a --- /dev/null +++ b/unittest/python/test_boost_variant.py @@ -0,0 +1,26 @@ +from boost_variant import V1, V2, VariantHolder, make_variant + +variant = make_variant() +assert isinstance(variant, V1) + +v1 = V1() +v1.v = 10 + +v2 = V2() +v2.v = "c" + +variant_holder = VariantHolder() + +variant_holder.variant = v1 +assert isinstance(variant_holder.variant, V1) +assert variant_holder.variant == v1.v +variant_holder.variant = 100 +assert variant_holder.variant == 100 +assert v1 == 100 +v1 = 1000 +assert variant_holder.variant == 1000 +assert v1 == 1000 + +variant_holder.variant = v2 +assert isinstance(variant_holder.variant, V1) +assert variant_holder.variant == v2.v -- GitLab