Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add std::unique_ptr support #433

Merged
merged 18 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading