-
Notifications
You must be signed in to change notification settings - Fork 68
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
Fix basic_iterator for output cursors that don't provide post_increment(). #60
base: master
Are you sure you want to change the base?
Conversation
…nt(). * Added a post-increment operator overload to basic_iterator that returns a self reference for non-input cursors that don't provide post_increment(). * Added a test to validate that output iterators that hold state are correctly updated for post-increment dereference assignment. * Updated a static_assert for ostream_iterator to reflect that post-increment now returns a self reference. This is consistent with the implementation of ostream_iterator in libstdc++.
*i++ = 3; | ||
CHECK(vec[0] == 1); | ||
CHECK(vec[1] == 2); | ||
CHECK(vec[2] == 3); |
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.
These checks fail without the corresponding change to basic_iterator.hpp. The problem was that a copy of the output iterator was being returned for each post-increment operation. The dereference assignment then updated the copy leaving the wrapped iterator in 'i' left pointing to the first element of the vector.
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.
These should work if you add a next
member to output_cursor
that increments i_
, and do not perform the increment in write
.
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.
For my actual use case (otext_iterator: https://github.com/tahonermann/text_view/blob/master/include/text_view_detail/otext_iterator.hpp), it isn't possible to separate the write and the increment. This test was modelled on that use case, though I failed to express this requirement in this PR.
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.
Actually, separating increment and dereference in this way results in undefined behavior for output iterators that wrap other output iterators if the post-increment operator doesn't return a self reference. In this case, the test is exercising an output iterator wrapping a random access iterator, so it isn't a problem. If the wrapped iterator was an actual output iterator, then the post-increment operation would have incremented the wrapped iterator held by i, the dereference operation would have dereferenced the wrapped iterator in the copy of i returned by the post-increment operation, and any subsequent increment operation on i would be undefined behavior. With the current design, the best way I see to avoid that undefined behavior is to implement post_increment to return a proxy that implements the dereference and assignment operators. I suppose that is ok, but since it is non-trivial to do, having a solution that allows a cursor to opt-in to having post-increment return a self reference seems desirable.
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.
I suppose that is ok, but since it is non-trivial to do, having a solution that allows a cursor to opt-in to having post-increment return a self reference seems desirable.
You know you've written too many iterator hacks when this seems natural to you (https://github.com/CaseyCarter/text_view/tree/otext_iterator_fix):
void next() noexcept {}
auto post_increment() noexcept {
class proxy {
otext_cursor& self_;
public:
proxy(otext_cursor& self) noexcept : self_{self} {}
proxy& operator*() noexcept {
return *this;
}
proxy& operator=(const state_transition_type &stt) {
self_.write(stt);
return *this;
}
proxy& operator=(const character_type_t<encoding_type> &value) {
self_.write(value);
return *this;
}
};
return proxy{*this};
}
But I agree with your point: we've crossed a boundary into a place where it would be easier to avoid basic_iterator
and simply write an iterator by hand. This design needs to do a better job with this use.
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.
Ha! Nice, thanks! :)
{ | ||
++*this; | ||
return *this; | ||
} |
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 fix; post-increment must return a self reference rather than a copy for output iterators that hold state.
@@ -49,7 +49,7 @@ int main() { | |||
static_assert(models::Same<I&, decltype(*i)>); | |||
static_assert(models::Same<I&, decltype(*i = 42)>); | |||
static_assert(models::Same<I&, decltype(++i)>); | |||
static_assert(models::Same<I, decltype(i++)>); | |||
static_assert(models::Same<I&, decltype(i++)>); |
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.
post-increment of an ostream_iterator now returns a self reference rather than a copy.
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.
...which does not conform to N4620 [ostream.iterator.ops]/3.
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.
I'm guessing N4620 is the next revision of the Ranges working draft? I don't see it published yet. I see what you are referring to in N4569 though.
The (almost totally undocumented) design for output cursors is that you either:
|
Thanks for the detailed explanation regarding output curstors, next and post_increment. That was very informative. |
Why is it called
I'm sure it can. |
@ericniebler, for otext_cursor, the increment of the wrapped iterator necessarily needs to happen in write; that is what lead me to suggesting this change. The symptom I experienced was that, when an otext_iterator oiwrapped a forward-or-better iterator, post-increment/dereference/assign would call write on a copy of oi leaving oi holding a wrapped iterator that was not incremented. The next post-increment/dereference/assign then wrote to the same location as the previous operation. |
Because we don't have to differentiate prefix and postfix versions of the single name
I added
The current behavior for output cursors without |
Which is all on me: ericniebler/stl2#137. Apparently we were thinking about |
…vertible to the iterator type.
My GitHub-fu is weak. I intended to submit a new pull request for commit 148eedb, but my new commit ended up added to this pull request. This latest commit updated OutputIterator to add a requirement that the type returned by the post-increment operator be convertible to the iterator type. OutputIterator only requires WeaklyIncrementable since EqualityComparable is not required and WeaklyIncrementable doesn't require the post-increment conversion to iterator type because InputIterator doesn't. I just coded a direct requirement, I'm sure you'll want to code this differently. The commit updates the test I added previously to add post_increment with a proxy and modifies one of the writes to the vector to use a copy initialized from a post-increment of the iterator. The constructor added to the mixin provides the necessary conversion. Remove the constructor and the test will fail to compile both because the static assert of OutputIterator fails and because the initialization of the copy fails due to the missing conversion constructor. Remove the additions to OutputIterator and you'll see the static assert disappear. |
…rement proxy from the output cursor test. These implementations added in commit 148eedb cause the test to no longer exercise the changes made in commit 862126a as originally intended. I don't recall why I was inclined to add them; I guess I was excited to discover that implementing next() and post_increment() sufficed to define stateful output iterators.
I pushed another commit to make the test I added actually test the change I originally intended it to test. Not sure what I was thinking adding implementations of next() and post_increment() in that intermediate commit. |
Tom fail. This "fix" only works for cursors that don't implement
This suffices to implement stateful output iterators like This seems like a pretty narrow solution; I don't know how many output iterators this addresses. Thoughts? |
…at returns a self-reference to output iterators that do not implement next(). The "fix" originally proposed in commit 862126a did not work for stateful output iterators with next() implementations that have side effects. This change restricts the return of a self reference to output iterators that implement neither next() nor post_increment(). Stateful output iterators that implement next() must implement post_increment() to return a proxy.
This is feeling a bit like trying to make a dollar out of fifteen cents. An overly complex IMO we should defer all |
Yes, apologies to Tom. I had planned to address |
Aw, man. I can't believe you aren't going to drop everything else you're doing for the betterment of the entire community just to focus on my pet issue.
Sounds good. I'll keep my thinking cap on. |
cc0fd73
to
ecce5ee
Compare
Added a post-increment operator overload to basic_iterator that returns a
self reference for non-input cursors that don't provide post_increment().
Added a test to validate that output iterators that hold state are correctly
updated for post-increment dereference assignment.
Updated a static_assert for ostream_iterator to reflect that post-increment
now returns a self reference. This is consistent with the implementation of
ostream_iterator in libstdc++.