diff --git a/doc/collections_as_container.md b/doc/collections_as_container.md index 6b8461e5b..233918d57 100644 --- a/doc/collections_as_container.md +++ b/doc/collections_as_container.md @@ -7,7 +7,7 @@ The PODIO `Collection`s interface was designed to mimic the standard *Container* On the implementation level most of the differences with respect to the *Container* comes from the fact that in order to satisfy the additional semantics a `Collection` doesn't directly store [user layer objects](design.md#the-user-layer). Instead, [data layer objects](design.md#the-internal-data-layer) are stored and user layer objects are constructed and returned when needed. Similarly, the `Collection` iterators operate on the user layer objects but don't expose `Collection`'s storage directly to the users. Instead, they construct and return user layer objects when needed. In other words, a `Collection` utilizes the user layer type as a reference type instead of using plain references (`&` or `&&`) to stored data layer types. -As a consequence some of the **standard algorithms may not work** with PODIO `Collection` iterators. See [standard algorithm documentation](#collection-and-standard-algorithms) below. +As a consequence some of the **standard algorithms may not work** with PODIO `Collection` iterators. See [standard algorithm documentation](#collection-and-standard-algorithms) below. The following tables list the compliance of a PODIO generated collection with the *Container* named requirement, stating which member types, interfaces, or concepts are fulfilled and which are not. Additionally, there are some comments explaining missing parts or pointing out differences in behaviour. @@ -16,12 +16,12 @@ The following tables list the compliance of a PODIO generated collection with th | Name | Type | Requirements | Fulfilled by Collection? | Comment | |------|------|--------------|--------------------------|---------| | `value_type` | `T` | *[Erasable](https://en.cppreference.com/w/cpp/named_req/Erasable)* | ✔️ yes | Defined as an immutable user layer object type | -| `reference` | `T&` | | ❌ no | Not defined | +| `reference` | `T&` | | ❌ no | Not defined | | `const_reference` | `const T&` | | ❌ no | Not defined | -| `iterator` | Iterator whose `value_type` is `T` | [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) convertible to `const_iterator` | ❌ no | Defined as podio `MutableCollectionIterator`. `iterator::value_type` not defined, not [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) ([see below](#legacyforwarditerator)), not convertible to `const_iterator`| -| `const_iterator` | Constant iterator whose `value_type` is `T` | [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no | Defined as podio `CollectionIterator`. `const_iterator::value_type` not defined, not [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) ([see below](#legacyforwarditerator)) -| `difference_type`| Signed integer | Must be the same as `std::iterator_traits::difference_type` for `iterator` and `const_iterator` | ❌ no | `std::iterator_traits::difference_type` not defined | -| `size_type` | Unsigned integer | Large enough to represent all positive values of `difference_type` | ✔️ yes | | +| `iterator` | Iterator whose `value_type` is `T` | [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) convertible to `const_iterator` | ❌ no | Defined as podio `MutableCollectionIterator`. Not [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) ([see below](#legacyforwarditerator)), not convertible to `const_iterator`| +| `const_iterator` | Constant iterator whose `value_type` is `T` | [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no | Defined as podio `CollectionIterator`. Not [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) ([see below](#legacyforwarditerator)) +| `difference_type`| Signed integer | Must be the same as `std::iterator_traits::difference_type` for `iterator` and `const_iterator` | ✔️ yes | | +| `size_type` | Unsigned integer | Large enough to represent all positive values of `difference_type` | ✔️ yes | | ### Container member functions and operators @@ -31,18 +31,18 @@ The following tables list the compliance of a PODIO generated collection with th | `C(a)` | `C` | Creates a copy of `a` | ❌ no | Not defined, non-copyable by design | | `C(rv)` | `C` | Moves `rv` | ✔️ yes | | | `a = b` | `C&` | Destroys or copy-assigns all elements of `a` from elements of `b` | ❌ no | Not defined, non-copyable by design | -| `a = rv` | `C&` | Destroys or move-assigns all elements of `a` from elements of `rv` | ✔️ yes | | -| `a.~C()` | `void` | Destroys all elements of `a` and frees all memory| ✔️ yes | Invalidates all handles retrieved from this collection | -| `a.begin()` | `(const_)iterator` | Iterator to the first element of `a` | ✔️ yes | | +| `a = rv` | `C&` | Destroys or move-assigns all elements of `a` from elements of `rv` | ✔️ yes | | +| `a.~C()` | `void` | Destroys all elements of `a` and frees all memory| ✔️ yes | Invalidates all handles retrieved from this collection | +| `a.begin()` | `(const_)iterator` | Iterator to the first element of `a` | ✔️ yes | | | `a.end()` | `(const_)iterator` | Iterator to one past the last element of `a` | ✔️ yes | | | `a.cbegin()` | `const_iterator` | Same as `const_cast(a).begin()` | ✔️ yes | | -| `a.cend()` | `const_iterator` | Same as `const_cast(a).end()`| ✔️ yes | | +| `a.cend()` | `const_iterator` | Same as `const_cast(a).end()`| ✔️ yes | | | `a == b` | Convertible to `bool` | Same as `std::equal(a.begin(), a.end(), b.begin(), b.end())`| ❌ no | Not defined | | `a != b` | Convertible to `bool` | Same as `!(a == b)` | ❌ no | Not defined | | `a.swap(b)` | `void` | Exchanges the values of `a` and `b` | ❌ no | Not defined | | `swap(a,b)` | `void` | Same as `a.swap(b)` | ❌ no | `a.swap(b)` not defined | | `a.size()` | `size_type` | Same as `std::distance(a.begin(), a.end())` | ✔️ yes | | -| `a.max_size()` | `size_type` | `b.size()` where b is the largest possible container | ✔️ yes | | +| `a.max_size()` | `size_type` | `b.size()` where `b` is the largest possible container | ✔️ yes | | | `a.empty()` | Convertible to `bool` | Same as `a.begin() == a.end()` | ✔️ yes | | ## Collection as an *AllocatorAwareContainer* @@ -53,9 +53,9 @@ PODIO collections don't provide a customization point for allocators and use onl ### AllocatorAwareContainer types -| Name | Requirements | Fulfilled by Collection? | Comment | +| Name | Requirements | Fulfilled by Collection? | Comment | |------|--------------|--------------------------|---------| -| `allocator_type` | `allocator_type::value_type` same as `value_type` | ❌ no | `allocator_type` not defined | +| `allocator_type` | `allocator_type::value_type` same as `value_type` | ❌ no | `allocator_type` not defined | ### *AllocatorAwareContainer* expression and statements @@ -66,12 +66,13 @@ The PODIO Collections currently are not checked against expression and statement The C++ specifies a set of named requirements for iterators. Starting with C++20 the standard specifies also iterator concepts. The requirements imposed by the concepts and named requirements are similar but not identical. In the following tables a convention from `Collection` is used: `iterator` stands for PODIO `MutableCollectionIterator` and `const_iterator` stands for PODIO `CollectionIterator`. + ### Iterator summary | Named requirement | `iterator` | `const_iterator` | |-------------------|-----------------------|-----------------------------| -| [LegacyIterator](https://en.cppreference.com/w/cpp/named_req/Iterator) | ❌ no ([see below](#legacyiterator)) | ❌ no ([see below](#legacyiterator)) | -| [LegacyInputIterator](https://en.cppreference.com/w/cpp/named_req/InputIterator) | ❌ no ([see below](#legacyinputiterator)) | ❌ no ([see below](#legacyinputiterator)) | +| [LegacyIterator](https://en.cppreference.com/w/cpp/named_req/Iterator) | ✔️ yes ([see below](#legacyiterator)) | ✔️ yes ([see below](#legacyiterator)) | +| [LegacyInputIterator](https://en.cppreference.com/w/cpp/named_req/InputIterator) | ✔️ yes ([see below](#legacyinputiterator)) | ✔️ yes ([see below](#legacyinputiterator)) | | [LegacyForwardIterator](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no ([see below](#legacyforwarditerator)) | ❌ no ([see below](#legacyforwarditerator)) | | [LegacyOutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator) | ❌ no ([see below](#legacyoutputiterator)) | ❌ no ([see below](#legacyoutputiterator)) | | [LegacyBidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator) | ❌ no | ❌ no | @@ -80,12 +81,12 @@ In the following tables a convention from `Collection` is used: `iterator` stand | Concept | `iterator` | `const_iterator` | |---------|------------------------|------------------------------| -| `std::indirectly_readable` | ❌ no | ❌ no | +| `std::indirectly_readable` | ✔️ yes | ✔️ yes | | `std::indirectly_writable` | ❌ no | ❌ no | -| `std::weakly_incrementable` | ❌ no | ❌ no | -| `std::incrementable` | ❌ no | ❌ no | -| `std::input_or_output_iterator` | ❌ no | ❌ no | -| `std::input_iterator` | ❌ no | ❌ no | +| `std::weakly_incrementable` | ✔️ yes | ✔️ yes | +| `std::incrementable` | ✔️ yes | ✔️ yes | +| `std::input_or_output_iterator` | ✔️ yes | ✔️ yes | +| `std::input_iterator` | ✔️ yes | ✔️ yes | | `std::output_iterator` | ❌ no | ❌ no | | `std::forward_iterator` | ❌ no | ❌ no | | `std::bidirectional_iterator` | ❌ no | ❌ no | @@ -96,15 +97,15 @@ In the following tables a convention from `Collection` is used: `iterator` stand | Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | |-------------|-------------------------------------------|---------| -| [*CopyConstructible*](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) | ❌ no / ❌ no | Move constructor and copy constructor not defined | -| [*CopyAssignable*](https://en.cppreference.com/w/cpp/named_req/CopyAssignable) | ❌ no / ❌ no | Move assignment and copy assignment not defined | +| [*CopyConstructible*](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) | ✔️ yes / ✔️ yes | | +| [*CopyAssignable*](https://en.cppreference.com/w/cpp/named_req/CopyAssignable) | ✔️ yes / ✔️ yes | | | [*Destructible*](https://en.cppreference.com/w/cpp/named_req/Destructible) | ✔️ yes / ✔️ yes | | -| [*Swappable*](https://en.cppreference.com/w/cpp/named_req/Swappable) | ❌ no / ❌ no | | -| `std::iterator_traits::value_type` (Until C++20 ) | ❌ no / ❌ no | Not defined | -| `std::iterator_traits::difference_type` | ❌ no / ❌ no | Not defined | -| `std::iterator_traits::reference` | ❌ no / ❌ no | Not defined | -| `std::iterator_traits::pointer` | ❌ no / ❌ no | Not defined | -| `std::iterator_traits::iterator_category` | ❌ no / ❌ no | Not defined | +| [*Swappable*](https://en.cppreference.com/w/cpp/named_req/Swappable) | ✔️ yes / ✔️ yes | | +| `std::iterator_traits::value_type` (Until C++20 ) | ✔️ yes / ✔️ yes | | +| `std::iterator_traits::difference_type` | ✔️ yes / ✔️ yes | | +| `std::iterator_traits::reference` | ✔️ yes / ✔️ yes | | +| `std::iterator_traits::pointer` | ✔️ yes / ✔️ yes | | +| `std::iterator_traits::iterator_category` | ✔️ yes / ✔️ yes | | | Expression | Return type | Semantics | Fulfilled by `iterator`/`const_iterator`? | Comment | |------------|-------------|-----------|-------------------------------------------|---------| @@ -115,49 +116,48 @@ In the following tables a convention from `Collection` is used: `iterator` stand | Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | |-------------|-------------------------------------------|---------| -| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ❌ no / ❌ no | [See above](#legacyiterator) | +| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ✔️ yes / ✔️ yes | [See above](#legacyiterator) | | [*EqualityComparable*](https://en.cppreference.com/w/cpp/named_req/EqualityComparable) | ✔️ yes / ✔️ yes | | | Expression | Return type | Semantics | Fulfilled by `iterator`/`const_iterator`? | Comment | |------------|-------------|-----------|-------------------------------------------|---------| -| `i != j` | Contextually convertible to `bool` | Same as `!(i==j)` | ✔️ yes / ✔️ yes | | -| `*i` | `reference`, convertible to `value_type` | | ❌ no / ❌ no | `reference` and `value_type` not defined | +| `i != j` | Contextually convertible to `bool` | Same as `!(i==j)` | ✔️ yes / ✔️ yes | | +| `*i` | `reference`, convertible to `value_type` | | ✔️ yes / ✔️ yes | | | `i->m` | | Same as `(*i).m` | ✔️ yes / ✔️ yes | | | `++r` | `It&` | | ✔️ yes / ✔️ yes | | -| `(void)r++` | | Same as `(void)++r` | ❌ no / ❌ no | Post-increment not defined | -| `*r++` | Convertible to `value_type` | Same as `value_type x = *r; ++r; return x;` | ❌ no / ❌ no | Post-increment and `value_type` not defined | +| `(void)r++` | | Same as `(void)++r` | ✔️ yes / ✔️ yes | | +| `*r++` | Convertible to `value_type` | Same as `value_type x = *r; ++r; return x;` | ✔️ yes / ✔️ yes | | ### LegacyForwardIterator In addition to the *LegacyForwardIterator* the C++ standard specifies also the *mutable LegacyForwardIterator*, which is both *LegacyForwardIterator* and *LegacyOutputIterator*. The term **mutable** used in this context doesn't imply mutability in the sense used in the PODIO. - | Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | |-------------|-------------------------------------------|---------| -| [*LegacyInputIterator*](https://en.cppreference.com/w/cpp/named_req/InputIterator) | ❌ no / ❌ no | [See above](#legacyinputiterator)| -| [*DefaultConstructible*](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) | ❌ no / ❌ no | Value initialization not defined | -| If *mutable* iterator then `reference` same as `value_type&` or `value_type&&`, otherwise same as `const value_type&` or `const value_type&&` | ❌ no / ❌ no | `reference` and `value_type` not defined | -| [Multipass guarantee](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no / ❌ no | Copy constructor not defined | -| [Singular iterators](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no / ❌ no | Value initialization not defined | +| [*LegacyInputIterator*](https://en.cppreference.com/w/cpp/named_req/InputIterator) | ✔️ yes / ✔️ yes | [See above](#legacyinputiterator)| +| [*DefaultConstructible*](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible) | ✔️ yes / ✔️ yes | | +| If *mutable* iterator then `reference` same as `value_type&` or `value_type&&`, otherwise same as `const value_type&` or `const value_type&&` | ❌ no / ❌ no | `reference` type is not a reference (`&` or `&&`) | +| [Multipass guarantee](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ❌ no / ❌ no | References from dereferencing equal iterators aren't bound to the same object | +| [Singular iterators](https://en.cppreference.com/w/cpp/named_req/ForwardIterator) | ✔️ yes / ✔️ yes | | | Expression | Return type | Semantics | Fulfilled by `iterator`/`const_iterator`? | Comment | |------------|-------------|-----------|-------------------------------------------|---------| -| `i++` | `It` | Same as `It ip = i; ++i; return ip;` | ❌ no / ❌ no | Post-increment not defined | -| `*i++` | `reference` | | ❌ no / ❌ no | Post-increment and `reference` not defined | +| `i++` | `It` | Same as `It ip = i; ++i; return ip;` | ✔️ yes / ✔️ yes | | +| `*i++` | `reference` | | ✔️ yes / ✔️ yes | | ### LegacyOutputIterator | Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | |-------------|-------------------------------------------|---------| -| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ❌ no / ❌ no | [See above](#legacyiterator) | +| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ✔️ yes / ✔️ yes | [See above](#legacyiterator) | | Is pointer type or class type | ✔️ yes / ✔️ yes | | | Expression | Return type | Semantics | Fulfilled by `iterator`/`const_iterator`? | Comment | |------------|-------------|-----------|-------------------------------------------|---------| | `*r = o` | | | ❗ attention / ❗ attention | Defined but an assignment doesn't modify objects inside collection | -| `++r` | `It&` | | ✔️ yes / ✔️ yes | | -| `r++` | Convertible to `const It&` | Same as `It temp = r; ++r; return temp;` | ❌ no / ❌ no | Post-increment not defined | -| `*r++ = o` | | Same as `*r = o; ++r;`| ❌ no / ❌ no | Post-increment not defined | +| `++r` | `It&` | | ✔️ yes / ✔️ yes | | +| `r++` | Convertible to `const It&` | Same as `It temp = r; ++r; return temp;` | ✔️ yes / ✔️ yes | | +| `*r++ = o` | | Same as `*r = o; ++r;`| ❗ attention / ❗ attention | Defined but an assignment doesn't modify objects inside collection | ## Collection iterators and standard iterator adaptors @@ -167,17 +167,57 @@ In addition to the *LegacyForwardIterator* the C++ standard specifies also the * | `std::back_insert_iterator` | ❗ attention | Compatible only with SubsetCollections, otherwise throws `std::invalid_argument` | | `std::front_insert_iterator` | ❌ no | `push_front` not defined | | `std::insert_iterator` | ❌ no | `insert` not defined | -| `std::const_iterator` | ❌ no | `iterator` and `const_iterator` not *LegacyInputIterator* or `std::input_iterator` | -| `std::move_iterator` | ❌ no | `iterator` and `const_iterator` not *LegacyInputIterator* or `std::input_iterator` | -| `std::counted_iterator` | ❌ no | `iterator` and `const_iterator` not `std::input_or_output_iterator` | - +| `std::const_iterator` (C++23) | ❗ attention | `operator->` not defined as `iterator`'s and `const_iterator`'s `operator->` are non-`const`. | +| `std::move_iterator` | ✔️ yes | Limited usefulness since dereference returns `reference` type not rvalue reference (`&&`) | +| `std::counted_iterator` | ❗ attention | `operator->` not defined as it requires `std::contiguous_iterator` | + + +## Collection as a *range* + +| Concept | Fulfilled by Collection? | +|---------|--------------------------| +| `std::ranges::range` | ✔️ yes | +| `std::ranges::borrowed_range` | ❌ no | +| `std::ranges::sized_range` | ✔️ yes | +| `std::ranges::input_range` | ✔️ yes | +| `std::ranges::output_range` | ❌ no | +| `std::ranges::forward_range` | ❌ no | +| `std::ranges::bidirectional_range` | ❌ no | +| `std::ranges::random_access_range` | ❌ no | +| `std::ranges::contiguous_range` | ❌ no | +| `std::ranges::common_range` | ✔️ yes | +| `std::ranges::viewable_range` | ✔️ yes | ## Collection and standard algorithms -Most of the standard algorithms require the iterators to be at least *InputIterator*. The iterators of PODIO collection don't fulfil this requirement, therefore they are not compatible with standard algorithms according to the specification. +Most of the standard algorithms require iterators to meet specific named requirements, such as [*LegacyInputIterator*](https://en.cppreference.com/w/cpp/named_req/InputIterator), [*LegacyForwardIterator*](https://en.cppreference.com/w/cpp/named_req/ForwardIterator), or [*LegacyRandomAccessIterator*](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator). These requirements are not always strictly enforced at compile time, making it essential to understand the allowed iterator category when using standard algorithms. + +The iterators of PODIO collections conform to the [**LegacyInputIterator**](https://en.cppreference.com/w/cpp/named_req/InputIterator) named requirement, therefore are guaranteed to work with any algorithm requiring [**LegacyIterator**](https://en.cppreference.com/w/cpp/named_req/Iterator) or [**LegacyInputIterator**](https://en.cppreference.com/w/cpp/named_req/InputIterator). -In practice, some algorithms may still compile with the collections depending on the implementation of a given algorithm. In general, the standard **algorithms mutating a collection will give wrong results**, while the standard algorithms not mutating a collection in principle should give correct results if they compile. +Algorithms requiring a different iterator category may compile but do not guarantee correct results. An important case are mutating algorithms requiring the iterators to be [*writable*](https://en.cppreference.com/w/cpp/iterator), or [*LegacyOutputIterator*](https://en.cppreference.com/w/cpp/named_req/OutputIterator) or *mutable*, which are not compatible and will produce incorrect results. + +For example: +```c++ +std::find_if(std::begin(collection), std::end(collection), predicate ); // requires InputIterator -> OK +std::adjacent_find(std::begin(collection), std::end(collection), predicate ); // requires ForwardIterator -> might compile, unspecified result +std::fill(std::begin(collection), std::end(collection), value ); // requires ForwardIterator and writable -> might compile, wrong result +std::sort(std::begin(collection), std::end(collection)); // requires RandomAccessIterator and Swappable -> might compile, wrong result +``` ## Standard range algorithms -The standard range algorithm use constrains to operate at least on `std::input_iterator`s and `std::ranges::input_range`s. The iterators of PODIO collection don't model these concepts, therefore can't be used with standard range algorithms. The range algorithms won't compile with PODIO `Collection` iterators. +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::input_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::input_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++ +std::ranges::find_if(collection, predicate ); // requires input_range -> OK +std::ranges::adjacent_find(collection, predicate ); // requires forward_range -> won't compile +std::ranges::fill(collection, value ); // requires output_range -> won't compile +std::ranges::sort(collection); // requires random_access_range and sortable -> won't compile +``` diff --git a/python/templates/macros/iterator.jinja2 b/python/templates/macros/iterator.jinja2 index 470953666..d3b512f94 100644 --- a/python/templates/macros/iterator.jinja2 +++ b/python/templates/macros/iterator.jinja2 @@ -3,27 +3,39 @@ {% set ptr_init = 'podio::utils::MaybeSharedPtr<' + class.bare_type +'Obj>{nullptr}' %} class {{ iterator_type }} { public: + using value_type = {{ class.bare_type }}; + using difference_type = ptrdiff_t; + using reference = {{ prefix }}{{ class.bare_type }}; + using pointer = {{ prefix }}{{ class.bare_type }}*; + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::input_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; - {{ iterator_type }}(const {{ iterator_type }}&) = delete; - {{ iterator_type }}& operator=(const {{ iterator_type }}&) = delete; + {{ iterator_type }}(const {{ iterator_type }}&) = default; + {{ iterator_type }}({{ iterator_type }}&&) = default; + {{ iterator_type }}& operator=(const {{ iterator_type }}&) = default; + {{ iterator_type }}& operator=({{iterator_type}}&&) = default; + ~{{ iterator_type }}() = default; bool operator!=(const {{ iterator_type}}& x) const { - return m_index != x.m_index; // TODO: may not be complete + return m_index != x.m_index; } bool operator==(const {{ iterator_type }}& x) const { - return m_index == x.m_index; // TODO: may not be complete + return m_index == x.m_index; } - {{ prefix }}{{ class.bare_type }} operator*(); - {{ prefix }}{{ class.bare_type }}* operator->(); + reference operator*() const; + pointer operator->(); {{ iterator_type }}& operator++(); + {{ iterator_type }} operator++(int); private: - size_t m_index; - {{ prefix }}{{ class.bare_type }} m_object; - const {{ class.bare_type }}ObjPointerContainer* m_collection; + size_t m_index{0}; + {{ prefix }}{{ class.bare_type }} m_object { {{ ptr_init }} }; + const {{ class.bare_type }}ObjPointerContainer* m_collection{nullptr}; }; {% endwith %} {% endmacro %} @@ -32,12 +44,11 @@ private: {% macro iterator_definitions(class, prefix='') %} {% with iterator_type = class.bare_type + prefix + 'CollectionIterator' %} {% set ptr_type = 'podio::utils::MaybeSharedPtr<' + class.bare_type +'Obj>' %} -{{ prefix }}{{ class.bare_type }} {{ iterator_type }}::operator*() { - m_object.m_obj = {{ ptr_type }}((*m_collection)[m_index]); - return m_object; +{{ iterator_type }}::reference {{ iterator_type }}::operator*() const { + return reference{ {{ ptr_type }}((*m_collection)[m_index]) }; } -{{ prefix }}{{ class.bare_type }}* {{ iterator_type }}::operator->() { +{{ iterator_type }}::pointer {{ iterator_type }}::operator->() { m_object.m_obj = {{ ptr_type }}((*m_collection)[m_index]); return &m_object; } @@ -47,5 +58,11 @@ private: return *this; } +{{ iterator_type }} {{ iterator_type }}::operator++(int) { + auto copy = *this; + ++m_index; + return copy; +} + {% endwith %} {% endmacro %} diff --git a/tests/unittests/std_interoperability.cpp b/tests/unittests/std_interoperability.cpp index cde0fdb54..868452f47 100644 --- a/tests/unittests/std_interoperability.cpp +++ b/tests/unittests/std_interoperability.cpp @@ -1,10 +1,15 @@ #include "datamodel/ExampleHit.h" #include "datamodel/ExampleHitCollection.h" #include "datamodel/MutableExampleHit.h" + #include + +#include +#include #include #include #include +#include #include #include @@ -299,21 +304,26 @@ TEST_CASE("Collection container types", "[collection][container][types][std]") { // iterator STATIC_REQUIRE(traits::has_iterator_v); DOCUMENTED_STATIC_FAILURE(std::is_convertible_v); + STATIC_REQUIRE(traits::has_value_type_v); + STATIC_REQUIRE( + std::is_same_v::value_type>); // const_iterator STATIC_REQUIRE(traits::has_const_iterator_v); + STATIC_REQUIRE(traits::has_value_type_v); + STATIC_REQUIRE( + std::is_same_v::value_type>); // difference_type STATIC_REQUIRE(traits::has_difference_type_v); STATIC_REQUIRE(std::is_signed_v); STATIC_REQUIRE(std::is_integral_v); - DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); - // STATIC_REQUIRE( - // std::is_same_v::difference_type>); - DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); - // STATIC_REQUIRE(std::is_same_v::difference_type>); + STATIC_REQUIRE(traits::has_difference_type_v>); + STATIC_REQUIRE( + std::is_same_v::difference_type>); + STATIC_REQUIRE(traits::has_difference_type_v>); + STATIC_REQUIRE(std::is_same_v::difference_type>); // size_type STATIC_REQUIRE(traits::has_size_type_v); @@ -403,37 +413,116 @@ TEST_CASE("Collection AllocatorAwareContainer types", "[collection][container][t } // TODO add tests for AllocatorAwareContainer statements and expressions -TEST_CASE("Collection and iterator concepts") { #if (__cplusplus >= 202002L) - SECTION("Iterator") { - DOCUMENTED_STATIC_FAILURE(std::indirectly_readable); - DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); - DOCUMENTED_STATIC_FAILURE(std::weakly_incrementable); - DOCUMENTED_STATIC_FAILURE(std::incrementable); - DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); - DOCUMENTED_STATIC_FAILURE(std::input_iterator); - DOCUMENTED_STATIC_FAILURE(std::output_iterator); - DOCUMENTED_STATIC_FAILURE(std::forward_iterator); - DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); - DOCUMENTED_STATIC_FAILURE(std::random_access_iterator); - DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); +TEST_CASE("Collection and iterator concepts", "[collection][container][iterator][std]") { + + SECTION("input_or_output_iterator") { + // weakly incrementable + // iterator + STATIC_REQUIRE(std::weakly_incrementable); + { + auto coll = CollectionType(); + coll.create(); + auto it1 = coll.begin(); + auto it2 = coll.begin(); + REQUIRE(it1 == it2); + ++it1; + it2++; + REQUIRE(it1 == it2); + it1 = coll.begin(); + REQUIRE(std::addressof(++it1) == std::addressof(it1)); + } + // const_iterator + STATIC_REQUIRE(std::weakly_incrementable); + { + auto coll = CollectionType(); + coll.create(); + auto it1 = coll.cbegin(); + auto it2 = coll.cbegin(); + REQUIRE(it1 == it2); + ++it1; + it2++; + REQUIRE(it1 == it2); + it1 = coll.cbegin(); + REQUIRE(std::addressof(++it1) == std::addressof(it1)); + } + + // incrementable + // iterator + STATIC_REQUIRE(std::incrementable); + { + auto coll = CollectionType(); + coll.create(); + auto a = coll.begin(); + auto b = coll.begin(); + REQUIRE(bool(a == b)); + REQUIRE(bool(a++ == b)); + a = coll.begin(); + REQUIRE(bool(a == b)); + REQUIRE(bool(((void)a++, a) == ++b)); + } + // const_iterator + STATIC_REQUIRE(std::incrementable); + { + auto coll = CollectionType(); + coll.create(); + auto a = coll.cbegin(); + auto b = coll.cbegin(); + REQUIRE(bool(a == b)); + REQUIRE(bool(a++ == b)); + a = coll.cbegin(); + REQUIRE(bool(a == b)); + REQUIRE(bool(((void)a++, a) == ++b)); + } + + // input_or_output_iterator + // iterator + STATIC_REQUIRE(std::input_or_output_iterator); + // const_iterator + STATIC_REQUIRE(std::input_or_output_iterator); } - SECTION("Const_iterator") { - DOCUMENTED_STATIC_FAILURE(std::indirectly_readable); - DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); - DOCUMENTED_STATIC_FAILURE(std::weakly_incrementable); - DOCUMENTED_STATIC_FAILURE(std::incrementable); - DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); - DOCUMENTED_STATIC_FAILURE(std::input_iterator); - DOCUMENTED_STATIC_FAILURE(std::output_iterator); - DOCUMENTED_STATIC_FAILURE(std::forward_iterator); - DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); - DOCUMENTED_STATIC_FAILURE(std::random_access_iterator); - DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); + + SECTION("input_iterator") { + // indirectly_readable + // iterator + STATIC_REQUIRE(std::indirectly_readable); + // const_iterator + STATIC_REQUIRE(std::indirectly_readable); + + // input_iterator + // iterator + STATIC_REQUIRE(std::input_iterator); + // const_iterator + STATIC_REQUIRE(std::input_iterator); } -#endif } +TEST_CASE("Collection and unsupported iterator concepts", "[collection][container][iterator][std]") { + // std::indirectly_writable + DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); + DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); + DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); + DOCUMENTED_STATIC_FAILURE(std::indirectly_writable); + // std::output_iterator + DOCUMENTED_STATIC_FAILURE(std::output_iterator); + DOCUMENTED_STATIC_FAILURE(std::output_iterator); + DOCUMENTED_STATIC_FAILURE(std::output_iterator); + DOCUMENTED_STATIC_FAILURE(std::output_iterator); + // std::forward_iterator + DOCUMENTED_STATIC_FAILURE(std::forward_iterator); + DOCUMENTED_STATIC_FAILURE(std::forward_iterator); + // std::bidirectional_iterator + DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); + DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); + // std::random_access_iterator + DOCUMENTED_STATIC_FAILURE(std::random_access_iterator); + DOCUMENTED_STATIC_FAILURE(std::random_access_iterator); + // std::contiguous_iterator + DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); + DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); +} +#endif // __cplusplus >= 202002L + TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // the checks are duplicated for iterator and const_iterator as expectations on them are slightly different @@ -448,19 +537,19 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // CopyConstructible // iterator - DOCUMENTED_STATIC_FAILURE(std::is_move_constructible_v); - DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE(std::is_copy_constructible_v); // const_iterator - DOCUMENTED_STATIC_FAILURE(std::is_move_constructible_v); - DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); + STATIC_REQUIRE(std::is_move_constructible_v); + STATIC_REQUIRE(std::is_copy_constructible_v); // CopyAssignable // iterator - DOCUMENTED_STATIC_FAILURE(std::is_move_assignable_v); - DOCUMENTED_STATIC_FAILURE(std::is_copy_assignable_v); + STATIC_REQUIRE(std::is_move_assignable_v); + STATIC_REQUIRE(std::is_copy_assignable_v); // const_iterator - DOCUMENTED_STATIC_FAILURE(std::is_move_assignable_v); - DOCUMENTED_STATIC_FAILURE(std::is_copy_assignable_v); + STATIC_REQUIRE(std::is_move_assignable_v); + STATIC_REQUIRE(std::is_copy_assignable_v); // Destructible // iterator @@ -470,40 +559,40 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // Swappable // iterator - DOCUMENTED_STATIC_FAILURE(std::is_swappable_v); + STATIC_REQUIRE(std::is_swappable_v); // const_iterator - DOCUMENTED_STATIC_FAILURE(std::is_swappable_v); + STATIC_REQUIRE(std::is_swappable_v); #if (__cplusplus < 202002L) // std::iterator_traits::value_type (required prior to C++20) // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v>); + STATIC_REQUIRE(traits::has_value_type_v>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v>); + STATIC_REQUIRE(traits::has_value_type_v>); #endif // std::iterator_traits::difference_type // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); + STATIC_REQUIRE(traits::has_difference_type_v>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); + STATIC_REQUIRE(traits::has_difference_type_v>); // std::iterator_traits::reference // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); + STATIC_REQUIRE(traits::has_reference_v>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); + STATIC_REQUIRE(traits::has_reference_v>); // std::iterator_traits::pointer // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_pointer_v>); + STATIC_REQUIRE(traits::has_pointer_v>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_pointer_v>); + STATIC_REQUIRE(traits::has_pointer_v>); // std::iterator_traits::iterator_category // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); + STATIC_REQUIRE(traits::has_iterator_category_v>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); + STATIC_REQUIRE(traits::has_iterator_category_v>); // *r // iterator @@ -544,20 +633,19 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // *i // iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - // STATIC_REQUIRE(std::is_same_v::reference, - // decltype(*std::declval())>); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE(std::is_convertible_v()), - // std::iterator_traits::value_type>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE(std::is_same_v::reference, decltype(*std::declval())>); + STATIC_REQUIRE(traits::has_value_type_v); + STATIC_REQUIRE( + std::is_convertible_v()), std::iterator_traits::value_type>); // const_iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - // STATIC_REQUIRE(std::is_same_v::reference, - // decltype(*std::declval())>); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE(std::is_convertible_v()), - // std::iterator_traits::value_type>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE( + std::is_same_v::reference, decltype(*std::declval())>); + STATIC_REQUIRE(traits::has_value_type_v); + STATIC_REQUIRE(std::is_convertible_v()), + std::iterator_traits::value_type>); // i->m // iterator @@ -582,79 +670,77 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // (void)r++ // iterator STATIC_REQUIRE(traits::has_preincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE( - // std::is_same_v()), decltype((void)std::declval()++)>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE( + std::is_same_v()), decltype((void)std::declval()++)>); // const_iterator STATIC_REQUIRE(traits::has_preincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE(std::is_same_v()), - // decltype((void)std::declval()++)>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_same_v()), + decltype((void)std::declval()++)>); // *r++ // iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v>); - // STATIC_REQUIRE( - // std::is_convertible_v()++), std::iterator_traits::value_type>); + STATIC_REQUIRE(traits::has_value_type_v>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE( + std::is_convertible_v()++), std::iterator_traits::value_type>); // const_iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v>); - // STATIC_REQUIRE(std::is_convertible_v()++), - // std::iterator_traits::value_type>); + STATIC_REQUIRE(traits::has_value_type_v>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_convertible_v()++), + std::iterator_traits::value_type>); // iterator_category - not strictly necessary but advised // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + STATIC_REQUIRE(std::is_base_of_v::iterator_category>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + STATIC_REQUIRE( + std::is_base_of_v::iterator_category>); } // end of LegacyInputIterator // Mutable LegacyForwardIterator (LegacyForwardIterator that is also LegacyOutputIterator): // - reference same as value_type& or value_type&& // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE( - // std::is_same_v::reference, std::iterator_traits::value_type&> || - // std::is_same_v::reference, std::iterator_traits::value_type&&>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE( + std::is_same_v::reference, std::iterator_traits::value_type&> || + std::is_same_v::reference, std::iterator_traits::value_type&&>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE( - // std::is_same_v::reference, - // std::iterator_traits::value_type&> || - // std::is_same_v::reference, - // std::iterator_traits::value_type&&>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE(std::is_same_v::reference, + std::iterator_traits::value_type&> || + std::is_same_v::reference, + std::iterator_traits::value_type&&>); // (Immutable) iterator: // - reference same as const value_type& or const value_type&& // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE(std::is_same_v::reference, - // const std::iterator_traits::value_type&> || - // std::is_same_v::reference, - // const std::iterator_traits::value_type&&>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE( + std::is_same_v::reference, const std::iterator_traits::value_type&> || + std::is_same_v::reference, const std::iterator_traits::value_type&&>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); - DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v); - // STATIC_REQUIRE(std::is_same_v::reference, - // const std::iterator_traits::value_type&> || - // std::is_same_v::reference, - // const std::iterator_traits::value_type&&>); + STATIC_REQUIRE(traits::has_reference_v); + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE(std::is_same_v::reference, + const std::iterator_traits::value_type&> || + std::is_same_v::reference, + const std::iterator_traits::value_type&&>); // DefaultConstructible // iterator - DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); + STATIC_REQUIRE(std::is_default_constructible_v); // const_iterator - DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); + STATIC_REQUIRE(std::is_default_constructible_v); // Multipass guarantee // iterator @@ -670,11 +756,15 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { REQUIRE(*a == *b); REQUIRE(++a == ++b); REQUIRE(*a == *b); - DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); - // auto a_copy = a; - // ++a_copy; - // REQUIRE(a == b); - // REQUIRE(*a == *b); + STATIC_REQUIRE(std::is_copy_constructible_v); + auto a_copy = a; + ++a_copy; + REQUIRE(a == b); + REQUIRE(*a == *b); + // bound to the same object + const auto& ref_a = *a; + const auto& ref_b = *b; + DOCUMENTED_FAILURE(std::addressof(ref_a) == std::addressof(ref_b)); // const_iterator auto ca = coll.cbegin(); @@ -683,58 +773,57 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { REQUIRE(*ca == *cb); REQUIRE(++ca == ++cb); REQUIRE(*ca == *cb); - DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); - // auto ca_copy = ca; - // ++ca_copy; - // REQUIRE(ca == cb); - // REQUIRE(*ca == *cb); + STATIC_REQUIRE(std::is_copy_constructible_v); + auto ca_copy = ca; + ++ca_copy; + REQUIRE(ca == cb); + REQUIRE(*ca == *cb); + // bound to the same object + const auto& ref_ca = *ca; + const auto& ref_cb = *cb; + DOCUMENTED_FAILURE(std::addressof(ref_ca) == std::addressof(ref_cb)); } // Singular iterators // iterator STATIC_REQUIRE(traits::has_equality_comparator_v); - DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); - //{ - // REQUIRE(iterator{} == iterator{}); - //} + STATIC_REQUIRE(std::is_default_constructible_v); + { REQUIRE(iterator{} == iterator{}); } // const_iterator STATIC_REQUIRE(traits::has_equality_comparator_v); - DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); - //{ - // REQUIRE(const_iterator{} == const_iterator{}); - //} + STATIC_REQUIRE(std::is_default_constructible_v); + { REQUIRE(const_iterator{} == const_iterator{}); } // i++ // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE(std::is_same_v()++), iterator>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_same_v()++), iterator>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE(std::is_same_v()++), const_iterator>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_same_v()++), const_iterator>); // *i++ // iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); - // STATIC_REQUIRE(std::is_same_v()++), - // std::iterator_traits::reference>); + STATIC_REQUIRE(traits::has_reference_v>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_same_v()++), std::iterator_traits::reference>); // const_iterator STATIC_REQUIRE(traits::has_indirection_v); - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); - // STATIC_REQUIRE( - // std::is_same_v()++), - // std::iterator_traits::reference>); + STATIC_REQUIRE(traits::has_reference_v>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE( + std::is_same_v()++), std::iterator_traits::reference>); // iterator_category - not strictly necessary but advised // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category>); } // end of LegacyForwardIterator @@ -783,57 +872,73 @@ TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { // r++ // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE(std::is_convertible_v()++), const iterator&>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_convertible_v()++), const iterator&>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_postincrement_v); - // STATIC_REQUIRE(std::is_convertible_v()++), const const_iterator&>); + STATIC_REQUIRE(traits::has_postincrement_v); + STATIC_REQUIRE(std::is_convertible_v()++), const const_iterator&>); // *r++ = o // iterator DOCUMENTED_STATIC_FAILURE(traits::has_dereference_assignment_increment_v); - DOCUMENTED_STATIC_FAILURE(traits::has_dereference_assignment_increment_v); - // TODO add runtime check for assignment validity like in '*r = o' case + STATIC_REQUIRE(traits::has_dereference_assignment_increment_v); + { + auto coll = CollectionType{}; + auto item = coll.create(13ull, 0., 0., 0., 0.); + REQUIRE(coll.begin()->cellID() == 13ull); + auto new_item = CollectionType::mutable_type{42ull, 0., 0., 0., 0.}; + *coll.begin()++ = new_item; + DOCUMENTED_FAILURE(coll.begin()->cellID() == 42ull); + } // const_iterator - DOCUMENTED_STATIC_FAILURE( - traits::has_dereference_assignment_increment_v); - DOCUMENTED_STATIC_FAILURE( - traits::has_dereference_assignment_increment_v); - // TODO add runtime check for assignment validity like in '*r = o' case + STATIC_REQUIRE(traits::has_dereference_assignment_increment_v); + STATIC_REQUIRE(traits::has_dereference_assignment_increment_v); + { + auto coll = CollectionType{}; + auto item = coll.create(13ull, 0., 0., 0., 0.); + REQUIRE(coll.cbegin()->cellID() == 13ull); + auto new_item = CollectionType::mutable_type{42ull, 0., 0., 0., 0.}; + *coll.cbegin()++ = new_item; + DOCUMENTED_FAILURE(coll.cbegin()->cellID() == 42ull); + new_item.cellID(44ull); + *coll.cbegin()++ = static_cast(new_item); + DOCUMENTED_FAILURE(coll.cbegin()->cellID() == 44ull); + } // iterator_category - not strictly necessary but advised // Derived either from: // - std::output_iterator_tag // - std::forward_iterator_tag (for mutable LegacyForwardIterators) // iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category> || - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category> || + std::is_base_of_v::iterator_category>); // const_iterator - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE(std::is_base_of_v::iterator_category> || - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category> || + std::is_base_of_v::iterator_category>); } // end of LegacyOutputIterator } TEST_CASE("Collection and std iterator adaptors", "[collection][container][adapter][std]") { - auto coll = CollectionType(); SECTION("Reverse iterator") { // iterator STATIC_REQUIRE(traits::has_iterator_v); - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE( - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category>); #if (__cplusplus >= 202002L) DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); #endif + // TODO add runtime checks here // const_iterator STATIC_REQUIRE(traits::has_const_iterator_v); - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE( - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + DOCUMENTED_STATIC_FAILURE( + std::is_base_of_v::iterator_category>); #if (__cplusplus >= 202002L) DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); #endif @@ -847,6 +952,7 @@ TEST_CASE("Collection and std iterator adaptors", "[collection][container][adapt STATIC_REQUIRE(traits::has_push_back_v); STATIC_REQUIRE(traits::has_push_back_v); + auto coll = CollectionType(); auto it = std::back_inserter(coll); // insert immutable to not-SubsetCollection REQUIRE_THROWS_AS(it = CollectionType::value_type{}, std::invalid_argument); @@ -886,43 +992,218 @@ TEST_CASE("Collection and std iterator adaptors", "[collection][container][adapt // TODO add runtime checks here } +#if (__cplusplus >= 202302L) SECTION("Const iterator") { - // C++23 required - DOCUMENTED_STATIC_FAILURE((__cplusplus >= 202302L)); + // iterator + STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(std::input_iterator); + // make_const_iterator(iterator) gives basic_const_iterator not our iterator or our const_iterator + STATIC_REQUIRE(std::is_same_v, std::basic_const_iterator>); + { + auto coll = CollectionType(); + coll.create().cellID(42); + auto cit = std::make_const_iterator(std::begin(coll)); + REQUIRE((*cit).cellID() == 42); + // can't -> because iterators' -> is non-const + DOCUMENTED_STATIC_FAILURE(traits::has_member_of_pointer_v>); + // REQUIRE(cit->cellID() == 42) + // REQUIRE(counted.base()->cellID() == 42); + } + // const_iterator + STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(std::input_iterator); + // make_const_iterator(iterator) gives basic_const_iterator not our iterator or our const_iterator + STATIC_REQUIRE(std::is_same_v, std::basic_const_iterator>); + { + auto coll = CollectionType(); + coll.create().cellID(42); + auto cit = std::make_const_iterator(std::cbegin(coll)); + REQUIRE((*cit).cellID() == 42); + // can't -> because const_iterators' -> is non-const + DOCUMENTED_STATIC_FAILURE(traits::has_member_of_pointer_v>); + // REQUIRE(cit->cellID() == 42) + // REQUIRE(counted.base()->cellID() == 42); + } } +#endif SECTION("Move iterator") { // iterator STATIC_REQUIRE(traits::has_iterator_v); - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE( - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + STATIC_REQUIRE(std::is_base_of_v::iterator_category>); #if (__cplusplus >= 202002L) - DOCUMENTED_STATIC_FAILURE(std::input_iterator); + STATIC_REQUIRE(std::input_iterator); #endif - // TODO add more checks here + STATIC_REQUIRE(std::is_same_v::reference>); // const_iterator STATIC_REQUIRE(traits::has_iterator_v); - DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); - // STATIC_REQUIRE( - // std::is_base_of_v::iterator_category>); + STATIC_REQUIRE(traits::has_iterator_category_v>); + STATIC_REQUIRE(std::is_base_of_v::iterator_category>); #if (__cplusplus >= 202002L) - DOCUMENTED_STATIC_FAILURE(std::input_iterator); + STATIC_REQUIRE(std::input_iterator); #endif - // TODO add more checks here + STATIC_REQUIRE(std::is_same_v::reference>); } #if (__cplusplus >= 202002L) SECTION("Counted iterator") { // iterator - DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); - // TODO add runtime checks + STATIC_REQUIRE(std::input_or_output_iterator); + { + auto coll = CollectionType(); + coll.create().cellID(42); + auto counted = std::counted_iterator(coll.begin(), coll.size()); + REQUIRE(counted != std::default_sentinel); + REQUIRE(counted.count() == 1); + REQUIRE((*counted).cellID() == 42); + DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); // counted-> requires std::contiguous_iterator + // REQUIRE(counted->cellID() == 42); + DOCUMENTED_STATIC_FAILURE(traits::has_member_of_pointer_v); // can't base()->() because + // base() returns const object + // REQUIRE(counted.base()->cellID() == 42); + REQUIRE(++counted == std::default_sentinel); + } // const_iterator - DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); - // TODO add runtime checks + STATIC_REQUIRE(std::input_or_output_iterator); + { + auto coll = CollectionType(); + coll.create().cellID(42); + auto counted = std::counted_iterator(coll.cbegin(), coll.size()); + REQUIRE(counted != std::default_sentinel); + REQUIRE(counted.count() == 1); + REQUIRE((*counted).cellID() == 42); + DOCUMENTED_STATIC_FAILURE(std::contiguous_iterator); // counted-> requires std::contiguous_iterator + // REQUIRE(counted->cellID() == 42) + DOCUMENTED_STATIC_FAILURE(traits::has_member_of_pointer_v); // can't base()->() because + // base() returns const object + // REQUIRE(counted.base()->cellID() == 42); + REQUIRE(++counted == std::default_sentinel); + } } #endif } +#if (__cplusplus >= 202002L) +TEST_CASE("Collection as range", "[collection][ranges][std]") { + CollectionType coll; + coll.create(); + + // std::ranges::range + STATIC_REQUIRE(std::ranges::range); + // std::range::borrowed_range + DOCUMENTED_STATIC_FAILURE(std::ranges::borrowed_range); + // std::range::sized_range + STATIC_REQUIRE(std::ranges::sized_range); + REQUIRE(std::ranges::size(coll) == 1); + REQUIRE(std::ranges::size(coll) == + static_cast(std::ranges::distance(std::begin(coll), std::end(coll)))); + // std::ranges::input_range + STATIC_REQUIRE(std::ranges::input_range); + // std::range::output_range + DOCUMENTED_STATIC_FAILURE(std::ranges::output_range); + DOCUMENTED_STATIC_FAILURE(std::ranges::output_range); + // std::range::forward_range + DOCUMENTED_STATIC_FAILURE(std::ranges::forward_range); + // std::range::bidirectional_range + DOCUMENTED_STATIC_FAILURE(std::ranges::bidirectional_range); + // std::range::random_access_range + DOCUMENTED_STATIC_FAILURE(std::ranges::random_access_range); + // std::range::contiguous_range + DOCUMENTED_STATIC_FAILURE(std::ranges::contiguous_range); + // std::range::common_range + STATIC_REQUIRE(std::ranges::common_range); + // std::range::viewable_range + STATIC_REQUIRE(std::ranges::viewable_range); +} +#endif + +TEST_CASE("Collection and std algorithms", "[collection][iterator][std]") { + auto coll = CollectionType(); + coll.create().cellID(1); + coll.create().cellID(5); + coll.create().cellID(2); + coll.create().cellID(2); + coll.create().cellID(3); + + // std::find_if + auto it = std::find_if(std::cbegin(coll), std::cend(coll), [](const auto& x) { return x.cellID() == 5; }); + REQUIRE(it != std::cend(coll)); + REQUIRE(it == ++std::cbegin(coll)); + it = std::find_if(std::cbegin(coll), std::cend(coll), [](const auto& x) { return x.cellID() == 0; }); + REQUIRE(it == std::cend(coll)); + + // std::count_if + REQUIRE(2 == std::count_if(std::cbegin(coll), std::cend(coll), [](const auto& x) { return x.cellID() > 2; })); + + // std::copy_if + auto subcoll = CollectionType{}; + subcoll.setSubsetCollection(); + std::copy_if(std::begin(coll), std::end(coll), std::back_inserter(subcoll), + [](const auto& x) { return x.cellID() > 2; }); + REQUIRE(subcoll.size() == 2); + REQUIRE(subcoll[0].cellID() == 5); + REQUIRE(subcoll[1].cellID() == 3); + + // Algorithms requiring iterator category not supported by collection iterators + // are not checked here as their compilation and results are unspecified +} + +#if (__cplusplus >= 202002L) + +TEST_CASE("Collection and std ranges algorithms", "[collection][ranges][std]") { + auto coll = CollectionType(); + coll.create().cellID(1); + coll.create().cellID(5); + coll.create().cellID(2); + coll.create().cellID(2); + coll.create().cellID(3); + + // std::ranges_find_if + auto it = std::ranges::find_if(coll, [](const auto& x) { return x.cellID() == 5; }); + REQUIRE(it != std::end(coll)); + REQUIRE(it == ++std::begin(coll)); + it = std::ranges::find_if(coll, [](const auto& x) { return x.cellID() == 0; }); + REQUIRE(it == std::end(coll)); + + // std::ranges::count_if + REQUIRE(2 == std::ranges::count_if(coll, [](const auto& x) { return x.cellID() > 2; })); + + // std::ranges_copy_if + auto subcoll = CollectionType{}; + subcoll.setSubsetCollection(); + std::ranges::copy_if(coll, std::back_inserter(subcoll), [](const auto& x) { return x.cellID() > 2; }); + REQUIRE(subcoll.size() == 2); + REQUIRE(subcoll[0].cellID() == 5); + REQUIRE(subcoll[1].cellID() == 3); +} + +// helper concept for unsupported algorithm compilation test +template +concept is_range_adjacent_findable = requires(T coll) { + std::ranges::adjacent_find(coll, [](const auto& a, const auto& b) { return a.cellID() == b.cellID(); }); +}; + +// helper concept for unsupported algorithm compilation test +template +concept is_range_sortable = requires(T coll) { + std::ranges::sort(coll, [](const auto& a, const auto& b) { return a.cellID() < b.cellID(); }); +}; + +// helper concept for unsupported algorithm compilation test +template +concept is_range_fillable = requires(T coll) { + std::ranges::fill(coll, typename T::value_type{}); +}; + +TEST_CASE("Collection and unsupported std ranges algorithms", "[collection][ranges][std]") { + // check that algorithms requiring unsupported iterator concepts won't compile + DOCUMENTED_STATIC_FAILURE(is_range_adjacent_findable); + DOCUMENTED_STATIC_FAILURE(is_range_sortable); + DOCUMENTED_STATIC_FAILURE(is_range_fillable); +} + +#endif // __cplusplus >= 202002L + #undef DOCUMENTED_STATIC_FAILURE #undef DOCUMENTED_FAILURE