From bfebd9c48ec479b6df82926290aca1fcb9922da0 Mon Sep 17 00:00:00 2001 From: Joris Vaillant <joris.vaillant@inria.fr> Date: Thu, 1 Feb 2024 17:26:11 +0100 Subject: [PATCH] unique_ptr: Return unique_ptr internal reference --- include/eigenpy/std_unique_ptr.hpp | 65 +++++++++++++++++++++++++- include/eigenpy/utils/traits.hpp | 7 ++- unittest/python/test_std_unique_ptr.py | 28 ++++++++++- unittest/std_unique_ptr.cpp | 19 ++++++-- 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/include/eigenpy/std_unique_ptr.hpp b/include/eigenpy/std_unique_ptr.hpp index 45ac265d..15e1dd6c 100644 --- a/include/eigenpy/std_unique_ptr.hpp +++ b/include/eigenpy/std_unique_ptr.hpp @@ -16,6 +16,7 @@ namespace eigenpy { 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 @@ -27,6 +28,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 @@ -38,7 +40,30 @@ unique_ptr_to_python(std::unique_ptr<T>&& x) { } } -} // namespace details +/// 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 +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_class_or_union_remove_cvref<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 { @@ -60,12 +85,48 @@ struct StdUniquePtrResultConverter { }; }; +/// 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 details::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 StdUniquePtrResultConverter result_converter; + 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 (PyLong_Check(result) || PyBool_Check(result) || PyFloat_Check(result)) { + return result; + } + return bp::return_internal_reference<>::postcall(args_, result); + } }; } // namespace eigenpy diff --git a/include/eigenpy/utils/traits.hpp b/include/eigenpy/utils/traits.hpp index 9ce6b4fa..1f78a8d5 100644 --- a/include/eigenpy/utils/traits.hpp +++ b/include/eigenpy/utils/traits.hpp @@ -17,11 +17,14 @@ struct is_class_or_union : std::integral_constant<bool, std::is_class<T>::value || std::is_union<T>::value> {}; +template <typename T> +struct remove_cvref : std::remove_cv<typename std::remove_reference<T>::type> { +}; + /// 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> {}; + : is_class_or_union<typename remove_cvref<T>::type> {}; } // namespace details diff --git a/unittest/python/test_std_unique_ptr.py b/unittest/python/test_std_unique_ptr.py index cb8f7d46..6feb408f 100644 --- a/unittest/python/test_std_unique_ptr.py +++ b/unittest/python/test_std_unique_ptr.py @@ -1,4 +1,10 @@ -from std_unique_ptr import make_unique_int, make_unique_v1, make_unique_null, V1 +from std_unique_ptr import ( + make_unique_int, + make_unique_v1, + make_unique_null, + V1, + UniquePtrHolder, +) v = make_unique_int() assert isinstance(v, int) @@ -10,3 +16,23 @@ assert v.v == 10 v = make_unique_null() assert v is None + +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 diff --git a/unittest/std_unique_ptr.cpp b/unittest/std_unique_ptr.cpp index 2fdad8c3..9c163ac8 100644 --- a/unittest/std_unique_ptr.cpp +++ b/unittest/std_unique_ptr.cpp @@ -22,6 +22,9 @@ std::unique_ptr<V1> make_unique_v1() { return std::make_unique<V1>(10); } std::unique_ptr<V1> make_unique_null() { return nullptr; } struct UniquePtrHolder { + UniquePtrHolder() + : int_ptr(std::make_unique<int>(20)), v1_ptr(std::make_unique<V1>(200)) {} + std::unique_ptr<int> int_ptr; std::unique_ptr<V1> v1_ptr; std::unique_ptr<V1> null_ptr; @@ -38,8 +41,16 @@ BOOST_PYTHON_MODULE(std_unique_ptr) { eigenpy::StdUniquePtrCallPolicies()); bp::def("make_unique_null", make_unique_null, eigenpy::StdUniquePtrCallPolicies()); - // TODO allow access with a CallPolicie like return_internal_reference - // boost::python::class_<UniquePtrHolder>("UniquePtrHolder", bp::init<>()) - // .add_property("int_ptr", bp::make_getter(&UniquePtrHolder::int_ptr), - // bp::make_setter(&UniquePtrHolder::int_ptr)); + + 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())); } -- GitLab