Skip to content

Commit 5ea4023

Browse files
authored
Merge pull request #433 from jorisv/topic/add_std_unique_ptr
Add `std::unique_ptr` support
2 parents fd0896e + a75379e commit 5ea4023

15 files changed

+448
-37
lines changed

.github/workflows/macos-linux-conda.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
with:
4141
path: .ccache
4242
key: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-${{ github.sha }}
43-
restore-keys: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.python-version }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
43+
restore-keys: ccache-macos-linux-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
4444

4545
- uses: conda-incubator/setup-miniconda@v3
4646
with:

.github/workflows/windows-conda.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ jobs:
3434
- uses: actions/cache@v3
3535
with:
3636
path: .ccache
37-
key: ccache-windows-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-${{ github.sha }}
38-
restore-keys: ccache-windows-conda-${{ matrix.os }}-${{ matrix.build_type }}-${{ matrix.cxx_options }}-${{ matrix.python-version }}-
37+
key: ccache-windows-conda-${{ matrix.os }}-${{ github.sha }}
38+
restore-keys: ccache-windows-conda-${{ matrix.os }}-
3939

4040
- name: Build Eigenpy
4141
shell: cmd /C CALL {0}

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010
- Support for `Eigen::SparseMatrix` types ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))
1111
- Support for `boost::variant` types with `VariantConverter` ([#430](https://github.com/stack-of-tasks/eigenpy/pull/430))
1212
- Support for `std::variant` types with `VariantConverter` ([#431](https://github.com/stack-of-tasks/eigenpy/pull/431))
13+
- 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))
14+
- Support for `std::unique_ptr` as an internal reference with `ReturnInternalStdUniquePtr` ([#433](https://github.com/stack-of-tasks/eigenpy/pull/433))
1315

1416
### Fixed
1517
- Fix the issue of missing exposition of Eigen types with __int64 scalar type ([#426](https://github.com/stack-of-tasks/eigenpy/pull/426))

CMakeLists.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ search_for_boost_python(REQUIRED)
104104
# ----------------------------------------------------
105105
set(${PROJECT_NAME}_UTILS_HEADERS
106106
include/eigenpy/utils/scalar-name.hpp include/eigenpy/utils/is-approx.hpp
107-
include/eigenpy/utils/is-aligned.hpp)
107+
include/eigenpy/utils/is-aligned.hpp include/eigenpy/utils/traits.hpp
108+
include/eigenpy/utils/python-compat.hpp)
108109

109110
set(${PROJECT_NAME}_SOLVERS_HEADERS
110111
include/eigenpy/solvers/solvers.hpp
@@ -167,6 +168,7 @@ set(${PROJECT_NAME}_HEADERS
167168
include/eigenpy/scipy-allocator.hpp
168169
include/eigenpy/scipy-type.hpp
169170
include/eigenpy/variant.hpp
171+
include/eigenpy/std-unique-ptr.hpp
170172
include/eigenpy/swig.hpp
171173
include/eigenpy/version.hpp)
172174

include/eigenpy/eigenpy.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#include "eigenpy/eigen-typedef.hpp"
1111
#include "eigenpy/expose.hpp"
1212

13+
/// Custom CallPolicies
14+
#include "eigenpy/std-unique-ptr.hpp"
15+
1316
#define ENABLE_SPECIFIC_MATRIX_TYPE(TYPE) \
1417
::eigenpy::enableEigenPySpecific<TYPE>();
1518

include/eigenpy/std-unique-ptr.hpp

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
//
2+
// Copyright (c) 2024 INRIA
3+
//
4+
5+
#ifndef __eigenpy_utils_std_unique_ptr_hpp__
6+
#define __eigenpy_utils_std_unique_ptr_hpp__
7+
8+
#include "eigenpy/fwd.hpp"
9+
#include "eigenpy/utils/traits.hpp"
10+
#include "eigenpy/utils/python-compat.hpp"
11+
12+
#include <boost/python.hpp>
13+
14+
#include <memory>
15+
#include <type_traits>
16+
17+
namespace eigenpy {
18+
19+
namespace details {
20+
21+
/// Transfer std::unique_ptr ownership to an owning holder
22+
template <typename T>
23+
typename std::enable_if<!is_python_primitive_type<T>::value, PyObject*>::type
24+
unique_ptr_to_python(std::unique_ptr<T>&& x) {
25+
typedef bp::objects::pointer_holder<std::unique_ptr<T>, T> holder_t;
26+
if (!x) {
27+
return bp::detail::none();
28+
} else {
29+
return bp::objects::make_ptr_instance<T, holder_t>::execute(x);
30+
}
31+
}
32+
33+
/// Convert and copy the primitive value to python
34+
template <typename T>
35+
typename std::enable_if<is_python_primitive_type<T>::value, PyObject*>::type
36+
unique_ptr_to_python(std::unique_ptr<T>&& x) {
37+
if (!x) {
38+
return bp::detail::none();
39+
} else {
40+
return bp::to_python_value<const T&>()(*x);
41+
}
42+
}
43+
44+
/// std::unique_ptr keep the ownership but a reference to the std::unique_ptr
45+
/// value is created
46+
template <typename T>
47+
typename std::enable_if<!is_python_primitive_type<T>::value, PyObject*>::type
48+
internal_unique_ptr_to_python(std::unique_ptr<T>& x) {
49+
if (!x) {
50+
return bp::detail::none();
51+
} else {
52+
return bp::detail::make_reference_holder::execute(x.get());
53+
}
54+
}
55+
56+
/// Convert and copy the primitive value to python
57+
template <typename T>
58+
typename std::enable_if<is_python_primitive_type<T>::value, PyObject*>::type
59+
internal_unique_ptr_to_python(std::unique_ptr<T>& x) {
60+
if (!x) {
61+
return bp::detail::none();
62+
} else {
63+
return bp::to_python_value<const T&>()(*x);
64+
}
65+
}
66+
67+
/// result_converter of StdUniquePtrCallPolicies
68+
struct StdUniquePtrResultConverter {
69+
template <typename T>
70+
struct apply {
71+
struct type {
72+
typedef typename T::element_type element_type;
73+
74+
PyObject* operator()(T&& x) const {
75+
return unique_ptr_to_python(std::forward<T>(x));
76+
}
77+
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
78+
PyTypeObject const* get_pytype() const {
79+
return bp::to_python_value<const element_type&>().get_pytype();
80+
}
81+
#endif
82+
};
83+
};
84+
};
85+
86+
/// result_converter of ReturnInternalStdUniquePtr
87+
struct InternalStdUniquePtrConverter {
88+
template <typename T>
89+
struct apply {
90+
struct type {
91+
typedef typename remove_cvref<T>::type::element_type element_type;
92+
93+
PyObject* operator()(T x) const {
94+
return internal_unique_ptr_to_python(x);
95+
}
96+
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
97+
PyTypeObject const* get_pytype() const {
98+
return bp::to_python_value<const element_type&>().get_pytype();
99+
}
100+
#endif
101+
};
102+
};
103+
};
104+
105+
} // namespace details
106+
107+
/// CallPolicies to get std::unique_ptr value from a function
108+
/// that return an std::unique_ptr.
109+
/// If the object inside the std::unique_ptr is a class or an union
110+
/// it will be moved. In other case, it will be copied.
111+
struct StdUniquePtrCallPolicies : bp::default_call_policies {
112+
typedef details::StdUniquePtrResultConverter result_converter;
113+
};
114+
115+
/// Variant of \see bp::return_internal_reference that extract std::unique_ptr
116+
/// content reference before converting it into a PyObject
117+
struct ReturnInternalStdUniquePtr : bp::return_internal_reference<> {
118+
typedef details::InternalStdUniquePtrConverter result_converter;
119+
120+
template <class ArgumentPackage>
121+
static PyObject* postcall(ArgumentPackage const& args_, PyObject* result) {
122+
// Don't run return_internal_reference postcall on primitive type
123+
if (PyInt_Check(result) || PyBool_Check(result) || PyFloat_Check(result) ||
124+
PyStr_Check(result) || PyComplex_Check(result)) {
125+
return result;
126+
}
127+
return bp::return_internal_reference<>::postcall(args_, result);
128+
}
129+
};
130+
131+
} // namespace eigenpy
132+
133+
namespace boost {
134+
namespace python {
135+
136+
/// Specialize to_python_value for std::unique_ptr
137+
template <typename T>
138+
struct to_python_value<const std::unique_ptr<T>&>
139+
: eigenpy::details::StdUniquePtrResultConverter::apply<
140+
std::unique_ptr<T> >::type {};
141+
142+
} // namespace python
143+
} // namespace boost
144+
145+
#endif // ifndef __eigenpy_utils_std_unique_ptr_hpp__

include/eigenpy/ufunc.hpp

+2-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "eigenpy/register.hpp"
1111
#include "eigenpy/user-type.hpp"
12+
#include "eigenpy/utils/python-compat.hpp"
1213

1314
namespace eigenpy {
1415
namespace internal {
@@ -207,11 +208,7 @@ void registerCommonUfunc() {
207208
const int type_code = Register::getTypeCode<Scalar>();
208209

209210
PyObject *numpy_str;
210-
#if PY_MAJOR_VERSION >= 3
211-
numpy_str = PyUnicode_FromString("numpy");
212-
#else
213-
numpy_str = PyString_FromString("numpy");
214-
#endif
211+
numpy_str = PyStr_FromString("numpy");
215212
PyObject *numpy;
216213
numpy = PyImport_Import(numpy_str);
217214
Py_DECREF(numpy_str);
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Copyright (c) 2024 INRIA
3+
//
4+
//
5+
6+
#ifndef __eigenpy_utils_python_compat_hpp__
7+
#define __eigenpy_utils_python_compat_hpp__
8+
9+
#if PY_MAJOR_VERSION >= 3
10+
11+
#define PyInt_Check PyLong_Check
12+
13+
#define PyStr_Check PyUnicode_Check
14+
#define PyStr_FromString PyUnicode_FromString
15+
16+
#else
17+
18+
#define PyStr_Check PyString_Check
19+
#define PyStr_FromString PyString_FromString
20+
21+
#endif
22+
23+
#endif // ifndef __eigenpy_utils_python_compat_hpp__

include/eigenpy/utils/traits.hpp

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// Copyright (c) 2024 INRIA
3+
//
4+
//
5+
6+
#ifndef __eigenpy_utils_traits_hpp__
7+
#define __eigenpy_utils_traits_hpp__
8+
9+
#include <type_traits>
10+
#include <string>
11+
#include <complex>
12+
13+
namespace eigenpy {
14+
15+
namespace details {
16+
17+
/// Trait to remove const&
18+
template <typename T>
19+
struct remove_cvref : std::remove_cv<typename std::remove_reference<T>::type> {
20+
};
21+
22+
/// Trait to detect if T is a class or an union
23+
template <typename T>
24+
struct is_class_or_union
25+
: std::integral_constant<bool, std::is_class<T>::value ||
26+
std::is_union<T>::value> {};
27+
28+
/// trait to detect if T is a std::complex managed by Boost Python
29+
template <typename T>
30+
struct is_python_complex : std::false_type {};
31+
32+
/// From boost/python/converter/builtin_converters
33+
template <>
34+
struct is_python_complex<std::complex<float> > : std::true_type {};
35+
template <>
36+
struct is_python_complex<std::complex<double> > : std::true_type {};
37+
template <>
38+
struct is_python_complex<std::complex<long double> > : std::true_type {};
39+
40+
template <typename T>
41+
struct is_python_primitive_type_helper
42+
: std::integral_constant<bool, !is_class_or_union<T>::value ||
43+
std::is_same<T, std::string>::value ||
44+
std::is_same<T, std::wstring>::value ||
45+
is_python_complex<T>::value> {};
46+
47+
/// Trait to detect if T is a Python primitive type
48+
template <typename T>
49+
struct is_python_primitive_type
50+
: is_python_primitive_type_helper<typename remove_cvref<T>::type> {};
51+
52+
} // namespace details
53+
54+
} // namespace eigenpy
55+
56+
#endif // ifndef __eigenpy_utils_traits_hpp__

include/eigenpy/variant.hpp

+7-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define __eigenpy_utils_variant_hpp__
77

88
#include "eigenpy/fwd.hpp"
9+
#include "eigenpy/utils/traits.hpp"
10+
#include "eigenpy/utils/python-compat.hpp"
911

1012
#include <boost/python.hpp>
1113
#include <boost/variant.hpp>
@@ -146,7 +148,7 @@ struct NumericConvertibleImpl<
146148
std::is_integral<T>::value>::type> {
147149
static void* convertible(PyObject* obj) {
148150
// PyLong return true for bool type
149-
return (PyLong_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
151+
return (PyInt_Check(obj) && !PyBool_Check(obj)) ? obj : nullptr;
150152
}
151153

152154
static PyTypeObject const* expected_pytype() { return &PyLong_Type; }
@@ -204,18 +206,6 @@ struct VariantValueToObject : VariantVisitorType<PyObject*, Variant> {
204206
using Base::operator();
205207
};
206208

207-
/// Trait to detect if T is a class or an union
208-
template <typename T>
209-
struct is_class_or_union
210-
: std::integral_constant<bool, std::is_class<T>::value ||
211-
std::is_union<T>::value> {};
212-
213-
/// Trait to remove cvref and call is_class_or_union
214-
template <typename T>
215-
struct is_class_or_union_remove_cvref
216-
: is_class_or_union<typename std::remove_cv<
217-
typename std::remove_reference<T>::type>::type> {};
218-
219209
/// Convert {boost,std}::variant<class...> alternative reference to a Python
220210
/// object. This converter return the alternative reference. The code that
221211
/// create the reference holder is taken from \see
@@ -231,14 +221,14 @@ struct VariantRefToObject : VariantVisitorType<PyObject*, Variant> {
231221
}
232222

233223
template <typename T,
234-
typename std::enable_if<!is_class_or_union_remove_cvref<T>::value,
224+
typename std::enable_if<is_python_primitive_type<T>::value,
235225
bool>::type = true>
236226
result_type operator()(T t) const {
237227
return bp::incref(bp::object(t).ptr());
238228
}
239229

240230
template <typename T,
241-
typename std::enable_if<is_class_or_union_remove_cvref<T>::value,
231+
typename std::enable_if<!is_python_primitive_type<T>::value,
242232
bool>::type = true>
243233
result_type operator()(T& t) const {
244234
return bp::detail::make_reference_holder::execute(&t);
@@ -312,7 +302,8 @@ struct ReturnInternalVariant : bp::return_internal_reference<> {
312302
template <class ArgumentPackage>
313303
static PyObject* postcall(ArgumentPackage const& args_, PyObject* result) {
314304
// Don't run return_internal_reference postcall on primitive type
315-
if (PyLong_Check(result) || PyBool_Check(result) || PyFloat_Check(result)) {
305+
if (PyInt_Check(result) || PyBool_Check(result) || PyFloat_Check(result) ||
306+
PyStr_Check(result) || PyComplex_Check(result)) {
316307
return result;
317308
}
318309
return bp::return_internal_reference<>::postcall(args_, result);

0 commit comments

Comments
 (0)