Skip to content

Commit

Permalink
add support for std::random_access_iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
m-fila committed Dec 19, 2024
1 parent 202bf44 commit 4abfadd
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 11 deletions.
8 changes: 4 additions & 4 deletions doc/collections_as_container.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ In the following tables a convention from `Collection` is used: `iterator` stand
| `std::output_iterator` | ❌ no | ❌ no |
| `std::forward_iterator` | ✔️ yes (see note below) | ✔️ yes (see note below) |
| `std::bidirectional_iterator` | ✔️ yes | ✔️ yes |
| `std::random_access_iterator` | ❌ no | ❌ no |
| `std::random_access_iterator` | ✔️ yes | ✔️ yes |
| `std::contiguous_iterator` | ❌ no | ❌ no |

> [!NOTE]
Expand Down Expand Up @@ -187,7 +187,7 @@ In addition to the *LegacyForwardIterator* the C++ standard specifies also the *
| `std::ranges::output_range` | ❌ no |
| `std::ranges::forward_range` | ✔️ yes |
| `std::ranges::bidirectional_range` | ✔️ yes |
| `std::ranges::random_access_range` | ❌ no |
| `std::ranges::random_access_range` | ✔️ yes |
| `std::ranges::contiguous_range` | ❌ no |
| `std::ranges::common_range` | ✔️ yes |
| `std::ranges::viewable_range` | ✔️ yes |
Expand All @@ -212,11 +212,11 @@ std::sort(std::begin(collection), std::end(collection)); // requires RandomAcces
The arguments of standard range algorithms are checked at compile time and must fulfil certain iterator concepts, such as `std::input_iterator` or `std::ranges::input_range`.
The iterators of PODIO collections model the `std::bidirectional_iterator` concept, so range algorithms that require this iterator type will work correctly with PODIO iterators. If an algorithm compiles, it is guaranteed to work as expected.
The iterators of PODIO collections model the `std::random_access_iterator` concept, so range algorithms that require this iterator type will work correctly with PODIO iterators. If an algorithm compiles, it is guaranteed to work as expected.
In particular, the PODIO collections' iterators do not fulfil the `std::output_iterator` concept, and as a result, mutating algorithms relying on this iterator type will not compile.
Similarly the collections themselves model the `std::bidirectional_range` concept and can be used in the range algorithms that require that concept. The algorithms requiring unsupported range concept, such as `std::output_range`, won't compile.
Similarly the collections themselves model the `std::random_access_range` concept and can be used in the range algorithms that require that concept. The algorithms requiring unsupported range concept, such as `std::output_range`, won't compile.
For example:
```c++
Expand Down
47 changes: 44 additions & 3 deletions python/templates/macros/iterator.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public:
using iterator_category = std::input_iterator_tag;
// `std::forward_iterator` is supported except that the pointers obtained with `operator->()`
// remain valid as long as the iterator is valid, not as long as the range is valid.
using iterator_concept = std::bidirectional_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;

{{ iterator_type }}(size_t index, const {{ class.bare_type }}ObjPointerContainer* collection) : m_index(index), m_object({{ ptr_init }}), m_collection(collection) {}
{{ iterator_type }}() = default;
Expand All @@ -21,8 +21,8 @@ public:
{{ iterator_type }}& operator=({{iterator_type}}&&) = default;
~{{ iterator_type }}() = default;

bool operator!=(const {{ iterator_type}}& x) const {
return m_index != x.m_index;
auto operator<=>(const {{ iterator_type}}& other) const {
return m_index <=> other.m_index;
}

bool operator==(const {{ iterator_type }}& x) const {
Expand All @@ -35,6 +35,13 @@ public:
{{ iterator_type }} operator++(int);
{{ iterator_type }}& operator--();
{{ iterator_type }} operator--(int);
{{ iterator_type }}& operator+=(difference_type n);
{{ iterator_type }} operator+(difference_type n) const;
friend {{ iterator_type }} operator+(difference_type n, const {{ iterator_type }}& it);
{{ iterator_type }}& operator-=(difference_type n);
{{ iterator_type }} operator-(difference_type n) const;
reference operator[](difference_type n) const;
difference_type operator-(const {{ iterator_type }}& other) const;

private:
size_t m_index{0};
Expand Down Expand Up @@ -79,5 +86,39 @@ private:
return copy;
}

{{ iterator_type }}& {{ iterator_type }}::operator+=(difference_type n) {
m_index += n;
return *this;
}

{{ iterator_type }} {{ iterator_type }}::operator+(difference_type n) const {
auto copy = *this;
copy += n;
return copy;
}

{{ iterator_type }} operator+({{ iterator_type }}::difference_type n, const {{ iterator_type }}& it) {
return it + n;
}

{{ iterator_type }}& {{ iterator_type }}::operator-=(difference_type n) {
m_index -= n;
return *this;
}

{{ iterator_type }} {{ iterator_type }}::operator-(difference_type n) const {
auto copy = *this;
copy -= n;
return copy;
}

{{ iterator_type }}::reference {{ iterator_type }}::operator[](difference_type n) const {
return reference{ {{ ptr_type }}((*m_collection)[m_index + n]) };
}

{{ iterator_type }}::difference_type {{ iterator_type }}::operator-(const {{ iterator_type }}& other) const {
return m_index - other.m_index;
}

{% endwith %}
{% endmacro %}
164 changes: 160 additions & 4 deletions tests/unittests/std_interoperability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,165 @@ TEST_CASE("Collection and iterator concepts", "[collection][container][iterator]
REQUIRE(--(++a) == b);
REQUIRE(++(--a) == b);
}

SECTION("random_access_iterator") {
// iterator
STATIC_REQUIRE(std::totally_ordered<iterator>);
{
auto coll = CollectionType();
coll.create();
coll.create();
coll.create();
auto a = coll.begin();
auto b = coll.begin();
REQUIRE(!(a < b));
REQUIRE(!(a > b));
REQUIRE((a == b));
b = ++coll.begin();
REQUIRE((a < b));
REQUIRE(!(a > b));
REQUIRE(!(a == b));
a = ++coll.begin();
b = coll.begin();
REQUIRE(!(a < b));
REQUIRE(a > b);
REQUIRE(!(a == b));
auto c = coll.begin();
a = c++;
b = c++;
REQUIRE(a < b);
REQUIRE(b < c);
REQUIRE(a < c);
REQUIRE((a > b) == (b < a));
REQUIRE((a >= b) == !(a < b));
REQUIRE((a <= b) == !(a > b));
}
STATIC_REQUIRE(std::sized_sentinel_for<iterator, iterator>);
{
auto coll = CollectionType();
coll.create();
auto i = coll.begin();
auto s = coll.end();
auto n = 1;
REQUIRE(s - i == n);
REQUIRE(i - s == -n);
}
STATIC_REQUIRE(std::random_access_iterator<iterator>);
{
auto coll = CollectionType();
coll.create().cellID(42);
coll.create().cellID(43);
coll.create().cellID(44);
coll.create().cellID(45);
auto a = coll.begin();
auto n = 2;
auto b = a + n;
REQUIRE((a += n) == b);
a = coll.begin();
REQUIRE(std::addressof(a += n) == std::addressof(a));
a = coll.begin();
auto k = a + n;
REQUIRE(k == (a += n));
a = coll.begin();
REQUIRE((a + n) == (n + a));
auto x = 1;
auto y = 2;
REQUIRE((a + (x + y)) == ((a + x) + y));
REQUIRE((a + 0) == a);
b = a + n;
REQUIRE((--b) == (a + n - 1));
b = a + n;
REQUIRE((b += -n) == a);
b = a + n;
REQUIRE((b -= +n) == a);
b = a + n;
REQUIRE(std::addressof(b -= n) == std::addressof(b));
b = a + n;
k = b - n;
REQUIRE(k == (b -= n));
b = a + n;
REQUIRE(a[n] == *b);
REQUIRE(a <= b);
}
// const_iterator
STATIC_REQUIRE(std::totally_ordered<const_iterator>);
{
auto coll = CollectionType();
coll.create();
coll.create();
coll.create();
auto a = coll.cbegin();
auto b = coll.cbegin();
REQUIRE(!(a < b));
REQUIRE(!(a > b));
REQUIRE((a == b));
b = ++coll.cbegin();
REQUIRE((a < b));
REQUIRE(!(a > b));
REQUIRE(!(a == b));
a = ++coll.cbegin();
b = coll.cbegin();
REQUIRE(!(a < b));
REQUIRE(a > b);
REQUIRE(!(a == b));
auto c = coll.cbegin();
a = c++;
b = c++;
REQUIRE(a < b);
REQUIRE(b < c);
REQUIRE(a < c);
REQUIRE((a > b) == (b < a));
REQUIRE((a >= b) == !(a < b));
REQUIRE((a <= b) == !(a > b));
}
STATIC_REQUIRE(std::sized_sentinel_for<const_iterator, const_iterator>);
{
auto coll = CollectionType();
coll.create();
auto i = coll.cbegin();
auto s = coll.cend();
auto n = 1;
REQUIRE(s - i == n);
REQUIRE(i - s == -n);
}
STATIC_REQUIRE(std::random_access_iterator<const_iterator>);
{
auto coll = CollectionType();
coll.create().cellID(42);
coll.create().cellID(43);
coll.create().cellID(44);
coll.create().cellID(45);
auto a = coll.cbegin();
auto n = 2;
auto b = a + n;
REQUIRE((a += n) == b);
a = coll.cbegin();
REQUIRE(std::addressof(a += n) == std::addressof(a));
a = coll.cbegin();
auto k = a + n;
REQUIRE(k == (a += n));
a = coll.cbegin();
REQUIRE((a + n) == (n + a));
auto x = 1;
auto y = 2;
REQUIRE((a + (x + y)) == ((a + x) + y));
REQUIRE((a + 0) == a);
b = a + n;
REQUIRE((--b) == (a + n - 1));
b = a + n;
REQUIRE((b += -n) == a);
b = a + n;
REQUIRE((b -= +n) == a);
b = a + n;
REQUIRE(std::addressof(b -= n) == std::addressof(b));
b = a + n;
k = b - n;
REQUIRE(k == (b -= n));
b = a + n;
REQUIRE(a[n] == *b);
REQUIRE(a <= b);
}
}
}

TEST_CASE("Collection and unsupported iterator concepts", "[collection][container][iterator][std]") {
Expand All @@ -582,9 +741,6 @@ TEST_CASE("Collection and unsupported iterator concepts", "[collection][containe
DOCUMENTED_STATIC_FAILURE(std::output_iterator<iterator, CollectionType::mutable_type>);
DOCUMENTED_STATIC_FAILURE(std::output_iterator<const_iterator, CollectionType::value_type>);
DOCUMENTED_STATIC_FAILURE(std::output_iterator<const_iterator, CollectionType::mutable_type>);
// std::random_access_iterator
DOCUMENTED_STATIC_FAILURE(std::random_access_iterator<iterator>);
DOCUMENTED_STATIC_FAILURE(std::random_access_iterator<const_iterator>);
// std::contiguous_iterator
DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator<iterator>);
DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator<const_iterator>);
Expand Down Expand Up @@ -1224,7 +1380,7 @@ TEST_CASE("Collection as range", "[collection][ranges][std]") {
// std::range::bidirectional_range
STATIC_REQUIRE(std::ranges::bidirectional_range<CollectionType>);
// std::range::random_access_range
DOCUMENTED_STATIC_FAILURE(std::ranges::random_access_range<CollectionType>);
STATIC_REQUIRE(std::ranges::random_access_range<CollectionType>);
// std::range::contiguous_range
DOCUMENTED_STATIC_FAILURE(std::ranges::contiguous_range<CollectionType>);
// std::range::common_range
Expand Down

0 comments on commit 4abfadd

Please sign in to comment.