diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7002b5af9739814011f36df96046e8f00f6d1e03..e48c9105e4e3ce848ebb3f29ec899b3ce96876ee 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
diff --git a/include/eigenpy/std-unique-ptr.hpp b/include/eigenpy/std-unique-ptr.hpp
index dcb95dc372ba3d52d42f2da11685caeb5eaf9533..dfc9f5a139d2d4c2d873376d2012edf833958649 100644
--- a/include/eigenpy/std-unique-ptr.hpp
+++ b/include/eigenpy/std-unique-ptr.hpp
@@ -7,6 +7,7 @@
 
 #include "eigenpy/fwd.hpp"
 #include "eigenpy/utils/traits.hpp"
+#include "eigenpy/utils/python-compat.hpp"
 
 #include <boost/python.hpp>
 
@@ -19,8 +20,7 @@ namespace details {
 
 /// Transfer std::unique_ptr ownership to an owning holder
 template <typename T>
-typename std::enable_if<is_class_or_union_remove_cvref<T>::value,
-                        PyObject*>::type
+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) {
@@ -32,8 +32,7 @@ unique_ptr_to_python(std::unique_ptr<T>&& x) {
 
 /// Convert and copy the primitive value to python
 template <typename T>
-typename std::enable_if<!is_class_or_union_remove_cvref<T>::value,
-                        PyObject*>::type
+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();
@@ -45,8 +44,7 @@ unique_ptr_to_python(std::unique_ptr<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_class_or_union_remove_cvref<T>::value,
-                        PyObject*>::type
+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();
@@ -57,8 +55,7 @@ internal_unique_ptr_to_python(std::unique_ptr<T>& x) {
 
 /// Convert and copy the primitive value to python
 template <typename T>
-typename std::enable_if<!is_class_or_union_remove_cvref<T>::value,
-                        PyObject*>::type
+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();
@@ -123,7 +120,8 @@ struct ReturnInternalStdUniquePtr : 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);
diff --git a/include/eigenpy/ufunc.hpp b/include/eigenpy/ufunc.hpp
index cb6695ac913879f2dd5ddbf22b53883e82935f3c..129438cf15c8b43e7cffdc95102988fadc9b46f0 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 0000000000000000000000000000000000000000..7ffbc9de474ddaa82fe9e5f7eabacf3c49d2d019
--- /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
index 9b8e020cf4c2ce7e178bbc897202d0264df1f745..b7525dea9f6457348fc8525f631ec38804aca528 100644
--- a/include/eigenpy/utils/traits.hpp
+++ b/include/eigenpy/utils/traits.hpp
@@ -7,25 +7,47 @@
 #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 remove_cvref : std::remove_cv<typename std::remove_reference<T>::type> {
-};
+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 remove cvref and call is_class_or_union
+/// Trait to detect if T is a Python primitive type
 template <typename T>
-struct is_class_or_union_remove_cvref
-    : is_class_or_union<typename remove_cvref<T>::type> {};
+struct is_python_primitive_type
+    : is_python_primitive_type_helper<typename remove_cvref<T>::type> {};
 
 }  // namespace details
 
diff --git a/include/eigenpy/variant.hpp b/include/eigenpy/variant.hpp
index f989a0f9e8ea1befa65e1d5084dd03b252417188..bf787c3fc9864d6b2084c1020258492ce4fdc74e 100644
--- a/include/eigenpy/variant.hpp
+++ b/include/eigenpy/variant.hpp
@@ -7,6 +7,7 @@
 
 #include "eigenpy/fwd.hpp"
 #include "eigenpy/utils/traits.hpp"
+#include "eigenpy/utils/python-compat.hpp"
 
 #include <boost/python.hpp>
 #include <boost/variant.hpp>
@@ -147,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; }
@@ -220,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);
@@ -301,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);
diff --git a/unittest/python/test_std_unique_ptr.py b/unittest/python/test_std_unique_ptr.py
index 6feb408f8c4b93c90d338cc398b88c92c04fdcae..8b56460f33cefdf10ccb4338ff970d597676c955 100644
--- a/unittest/python/test_std_unique_ptr.py
+++ b/unittest/python/test_std_unique_ptr.py
@@ -2,6 +2,8 @@ from std_unique_ptr import (
     make_unique_int,
     make_unique_v1,
     make_unique_null,
+    make_unique_str,
+    make_unique_complex,
     V1,
     UniquePtrHolder,
 )
@@ -17,6 +19,14 @@ 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
@@ -33,6 +43,19 @@ assert v.v == 200
 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/std_unique_ptr.cpp b/unittest/std_unique_ptr.cpp
index ab99a4daeb9c3836258ead060a19377d8e2425c0..a95a5d2477f0e2c577c6a15adb1aa26e31aaa1a7 100644
--- a/unittest/std_unique_ptr.cpp
+++ b/unittest/std_unique_ptr.cpp
@@ -5,6 +5,8 @@
 #include <eigenpy/std-unique-ptr.hpp>
 
 #include <memory>
+#include <string>
+#include <complex>
 
 namespace bp = boost::python;
 
@@ -21,13 +23,26 @@ 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)) {}
+      : 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) {
@@ -39,6 +54,8 @@ BOOST_PYTHON_MODULE(std_unique_ptr) {
   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<>())
@@ -50,5 +67,11 @@ BOOST_PYTHON_MODULE(std_unique_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()));
 }