-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read-only bindings for C++ const classes #717
Comments
It's theoretically possible, but it would really depend on how much complexity it added to pybind11 and what the limitations would be. I don't think either would be small, but I don't think anyone has thought deeply about it since early pybind11 days (well before my time). |
A key change which happened a while back is that the
This means that several Python objects (possibly with different types) can map to the same C++ instance, which makes supporting |
I've pushed a sketch of how pybind11 'const' support could work here: wjakob@6491cf2. The change introduces a per-instance #include <pybind11/pybind11.h>
namespace py = pybind11;
struct Pet {
Pet(const std::string &name) : name(name) { }
void set_name(const std::string &name_) { name = name_; }
const std::string &get_name() const { return name; }
std::string name;
};
struct PetShop {
Pet &polly() { return polly_; }
const Pet &polly_const() const { return polly_; }
Pet polly_{"polly"};
};
PYBIND11_PLUGIN(example) {
py::module m("example", "pybind11 example plugin");
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def("set_name", &Pet::set_name)
.def("name", &Pet::get_name);
py::class_<PetShop>(m, "PetShop")
.def(py::init<>())
.def("polly", &PetShop::polly, py::return_value_policy::reference_internal)
.def("polly_const", &PetShop::polly_const, py::return_value_policy::reference_internal);
return m.ptr();
} Example: Python 3.5.2 |Anaconda 4.2.0 (x86_64)| (default, Jul 2 2016, 17:52:12)
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> p = example.PetShop()
>>> a = p.get_polly()
>>> b = p.get_polly_const()
>>> a.set_name("Polly 2")
>>> print(b.name())
Polly 2
>>> b.set_name("Polly 3")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: set_name(): incompatible function arguments. The following argument types are supported:
1. (self: example.Pet, arg0: str) -> None
Invoked with: <example.Pet object at 0x10072f8a0>, 'Polly 3' There are still a bunch of problems though:
The extra checks add +1.5% to the binary size of our test suite. I just thought I'd throw this out to see what others think, and whether this is something we generally want. |
Interesting approach! Some thoughts: If I'm understanding correctly, there will be two separate Python instances for the same reference. I think it would be preferable to just have one with 1.5% seems pretty reasonable for this feature. |
If I follow your suggestion correctly, I wonder if that will lead to desirable semantics? In that case, in the above example, the second call to |
Yeah, it would. On the other hand, |
I suppose I'd think of it along the lines of, in C++: If I have a mutable lvalue reference |
I think there is a problematic aspect of this, which might lead to hard-to-find bugs. Imagine the following contrived piece of code which should probably fail:
However, if |
Hmm, yeah, that's true; the promotion would ideally need to be coupled with a demotion when the non-const reference goes out of scope, which doesn't seem feasible. So I suppose the dual instances tradeoff is indeed the better approach. |
One concern with the dual instance method: How would this be handled with inheritance? This point may be moot if your concern is the C++ As a potential related solution, I tinkered with teaching Python some basics of C++ const-ness using I have tinkered with some other basic "extension modules" here, and the paradigm seems simple enough: UPDATE: |
Please correct me if I'm wrong here. From type system point of view Python has no concept of const, but it could be easily emulated via explicit inheritance scheme: class constA(object):
pass
class A(constA):
pass This hack is a way to propagate const-correctness from C++ to Python. Advantages:
Disadvantages:
At first glance this approach is free of inheritance and mutated blackbox issues described above. What do you think about it? Here is fast&dirty illustration of the idea. Since you can't pass class A{
public:
std::string f() const{ return std::string(__PRETTY_FUNCTION__);}
std::string g(){ return std::string(__PRETTY_FUNCTION__); }
};
namespace py = pybind11;
template<typename T>
struct ConstValueWrapper{
static_assert(!std::is_const<T>::value,"");
static_assert(!std::is_reference<T>::value,"");
static_assert(!std::is_pointer<T>::value,"");
const T& cref() const{return value;}
protected:
T& ref(){return this->value;}
T value;
};
template<typename T>
struct ValueWrapper : public ConstValueWrapper<T> {
using ConstValueWrapper<T>::ref;
};
PYBIND11_MODULE(example, m) {
py::class_<ConstValueWrapper<A>>(m,"constA")
.def(py::init<>())
.def("f",[](const ConstValueWrapper<A>& ref){ return ref.cref().f(); })
;
py::class_<ValueWrapper<A>,ConstValueWrapper<A>>(m,"A")
.def(py::init<>())
.def("g",[](ValueWrapper<A>* ref){ return ref->ref().g(); })
;
} Usage from python from example import *
def accept_const_A(a: constA):
assert isinstance(a,constA)
def accept_A(a: A):
assert isinstance(a, A)
a = A()
consta = constA()
accept_const_A(a) # ok, prints "std::__cxx11::string A::f() const"
accept_const_A(consta) #ok, prints "std::__cxx11::string A::f() const"
accept_A(a) # ok, prints "std::__cxx11::string A::f() const \n std::__cxx11::string A::g()"
accept_A(consta) # assertion triggered (PyCharm is able to find this simple error)
class C(A):
pass
c = C() # there is no way to obtain pure-python const C
accept_A(c) # ok
accept_const_A(c) # ok |
Cool stuff! That does look much simpler for inheritance!
I guess I hadn't highlighted this in the solution I mentioned, but since Python is rich enough (for better and worse) to offer
Sorry for my ignorance, but could you specify what issues these are? One concern here is that this approach does not handle inheritance, as you mentioned. If you have a method that intends to pass a This is handled using the const-proxy approach (this is in pure-Python, but can be easily decorated in C++):
Perhaps you could write a wrapper around If you're interested in using it, I have some basic C++14 code for wrapping functions (methods, pointers, functors, etc.) if you have a pattern that you can write with some specializations + SFINAE: |
By blackbox issue I mean a problem described here #717 (comment)
I disagree on that. Here I do not expose to python |
Dear Developers,
I wonder if it will be possible update Pybind11 so it honor C++ const correctness? In my package i have lots of functions that return
MyClass const &
and our users mistakenly tries to alter such objects witch lead to undefined behavior. If suchconst
objects could be bound in some kind 'read-only' mode it would be really helpful!Thank you,
The text was updated successfully, but these errors were encountered: