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