@@ -22,86 +22,280 @@ struct callable_type_data;
2222
2323namespace pydynd {
2424
25- /* *
26- * A container class for managing the local lifetime of
27- * PyObject *.
28- *
29- * Throws an exception if the object passed into the constructor
30- * is NULL.
31- */
32- class pyobject_ownref {
33- PyObject *m_obj;
25+ template <bool not_null = false >
26+ inline void incref (PyObject *) noexcept ;
27+
28+ template <>
29+ inline void incref<false >(PyObject *obj) noexcept
30+ {
31+ Py_XINCREF (obj);
32+ }
33+
34+ template <>
35+ inline void incref<true >(PyObject *obj) noexcept
36+ {
37+ Py_INCREF (obj);
38+ }
39+
40+ template <bool not_null>
41+ inline void decref (PyObject *) noexcept ;
42+
43+ template <>
44+ inline void decref<false >(PyObject *obj) noexcept
45+ {
46+ Py_XDECREF (obj);
47+ }
48+
49+ template <>
50+ inline void decref<true >(PyObject *obj) noexcept
51+ {
52+ Py_DECREF (obj);
53+ }
54+
55+ template <bool owns_ref, bool not_null>
56+ inline void incref_if_owned (PyObject *obj) noexcept ;
57+
58+ template <>
59+ inline void incref_if_owned<true , true >(PyObject *obj) noexcept
60+ {
61+ Py_INCREF (obj);
62+ }
63+
64+ template <>
65+ inline void incref_if_owned<true , false >(PyObject *obj) noexcept
66+ {
67+ Py_XINCREF (obj);
68+ }
69+
70+ template <>
71+ inline void incref_if_owned<false , true >(PyObject *obj) noexcept
72+ {
73+ }
74+
75+ template <>
76+ inline void incref_if_owned<false , false >(PyObject *obj) noexcept
77+ {
78+ }
79+
80+ template <bool owns_ref, bool not_null>
81+ inline void decref_if_owned (PyObject *obj) noexcept ;
82+
83+ template <>
84+ inline void decref_if_owned<true , true >(PyObject *obj) noexcept
85+ {
86+ Py_DECREF (obj);
87+ }
88+
89+ template <>
90+ inline void decref_if_owned<true , false >(PyObject *obj) noexcept
91+ {
92+ Py_XDECREF (obj);
93+ }
94+
95+ template <>
96+ inline void decref_if_owned<false , true >(PyObject *obj) noexcept
97+ {
98+ }
99+
100+ template <>
101+ inline void decref_if_owned<false , false >(PyObject *obj) noexcept
102+ {
103+ }
34104
35- // Non-copyable
36- pyobject_ownref ( const pyobject_ownref &);
37- pyobject_ownref & operator =( const pyobject_ownref &) ;
105+ template < bool owns_ref = true , bool not_null = true >
106+ class py_ref {
107+ PyObject *o ;
38108
39109public:
40- inline pyobject_ownref () : m_obj(NULL ) {}
41- inline explicit pyobject_ownref (PyObject *obj) : m_obj(obj)
110+ // Explicit specializations for default constructor
111+ // are defined after the class declaration.
112+ // If the class is allowed to be null, it default-initializes to null.
113+ // If the class is not allowed to be null, it default-initializes to None.
114+ py_ref () noexcept ;
115+
116+ // First define an accessor to get the PyObject pointer from
117+ // the wrapper class.
118+ PyObject *get () noexcept { return o; }
119+
120+ // All copy and move constructors are defined as noexcept.
121+ // If the conversion or move operation would move from a type
122+ // that can be null to one that can not, the corresponding
123+ // constructor is declared explicit.
124+
125+ /* If:
126+ * this type allows null,
127+ * Or:
128+ * this type doesn't allow null
129+ * and the input type doesn't allow null,
130+ * Then:
131+ * Allow implicit conversions from the other py_ref type to this type.
132+ */
133+ template <bool other_owns, bool other_not_null,
134+ typename std::enable_if_t <!not_null || (not_null && other_not_null)> * = nullptr >
135+ py_ref (const py_ref<other_owns, other_not_null> &other) noexcept
42136 {
43- if (obj == NULL ) {
44- throw std::runtime_error (" propagating a Python exception..." );
45- }
137+ o = other.o ;
138+ incref_if_owned<owns_ref, not_null>(o);
46139 }
47140
48- inline pyobject_ownref (PyObject *obj, bool inc_ref) : m_obj(obj)
141+ /* If:
142+ * this type doesn't allow null,
143+ * and the input type does,
144+ * Then:
145+ * Require that conversions from the other py_ref type to this type be explcit.
146+ */
147+ template <bool other_owns, bool other_not_null, typename std::enable_if_t <not_null && !other_not_null> * = nullptr >
148+ explicit py_ref (const py_ref<other_owns, other_not_null> &other) noexcept
49149 {
50- if (obj == NULL ) {
51- throw std::runtime_error (" propagating a Python exception..." );
52- }
53- if (inc_ref) {
54- Py_INCREF (m_obj);
55- }
150+ o = other.o ;
151+ incref_if_owned<owns_ref, not_null>(o);
56152 }
57153
58- inline ~pyobject_ownref () { Py_XDECREF (m_obj); }
59-
60- inline PyObject ** obj_addr () { return &m_obj; }
154+ // Move constructors are really only useful for moving
155+ // between types that own their references.
156+ // Only define them for those cases.
61157
62- /* *
63- * Resets the reference owned by this object to the one provided.
64- * This steals a reference to the input parameter, 'obj'.
65- *
66- * \param obj The reference to replace the current one in this object .
158+ /* If:
159+ * both this type and the input type own their reference,
160+ * and we are not moving from a type that allows null values to one that does not,
161+ * Then:
162+ * a move operation can be implicitly performed .
67163 */
68- inline void reset (PyObject *obj)
164+ template <bool other_not_null, typename std::enable_if_t <owns_ref && (other_not_null || !not_null)> * = nullptr >
165+ py_ref (py_ref<true , other_not_null> &&other) noexcept
69166 {
70- if (obj == NULL ) {
71- throw std::runtime_error (" propagating a Python exception..." );
72- }
73- Py_XDECREF (m_obj);
74- m_obj = obj;
167+ o = other.o ;
75168 }
76169
77- /* *
78- * Clears the owned reference to NULL.
170+ /* If:
171+ * both this type and the input type own their reference,
172+ * and we are moving from a type that allows null values to one that does not,
173+ * Then:
174+ * only an explicit move operation can be performed.
79175 */
80- inline void clear ()
176+ template <bool other_not_null, typename std::enable_if_t <owns_ref && !other_not_null && not_null> * = nullptr >
177+ explicit py_ref (py_ref<true , other_not_null> &&other) noexcept
81178 {
82- Py_XDECREF (m_obj);
83- m_obj = NULL ;
179+ o = other.o ;
84180 }
85181
86- /* * Returns a borrowed reference. */
87- inline PyObject *get () const { return m_obj; }
88-
89- /* * Returns a borrowed reference. */
90- inline operator PyObject *() { return m_obj; }
182+ /* When constructing from a PyObject*, the boolean value `consume_ref` must
183+ * be passed to the constructor so it is clear whether or not to
184+ * increment, decrement, or do nothing the reference when storing it in the
185+ * desired smart pointer type. If you do not intend for the smart
186+ * pointer to capture the reference that you own, you should
187+ * specify `consume_ref` as false regardless of whether or not you
188+ * own the reference represented in the PyObject* you pass in.
189+ */
190+ explicit py_ref (PyObject *obj, bool consume_ref) noexcept
191+ {
192+ o = obj;
193+ if (consume_ref) {
194+ decref_if_owned<!owns_ref, not_null>(o);
195+ }
196+ else {
197+ incref_if_owned<owns_ref, not_null>(o);
198+ }
199+ }
91200
92- /* *
93- * Returns the reference owned by this object,
94- * use it like "return obj.release()". After the
95- * call, this object contains NULL.
201+ ~py_ref () { decref_if_owned<owns_ref, not_null>(o); }
202+
203+ // For assignment operators, only allow assignment
204+ // in cases where implicit conversions are also allowed.
205+ // This forces explicit handling of cases that could
206+ // need to have an exception raised.
207+ // For that reason, these are all marked as noexcept.
208+ // Assignment never comsumes a reference.
209+
210+ /* If:
211+ * this type allows null,
212+ * Or:
213+ * this type doesn't allow null
214+ * and the input type doesn't allow null,
215+ * Then:
216+ * Allow assignment from the other py_ref type to this type.
96217 */
97- inline PyObject *release ()
218+ template <bool other_owns, bool other_not_null,
219+ typename std::enable_if_t <!not_null || (not_null && other_not_null)> * = nullptr >
220+ py_ref<owns_ref, not_null> operator =(const py_ref<other_owns, other_not_null> &other) noexcept
98221 {
99- PyObject *result = m_obj;
100- m_obj = NULL ;
101- return result;
222+ decref_if_owned<owns_ref, not_null>(o);
223+ o = other.o ;
224+ incref_if_owned<owns_ref, not_null>(o);
225+ }
226+
227+ // Check if the wrapped pointer is null.
228+ // Always return true if it is not null by definition.
229+ bool is_null () noexcept
230+ {
231+ // This function will be a no-op returning true
232+ // when not_null is false.
233+ if (not_null || (o != nullptr )) {
234+ return false ;
235+ }
236+ return true ;
102237 }
238+
239+ // A debug version of is_null with a purely dynamic check.
240+ bool is_null_dbg () noexcept { return o != nullptr ; }
103241};
104242
243+ // Default constructors for various cases.
244+ template <>
245+ inline py_ref<true , true >::py_ref() noexcept
246+ {
247+ o = Py_None;
248+ incref<true >(o);
249+ }
250+
251+ template <>
252+ inline py_ref<true , false >::py_ref() noexcept
253+ {
254+ o = nullptr ;
255+ }
256+
257+ template <>
258+ inline py_ref<false , true >::py_ref() noexcept
259+ {
260+ o = Py_None;
261+ }
262+
263+ template <>
264+ inline py_ref<false , false >::py_ref() noexcept
265+ {
266+ o = nullptr ;
267+ }
268+
269+ // Convenience aliases for the templated smart pointer classes.
270+ // All the template arguments to py_ref have defaults,
271+ // so py_ref can already be used for pointers that
272+ // own their reference and cannot be null.
273+ // The following aliases fill in the other cases.
274+
275+ using py_ref_with_null = py_ref<true , false >;
276+
277+ using py_borref = py_ref<false , true >;
278+
279+ using py_borref_with_null = py_ref<false , false >;
280+
281+ // To help with the transition to the new classes.
282+ using pyobject_ownref = py_ref<true , false >;
283+
284+ /* Check if a wrapped pointer is null.
285+ * If it is not, return the pointer
286+ * wrapped in the corresponding not_null wrapper type.
287+ * If it is, raise an exception.
288+ * This can be used to forward exceptions from Python.
289+ */
290+ template <bool owns_ref, bool not_null>
291+ py_ref<owns_ref, true > check_null (py_ref<owns_ref, not_null> &o)
292+ {
293+ if (o.is_null ()) {
294+ throw std::runtime_error (" Unexpected null pointer." );
295+ }
296+ return reinterpret_cast <py_ref_tmpl<owns_ref, true >>(o);
297+ }
298+
105299class PyGILState_RAII {
106300 PyGILState_STATE m_gstate;
107301
@@ -149,8 +343,7 @@ inline intptr_t pyobject_as_index(PyObject *index)
149343#endif
150344 }
151345 else {
152- throw std::runtime_error (
153- " Value returned from PyNumber_Index is not an int or long" );
346+ throw std::runtime_error (" Value returned from PyNumber_Index is not an int or long" );
154347 }
155348 if (result == -1 && PyErr_Occurred ()) {
156349 throw std::exception ();
@@ -170,8 +363,7 @@ inline int pyobject_as_int_index(PyObject *index)
170363 throw std::exception ();
171364 }
172365 if (((unsigned long )result & 0xffffffffu ) != (unsigned long )result) {
173- throw std::overflow_error (
174- " overflow converting Python integer to 32-bit int" );
366+ throw std::overflow_error (" overflow converting Python integer to 32-bit int" );
175367 }
176368 return (int )result;
177369}
@@ -248,8 +440,7 @@ inline std::string pyobject_repr(PyObject *obj)
248440 return pystring_as_string (src_repr.get ());
249441}
250442
251- inline void pyobject_as_vector_string (PyObject *list_string,
252- std::vector<std::string> &vector_string)
443+ inline void pyobject_as_vector_string (PyObject *list_string, std::vector<std::string> &vector_string)
253444{
254445 Py_ssize_t size = PySequence_Size (list_string);
255446 vector_string.resize (size);
@@ -259,9 +450,7 @@ inline void pyobject_as_vector_string(PyObject *list_string,
259450 }
260451}
261452
262- inline void pyobject_as_vector_intp (PyObject *list_index,
263- std::vector<intptr_t > &vector_intp,
264- bool allow_int)
453+ inline void pyobject_as_vector_intp (PyObject *list_index, std::vector<intptr_t > &vector_intp, bool allow_int)
265454{
266455 if (allow_int) {
267456 // If permitted, convert an int into a size-1 list
@@ -389,8 +578,7 @@ inline void mark_axis(PyObject *int_axis, int ndim, dynd::bool1 *reduce_axes)
389578 *
390579 * Returns the number of axes which were set.
391580 */
392- inline int pyarg_axis_argument (PyObject *axis, int ndim,
393- dynd::bool1 *reduce_axes)
581+ inline int pyarg_axis_argument (PyObject *axis, int ndim, dynd::bool1 *reduce_axes)
394582{
395583 int axis_count = 0 ;
396584
0 commit comments