Skip to content

Commit

Permalink
unique_ptr: Manage string and complex type
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisv committed Feb 5, 2024
1 parent f565ce8 commit 558e391
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions include/eigenpy/std-unique-ptr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "eigenpy/fwd.hpp"
#include "eigenpy/utils/traits.hpp"
#include "eigenpy/utils/python-compat.hpp"

#include <boost/python.hpp>

Expand All @@ -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) {
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 2 additions & 5 deletions include/eigenpy/ufunc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "eigenpy/register.hpp"
#include "eigenpy/user-type.hpp"
#include "eigenpy/utils/python-compat.hpp"

namespace eigenpy {
namespace internal {
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions include/eigenpy/utils/python-compat.hpp
Original file line number Diff line number Diff line change
@@ -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__
32 changes: 27 additions & 5 deletions include/eigenpy/utils/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 6 additions & 4 deletions include/eigenpy/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 24 additions & 1 deletion unittest/python/test_std_unique_ptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
make_unique_int,
make_unique_v1,
make_unique_null,
make_unique_str,
make_unique_complex,
V1,
UniquePtrHolder,
)
Expand All @@ -17,6 +19,14 @@
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
Expand All @@ -33,6 +43,19 @@
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
25 changes: 24 additions & 1 deletion unittest/std_unique_ptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <eigenpy/std-unique-ptr.hpp>

#include <memory>
#include <string>
#include <complex>

namespace bp = boost::python;

Expand All @@ -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) {
Expand All @@ -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<>())
Expand All @@ -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()));
}

0 comments on commit 558e391

Please sign in to comment.