-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add make_value_iterator #3271
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
Add make_value_iterator #3271
Changes from all commits
009a745
cd94fb6
482759d
2f0c4e3
37823fa
7958d86
380e692
7e51ad3
a631b6c
ed9fdfb
04c3521
653df59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1954,25 +1954,52 @@ inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_t | |
| return res; | ||
| } | ||
|
|
||
| template <typename Iterator, typename Sentinel, bool KeyIterator, return_value_policy Policy> | ||
| /* There are a large number of apparently unused template arguments because | ||
| * each combination requires a separate py::class_ registration. | ||
| */ | ||
| template <typename Access, return_value_policy Policy, typename Iterator, typename Sentinel, typename ValueType, typename... Extra> | ||
| struct iterator_state { | ||
| Iterator it; | ||
| Sentinel end; | ||
| bool first_or_done; | ||
| }; | ||
|
|
||
| PYBIND11_NAMESPACE_END(detail) | ||
| // Note: these helpers take the iterator by non-const reference because some | ||
| // iterators in the wild can't be dereferenced when const. | ||
| template <typename Iterator> | ||
| struct iterator_access { | ||
| using result_type = decltype((*std::declval<Iterator>())); | ||
| // NOLINTNEXTLINE(readability-const-return-type) // PR #3263 | ||
| result_type operator()(Iterator &it) const { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the compiler automatically inline these short functions? If not, shall we add There may be some size reduction if the compiler does not need to generating symbols for each template instantiation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Member functions defined inside the class implicitly have inline linkage. That doesn't mean the compiler has to inline them, but since the binary size is identical to master (when using the same test suite) I think it's safe to assume they're inlined. |
||
| return *it; | ||
| } | ||
| }; | ||
|
|
||
| /// Makes a python iterator from a first and past-the-end C++ InputIterator. | ||
| template <return_value_policy Policy = return_value_policy::reference_internal, | ||
| template <typename Iterator> | ||
| struct iterator_key_access { | ||
| using result_type = decltype(((*std::declval<Iterator>()).first)); | ||
| result_type operator()(Iterator &it) const { | ||
| return (*it).first; | ||
| } | ||
| }; | ||
|
|
||
| template <typename Iterator> | ||
| struct iterator_value_access { | ||
| using result_type = decltype(((*std::declval<Iterator>()).second)); | ||
| result_type operator()(Iterator &it) const { | ||
| return (*it).second; | ||
| } | ||
| }; | ||
|
|
||
| template <typename Access, | ||
| return_value_policy Policy, | ||
| typename Iterator, | ||
| typename Sentinel, | ||
| #ifndef DOXYGEN_SHOULD_SKIP_THIS // Issue in breathe 4.26.1 | ||
| typename ValueType = decltype(*std::declval<Iterator>()), | ||
| #endif | ||
| typename ValueType, | ||
| typename... Extra> | ||
| iterator make_iterator(Iterator first, Sentinel last, Extra &&... extra) { | ||
| using state = detail::iterator_state<Iterator, Sentinel, false, Policy>; | ||
| iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&... extra) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does inline ere change the binary size. and should we move this function to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's already in the detail namespace. Feel free to try it; I generally tend to trust compilers to make sensible decisions about inlining these days. |
||
| using state = detail::iterator_state<Access, Policy, Iterator, Sentinel, ValueType, Extra...>; | ||
| // TODO: state captures only the types of Extra, not the values | ||
|
|
||
| if (!detail::get_type_info(typeid(state), false)) { | ||
| class_<state>(handle(), "iterator", pybind11::module_local()) | ||
|
|
@@ -1986,43 +2013,63 @@ iterator make_iterator(Iterator first, Sentinel last, Extra &&... extra) { | |
| s.first_or_done = true; | ||
| throw stop_iteration(); | ||
| } | ||
| return *s.it; | ||
| return Access()(s.it); | ||
| // NOLINTNEXTLINE(readability-const-return-type) // PR #3263 | ||
| }, std::forward<Extra>(extra)..., Policy); | ||
| } | ||
|
|
||
| return cast(state{first, last, true}); | ||
| } | ||
|
|
||
| /// Makes an python iterator over the keys (`.first`) of a iterator over pairs from a | ||
| PYBIND11_NAMESPACE_END(detail) | ||
|
|
||
| /// Makes a python iterator from a first and past-the-end C++ InputIterator. | ||
| template <return_value_policy Policy = return_value_policy::reference_internal, | ||
| typename Iterator, | ||
| typename Sentinel, | ||
| typename ValueType = typename detail::iterator_access<Iterator>::result_type, | ||
| typename... Extra> | ||
| iterator make_iterator(Iterator first, Sentinel last, Extra &&... extra) { | ||
| return detail::make_iterator_impl< | ||
| detail::iterator_access<Iterator>, | ||
| Policy, | ||
| Iterator, | ||
| Sentinel, | ||
| ValueType, | ||
| Extra...>(first, last, std::forward<Extra>(extra)...); | ||
| } | ||
|
|
||
| /// Makes a python iterator over the keys (`.first`) of a iterator over pairs from a | ||
| /// first and past-the-end InputIterator. | ||
| template <return_value_policy Policy = return_value_policy::reference_internal, | ||
| typename Iterator, | ||
| typename Sentinel, | ||
| #ifndef DOXYGEN_SHOULD_SKIP_THIS // Issue in breathe 4.26.1 | ||
| typename KeyType = decltype((*std::declval<Iterator>()).first), | ||
| #endif | ||
| typename KeyType = typename detail::iterator_key_access<Iterator>::result_type, | ||
| typename... Extra> | ||
| iterator make_key_iterator(Iterator first, Sentinel last, Extra &&...extra) { | ||
| using state = detail::iterator_state<Iterator, Sentinel, true, Policy>; | ||
|
|
||
| if (!detail::get_type_info(typeid(state), false)) { | ||
| class_<state>(handle(), "iterator", pybind11::module_local()) | ||
| .def("__iter__", [](state &s) -> state& { return s; }) | ||
| .def("__next__", [](state &s) -> detail::remove_cv_t<KeyType> { | ||
| if (!s.first_or_done) | ||
| ++s.it; | ||
| else | ||
| s.first_or_done = false; | ||
| if (s.it == s.end) { | ||
| s.first_or_done = true; | ||
| throw stop_iteration(); | ||
| } | ||
| return (*s.it).first; | ||
| }, std::forward<Extra>(extra)..., Policy); | ||
| } | ||
| return detail::make_iterator_impl< | ||
| detail::iterator_key_access<Iterator>, | ||
| Policy, | ||
| Iterator, | ||
| Sentinel, | ||
| KeyType, | ||
| Extra...>(first, last, std::forward<Extra>(extra)...); | ||
| } | ||
|
|
||
| return cast(state{first, last, true}); | ||
| /// Makes a python iterator over the values (`.second`) of a iterator over pairs from a | ||
| /// first and past-the-end InputIterator. | ||
| template <return_value_policy Policy = return_value_policy::reference_internal, | ||
| typename Iterator, | ||
| typename Sentinel, | ||
| typename ValueType = typename detail::iterator_value_access<Iterator>::result_type, | ||
| typename... Extra> | ||
| iterator make_value_iterator(Iterator first, Sentinel last, Extra &&...extra) { | ||
| return detail::make_iterator_impl< | ||
| detail::iterator_value_access<Iterator>, | ||
| Policy, Iterator, | ||
| Sentinel, | ||
| ValueType, | ||
| Extra...>(first, last, std::forward<Extra>(extra)...); | ||
| } | ||
|
|
||
| /// Makes an iterator over values of an stl container or other container supporting | ||
|
|
@@ -2039,6 +2086,13 @@ template <return_value_policy Policy = return_value_policy::reference_internal, | |
| return make_key_iterator<Policy>(std::begin(value), std::end(value), extra...); | ||
| } | ||
|
|
||
| /// Makes an iterator over the values (`.second`) of a stl map-like container supporting | ||
| /// `std::begin()`/`std::end()` | ||
| template <return_value_policy Policy = return_value_policy::reference_internal, | ||
| typename Type, typename... Extra> iterator make_value_iterator(Type &value, Extra&&... extra) { | ||
| return make_value_iterator<Policy>(std::begin(value), std::end(value), extra...); | ||
| } | ||
|
|
||
| template <typename InputType, typename OutputType> void implicitly_convertible() { | ||
| struct set_flag { | ||
| bool &flag; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The struct doesn't explicitly depend on typename Access -- it makes me wonder if we need a new iterator_state per Access and Policy type?
Seems like we are creating Python surrogates (class_) a few lines later, and maybe we want to differentiate the types because of that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. Using
AccessreplacesKeyIterator, which could only express two possible access methods.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. If we missed Access here then we would be reusing a type with a wrong Accessor() object; a similar reason for ValueType. Do you see why we need Sentinel or Extra?
Here is a way to make this dependency explicit, but it feels a bit too verbose:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra is for the same reason as Access: if you don't have it there, then two different calls to
make_iterator_implwith differentExtrawon't work, because they'll share the same Python class and it'll only be defined correctly for the Extra given in the first call. iterator_state has a member of type Sentinel.I don't have any strong objections, but I also feel it doesn't really add anything (and is verbose). If you want me to implement that let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we both think capturing the types explicitly is too verbose.
Perhaps just add a comment here that we need to ensure a new type is emitted for all of these template arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Done.