Skip to content

Commit

Permalink
Merge pull request #433 from jorisv/topic/add_std_unique_ptr
Browse files Browse the repository at this point in the history
Add `std::unique_ptr` support
  • Loading branch information
jorisv authored Feb 5, 2024
2 parents fd0896e + a75379e commit 5ea4023
Show file tree
Hide file tree
Showing 15 changed files with 448 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/macos-linux-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/windows-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
4 changes: 3 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 Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions include/eigenpy/eigenpy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>();

Expand Down
145 changes: 145 additions & 0 deletions include/eigenpy/std-unique-ptr.hpp
Original file line number Diff line number Diff line change
@@ -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__
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__
56 changes: 56 additions & 0 deletions include/eigenpy/utils/traits.hpp
Original file line number Diff line number Diff line change
@@ -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__
23 changes: 7 additions & 16 deletions include/eigenpy/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 5ea4023

Please sign in to comment.