Skip to content

Commit

Permalink
[cpp2py] Add option wrapped_members_as_shared_refs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Wentzell committed May 26, 2020
1 parent 5773932 commit 83ea698
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 19 deletions.
4 changes: 3 additions & 1 deletion bin/c++2py.in
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ parser.add_argument('--includes', '-I', action='append', help='Includes to pass
parser.add_argument('--system_includes', '-isystem', action='append', help='System includes to pass to clang')
parser.add_argument('--cxxflags', default = '', help='Options to pass to clang')
parser.add_argument('--target_file_only', action='store_true', help='Disable recursion into included header files')
parser.add_argument('--wrapped_members_as_shared_refs', action='store_true', help='Disable recursion into included header files')

args = parser.parse_args()

Expand Down Expand Up @@ -77,7 +78,8 @@ W= Cpp2Desc(filename = args.filename,
shell_command = shell_command,
parse_all_comments = args.parse_all_comments,
namespace_to_factor= (), # unused now
target_file_only = args.target_file_only
target_file_only = args.target_file_only,
wrapped_members_as_shared_refs = args.wrapped_members_as_shared_refs
)

# Make the desc file
Expand Down
46 changes: 34 additions & 12 deletions c++/cpp2py/py_converter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,36 @@ namespace cpp2py {

// default version is that the type is wrapped.
// Will be specialized for type which are just converted.
template <typename T> struct py_converter {
template <typename TUREF> struct py_converter {

using T = std::decay_t<TUREF>;
static constexpr bool is_ref = std::is_reference_v<TUREF>;

typedef struct {
PyObject_HEAD;
T *_c;
PyObject *parent = nullptr;
} py_type;

using is_wrapped_type = void; // to recognize

template <typename U> static PyObject *c2py(U &&x) {
template <typename U> static PyObject *c2py(U &&x, PyObject *parent = nullptr) {
PyTypeObject *p = get_type_ptr(typeid(T));
if (p == nullptr) return NULL;
py_type *self = (py_type *)p->tp_alloc(p, 0);
if (self != NULL) { self->_c = new T{std::forward<U>(x)}; }
if (self != NULL) {
if constexpr (is_ref && wrapped_members_as_shared_refs) {
// Keep parent alive for lifetime of self
if (parent != nullptr) {
self->parent = parent;
Py_INCREF(parent);
self->_c = &x;
return (PyObject *)self;
}
}
// Create heap copy of x to guarantee lifetime
self->_c = new T{std::forward<U>(x)};
}
return (PyObject *)self;
}

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

// is_wrapped<T> if py_converter has been reimplemented.
template <typename T, class = void> struct is_wrapped : std::false_type {};
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};

template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;

// helpers for better error message
// some class (e.g. range !) only have ONE conversion, i.e. C -> Py, but not both
// we need to distinguish
Expand All @@ -168,9 +190,15 @@ namespace cpp2py {
struct does_have_a_converterC2Py<T, std::void_t<decltype(py_converter<std::decay_t<T>>::c2py(std::declval<T>()))>> : std::true_type {};

// We only use these functions in the code, not directly the converter
template <typename T> static PyObject *convert_to_python(T &&x) {
template <typename T> static PyObject *convert_to_python(T &&x, PyObject *parent = nullptr) {
static_assert(does_have_a_converterC2Py<T>::value, "The type does not have a converter from C++ to Python");
return py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
PyObject *r;
if constexpr (is_wrapped_v<std::decay_t<T>>) {
r = py_converter<T>::c2py(std::forward<T>(x), parent);
} else { // Converted type
r = py_converter<std::decay_t<T>>::c2py(std::forward<T>(x));
}
return r;
}
template <typename T> static bool convertible_from_python(PyObject *ob, bool raise_exception) {
return py_converter<T>::is_convertible(ob, raise_exception);
Expand All @@ -188,12 +216,6 @@ namespace cpp2py {
*
*/

// is_wrapped<T> if py_converter has been reimplemented.
template <typename T, class = void> struct is_wrapped : std::false_type {};
template <typename T> struct is_wrapped<T, typename py_converter<T>::is_wrapped_type> : std::true_type {};

template <typename T> constexpr bool is_wrapped_v = is_wrapped<T>::value;

template <typename T> static auto convert_from_python(PyObject *ob) -> decltype(py_converter<T>::py2c(ob)) {
static_assert(does_have_a_converterPy2C<T>::value, "The type does not have a converter from Python to C++");
return py_converter<T>::py2c(ob);
Expand Down
7 changes: 5 additions & 2 deletions cpp2py/cpp2desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Cpp2Desc:
""" """
def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= (), appname= '',
modulename = '', moduledoc ='', use_properties = False, members_read_only = True, converters = (),
compiler_options=None, includes= None, system_includes= None, libclang_location = None, shell_command = '', parse_all_comments = True, target_file_only = False):
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):
"""
Parse the file at construction
Expand Down Expand Up @@ -59,9 +59,12 @@ def __init__(self, filename, namespaces=(), classes= (), namespace_to_factor= ()
target_file_only : bool
Neglect any included files during desc generation [default = False]
wrapped_members_as_shared_refs : bool
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]
"""
for x in ['filename', 'namespaces', 'classes', 'namespace_to_factor', 'appname', 'modulename', 'moduledoc',
'use_properties', 'members_read_only', 'shell_command', 'target_file_only']:
'use_properties', 'members_read_only', 'shell_command', 'target_file_only', 'wrapped_members_as_shared_refs']:
setattr(self, x, locals()[x])
self.DE = dependency_analyzer.DependencyAnalyzer(converters)
# parse the file
Expand Down
2 changes: 1 addition & 1 deletion cpp2py/mako/desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from cpp2py.wrap_generator import *

# The module
module = module_(full_name = "${W.modulename}", doc = r"${doc.replace_latex(W.moduledoc)}", app_name = "${W.appname}")
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})

# Imports
%if import_list:
Expand Down
9 changes: 7 additions & 2 deletions cpp2py/mako/wrap.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include<iostream> //for std::cout...
using dcomplex = std::complex<double>;

// global options
constexpr bool wrapped_members_as_shared_refs = ${int(module.wrapped_members_as_shared_refs)};

// first the basic stuff
#include <cpp2py/cpp2py.hpp>
#include <cpp2py/converters/string.hpp>
Expand Down Expand Up @@ -242,6 +245,7 @@ static PyObject* ${c.py_type}_richcompare (PyObject *a, PyObject *b, int op);
typedef struct {
PyObject_HEAD
${c.c_type} * _c;
PyObject * parent = nullptr;
} ${c.py_type};

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

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

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

static PyObject * ${c.py_type}__get_member_${m.py_name} (PyObject *self, void *closure) {
auto & self_c = convert_from_python<${c.c_type}>(self);
return convert_to_python(self_c.${m.c_name});
return convert_to_python(self_c.${m.c_name}, self);
}

%if not m.read_only:
Expand Down
3 changes: 2 additions & 1 deletion cpp2py/wrap_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ class module_:
"""
Representation of a module
"""
def __init__(self, full_name, doc = '', app_name = None) :
def __init__(self, full_name, doc = '', app_name = None, wrapped_members_as_shared_refs = False) :
"""
Parameters
----------
Expand All @@ -701,6 +701,7 @@ def __init__(self, full_name, doc = '', app_name = None) :
"""
self.full_name = full_name if app_name is None or app_name=="triqs" else app_name+"."+full_name
self.wrapped_members_as_shared_refs = wrapped_members_as_shared_refs
self.name = full_name.rsplit('.',1)[-1]
self.doc = doc
self.classes = {} # dict : string -> class_. Key is the Python type
Expand Down

0 comments on commit 83ea698

Please sign in to comment.