-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Make handle and related classes const correct. #52
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
Conversation
This gives handle classes a typical pointer semantics with respects to constness.
I'm not sure I agree with all of the changes -- take for instance begin() and end(), which now return a normal (non-const iterator). This could for instance be used to modify entries of a |
In general I wanted handle and derived classes to behave in the same way as pointers do with respect to constness, i.e., const is not transitive. For example: const shared_ptr<vector<int>> x = ..., y = ...;
// Changing pointer is not allowed:
x = y;
// Changing pointee is allowed:
x->clear();
x->push_back(1); Thus, behaviour of begin() and end() is intentional. |
While you are technically correct in regards to pointers, we cannot generalize this to handles. For example, we cannot make the equivalent to: shared_ptr<const vector<int>> x = ..., y = ...;
// Changing pointer is allowed:
x = y;
// Changing pointee is not allowed:
x->clear();
x->push_back(1); and this is what users general expect from |
I would give the following arguments for proposed behaviour:
d = defaultdict(int)
print(d) # defaultdict(<class 'int'>, {})
d[0]
print(d) # defaultdict(<class 'int'>, {0: 0})
void modify(py::object x) {
x["key"] = value;
}
const py::object x {...}
// Not allowed:
//x["key"] = value;
// But following is allowed and modifies the same Python object:
modify(x); |
Very well thought through arguments, this would be my response:
Not quite sure I agree here. Handles have methods that directly access the pointee, for example, with pointers one would need to do: shared_ptr<vector<int>> x = ...;
x->operator[](4) = 5 while with handles we would do py:array x = ...;
x[4] = 5 thus in some sense the handle "is" the object. In the other sense (owning data), you are correct. I'd actually liken handles the most with references, and references use const in the more strict sense. int x = ...;
int& y = x;
y = 5; // Also updates x
const int& z = x;
z = 5; // Not possibly since z is declared const
Easily your strongest argument and an obvious issue. My approach would be to assume getters are const and setters are not. This is for example how PyCXX does it. Related issue: your example would currently be problematic given the current code since py::map map = ...;
map[0]; // Returns an py::accessor, does not call PyObject_GetItem
auto x = map[0]; // Returns a py::accessor, does not call PyObject_GetItem
py::object x = map[0]; // implicit conversion to py::object, calls PyObject_GetItem The code is here.
I agree, but this applies to most of c++ since const can pretty much always be |
@wjakob what is your opinion about this? I will also add that apart from already mentioned PyCXX, the boost::python While I still think the benefit of transitive const is limited, I don't have |
Ultimately |
Make handle and related classes const correct.
This gives handle classes a typical pointer semantics with respects to
constness.
It seems that now only buffer class that originally triggered issue #35 is left
to be fixed. But this should be trivial once ownership of Py_buffer is moved to
buffer_info #45.