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