-
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?
Changes from 1 commit
862126a
148eedb
1f1f69f
23c5990
a9481b2
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 |
---|---|---|
|
@@ -310,6 +310,30 @@ std::ostream& operator<<(std::ostream& os, R&& rng) { | |
return os; | ||
} | ||
|
||
template <stl2::Iterator I> | ||
class output_cursor { | ||
public: | ||
struct mixin : protected stl2::detail::ebo_box<output_cursor> { | ||
mixin() = default; | ||
using mixin::ebo_box::ebo_box; | ||
}; | ||
|
||
output_cursor() = default; | ||
constexpr output_cursor(I i) noexcept | ||
: i_{i} {} | ||
|
||
template <class T> | ||
requires stl2::OutputIterator<I, const T&>() | ||
constexpr void write(const T& t) noexcept { | ||
*i_++ = t; | ||
} | ||
|
||
private: | ||
I i_; | ||
}; | ||
template <stl2::Iterator I> | ||
using output_iterator = stl2::basic_iterator<output_cursor<I>>; | ||
|
||
void test_fl() { | ||
std::cout << "test_fl:\n"; | ||
::forward_list<int> list = {0, 1, 2, 3}; | ||
|
@@ -484,7 +508,7 @@ void test_counted() { | |
CHECK((one <= three)); | ||
CHECK(!(one >= three)); | ||
} | ||
} | ||
} | ||
|
||
void test_always() { | ||
std::cout << "test_always:\n"; | ||
|
@@ -598,6 +622,34 @@ void test_proxy_array() { | |
CHECK(a[3] == 1); | ||
} | ||
|
||
void test_output() { | ||
std::cout << "test_output:\n"; | ||
|
||
auto vec = std::vector<int>{0,0,0}; | ||
auto wi = vec.begin(); | ||
|
||
using I = ::output_iterator<decltype(wi)>; | ||
static_assert(stl2::models::WeaklyIncrementable<I>); | ||
static_assert(!stl2::models::Incrementable<I>); | ||
static_assert(!stl2::models::Decrementable<I>); | ||
static_assert(!stl2::models::Readable<I>); | ||
static_assert(stl2::models::Writable<I, int>); | ||
static_assert(stl2::models::DefaultConstructible<I>); | ||
static_assert(stl2::models::Copyable<I>); | ||
static_assert(stl2::models::Semiregular<I>); | ||
static_assert(stl2::models::Iterator<I>); | ||
static_assert(!stl2::models::InputIterator<I>); | ||
static_assert(stl2::models::OutputIterator<I, int>); | ||
|
||
I i{wi}; | ||
*i++ = 1; | ||
*i++ = 2; | ||
*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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. These should work if you add a 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. 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more.
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 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. Ha! Nice, thanks! :) |
||
} | ||
|
||
int main() { | ||
test_rv(); | ||
test_fl(); | ||
|
@@ -606,5 +658,6 @@ int main() { | |
test_always(); | ||
test_back_inserter(); | ||
test_proxy_array(); | ||
test_output(); | ||
return ::test_result(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe 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 commentThe 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 commentThe 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. |
||
|
||
static_assert(noexcept(I{})); | ||
static_assert(noexcept(I{ss})); | ||
|
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.