Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
513 changes: 510 additions & 3 deletions autowrap/CodeGenerator.py

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion autowrap/DeclResolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class is the same as the name of the C++ class.
There are a few additional hints you can give to the wrapper, for classes these are:
- wrap-ignore: will not create a wrapper for this class (e.g. abstract
base class that needs to be known to Cython but cannot be wrapped)
- wrap-view: generate a companion ${ClassName}View class that provides
in-place access to members. The view class allows direct
modification of nested objects without copies. Methods
returning T& (mutable reference) return views instead of copies.
- wrap-manual-memory: will allow the user to provide manual memory
management of self.inst, therefore the class will
not provide the automated __dealloc__ and inst
Expand Down Expand Up @@ -173,6 +177,7 @@ class ResolvedClass(object):
cpp_decl: PXDParser.CppClassDecl
ns: AnyStr
wrap_ignore: bool
wrap_view: bool
no_pxd_import: bool
wrap_manual_memory: Union[bool, List[AnyStr]]
wrap_hash: List[AnyStr]
Expand All @@ -193,6 +198,7 @@ def __init__(self, name, methods, attributes, decl, instance_map, local_map):
self.ns = get_namespace(decl.pxd_path, DEFAULT_NAMESPACE)

self.wrap_ignore = decl.annotations.get("wrap-ignore", False)
self.wrap_view = decl.annotations.get("wrap-view", False)
self.no_pxd_import = decl.annotations.get("no-pxd-import", False)
self.wrap_manual_memory = decl.annotations.get("wrap-manual-memory", [])
# fix previous code where we had a bool ...
Expand Down Expand Up @@ -560,8 +566,14 @@ def _resolve_class_decl(class_decl, typedef_mapping, i_mapping):
for mname, mdcls in class_decl.methods.items():
for mdcl in mdcls:
ignore = mdcl.annotations.get("wrap-ignore", False)
# Keep wrap-ignore methods that return mutable references (T&)
# since these are useful for wrap-view generation even when
# the main class method is ignored. Filter out other wrap-ignore methods.
if ignore:
continue
result_type = mdcl.result_type
if not (result_type.is_ref and not result_type.is_const):
# Not a mutable reference return - skip as before
continue
if mdcl.name == class_decl.name:
r_method = _resolve_constructor(cinst_name, mdcl, i_mapping, local_mapping)
else:
Expand Down
55 changes: 55 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ and for methods by putting them on the same line. The currently supported
directives are:

- `wrap-ignore`: Will not create a wrapper for this function or class (e.g. abstract base class that needs to be known to Cython but cannot be wrapped)
- `wrap-view`: Generate a companion `${ClassName}View` class that provides in-place access to members. The main wrapper class returns copies (safe, but modifications don't affect the original). The view class holds a reference to the parent and allows direct modification:
- Public attributes become view properties (getters return views of nested objects, setters modify in-place)
- Methods returning `T&` (mutable reference) return views instead of copies (note: these methods need `wrap-ignore` on main class due to Cython limitations with reference returns)
- Views keep the parent object alive - chaining and storing views is safe
- Use `obj.view()` on the main class to get a view instance
- `wrap-iter-begin`: For begin iterators
- `wrap-iter-end`: For end iterators
- `wrap-attach`: Attach to a specific class (can be used for static functions or nested classes)
Expand Down Expand Up @@ -280,6 +285,56 @@ namespace std {
}
```

#### wrap-view Example

The `wrap-view` directive generates a companion view class for in-place member modification:

```cython
cdef cppclass Inner:
# wrap-view
int value

cdef cppclass Outer:
# wrap-view
Inner inner_member # Public attribute
Inner & getInner() # wrap-ignore (T& returns need this)
const Inner & getConstInner() # wrap-ignore (const T& also needs this)
```

**Note:** Methods returning `T&` or `const T&` must be marked `wrap-ignore` on the main
class because Cython cannot properly handle reference return types. However, these methods
are still generated on the view class where they work correctly via pointer access.

This generates `Inner`, `InnerView`, `Outer`, and `OuterView` classes. Usage:

```python
>>> outer = Outer()
>>> outer.inner_member.value = 42 # Modifies a COPY - original unchanged!
>>> view = outer.view()
>>> view.inner_member.value = 42 # Modifies IN-PLACE - original changed!
>>> inner_view = view.getInner() # Returns InnerView, not copy
>>> inner_view.value = 100 # In-place modification
```

**Chaining:** Views can be chained in a single expression:

```python
>>> container.view().getOuter().getInner().value = 999
```

**Lifetime:** Views keep the root object alive. You can safely:
- Store views and use them later
- Delete intermediate views in a chain
- Even delete the original object - views hold a reference to keep it alive

```python
>>> def get_deep_view():
... c = Container()
... return c.view().getOuter().getInner()
>>> view = get_deep_view() # Container went out of scope but view keeps it alive
>>> view.value = 42 # Still works!
```

### Docstrings

Docstrings can be added to classes and methods using the `wrap-doc` statement. Multi line docs are supported with
Expand Down
Loading