From eda978e003263fd7a162716d3447bb4aba92332e Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 15 Mar 2016 15:05:40 +0100 Subject: [PATCH] support for opaque types --- CMakeLists.txt | 1 + docs/advanced.rst | 54 ++++++++++++++++++++++++++++++++++++++++- example/example.cpp | 2 ++ example/example14.cpp | 29 ++++++++++++++++++++++ example/example14.py | 14 +++++++++++ example/example14.ref | 6 +++++ include/pybind11/cast.h | 15 ++++++++++++ 7 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 example/example14.cpp create mode 100644 example/example14.py create mode 100644 example/example14.ref diff --git a/CMakeLists.txt b/CMakeLists.txt index f8a489c87..900e26ecf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,6 +119,7 @@ set(PYBIND11_EXAMPLES example/example11.cpp example/example12.cpp example/example13.cpp + example/example14.cpp example/issues.cpp ) diff --git a/docs/advanced.rst b/docs/advanced.rst index f24d3b506..74611bd20 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1021,7 +1021,7 @@ Partitioning code over multiple extension modules It's straightforward to split binding code over multiple extension modules, while referencing types that are declared elsewhere. Everything "just" works without any special -precautions. One exception to this rule occurs when wanting to extend a type declared +precautions. One exception to this rule occurs when extending a type declared in another extension module. Recall the basic example from Section :ref:`inheritance`. @@ -1063,3 +1063,55 @@ Naturally, both methods will fail when there are cyclic dependencies. py::class_(m, "Dog", py::base()) .def(py::init()) .def("bark", &Dog::bark); + +Treating STL data structures as opaque objects +============================================== + +pybind11 heavily relies on a template matching mechanism to convert parameters +and return values that are constructed from STL data types such as vectors, +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. + +The fundamental limitation of this approach is the internal conversion between +Python and C++ types involves a copy operation that prevents pass-by-reference +semantics. What does this mean? + +Suppose we bind the following function + +.. code-block:: cpp + + void append_1(std::vector &v) { + v.push_back(1); + } + +and call it as follows from Python: + +.. code-block:: python + + >>> v = [5, 6] + >>> append_1(v) + >>> print(v) + [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``. + +``opaque`` disables the underlying template 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 +wordy. The above function can be bound using the following wrapper code: + +.. code-block:: cpp + + m.def("append_1", [](py::opaque> &v) { append_1(v); }); + +Opaque types must also have a dedicated ``class_`` declaration to define a +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. diff --git a/example/example.cpp b/example/example.cpp index d84b456a3..a4fb4d3f4 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -22,6 +22,7 @@ void init_ex10(py::module &); void init_ex11(py::module &); void init_ex12(py::module &); void init_ex13(py::module &); +void init_ex14(py::module &); void init_issues(py::module &); PYBIND11_PLUGIN(example) { @@ -40,6 +41,7 @@ PYBIND11_PLUGIN(example) { init_ex11(m); init_ex12(m); init_ex13(m); + init_ex14(m); init_issues(m); return m.ptr(); diff --git a/example/example14.cpp b/example/example14.cpp new file mode 100644 index 000000000..5c6d918d9 --- /dev/null +++ b/example/example14.cpp @@ -0,0 +1,29 @@ +/* + example/example14.cpp -- opaque types + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "example.h" +#include +#include + +typedef std::vector StringList; + +void init_ex14(py::module &m) { + py::class_>(m, "StringList") + .def(py::init<>()) + .def("push_back", [](py::opaque &l, const std::string &str) { l->push_back(str); }) + .def("pop_back", [](py::opaque &l) { l->pop_back(); }) + .def("back", [](py::opaque &l) { return l->back(); }); + + 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; + }); +} diff --git a/example/example14.py b/example/example14.py new file mode 100644 index 000000000..c9c866520 --- /dev/null +++ b/example/example14.py @@ -0,0 +1,14 @@ +from __future__ import print_function +import sys + +sys.path.append('.') + +from example import StringList, print_opaque_list + +l = StringList() +l.push_back("Element 1") +l.push_back("Element 2") +print_opaque_list(l) +print("Back element is %s" % l.back()) +l.pop_back() +print_opaque_list(l) diff --git a/example/example14.ref b/example/example14.ref new file mode 100644 index 000000000..d738fefca --- /dev/null +++ b/example/example14.ref @@ -0,0 +1,6 @@ +Opaque list: + Element 1 + Element 2 +Back element is Element 2 +Opaque list: + Element 1 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 89fcac199..f36b3d5a6 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -17,6 +17,21 @@ #include NAMESPACE_BEGIN(pybind11) + +/// Thin wrapper type used to treat certain data types as opaque (e.g. STL vectors, etc.) +template class opaque { +public: + template opaque(Args&&... args) : value(std::forward(args)...) { } + operator Type&() { return value; } + operator const Type&() const { return value; } + operator Type*() { return &value; } + operator const Type*() const { return &value; } + Type* operator->() { return &value; } + const Type* operator->() const { return &value; } +private: + Type value; +}; + NAMESPACE_BEGIN(detail) /// Additional type information which does not fit into the PyTypeObject