-
Notifications
You must be signed in to change notification settings - Fork 60
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
Ensure iterators satisfy input_iterator concept for views and ranges #273
base: master
Are you sure you want to change the base?
Conversation
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 @wdconinc. Very nice work (and it seems like you didn't have much problems with the templates).
A few "higher level" questions:
- What is currently still missing to making the iterators satisfy the forward iterator concept?
- Do we still need input iterators if we can support forward iterators? If I understand the iterator nesting correctly, than the latter is a superset of the former functionality wise, right?
- Do the unittests need I/O at the moment? Or could they be put into Catch2 test cases?
- Would it be possible to add same static checks to the unittests, that essentially just asser that our iterators fulfill the concepts that we want?
Thanks for the comments. I had a Otherwise, I do think we have all the bits and pieces for forward_iterators and probably even birdirectional_iterator. I added reverse iterators, not because they are required for these concepts, but because they are typically implemented and may be useful. However, there are some issues with the It is true that a lot of this can go into unit tests. I was thinking that some of the memory issues would be picked up better by a full IO test, but then I realized that those are all disabled in the sanitizers so it's not quite as useful and unittests would be just fine. We should likely add a C++20 workflow to the testing, if that is possible given the LCG upstream. |
Ah, so if I understand, you were trying to Lines 197 to 198 in 1dc1723
That is a good point, but I think we could put |
Apparently, I think, one of the issues I'm currently running into to satisfy |
For views, we may need one more change: iterators must allow for a const dereference, which is not how we are defining them. What do you think of a mutable {{ prefix }}{{ class.bare_type }} operator*() const {
m_object.m_obj = (*m_collection)[m_index];
return m_object;
};
private:
size_t m_index;
mutable {{ prefix }}{{ class.bare_type }} m_object;
const {{ class.bare_type }}ObjPointerContainer* m_collection; With that change, I think most of the ranges functionality works. |
The unit tests pass the more advanced tests, but fail the following ones (doesn't fill correctly, doesn't find correctly), so that needs a bit of investigation. // fill from const
collection.clear();
collection.create();
auto const_hit = ExampleHit(0x42ULL, 0., 0., 0., 0.);
//FIXME this should work but we may need a conversion constructor!
//std::fill(collection.begin(), collection.end(), const_hit);
//REQUIRE(collection.begin()->cellID() == 0x42ULL);
// fill from mutable
collection.clear();
collection.create();
auto mutable_hit = MutableExampleHit(0x42ULL, 0., 0., 0., 0.);
std::fill(collection.begin(), collection.end(), mutable_hit);
//FIXME
//REQUIRE(collection.begin()->cellID() == 0x42ULL);
// find in collection
collection.clear();
collection.create();
collection.create(0x42ULL, 0., 0., 0., 0.);
auto found = std::find_if(collection.begin(), collection.begin(),
[](const auto& h){ return h.cellID() == 0x42ULL; });
STATIC_REQUIRE(std::is_same_v<decltype(found), ExampleHitMutableCollectionIterator>);
REQUIRE(found != collection.end());
//FIXME
//REQUIRE(found->cellID() == 0x42ULL); Other outstanding questions:
|
I guess the fact that |
Regarding making the {{ prefix }}{{ class.bare_type }} operator*() const {
return {(*m_collection)[m_index])};
}; (I am not entirely sure if the return like this works or if the |
I am not sure how strong the equality preserving constraint on dereferencing iterators is. In this case dereferencing the same (unincremented) iterator twice would result in two different objects (both pointing to the same internal data), not the same object (which we have now due to assignment). I am not sure if our implementation would treat those as equal. I'll run some tests on this. |
If this falls back to using
|
Ah, so that's where the operator== is :-) I couldn't find it. |
I think some of the CI failures are not related to the changes in this PR, e.g.: They should be fixed once #253 and #254 are merged. You can try to target the I will merge those this week, also to unclog some of the other PRs that are essentially depending on them. |
Yeah, the cvmfs action on macos is fickle (and it's annoying to debug them). There are some linux fails too, though also unrelated (unused capture in |
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 for the latest additions. For the read tests we usually just throw a std::runtime_error
if something is not as expected (at least for the time being). However, I am not entirely sure whether it is possible for all the tests in read_iterators.cpp
or read_views.cpp
to easily formulate conditions. In any case, since the iterators are directly using collection resources after I/O I wouldn't expect any problems there.
Overall this looks good to me, I just have one minor comment inline for one of the unittests.
I was going to remove those IO tests read_iterators and read_views again since they don't really test anything beyond the unit tests. |
The single remaining issue in the unit tests (commented out) is with the for (auto i = collection.begin(); i != collection.end(); i++) {
*i = MutableExampleHit(0x42ULL, 0., 0., 0., 0.);
} This compiles but fails to modify the collection because It is unfortunate that I think the line in the loop calls, in sequence and for the lhs only: MutableExampleHit ExampleHitMutableCollectionIterator::operator*() const {
return {(*m_collection)[m_index]};
}
MutableExampleHit::MutableExampleHit( ExampleHitObj* obj) : m_obj(obj) {
if (m_obj) m_obj->acquire();
}
MutableExampleHit& MutableExampleHit::operator=(MutableExampleHit other) {
swap(*this, other);
return *this;
} With mutable MutableExampleHit ExampleHitMutableCollectionIterator::operator*() const {
m_object.m_obj = (*m_collection)[m_index];
return m_object;
}
MutableExampleHit& MutableExampleHit::operator=(MutableExampleHit other) {
swap(*this, other);
return *this;
} |
Can we namespace std {
template<typename T>
std::fill(CollectionIterator, CollectionIterator, const T&) = delete;
} (and similar for all of the other iterators as well). I know opening I have played a bit with a normal Or would you want |
Deleting the |
So, I guess I'm ok with std::fill failing until someone figures out how to make it work, as long as we are somehow clear about this. |
Right. That is a lot to delete...
Failing in the sense of "compiling but not working" or actually deleting it to make it fail at compile time? What we can always do (at least at this point) is to properly document what is and what is not possible with our iterators. Which would essentially boil down to "everything that leaves the collection unchanged", I suppose. |
Maybe we should ensure that a dereferenced iterator lvalue is always a const object (even for mutable iterators!) so the assignment implied in the |
Co-authored-by: Thomas Madlener <[email protected]>
f25eeb4
to
784b902
Compare
I've restricted the iterators to just input_iterators, with explicit unit tests to check they are not more than that. Due to the limitation in the iterators, the ranges cannot be used with reverse views so those have been removed from the unit tests. |
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 for updating this. I have essentially just minor comments relating to tagging all related tests with one common tag and one placement of an #if
.
From the provided unit tests it looks like the std::fill
case still compiles, but then fails at runtime, right? I would have assumed our iterators not being forward iterators would make std::fill
fail at compile time, but that doesn't seem to be the case. I am not sure I understand why at this point.
Co-authored-by: Thomas Madlener <[email protected]>
Co-authored-by: Thomas Madlener <[email protected]>
I think this still relates to #281. To prevent |
Possible next ideas to explore:
|
I'm thinking the following is what we may need in {% if prefix == 'Mutable' %}
/// copy-assignment operator
{{ full_type }}& operator=({{ type }} other);
{{ full_type }}& operator=({{ full_type }} other);
{% else %}
/// copy-assignment operator deleted (use Mutable type)
{{ full_type }}& operator=({{ full_type }} other) = delete;
{% endif %} This would remove assignments like auto i = collection.begin();
*i = MutableExampleHit; since const-correctness of Ultimately this should mean that |
Nevermind, removing assignment violates |
Other things I tried today that didn't pan out:
|
I just tried to explicitly generate There is also some additional work in there which would in principle allow us to do |
Do we actually need a mutable iterator on collections? Can we just make it entirely non-mutable? Right now that will fail the following constructs: for (auto&& i : coll) {
i.energy(42); // make sure that we can indeed change the energy here for
// non-const collections
}
REQUIRE(hit1.energy() == 42); Are these used in the wild? It only fails at the |
Good question, I don't know. We could at probably flush out most of the use cases with a deprecation warning to see how many there are. On the other hand, I am not entirely sure if I like the idea of removing the iterators, since operations done through the iterators are working, but assignments through dereferenced iterators are failing. |
@tmadlener - should we resurrect this? |
I am not entirely sure how easy this is to resurrect, given that doing it “properly” would probably mean quite a bit of work. I will have to look at the discussion in here again and see what the current status is. Maybe there are things that can be salvaged for now and we come back to proper ranges support at a later point |
OK. Thanks |
Some changes to the iterators to make #272 work. This now enables all except for C++ ranges, and there are unit tests to go with it. C++20 unit tests still fail. This applies to all iterators, both mutable and const, but I think for input_iterators that is fine. For the forward iterators we probably need to split the iterators.jinja2 into two(?).
BEGINRELEASENOTES
ENDRELEASENOTES