Repository: stack-of-tasks/eigenpy (language: python)

Issue:
Support for unique_ptr<T>
Right now, we rely on Boost.Python's support for `shared_ptr<T>` and raw pointer types - but unlike pybind11 or nanobind, it doesn't support unique_ptr types. Maybe we could add support for this in eigenpy?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/macos-linux-conda.yml b/.github/workflows/macos-linux-conda.yml
index edc178441..3f4f23f66 100644
--- a/.github/workflows/macos-linux-conda.yml
+++ b/.github/workflows/macos-linux-conda.yml
@@ -40,7 +40,7 @@ jobs:
       with:
         path: .ccache
         key: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-${{ github.sha }}
-        restore-keys: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.python-version }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
+        restore-keys: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
 
     - uses: conda-incubator/setup-miniconda@v3
       with:
diff --git a/.github/workflows/windows-conda.yml b/.github/workflows/windows-conda.yml
index 74d923fd7..b3f8ad815 100644
--- a/.github/workflows/windows-conda.yml
+++ b/.github/workflows/windows-conda.yml
@@ -34,8 +34,8 @@ jobs:
     - uses: actions/cache@v3
       with:
         path: .ccache
-        key: ccache-windows-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-${{ github.sha }}
-        restore-keys: ccache-windows-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
+        key: ccache-windows-conda-${{ matrix.os }}-${{ github.sha }}
+        restore-keys: ccache-windows-conda-${{ matrix.os }}-
 
     - name: Build Eigenpy
       shell: cmd /C CALL {0}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e568f1ae6..8c936ecb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Support for `Eigen::SparseMatrix` types ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))
 - Support for `boost::variant` types with `VariantConverter` ([#430](https://github.com/stack-of-tasks/eigenpy/pull/430))
 - Support for `std::variant` types with `VariantConverter` ([#431](https://github.com/stack-of-tasks/eigenpy/pull/431))
+- Support for `std::unique_ptr` as a return types with `StdUniquePtrCallPolicies` and `boost::python::default_call_policies` ([#433](https://github.com/stack-of-tasks/eigenpy/pull/433))
+- Support for `std::unique_ptr` as an internal reference with `ReturnInternalStdUniquePtr` ([#433](https://github.com/stack-of-tasks/eigenpy/pull/433))
 
 ### Fixed
 - Fix the issue of missing exposition of Eigen types with __int64 scalar type ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d8d8a79e6..e48c9105e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -104,7 +104,8 @@ search_for_boost_python(REQUIRED)
 # ----------------------------------------------------
 set(${PROJECT_NAME}_UTILS_HEADERS
     include/eigenpy/utils/scalar-name.hpp include/eigenpy/utils/is-approx.hpp
-    include/eigenpy/utils/is-aligned.hpp)
+    include/eigenpy/utils/is-aligned.hpp include/eigenpy/utils/traits.hpp
+    include/eigenpy/utils/python-compat.hpp)
 
 set(${PROJECT_NAME}_SOLVERS_HEADERS
     include/eigenpy/solvers/solvers.hpp
@@ -167,6 +168,7 @@ set(${PROJECT_NAME}_HEADERS
     include/eigenpy/scipy-allocator.hpp
     include/eigenpy/scipy-type.hpp
     include/eigenpy/variant.hpp
+    include/eigenpy/std-unique-ptr.hpp
     include/eigenpy/swig.hpp
     include/eigenpy/version.hpp)
 
diff --git a/include/eigenpy/eigenpy.hpp b/include/eigenpy/eigenpy.hpp
index f4edf090d..97407d349 100644
--- a/include/eigenpy/eigenpy.hpp
+++ b/include/eigenpy/eigenpy.hpp
@@ -10,6 +10,9 @@
 #include "eigenpy/eigen-typedef.hpp"
 #include "eigenpy/expose.hpp"
 
+/// Custom CallPolicies
+#include "eigenpy/std-unique-ptr.hpp"
+
 #define ENABLE_SPECIFIC_MATRIX_TYPE(TYPE) \
   ::eigenpy::enableEigenPySpecific<TYPE>();
 
diff --git a/include/eigenpy/std-unique-ptr.hpp b/include/eigenpy/std-unique-ptr.hpp
new file mode 100644
index 000000000..dfc9f5a13
--- /dev/null
+++ b/include/eigenpy/std-unique-ptr.hpp
@@ -0,0 +1,145 @@
+//
+// Copyright (c) 2024 INRIA
+//
+
+#ifndef __eigenpy_utils_std_unique_ptr_hpp__
+#define __eigenpy_utils_std_unique_ptr_hpp__
+
+#include "eigenpy/fwd.hpp"
+#include "eigenpy/utils/traits.hpp"
+#include "eigenpy/utils/python-compat.hpp"
+
+#include <boost/python.hpp>
+
+#include <memory>
+#include <type_traits>
+
+namespace eigenpy {
+
+namespace details {
+
+/// Transfer std::unique_ptr ownership to an owning holder
+template <typename T>
+typename std::enable_if<!is_python_primitive_type<T>::value, PyObject*>::type
+unique_ptr_to_python(std::unique_ptr<T>&& x) {
+  typedef bp::objects::pointer_holder<std::unique_ptr<T>, T> holder_t;
+  if (!x) {
+    return bp::detail::none();
+  } else {
+    return bp::objects::make_ptr_instance<T, holder_t>::execute(x);
+  }
+}
+
+/// Convert and copy the primitive value to python
+template <typename T>
+typename std::enable_if<is_python_primitive_type<T>::value, PyObject*>::type
+unique_ptr_to_python(std::unique_ptr<T>&& x) {
+  if (!x) {
+    return bp::detail::none();
+  } else {
+    return bp::to_python_value<const T&>()(*x);
+  }
+}
+
+/// std::unique_ptr keep the ownership but a reference to the std::unique_ptr
+/// value is created
+template <typename T>
+typename std::enable_if<!is_python_primitive_type<T>::value, PyObject*>::type
+internal_unique_ptr_to_python(std::unique_ptr<T>& x) {
+  if (!x) {
+    return bp::detail::none();
+  } else {
+    return bp::detail::make_reference_holder::execute(x.get());
+  }
+}
+
+/// Convert and copy the primitive value to python
+template <typename T>
+typename std::enable_if<is_python_primitive_type<T>::value, PyObject*>::type
+internal_unique_ptr_to_python(std::unique_ptr<T>& x) {
+  if (!x) {
+    return bp::detail::none();
+  } else {
+    return bp::to_python_value<const T&>()(*x);
+  }
+}
+
+/// result_converter of StdUniquePtrCallPolicies
+struct StdUniquePtrResultConverter {
+  template <typename T>
+  struct apply {
+    struct type {
+      typedef typename T::element_type element_type;
+
+      PyObject* operator()(T&& x) const {
+        return unique_ptr_to_python(std::forward<T>(x));
+      }
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+      PyTypeObject const* get_pytype() const {
+        return bp::to_python_value<const element_type&>().get_pytype();
+      }
+#endif
+    };
+  };
+};
+
+/// result_converter of ReturnInternalStdUniquePtr
+struct InternalStdUniquePtrConverter {
+  template <typename T>
+  struct apply {
+    struct type {
+      typedef typename remove_cvref<T>::type::element_type element_type;
+
+      PyObject* operator()(T x) const {
+        return internal_unique_ptr_to_python(x);
+      }
+#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
+      PyTypeObject const* get_pytype() const {
+        return bp::to_python_value<const element_type&>().get_pytype();
+      }
+#endif
+    };
+  };
+};
+
+}  // namespace details
+
+/// CallPolicies to get std::unique_ptr value from a function
+/// that return an std::unique_ptr.
+/// If the object inside the std::unique_ptr is a class or an union
+/// it will be moved. In other case, it will be copied.
+struct StdUniquePtrCallPolicies : bp::default_call_policies {
+  typedef details::StdUniquePtrResultConverter result_converter;
+};
+
+/// Variant of \see bp::return_internal_reference that extract std::unique_ptr
+/// content reference before converting it into a PyObject
+struct ReturnInternalStdUniquePtr : bp::return_internal_reference<> {
+  typedef details::InternalStdUniquePtrConverter result_converter;
+
+  template <class ArgumentPackage>
+  static PyObject* postcall(ArgumentPackage const& args_, PyObject* result) {
+    // Don't run return_internal_reference postcall on primitive type
+    if (PyInt_Check(result) || PyBool_Check(result) || PyFloat_Check(result) ||
+        PyStr_Check(result) || PyComplex_Check(result)) {
+      return result;
+    }
+    return bp::return_internal_reference<>::postcall(args_, result);
+  }
+};
+
+}  // namespace eigenpy
+
+namespace boost {
+namespace python {
+
+/// Specialize to_python_value for std::unique_ptr
+template <typename T>
+struct to_python_value<const std::unique_ptr<T>&>
+    : eigenpy::details::StdUniquePtrResultConverter::apply<
+          std::unique_ptr<T> >::type {};
+
+}  // namespace python
+}  // namespace boost
+
+#endif  // ifndef __eigenpy_utils_std_unique_ptr_hpp__
diff --git a/include/eigenpy/ufunc.hpp b/include/eigenpy/ufunc.hpp
index cb6695ac9..129438cf1 100644
--- a/include/eigenpy/ufunc.hpp
+++ b/include/eigenpy/ufunc.hpp
@@ -9,6 +9,7 @@
 
 #include "eigenpy/register.hpp"
 #include "eigenpy/user-type.hpp"
+#include "eigenpy/utils/python-compat.hpp"
 
 namespace eigenpy {
 namespace internal {
@@ -207,11 +208,7 @@ void registerCommonUfunc() {
   const int type_code = Register::getTypeCode<Scalar>();
 
   PyObject *numpy_str;
-#if PY_MAJOR_VERSION >= 3
-  numpy_str = PyUnicode_FromString("numpy");
-#else
-  numpy_str = PyString_FromString("numpy");
-#endif
+  numpy_str = PyStr_FromString("numpy");
   PyObject *numpy;
   numpy = PyImport_Import(numpy_str);
   Py_DECREF(numpy_str);
diff --git a/include/eigenpy/utils/python-compat.hpp b/include/eigenpy/utils/python-compat.hpp
new file mode 100644
index 000000000..7ffbc9de4
--- /dev/null
+++ b/include/eigenpy/utils/python-compat.hpp
@@ -0,0 +1,23 @@
+//
+// Copyright (c) 2024 INRIA
+//
+//
+
+#ifndef __eigenpy_utils_python_compat_hpp__
+#define __eigenpy_utils_python_compat_hpp__
+
+#if PY_MAJOR_VERSION >= 3
+
+#define PyInt_Check PyLong_Check
+
+#define PyStr_Check PyUnicode_Check
+#define PyStr_FromString PyUnicode_FromString
+
+#else
+
+#define PyStr_Check PyString_Check
+#define PyStr_FromString PyString_FromString
+
+#endif
+
+#endif  // ifndef __eigenpy_utils_python_compat_hpp__
diff --git a/include/eigenpy/utils/traits.hpp b/include/eigenpy/utils/traits.hpp
new file mode 100644
index 000000000..b7525dea9
--- /dev/null
+++ b/include/eigenpy/utils/traits.hpp
@@ -0,0 +1,56 @@
+//
+// Copyright (c) 2024 INRIA
+//
+//
+
+#ifndef __eigenpy_utils_traits_hpp__
+#define __eigenpy_utils_traits_hpp__
+
+#include <type_traits>
+#include <string>
+#include <complex>
+
+namespace eigenpy {
+
+namespace details {
+
+/// Trait to remove const&
+template <typename T>
+struct remove_cvref : std::remove_cv<typename std::remove_reference<T>::type> {
+};
+
+/// Trait to detect if T is a class or an union
+template <typename T>
+struct is_class_or_union
+    : std::integral_constant<bool, std::is_class<T>::value ||
+                                       std::is_union<T>::value> {};
+
+/// trait to detect if T is a std::complex managed by Boost Python
+template <typename T>
+struct is_python_complex : std::false_type {};
+
+/// From boost/python/converter/builtin_converters
+template <>
+struct is_python_complex<std::complex<float> > : std::true_type {};
+template <>
+struct is_python_complex<std::complex<double> > : std::true_type {};
+template <>
+struct is_python_complex<std::complex<long double> > : std::true_type {};
+
+template <typename T>
+struct is_python_primitive_type_helper
+    : std::integral_constant<bool, !is_class_or_union<T>::value ||
+                                       std::is_same<T, std::string>::value ||
+                                       std::is_same<T, std::wstring>::value ||
+                                       is_python_complex<T>::value> {};
+
+/// Trait to detect if T is a Python primitive type
+template <typename T>
+struct is_python_primitive_type
+    : is_python_primitive_type_helper<typename remove_cvref<T>::type> {};
+
+}  // namespace details
+
+}  // namespace eigenpy
+
+#endif  // ifndef __eigenpy_utils_traits_hpp__
diff --git a/include/eigenpy/variant.hpp b/include/eigenpy/variant.hpp
index 4028f856b..bf787c3fc 100644
--- a/include/eigenpy/variant.hpp
+++ b/include/eigenpy/variant.hpp
@@ -6,6 +6,8 @@
 #define __eigenpy_utils_variant_hpp__
 
 #include "eigenpy/fwd.hpp"
+#include "eigenpy/utils/traits.hpp"
+#include "eigenpy/utils/python-compat.hpp"
 
 #include <boost/python.hpp>
 #include <boost/variant.hpp>
@@ -146,7 +148,7 @@ struct NumericConvertibleImpl<
                                std::is_integral<T>::value>::type> {
   static void* convertible(PyObject* obj) {
     // PyLong return true for bool type
-    return (PyLong_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
+    return (PyInt_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
   }
 
   static PyTypeObject const* expected_pytype() { return &PyLong_Type; }
@@ -204,18 +206,6 @@ struct VariantValueToObject : VariantVisitorType<PyObject*, Variant> {
   using Base::operator();
 };
 
-/// Trait to detect if T is a class or an union
-template <typename T>
-struct is_class_or_union
-    : std::integral_constant<bool, std::is_class<T>::value ||
-                                       std::is_union<T>::value> {};
-
-/// Trait to remove cvref and call is_class_or_union
-template <typename T>
-struct is_class_or_union_remove_cvref
-    : is_class_or_union<typename std::remove_cv<
-          typename std::remove_reference<T>::type>::type> {};
-
 /// Convert {boost,std}::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
@@ -231,14 +221,14 @@ struct VariantRefToObject : VariantVisitorType<PyObject*, Variant> {
   }
 
   template <typename T,
-            typename std::enable_if<!is_class_or_union_remove_cvref<T>::value,
+            typename std::enable_if<is_python_primitive_type<T>::value,
                                     bool>::type = true>
   result_type operator()(T t) const {
     return bp::incref(bp::object(t).ptr());
   }
 
   template <typename T,
-            typename std::enable_if<is_class_or_union_remove_cvref<T>::value,
+            typename std::enable_if<!is_python_primitive_type<T>::value,
                                     bool>::type = true>
   result_type operator()(T& t) const {
     return bp::detail::make_reference_holder::execute(&t);
@@ -312,7 +302,8 @@ struct ReturnInternalVariant : bp::return_internal_reference<> {
   template <class ArgumentPackage>
   static PyObject* postcall(ArgumentPackage const& args_, PyObject* result) {
     // Don't run return_internal_reference postcall on primitive type
-    if (PyLong_Check(result) || PyBool_Check(result) || PyFloat_Check(result)) {
+    if (PyInt_Check(result) || PyBool_Check(result) || PyFloat_Check(result) ||
+        PyStr_Check(result) || PyComplex_Check(result)) {
       return result;
     }
     return bp::return_internal_reference<>::postcall(args_, result);


Test patch:
diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt
index e1356f6d4..8dc425fa9 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(std_unique_ptr)
 
 function(config_test test tagname opttype)
   set(MODNAME ${test}_${tagname})
@@ -131,17 +132,16 @@ add_python_unit_test("py-std-vector" "unittest/python/test_std_vector.py"
 set_tests_properties("py-std-vector" PROPERTIES DEPENDS ${PYWRAP})
 
 add_python_unit_test("py-std-array" "unittest/python/test_std_array.py"
-                     "python;unittest")
-set_tests_properties("py-std-array" PROPERTIES DEPENDS ${PYWRAP})
+                     "unittest")
 
 add_python_unit_test("py-std-pair" "unittest/python/test_std_pair.py"
                      "unittest")
-set_tests_properties("py-std-pair" PROPERTIES DEPENDS ${PYWRAP})
 
 add_python_unit_test("py-user-struct" "unittest/python/test_user_struct.py"
-                     "python;unittest")
-set_tests_properties("py-user-struct" PROPERTIES DEPENDS ${PYWRAP})
+                     "unittest")
+
+add_python_unit_test("py-std-unique-ptr"
+                     "unittest/python/test_std_unique_ptr.py" "unittest")
 
 add_python_unit_test("py-bind-virtual" "unittest/python/test_bind_virtual.py"
-                     "python;unittest")
-set_tests_properties("py-bind-virtual" PROPERTIES DEPENDS ${PYWRAP})
+                     "unittest")
diff --git a/unittest/python/test_std_unique_ptr.py b/unittest/python/test_std_unique_ptr.py
new file mode 100644
index 000000000..8b56460f3
--- /dev/null
+++ b/unittest/python/test_std_unique_ptr.py
@@ -0,0 +1,61 @@
+from std_unique_ptr import (
+    make_unique_int,
+    make_unique_v1,
+    make_unique_null,
+    make_unique_str,
+    make_unique_complex,
+    V1,
+    UniquePtrHolder,
+)
+
+v = make_unique_int()
+assert isinstance(v, int)
+assert v == 10
+
+v = make_unique_v1()
+assert isinstance(v, V1)
+assert v.v == 10
+
+v = make_unique_null()
+assert v is None
+
+v = make_unique_str()
+assert isinstance(v, str)
+assert v == "str"
+
+v = make_unique_complex()
+assert isinstance(v, complex)
+assert v == 1 + 0j
+
+unique_ptr_holder = UniquePtrHolder()
+
+v = unique_ptr_holder.int_ptr
+assert isinstance(v, int)
+assert v == 20
+# v is a copy, int_ptr will not be updated
+v = 10
+assert unique_ptr_holder.int_ptr == 20
+
+v = unique_ptr_holder.v1_ptr
+assert isinstance(v, V1)
+assert v.v == 200
+# v is a ref, v1_ptr will be updated
+v.v = 10
+assert unique_ptr_holder.v1_ptr.v == 10
+
+v = unique_ptr_holder.null_ptr
+assert v is None
+
+v = unique_ptr_holder.str_ptr
+assert isinstance(v, str)
+assert v == "str"
+# v is a copy, str_ptr will not be updated
+v = "str_updated"
+assert unique_ptr_holder.str_ptr == "str"
+
+v = unique_ptr_holder.complex_ptr
+assert isinstance(v, complex)
+assert v == 1 + 0j
+# v is a copy, complex_ptr will not be updated
+v = 1 + 2j
+assert unique_ptr_holder.complex_ptr == 1 + 0j
diff --git a/unittest/python/test_variant.py.in b/unittest/python/test_variant.py.in
index b019514cd..a7590a3fa 100644
--- a/unittest/python/test_variant.py.in
+++ b/unittest/python/test_variant.py.in
@@ -6,7 +6,12 @@ V2 = variant_module.V2
 VariantHolder = variant_module.VariantHolder
 VariantFullHolder = variant_module.VariantFullHolder
 make_variant = variant_module.make_variant
-make_variant_full = variant_module.make_variant_full
+make_variant_full_none = variant_module.make_variant_full_none
+make_variant_full_float = variant_module.make_variant_full_float
+make_variant_full_int = variant_module.make_variant_full_int
+make_variant_full_bool = variant_module.make_variant_full_bool
+make_variant_full_str = variant_module.make_variant_full_str
+make_variant_full_complex = variant_module.make_variant_full_complex
 
 variant = make_variant()
 assert isinstance(variant, V1)
@@ -44,9 +49,34 @@ assert isinstance(variant_holder.variant, V2)
 assert variant_holder.variant.v == v2.v
 
 # Test variant that hold a None value
-v_full = make_variant_full()
+v_full = make_variant_full_none()
 assert v_full is None
 
+# Test variant that hold a float value
+v_full = make_variant_full_float()
+assert v_full == 3.14
+assert isinstance(v_full, float)
+
+# Test variant that hold a int value
+v_full = make_variant_full_int()
+assert v_full == 3
+assert isinstance(v_full, int)
+
+# Test variant that hold a bool value
+v_full = make_variant_full_bool()
+assert not v_full
+assert isinstance(v_full, bool)
+
+# Test variant that hold a str value
+v_full = make_variant_full_str()
+assert v_full == "str"
+assert isinstance(v_full, str)
+
+# Test variant that hold a complex value
+v_full = make_variant_full_complex()
+assert v_full == 1 + 0j
+assert isinstance(v_full, complex)
+
 variant_full_holder = VariantFullHolder()
 
 # Test None
@@ -81,3 +111,13 @@ assert isinstance(variant_full_holder.variant, int)
 variant_full_holder.variant = 3.14
 assert variant_full_holder.variant == 3.14
 assert isinstance(variant_full_holder.variant, float)
+
+# Test str
+variant_full_holder.variant = "str"
+assert variant_full_holder.variant == "str"
+assert isinstance(variant_full_holder.variant, str)
+
+# Test complex
+variant_full_holder.variant = 1 + 0j
+assert variant_full_holder.variant == 1 + 0j
+assert isinstance(variant_full_holder.variant, complex)
diff --git a/unittest/std_unique_ptr.cpp b/unittest/std_unique_ptr.cpp
new file mode 100644
index 000000000..a95a5d247
--- /dev/null
+++ b/unittest/std_unique_ptr.cpp
@@ -0,0 +1,77 @@
+/// @file
+/// @copyright Copyright 2023 CNRS INRIA
+
+#include <eigenpy/eigenpy.hpp>
+#include <eigenpy/std-unique-ptr.hpp>
+
+#include <memory>
+#include <string>
+#include <complex>
+
+namespace bp = boost::python;
+
+struct V1 {
+  V1() = default;
+  V1(double p_v) : v(p_v) {}
+
+  double v = 100;
+};
+
+std::unique_ptr<int> make_unique_int() { return std::make_unique<int>(10); }
+
+std::unique_ptr<V1> make_unique_v1() { return std::make_unique<V1>(10); }
+
+std::unique_ptr<V1> make_unique_null() { return nullptr; }
+
+std::unique_ptr<std::string> make_unique_str() {
+  return std::make_unique<std::string>("str");
+}
+
+std::unique_ptr<std::complex<double> > make_unique_complex() {
+  return std::make_unique<std::complex<double> >(1., 0.);
+}
+
+struct UniquePtrHolder {
+  UniquePtrHolder()
+      : int_ptr(std::make_unique<int>(20)),
+        v1_ptr(std::make_unique<V1>(200)),
+        str_ptr(std::make_unique<std::string>("str")),
+        complex_ptr(std::make_unique<std::complex<double> >(1., 0.)) {}
+
+  std::unique_ptr<int> int_ptr;
+  std::unique_ptr<V1> v1_ptr;
+  std::unique_ptr<V1> null_ptr;
+  std::unique_ptr<std::string> str_ptr;
+  std::unique_ptr<std::complex<double> > complex_ptr;
+};
+
+BOOST_PYTHON_MODULE(std_unique_ptr) {
+  eigenpy::enableEigenPy();
+
+  bp::class_<V1>("V1", bp::init<>()).def_readwrite("v", &V1::v);
+
+  bp::def("make_unique_int", make_unique_int);
+  bp::def("make_unique_v1", make_unique_v1);
+  bp::def("make_unique_null", make_unique_null,
+          eigenpy::StdUniquePtrCallPolicies());
+  bp::def("make_unique_str", make_unique_str);
+  bp::def("make_unique_complex", make_unique_complex);
+
+  boost::python::class_<UniquePtrHolder, boost::noncopyable>("UniquePtrHolder",
+                                                             bp::init<>())
+      .add_property("int_ptr",
+                    bp::make_getter(&UniquePtrHolder::int_ptr,
+                                    eigenpy::ReturnInternalStdUniquePtr()))
+      .add_property("v1_ptr",
+                    bp::make_getter(&UniquePtrHolder::v1_ptr,
+                                    eigenpy::ReturnInternalStdUniquePtr()))
+      .add_property("null_ptr",
+                    bp::make_getter(&UniquePtrHolder::null_ptr,
+                                    eigenpy::ReturnInternalStdUniquePtr()))
+      .add_property("str_ptr",
+                    bp::make_getter(&UniquePtrHolder::str_ptr,
+                                    eigenpy::ReturnInternalStdUniquePtr()))
+      .add_property("complex_ptr",
+                    bp::make_getter(&UniquePtrHolder::complex_ptr,
+                                    eigenpy::ReturnInternalStdUniquePtr()));
+}
diff --git a/unittest/variant.cpp.in b/unittest/variant.cpp.in
index 12f669937..5d8f75ebd 100644
--- a/unittest/variant.cpp.in
+++ b/unittest/variant.cpp.in
@@ -4,6 +4,9 @@
 #include <eigenpy/eigenpy.hpp>
 #include <eigenpy/variant.hpp>
 
+#include <string>
+#include <complex>
+
 #cmakedefine TEST_TYPE @TEST_TYPE@
 #define VARIANT TEST_TYPE
 
@@ -32,12 +35,18 @@ struct MyVariantNoneHelper<std::variant<Alternatives...> > {
 };
 #endif
 
-typedef typename MyVariantNoneHelper<VARIANT<V1, bool, int, double> >::type
+typedef typename MyVariantNoneHelper<
+    VARIANT<V1, bool, int, double, std::string, std::complex<double> > >::type
     MyVariantFull;
 
 MyVariant make_variant() { return V1(); }
 
-MyVariantFull make_variant_full() { return MyVariantFull(); }
+MyVariantFull make_variant_full_none() { return MyVariantFull(); }
+MyVariantFull make_variant_full_float() { return 3.14; }
+MyVariantFull make_variant_full_int() { return 3; }
+MyVariantFull make_variant_full_bool() { return false; }
+MyVariantFull make_variant_full_str() { return std::string("str"); }
+MyVariantFull make_variant_full_complex() { return std::complex<double>(1., 0.); }
 
 struct VariantHolder {
   MyVariant variant;
@@ -68,7 +77,12 @@ BOOST_PYTHON_MODULE(@MODNAME@) {
 
   typedef eigenpy::VariantConverter<MyVariantFull> ConverterFull;
   ConverterFull::registration();
-  bp::def("make_variant_full", make_variant_full);
+  bp::def("make_variant_full_none", make_variant_full_none);
+  bp::def("make_variant_full_float", make_variant_full_float);
+  bp::def("make_variant_full_int", make_variant_full_int);
+  bp::def("make_variant_full_bool", make_variant_full_bool);
+  bp::def("make_variant_full_str", make_variant_full_str);
+  bp::def("make_variant_full_complex", make_variant_full_complex);
 
   boost::python::class_<VariantFullHolder>("VariantFullHolder", bp::init<>())
       .add_property("variant",