Major improvements to the std::function<> caster (roundtrips, cyclic GC) #95
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This commit adds:
Roundtrip support! When a Python function has been wrapped in a
std::function<>
, subsequent conversion to a Python object will return the original Python function object.Note that this is the opposite of pybind11's
std::function<>
caster, where roundtrip support is implemented for C function pointers (which isn't possible in nanobind currently, and seems less useful in retrospect).Building on the previous point, the following C++ snippet
can be used to attempt a conversion of a
std::function<>
instance into a Python object. This will either return the function object or an invalid (!o.is_valid()
) object if the conversion fails.What is the point of these changes? A useful feature of nanobind is that one can set callback methods on bound C++ instances that redirect control flow back to Python.
A major potential issue here are reference leaks. What if the lambda function assigned to
a.f
captures some variables from the surrounding environment, which in turn reference the instancea
? Then we have a reference cycle that spans the Python <-> C++ boundary, and that whole set of objects will never be deleted.Fortunately, Python provides a garbage collector that can collect such cycles, but we must provide it with further information so that it can properly do its job. It must be able to traverse the C++ instance to discover contained Python objects.
Below is a fully worked out example that realizes such a traversal using the newly added features.
This commit also adds an example of such a cycle to the test suite, which will fail if it cannot be garbage-collected.