Skip to content

Commit 83ea698

Browse files
committed
[cpp2py] Add option wrapped_members_as_shared_refs
Previously member of a wrapped type were copied on access This commit introduces an option where access of wrapped members will avoid the copy at the cost of keeping the parent object alive for the full lifetime of the newly generated reference
1 parent 5773932 commit 83ea698

File tree

6 files changed

+52
-19
lines changed

6 files changed

+52
-19
lines changed

bin/c++2py.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ parser.add_argument('--includes', '-I', action='append', help='Includes to pass
3737
parser.add_argument('--system_includes', '-isystem', action='append', help='System includes to pass to clang')
3838
parser.add_argument('--cxxflags', default = '', help='Options to pass to clang')
3939
parser.add_argument('--target_file_only', action='store_true', help='Disable recursion into included header files')
40+
parser.add_argument('--wrapped_members_as_shared_refs', action='store_true', help='Disable recursion into included header files')
4041

4142
args = parser.parse_args()
4243

@@ -77,7 +78,8 @@ W= Cpp2Desc(filename = args.filename,
7778
shell_command = shell_command,
7879
parse_all_comments = args.parse_all_comments,
7980
namespace_to_factor= (), # unused now
80-
target_file_only = args.target_file_only
81+
target_file_only = args.target_file_only,
82+
wrapped_members_as_shared_refs = args.wrapped_members_as_shared_refs
8183
)
8284

8385
# Make the desc file

c++/cpp2py/py_converter.hpp

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,36 @@ namespace cpp2py {
116116

117117
// default version is that the type is wrapped.
118118
// Will be specialized for type which are just converted.
119-
template <typename T> struct py_converter {
119+
template <typename TUREF> struct py_converter {
120+
121+
using T = std::decay_t<TUREF>;
122+
static constexpr bool is_ref = std::is_reference_v<TUREF>;
120123

121124
typedef struct {
122125
PyObject_HEAD;
123126
T *_c;
127+
PyObject *parent = nullptr;
124128
} py_type;
125129

126130
using is_wrapped_type = void; // to recognize
127131

128-
template <typename U> static PyObject *c2py(U &&x) {
132+
template <typename U> static PyObject *c2py(U &&x, PyObject *parent = nullptr) {
129133
PyTypeObject *p = get_type_ptr(typeid(T));
130134
if (p == nullptr) return NULL;
131135
py_type *self = (py_type *)p->tp_alloc(p, 0);
132-
if (self != NULL) { self->_c = new T{std::forward<U>(x)}; }
136+
if (self != NULL) {
137+
if constexpr (is_ref && wrapped_members_as_shared_refs) {
138+
// Keep parent alive for lifetime of self
139+
if (parent != nullptr) {
140+
self->parent = parent;
141+
Py_INCREF(parent);
142+
self->_c = &x;
143+
return (PyObject *)self;
144+
}
145+
}
146+
// Create heap copy of x to guarantee lifetime
147+
self->_c = new T{std::forward<U>(x)};
148+
}
133149
return (PyObject *)self;
134150
}
135151

@@ -147,7 +163,7 @@ namespace cpp2py {
147163
if (p == nullptr) return false;
148164
if (PyObject_TypeCheck(ob, p)) {
149165
if (((py_type *)ob)->_c != NULL) return true;
150-
auto err = std::string{"Severe internal error : Python object of "} + p->tp_name + " has a _c NULL pointer !!";
166+
auto err = std::string{"Severe internal error : Python object of "} + p->tp_name + " has a _c NULL pointer !!";
151167
if (raise_exception) PyErr_SetString(PyExc_TypeError, err.c_str());
152168
return false;
153169
}
@@ -157,6 +173,12 @@ namespace cpp2py {
157173
}
158174
};
159175

176+
// is_wrapped<T> if py_converter has been reimplemented.
177+
template <typename T, class = void> struct is_wrapped : std::false_type {};
178+
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};
179+
180+
template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;
181+
160182
// helpers for better error message
161183
// some class (e.g. range !) only have ONE conversion, i.e. C -> Py, but not both
162184
// we need to distinguish
@@ -168,9 +190,15 @@ namespace cpp2py {
168190
struct does_have_a_converterC2Py<T, std::void_t<decltype(py_converter<std::decay_t<T>>::c2py(std::declval<T>()))>> : std::true_type {};
169191

170192
// We only use these functions in the code, not directly the converter
171-
template <typename T> static PyObject *convert_to_python(T &&x) {
193+
template <typename T> static PyObject *convert_to_python(T &&x, PyObject *parent = nullptr) {
172194
static_assert(does_have_a_converterC2Py<T>::value, "The type does not have a converter from C++ to Python");
173-
return py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
195+
PyObject *r;
196+
if constexpr (is_wrapped_v<std::decay_t<T>>) {
197+
r = py_converter<T>::c2py(std::forward<T>(x), parent);
198+
} else { // Converted type
199+
r = py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
200+
}
201+
return r;
174202
}
175203
template <typename T> static bool convertible_from_python(PyObject *ob, bool raise_exception) {
176204
return py_converter<T>::is_convertible(ob, raise_exception);
@@ -188,12 +216,6 @@ namespace cpp2py {
188216
*
189217
*/
190218

191-
// is_wrapped<T> if py_converter has been reimplemented.
192-
template <typename T, class = void> struct is_wrapped : std::false_type {};
193-
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};
194-
195-
template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;
196-
197219
template <typename T> static auto convert_from_python(PyObject *ob) -> decltype(py_converter<T>::py2c(ob)) {
198220
static_assert(does_have_a_converterPy2C<T>::value, "The type does not have a converter from Python to C++");
199221
return py_converter<T>::py2c(ob);

cpp2py/cpp2desc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Cpp2Desc:
88
""" """
99
def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= (), appname= '',
1010
modulename = '', moduledoc ='', use_properties = False, members_read_only = True, converters = (),
11-
compiler_options=None, includes= None, system_includes= None, libclang_location = None, shell_command = '', parse_all_comments = True, target_file_only = False):
11+
compiler_options=None, includes= None, system_includes= None, libclang_location = None, shell_command = '', parse_all_comments = True, target_file_only = False, wrapped_members_as_shared_refs = False):
1212
"""
1313
Parse the file at construction
1414
@@ -59,9 +59,12 @@ def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= ()
5959
6060
target_file_only : bool
6161
Neglect any included files during desc generation [default = False]
62+
63+
wrapped_members_as_shared_refs : bool
64+
For classes with members which are a wrapped type, do not copy them on access but return them as shared references instead. Note that members with types that are only converted (e.g. std::vector) will continue to be copied on access [default = False]
6265
"""
6366
for x in ['filename', 'namespaces', 'classes', 'namespace_to_factor', 'appname', 'modulename', 'moduledoc',
64-
'use_properties', 'members_read_only', 'shell_command', 'target_file_only']:
67+
'use_properties', 'members_read_only', 'shell_command', 'target_file_only', 'wrapped_members_as_shared_refs']:
6568
setattr(self, x, locals()[x])
6669
self.DE = dependency_analyzer.DependencyAnalyzer(converters)
6770
# parse the file

cpp2py/mako/desc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from cpp2py.wrap_generator import *
44

55
# The module
6-
module = module_(full_name = "${W.modulename}", doc = r"${doc.replace_latex(W.moduledoc)}", app_name = "${W.appname}")
6+
module = module_(full_name = "${W.modulename}", doc = r"${doc.replace_latex(W.moduledoc)}", app_name = "${W.appname}", wrapped_members_as_shared_refs = ${W.wrapped_members_as_shared_refs})
77

88
# Imports
99
%if import_list:

cpp2py/mako/wrap.cxx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include<iostream> //for std::cout...
55
using dcomplex = std::complex<double>;
66

7+
// global options
8+
constexpr bool wrapped_members_as_shared_refs = ${int(module.wrapped_members_as_shared_refs)};
9+
710
// first the basic stuff
811
#include <cpp2py/cpp2py.hpp>
912
#include <cpp2py/converters/string.hpp>
@@ -242,6 +245,7 @@ static PyObject* ${c.py_type}_richcompare (PyObject *a, PyObject *b, int op);
242245
typedef struct {
243246
PyObject_HEAD
244247
${c.c_type} * _c;
248+
PyObject * parent = nullptr;
245249
} ${c.py_type};
246250

247251
## The new function, only if there is constructor
@@ -257,7 +261,8 @@ static PyObject* ${c.py_type}_new(PyTypeObject *type, PyObject *args, PyObject *
257261

258262
// dealloc
259263
static void ${c.py_type}_dealloc(${c.py_type}* self) {
260-
if (self->_c != NULL) delete self->_c; // should never be null, but I protect it anyway
264+
if ((self->_c != NULL) and (self->parent == nullptr)) delete self->_c; // should never be null, but I protect it anyway
265+
Py_XDECREF(self->parent);
261266
Py_TYPE(self)->tp_free((PyObject*)self);
262267
}
263268

@@ -612,7 +617,7 @@ template <> struct py_converter<${en.c_name}> {
612617

613618
static PyObject * ${c.py_type}__get_member_${m.py_name} (PyObject *self, void *closure) {
614619
auto & self_c = convert_from_python<${c.c_type}>(self);
615-
return convert_to_python(self_c.${m.c_name});
620+
return convert_to_python(self_c.${m.c_name}, self);
616621
}
617622

618623
%if not m.read_only:

cpp2py/wrap_generator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ class module_:
688688
"""
689689
Representation of a module
690690
"""
691-
def __init__(self, full_name, doc = '', app_name = None) :
691+
def __init__(self, full_name, doc = '', app_name = None, wrapped_members_as_shared_refs = False) :
692692
"""
693693
Parameters
694694
----------
@@ -701,6 +701,7 @@ def __init__(self, full_name, doc = '', app_name = None) :
701701
702702
"""
703703
self.full_name = full_name if app_name is None or app_name=="triqs" else app_name+"."+full_name
704+
self.wrapped_members_as_shared_refs = wrapped_members_as_shared_refs
704705
self.name = full_name.rsplit('.',1)[-1]
705706
self.doc = doc
706707
self.classes = {} # dict : string -> class_. Key is the Python type

0 commit comments

Comments
 (0)