diff --git a/docs/advanced.rst b/docs/advanced.rst index 9df5cb3fe..8006cee03 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 a7d285a08..97b4d1fe9 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 82d14de84..95d014d6e 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 b4768efe9..63c46b7ec 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