aligator  0.14.0
A primal-dual augmented Lagrangian-type solver for nonlinear trajectory optimization.
 
Loading...
Searching...
No Matches
polymorphic.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <boost/core/demangle.hpp>
7#include <variant>
8
9// Required class template specialization for
10// boost::python::register_ptr_to_python<> to work.
11namespace boost::python {
12template <class T, class A> struct pointee<xyz::polymorphic<T, A>> {
13 typedef T type;
14};
15} // namespace boost::python
16
17namespace aligator {
18namespace python {
19namespace bp = boost::python;
20
23template <class Poly> inline void register_polymorphic_to_python() {
24 using X = typename bp::pointee<Poly>::type;
25 bp::objects::class_value_wrapper<
26 Poly, bp::objects::make_ptr_instance<
27 X, bp::objects::pointer_holder<Poly, X>>>();
28}
29
30template <class Poly> struct PolymorphicVisitor;
31
70
71template <class Base, class A>
73 : bp::def_visitor<PolymorphicVisitor<xyz::polymorphic<Base, A>>> {
75 static_assert(std::is_polymorphic_v<Base>, "Type should be polymorphic!");
76
77 template <class PyClass> void visit(PyClass &cl) const {
78 using T = typename PyClass::wrapped_type;
79 using meta = typename PyClass::metadata;
80 using held = typename meta::held_type;
81 typedef bp::converter::implicit<held, Poly> functions;
82
84 ALIGATOR_COMPILER_DIAGNOSTIC_IGNORED_DELETE_NON_ABSTRACT_NON_VIRTUAL_DTOR
85 bp::converter::registry::insert(
86 &functions::convertible, &functions::construct, bp::type_id<Poly>(),
87 &bp::converter::expected_from_python_type_direct<T>::get_pytype);
89
90 // Enable pickling of the Derived Python class.
91 // If a Python class inherit from cl, this call will allow pickle to
92 // serialize/unserialize all the Python class content.
93 // The C++ part will not be serialized but it's managed differently
94 // by PolymorphicWrapper.
95 if constexpr (std::is_base_of_v<boost::python::wrapper<Base>,
96 typename PyClass::wrapped_type>) {
97 cl.enable_pickling_(true);
98 }
99 }
100};
101
105template <class... Bases>
107 : bp::def_visitor<PolymorphicMultiBaseVisitor<Bases...>> {
108
109 template <class... Args> void visit(bp::class_<Args...> &cl) const {
111 }
112};
113
114// Specialize value_holder to allow switching to a non owning holder.
115// This code is similar to boost::python::value_holder code but allow to switch
116// between value or ptr at runtime.
117template <typename Value>
118struct OwningNonOwningHolder : boost::python::instance_holder {
119 typedef Value held_type;
120 typedef Value value_type;
121
122private:
123 struct PtrGetter {
124 Value *operator()(Value &v) { return &v; };
125 Value *operator()(Value *v) { return v; };
126 };
127
128 Value *get_ptr() { return std::visit(PtrGetter(), m_held); }
129
130public:
131 template <typename... Args>
132 OwningNonOwningHolder(PyObject *self, Args... args)
133 : m_held(Value(boost::python::objects::do_unforward(
134 std::forward<Args>(args), 0)...)) {
135 boost::python::detail::initialize_wrapper(self, get_ptr());
136 }
137
138private: // required holder implementation
139 void *holds(boost::python::type_info dst_t, bool) {
140 if (void *wrapped = holds_wrapped(dst_t, get_ptr(), get_ptr()))
141 return wrapped;
142
143 boost::python::type_info src_t = boost::python::type_id<Value>();
144 return src_t == dst_t ? get_ptr()
145 : boost::python::objects::find_static_type(
146 get_ptr(), src_t, dst_t);
147 }
148
149 template <class T>
150 inline void *holds_wrapped(boost::python::type_info dst_t,
151 boost::python::wrapper<T> *, T *p) {
152 return boost::python::type_id<T>() == dst_t ? p : 0;
153 }
154
155 inline void *holds_wrapped(boost::python::type_info, ...) { return 0; }
156
157public: // data members
158 std::variant<Value, Value *> m_held;
159};
160
161// This class replace boost::python::wrapper when we want to use
162// xyz::polymorphic to hold polymorphic class.
163//
164// The following class diagram describe how boost::python::wrapper work:
165//
166// PyDerived -----------|
167// ^ 1 m_self |
168// | |
169// PyBase <--- wrapper--
170// ^
171// |
172// Base
173//
174// PyDerived is a Python class that inherit from PyBase.
175// Wrapper will hold a non owning ptr to PyDerived.
176// This can lead to ownership issue when PyBase is stored in C++ object.
177//
178// To avoid this issue, at each PolymorphicWrapper copy, we will deep copy and
179// own m_self in copied_owner.
180// When copied, m self will default construct a new PyBase instance.
181// To avoid having two PyBase instance (the one copied in C++ and the other one
182// default constructed by Boost.Python) we store PyBase in a custom holder
183// (OwningNonOwningHolder) and we reset this holder to hold a ptr to the new C++
184// instance.
185template <typename _PyBase, typename _Base>
186struct PolymorphicWrapper : boost::python::wrapper<_Base> {
187 using PyBase = _PyBase;
188 using Base = _Base;
189
192 : bp::wrapper<Base>(o) {
193 deepcopy_owner();
194 }
196
198 if (this == &o) {
199 return *this;
200 }
201 bp::wrapper<Base>::operator=(o);
202 deepcopy_owner();
203 return *this;
204 }
206
207private:
208 void deepcopy_owner() {
209 namespace bp = boost::python;
210 if (PyObject *owner_ptr = bp::detail::wrapper_base_::get_owner(*this)) {
211 bp::object copy = bp::import("copy");
212 bp::object deepcopy = copy.attr("deepcopy");
213
214 // bp::object decrements the refcount at destruction
215 // so by calling it with borrowed we incref owner_ptr to avoid destroying
216 // it.
217 bp::object owner{bp::handle<>(bp::borrowed(owner_ptr))};
218
219 // Deepcopy is safer to copy derived class values but don't copy C++ class
220 // content.
221 // We store the copied owner into a member variable to take the
222 // ownership.
223 copied_owner = deepcopy(owner);
224 PyObject *copied_owner_ptr = copied_owner.ptr();
225
226 // copied_owner contains a OwningNonOwningHolder<PyBase> that contains a
227 // PyBase (different from this).
228 // We modify this value_holder specialization to store this ptr (non
229 // owning) instead. This will avoid that Python and C++ work with
230 // different object. Since copied_owner is owned by this, there is no
231 // memory leak.
232 // TODO check value holder type
233 bp::objects::instance<> *inst =
234 ((bp::objects::instance<> *)copied_owner_ptr);
235 OwningNonOwningHolder<PyBase> *value_holder =
236 dynamic_cast<OwningNonOwningHolder<PyBase> *>(inst->objects);
237 if (!value_holder) {
238 std::ostringstream error_msg;
239 error_msg << "OwningNonOwningHolder should be setup for "
240 << boost::core::demangle(typeid(PyBase).name()) << " type"
241 << std::endl;
242 throw std::logic_error(error_msg.str());
243 }
244 value_holder->m_held = static_cast<PyBase *>(this);
245
246 bp::detail::initialize_wrapper(copied_owner_ptr, this);
247 }
248 }
249
250private:
251 bp::object copied_owner;
252};
253
254namespace internal {
255
261template <class poly_ref, class MakeHolder> struct ToPythonIndirectPoly {
262 using poly_type = boost::remove_cv_ref_t<poly_ref>;
263
264 template <class U> PyObject *operator()(U const &x) const {
265 return execute(const_cast<U &>(x));
266 }
267#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
268 PyTypeObject const *get_pytype() {
269 return boost::python::converter::registered_pytype<poly_type>::get_pytype();
270 }
271#endif
272
273private:
274 template <class T, class A>
275 static PyObject *execute(const xyz::polymorphic<T, A> &p) {
276 if (p.valueless_after_move())
277 return bp::detail::none();
278 T *q = const_cast<T *>(boost::get_pointer(p));
279 assert(q);
280 return bp::to_python_indirect<const T &, MakeHolder>{}(q);
281 }
282};
283
284} // namespace internal
285} // namespace python
286} // namespace aligator
287
288namespace boost {
289namespace python {
290
291template <class T, class A, class MakeHolder>
292struct to_python_indirect<xyz::polymorphic<T, A> &, MakeHolder>
293 : aligator::python::internal::ToPythonIndirectPoly<xyz::polymorphic<T, A> &,
294 MakeHolder> {};
295
296template <class T, class A, class MakeHolder>
297struct to_python_indirect<const xyz::polymorphic<T, A> &, MakeHolder>
298 : aligator::python::internal::ToPythonIndirectPoly<
299 const xyz::polymorphic<T, A> &, MakeHolder> {};
300
301} // namespace python
302} // namespace boost
bool valueless_after_move() const noexcept
#define ALIGATOR_COMPILER_DIAGNOSTIC_PUSH
macros for pragma push/pop/ignore deprecated warnings
Definition fwd.hpp:44
#define ALIGATOR_COMPILER_DIAGNOSTIC_POP
Definition fwd.hpp:45
The Python bindings.
Definition blk-matrix.hpp:5
void register_polymorphic_to_python()
Expose a polymorphic value type, e.g. xyz::polymorphic<T, A>.
Main package namespace.
Definition data.hpp:14
T * get_pointer(::xyz::polymorphic< T, A > const &x)
Definition fwd.hpp:8
Definition fwd.hpp:52
std::variant< Value, Value * > m_held
OwningNonOwningHolder(PyObject *self, Args... args)
void visit(bp::class_< Args... > &cl) const
PolymorphicWrapper & operator=(PolymorphicWrapper &&o)=default
PolymorphicWrapper(const PolymorphicWrapper &o)
PolymorphicWrapper & operator=(const PolymorphicWrapper &o)
PolymorphicWrapper(PolymorphicWrapper &&o)=default