From 0871228f42905d265fcf096007bf2ed9664c3aa1 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 22 Apr 2016 16:52:15 +0200 Subject: [PATCH] opaque<> clarifications --- docs/advanced.rst | 44 +++++++++++++++++++++++++++++++++++++------ example/example14.cpp | 24 ++++++++++++++++++++--- example/example14.py | 15 +++++++++++++++ example/example14.ref | 9 ++++----- 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 9df5cb3f..8006cee0 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1145,8 +1145,8 @@ linked lists, hash tables, etc. This even works in a recursive manner, for instance to deal with lists of hash maps of pairs of elementary and custom types, etc. -A fundamental limitation of this approach is that the internal conversion -between Python and C++ types involves a copy operation that prevents +However, a fundamental limitation of this approach is that internal conversions +between Python and C++ types involve a copy operation that prevents pass-by-reference semantics. What does this mean? Suppose we bind the following function @@ -1167,10 +1167,41 @@ and call it as follows from Python: [5, 6] As you can see, when passing STL data structures by reference, modifications -are not propagated back the Python side. To deal with situations where this -desirable, pybind11 contains a simple template wrapper class named ``opaque``. +are not propagated back the Python side. A similar situation arises when +exposing STL data structures using the ``def_readwrite`` or ``def_readonly`` +functions: -``opaque`` disables the underlying template machinery for +.. code-block:: cpp + + /* ... definition ... */ + + class MyClass { + std::vector contents; + }; + + /* ... binding code ... */ + + py::class_(m, "MyClass") + .def(py::init<>) + .def_readwrite("contents", &MyClass::contents); + +In this case, properties can be read and written in their entirety. However, an +``append`` operaton involving such a list type has no effect: + +.. code-block:: python + + >>> m = MyClass() + >>> m.contents = [5, 6] + >>> print(m.contents) + [5, 6] + >>> m.contents.append(7) + >>> print(m.contents) + [5, 6] + +To deal with both of the above situations, pybind11 contains a simple template +wrapper class named ``opaque``. + +``opaque`` disables pybind11's template-based conversion machinery for ``T`` and can be used to treat STL types as opaque objects, whose contents are never inspected or extracted (thus, they can be passed by reference). The downside of this approach is that it the binding code becomes a bit more @@ -1186,7 +1217,8 @@ set of admissible operations. .. seealso:: The file :file:`example/example14.cpp` contains a complete example that - demonstrates how to create opaque types using pybind11 in more detail. + demonstrates how to create and expose opaque types using pybind11 in more + detail. Pickling support ================ diff --git a/example/example14.cpp b/example/example14.cpp index a7d285a0..97b4d1fe 100644 --- a/example/example14.cpp +++ b/example/example14.cpp @@ -13,6 +13,11 @@ typedef std::vector StringList; +class ClassWithSTLVecProperty { +public: + StringList stringList; +}; + void init_ex14(py::module &m) { py::class_>(m, "StringList") .def(py::init<>()) @@ -20,11 +25,24 @@ void init_ex14(py::module &m) { .def("pop_back", [](py::opaque &l) { l->pop_back(); }) .def("back", [](py::opaque &l) { return l->back(); }); + py::class_(m, "ClassWithSTLVecProperty") + .def(py::init<>()) + /* Need to cast properties to opaque types to avoid pybind11-internal + STL conversion code from becoming active */ + .def_readwrite("stringList", (py::opaque ClassWithSTLVecProperty:: *) + &ClassWithSTLVecProperty::stringList); + m.def("print_opaque_list", [](py::opaque &_l) { StringList &l = _l; - std::cout << "Opaque list: " << std::endl; - for (auto entry : l) - std::cout << " " << entry << std::endl; + std::cout << "Opaque list: ["; + bool first = true; + for (auto entry : l) { + if (!first) + std::cout << ", "; + std::cout << entry; + first = false; + } + std::cout << "]" << std::endl; }); m.def("return_void_ptr", []() { return (void *) 1234; }); diff --git a/example/example14.py b/example/example14.py index 82d14de8..95d014d6 100644 --- a/example/example14.py +++ b/example/example14.py @@ -4,10 +4,13 @@ import sys sys.path.append('.') from example import StringList, print_opaque_list +from example import ClassWithSTLVecProperty from example import return_void_ptr, print_void_ptr from example import return_null_str, print_null_str from example import return_unique_ptr +##### + l = StringList() l.push_back("Element 1") l.push_back("Element 2") @@ -16,9 +19,21 @@ print("Back element is %s" % l.back()) l.pop_back() print_opaque_list(l) +##### +cvp = ClassWithSTLVecProperty() +print_opaque_list(cvp.stringList) + +cvp.stringList = l +cvp.stringList.push_back("Element 3") +print_opaque_list(cvp.stringList) + +##### + print_void_ptr(return_void_ptr()) print(return_null_str()) print_null_str(return_null_str()) +##### + print(return_unique_ptr()) diff --git a/example/example14.ref b/example/example14.ref index b4768efe..63c46b7e 100644 --- a/example/example14.ref +++ b/example/example14.ref @@ -1,9 +1,8 @@ -Opaque list: - Element 1 - Element 2 +Opaque list: [Element 1, Element 2] Back element is Element 2 -Opaque list: - Element 1 +Opaque list: [Element 1] +Opaque list: [] +Opaque list: [Element 1, Element 3] Got void ptr : 1234 None Got null str : 0