diff --git a/doc/collections_as_container.md b/doc/collections_as_container.md new file mode 100644 index 000000000..6b8461e5b --- /dev/null +++ b/doc/collections_as_container.md @@ -0,0 +1,183 @@ +# PODIO Collection as a *Container* + +Comparison of the PODIO `Collection`s with a C++ named requirement [*Container*](https://en.cppreference.com/w/cpp/named_req/Container). + +The PODIO `Collection`s interface was designed to mimic the standard *Container* interface, in particular `std::vector`. Perfect compliance with the *Container* is not achieved as the `Collection`s are concerned with additional semantics such as mutable/immutable element access, associations and relations, and IO which that are not part of *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. + +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. + +### Container Types + +| 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 | +| `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 | | + +### Container member functions and operators + +| Expression | Return type | Semantics | Fulfilled by Collection? | Comment | +|------------|-------------|-----------|--------------------------|---------| +| `C()` | `C` | Creates an empty container | ✔️ yes | | +| `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.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 == 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.empty()` | Convertible to `bool` | Same as `a.begin() == a.end()` | ✔️ yes | | + +## Collection as an *AllocatorAwareContainer* + +The C++ standard specifies [AllocatorAwareContainer](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer) for containers that can use other allocators beside the default allocator. + +PODIO collections don't provide a customization point for allocators and use only the default allocator. Therefore they are not *AllocatorAwareContainers*. + +### AllocatorAwareContainer types + +| Name | Requirements | Fulfilled by Collection? | Comment | +|------|--------------|--------------------------|---------| +| `allocator_type` | `allocator_type::value_type` same as `value_type` | ❌ no | `allocator_type` not defined | + +### *AllocatorAwareContainer* expression and statements + +The PODIO Collections currently are not checked against expression and statements requirements for *AllocatorAwareContainer*. + +## Collection iterators as an *Iterator* + +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)) | +| [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 | +| [LegacyRandomAccessIterator](https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator) | ❌ no | ❌ no | +| [LegacyContiguousIterator](https://en.cppreference.com/w/cpp/named_req/ContiguousIterator) | ❌ no | ❌ no | + +| Concept | `iterator` | `const_iterator` | +|---------|------------------------|------------------------------| +| `std::indirectly_readable` | ❌ no | ❌ no | +| `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::output_iterator` | ❌ no | ❌ no | +| `std::forward_iterator` | ❌ no | ❌ no | +| `std::bidirectional_iterator` | ❌ no | ❌ no | +| `std::random_access_iterator` | ❌ no | ❌ no | +| `std::contiguous_iterator` | ❌ no | ❌ no | + +### LegacyIterator + +| 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 | +| [*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 | + +| Expression | Return type | Semantics | Fulfilled by `iterator`/`const_iterator`? | Comment | +|------------|-------------|-----------|-------------------------------------------|---------| +| `*r` | Unspecified | | ✔️ yes / ✔️ yes | | +| `++r` | `It&` | | ✔️ yes / ✔️ yes | | + +### LegacyInputIterator + +| Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | +|-------------|-------------------------------------------|---------| +| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ❌ no / ❌ no | [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->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 | + +### 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 | + +| 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 | + +### LegacyOutputIterator + +| Requirement | Fulfilled by `iterator`/`const_iterator`? | Comment | +|-------------|-------------------------------------------|---------| +| [*LegacyIterator*](https://en.cppreference.com/w/cpp/named_req/Iterator) | ❌ no / ❌ no | [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 | + +## Collection iterators and standard iterator adaptors + +| Adaptor | Compatible with Collection? | Comment | +|---------|-----------------------------|---------| +| `std::reverse_iterator` | ❌ no | `iterator` and `const_iterator` not *LegacyBidirectionalIterator* or `std::bidirectional_iterator` | +| `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` | + + +## 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. + +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. + +## 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. diff --git a/doc/index.rst b/doc/index.rst index 4c826ff50..5960b64c3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,5 +19,6 @@ Welcome to PODIO's documentation! advanced_topics.md templates.md python.md + collections_as_container.md cpp_api/api py_api/modules diff --git a/include/podio/CollectionBase.h b/include/podio/CollectionBase.h index ae6fa1d4a..2c0224557 100644 --- a/include/podio/CollectionBase.h +++ b/include/podio/CollectionBase.h @@ -55,6 +55,9 @@ class CollectionBase { /// number of elements in the collection virtual size_t size() const = 0; + /// maximal number of elements in the collection + virtual std::size_t max_size() const = 0; + /// Is the collection empty virtual bool empty() const = 0; diff --git a/include/podio/UserDataCollection.h b/include/podio/UserDataCollection.h index 0b2c9a092..35407cddb 100644 --- a/include/podio/UserDataCollection.h +++ b/include/podio/UserDataCollection.h @@ -77,6 +77,12 @@ class UserDataCollection : public CollectionBase { VectorMembersInfo m_vecmem_info{}; public: + using value_type = typename std::vector::value_type; + using const_iterator = typename std::vector::const_iterator; + using iterator = typename std::vector::iterator; + using difference_type = typename std::vector::difference_type; + using size_type = typename std::vector::size_type; + UserDataCollection() = default; /// Constructor from an existing vector (which will be moved from!) UserDataCollection(std::vector&& vec) : _vec(std::move(vec)) { @@ -133,6 +139,11 @@ class UserDataCollection : public CollectionBase { return _vec.size(); } + /// maximal number of elements in the collection + size_t max_size() const override { + return _vec.max_size(); + } + /// Is the collection empty bool empty() const override { return _vec.empty(); @@ -194,18 +205,24 @@ class UserDataCollection : public CollectionBase { // ----- some wrappers for std::vector and access to the complete std::vector (if really needed) - typename std::vector::iterator begin() { + iterator begin() { return _vec.begin(); } - typename std::vector::iterator end() { + iterator end() { return _vec.end(); } - typename std::vector::const_iterator begin() const { + const_iterator begin() const { return _vec.begin(); } - typename std::vector::const_iterator end() const { + const_iterator end() const { return _vec.end(); } + const_iterator cbegin() const { + return _vec.cbegin(); + } + const_iterator cend() const { + return _vec.cend(); + } typename std::vector::reference operator[](size_t idx) { return _vec[idx]; diff --git a/python/templates/Collection.cc.jinja2 b/python/templates/Collection.cc.jinja2 index c212784d3..004219e31 100644 --- a/python/templates/Collection.cc.jinja2 +++ b/python/templates/Collection.cc.jinja2 @@ -59,6 +59,10 @@ std::size_t {{ collection_type }}::size() const { return m_storage.entries.size(); } +std::size_t {{ collection_type }}::max_size() const { + return m_storage.entries.max_size(); +} + bool {{ collection_type }}::empty() const { return m_storage.entries.empty(); } diff --git a/python/templates/Collection.h.jinja2 b/python/templates/Collection.h.jinja2 index 6e1e45ada..b57864cc7 100644 --- a/python/templates/Collection.h.jinja2 +++ b/python/templates/Collection.h.jinja2 @@ -51,6 +51,8 @@ public: using value_type = {{ class.bare_type }}; using const_iterator = {{ class.bare_type }}CollectionIterator; using iterator = {{ class.bare_type }}MutableCollectionIterator; + using difference_type = ptrdiff_t; + using size_type = size_t; {{ class.bare_type }}Collection(); {{ class.bare_type }}Collection({{ class.bare_type }}CollectionData&& data, bool isSubsetColl); @@ -86,6 +88,9 @@ public: /// number of elements in the collection std::size_t size() const final; + /// maximal number of elements in the collection + std::size_t max_size() const final; + /// Is the collection empty bool empty() const final; @@ -153,12 +158,18 @@ public: const_iterator begin() const { return const_iterator(0, &m_storage.entries); } + const_iterator cbegin() const { + return begin(); + } iterator end() { return iterator(m_storage.entries.size(), &m_storage.entries); } const_iterator end() const { return const_iterator(m_storage.entries.size(), &m_storage.entries); } + const_iterator cend() const { + return end(); + } {% for member in Members %} std::vector<{{ member.full_type }}> {{ member.name }}(const size_t nElem = 0) const; diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index e57da8103..0cb3a7214 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -40,7 +40,7 @@ if(NOT Catch2_FOUND) endif() find_package(Threads REQUIRED) -add_executable(unittest_podio unittest.cpp frame.cpp buffer_factory.cpp interface_types.cpp) +add_executable(unittest_podio unittest.cpp frame.cpp buffer_factory.cpp interface_types.cpp std_interoperability.cpp) target_link_libraries(unittest_podio PUBLIC TestDataModel PRIVATE Catch2::Catch2WithMain Threads::Threads podio::podioRootIO) if (ENABLE_SIO) target_link_libraries(unittest_podio PRIVATE podio::podioSioIO) diff --git a/tests/unittests/std_interoperability.cpp b/tests/unittests/std_interoperability.cpp new file mode 100644 index 000000000..b0a97843a --- /dev/null +++ b/tests/unittests/std_interoperability.cpp @@ -0,0 +1,929 @@ +#include "datamodel/ExampleHit.h" +#include "datamodel/ExampleHitCollection.h" +#include "datamodel/MutableExampleHit.h" +#include +#include +#include +#include +#include +#include + +// DOCUMENTED_STATIC_FAILURE and DOCUMENTED_FAILURE macro are used to indicate checks that corresponds to requirements +// imposed but the standard but currently not met by podio. These check use inverted logic (they pass when the +// requirement is not met) in order to detect when the support for the requirement is added. +// On their failure: +// - replace DOCUMENTED_STATIC_FAILURE with STATIC_REQUIRE_FALSE or DOCUMENTED_FAILURE with REQUIRE_FALSE +// - uncomment checks below it +// - update documentation 'doc/collection_as_container.md' +#define DOCUMENTED_STATIC_FAILURE(...) STATIC_REQUIRE_FALSE(__VA_ARGS__) +#define DOCUMENTED_FAILURE(...) REQUIRE_FALSE(__VA_ARGS__) + +using CollectionType = ExampleHitCollection; +using iterator = CollectionType::iterator; +using const_iterator = CollectionType::const_iterator; + +namespace traits { + +// typename T::value_type +template +struct has_value_type : std::false_type {}; +template +struct has_value_type> : std::true_type {}; +template +inline constexpr bool has_value_type_v = has_value_type::value; + +// typename T::reference +template +struct has_reference : std::false_type {}; +template +struct has_reference> : std::true_type {}; +template +inline constexpr bool has_reference_v = has_reference::value; + +// typename T::const_reference +template +struct has_const_reference : std::false_type {}; +template +struct has_const_reference> : std::true_type {}; +template +inline constexpr bool has_const_reference_v = has_const_reference::value; + +// typename T::iterator +template +struct has_iterator : std::false_type {}; +template +struct has_iterator> : std::true_type {}; +template +inline constexpr bool has_iterator_v = has_iterator::value; + +// typename T::const_iterator +template +struct has_const_iterator : std::false_type {}; +template +struct has_const_iterator> : std::true_type {}; +template +inline constexpr bool has_const_iterator_v = has_const_iterator::value; + +// typename T::difference_type +template +struct has_difference_type : std::false_type {}; +template +struct has_difference_type> : std::true_type {}; +template +inline constexpr bool has_difference_type_v = has_difference_type::value; + +// typename T::size_type +template +struct has_size_type : std::false_type {}; +template +struct has_size_type> : std::true_type {}; +template +inline constexpr bool has_size_type_v = has_size_type::value; + +// typename T::pointer +template +struct has_pointer : std::false_type {}; +template +struct has_pointer> : std::true_type {}; +template +inline constexpr bool has_pointer_v = has_pointer::value; + +// typename T::allocator_type +template +struct has_allocator_type : std::false_type {}; +template +struct has_allocator_type> : std::true_type {}; +template +inline constexpr bool has_allocator_type_v = has_allocator_type::value; + +// is_erasable_allocator_unaware +template +struct is_erasable_allocator_unaware : std::false_type {}; +template +struct is_erasable_allocator_unaware< + T, + std::void_t>::destroy( + std::declval&>(), + std::declval>()))>> : std::true_type {}; +template +inline constexpr bool is_erasable_allocator_unaware_v = is_erasable_allocator_unaware::value; + +// typename T::iterator_category +template +struct has_iterator_category : std::false_type {}; +template +struct has_iterator_category> : std::true_type {}; +template +inline constexpr bool has_iterator_category_v = has_iterator_category::value; + +// T::begin() +template +struct has_begin : std::false_type {}; +template +struct has_begin().begin())>> : std::true_type {}; +template +inline constexpr bool has_begin_v = has_begin::value; + +// T::end() +template +struct has_end : std::false_type {}; +template +struct has_end().end())>> : std::true_type {}; +template +inline constexpr bool has_end_v = has_end::value; + +// T::cbegin() +template +struct has_cbegin : std::false_type {}; +template +struct has_cbegin().cbegin())>> : std::true_type {}; +template +inline constexpr bool has_cbegin_v = has_cbegin::value; + +// T::cend() +template +struct has_cend : std::false_type {}; +template +struct has_cend().cend())>> : std::true_type {}; +template +inline constexpr bool has_cend_v = has_cend::value; + +// T::operator==(T) +template +struct has_equality_comparator : std::false_type {}; +template +struct has_equality_comparator() == std::declval())>> : std::true_type {}; +template +inline constexpr bool has_equality_comparator_v = has_equality_comparator::value; + +// T::operator!=(T) +template +struct has_inequality_comparator : std::false_type {}; +template +struct has_inequality_comparator() != std::declval())>> : std::true_type {}; +template +inline constexpr bool has_inequality_comparator_v = has_inequality_comparator::value; + +// T::swap(T) +template +struct has_swap : std::false_type {}; +template +struct has_swap().swap(std::declval()))>> : std::true_type {}; +template +inline constexpr bool has_swap_v = has_swap::value; + +// T::size() +template +struct has_size : std::false_type {}; +template +struct has_size().size())>> : std::true_type {}; +template +inline constexpr bool has_size_v = has_size::value; + +// T::max_size() +template +struct has_max_size : std::false_type {}; +template +struct has_max_size().max_size())>> : std::true_type {}; +template +inline constexpr bool has_max_size_v = has_max_size::value; + +// T::empty() +template +struct has_empty : std::false_type {}; +template +struct has_empty().empty())>> : std::true_type {}; +template +inline constexpr bool has_empty_v = has_empty::value; + +// T::operator*() +template +struct has_indirection : std::false_type {}; +template +struct has_indirection())>> : std::true_type {}; +template +constexpr bool has_indirection_v = has_indirection::value; + +// T::operator->() +template +struct has_member_of_pointer : std::false_type {}; +template +struct has_member_of_pointer().operator->())>> : std::true_type {}; +template +constexpr bool has_member_of_pointer_v = has_member_of_pointer::value; + +// T::operator++() (preincrement) +template +struct has_preincrement : std::false_type {}; +template +struct has_preincrement())>> : std::true_type {}; +template +inline constexpr bool has_preincrement_v = has_preincrement::value; + +// T::operator++(int) (postincrement) +template +struct has_postincrement : std::false_type {}; +template +struct has_postincrement()++)>> : std::true_type {}; +template +inline constexpr bool has_postincrement_v = has_postincrement::value; + +// *It = val +template +struct has_dereference_assignment : std::false_type {}; +template +struct has_dereference_assignment() = std::declval())>> + : std::true_type {}; +template +inline constexpr bool has_dereference_assignment_v = has_dereference_assignment::value; + +// *It++ = val +template +struct has_dereference_assignment_increment : std::false_type {}; +template +struct has_dereference_assignment_increment()++ = std::declval())>> + : std::true_type {}; +template +inline constexpr bool has_dereference_assignment_increment_v = has_dereference_assignment_increment::value; + +// T::push_back(Value) +template +struct has_push_back : std::false_type {}; +template +struct has_push_back().push_back(std::declval()))>> + : std::true_type {}; +template +inline constexpr bool has_push_back_v = has_push_back::value; + +// T::push_front(Value) +template +struct has_push_front : std::false_type {}; +template +struct has_push_front().push_front(std::declval()))>> + : std::true_type {}; +template +inline constexpr bool has_push_front_v = has_push_front::value; + +// T::insert(Value) +template +struct has_insert : std::false_type {}; +template +struct has_insert().insert(std::declval(), std::declval()))>> + : std::true_type {}; +template +inline constexpr bool has_insert_v = has_insert::value; +} // namespace traits + +TEST_CASE("Collection container types", "[collection][container][types][std]") { + + // value_type + STATIC_REQUIRE(traits::has_value_type_v); + + // Erasable -allocator aware - mutually exclusive with Erasable -allocator not aware + DOCUMENTED_STATIC_FAILURE(traits::has_allocator_type_v); + // add check for `std::allocator_traits::destroy(m, p);` expression here + // STATIC_REQUIRE(...) + // Erasable -allocator not aware - mutually exclusive // Erasable -allocator aware + STATIC_REQUIRE(traits::is_erasable_allocator_unaware_v); + + // reference + DOCUMENTED_STATIC_FAILURE(traits::has_reference_v); + // STATIC_REQUIRE(std::is_same_v); + + // const_reference + DOCUMENTED_STATIC_FAILURE(traits::has_const_reference_v); + // STATIC_REQUIRE(std::is_same_v); + + // iterator + STATIC_REQUIRE(traits::has_iterator_v); + DOCUMENTED_STATIC_FAILURE(std::is_convertible_v); + + // const_iterator + STATIC_REQUIRE(traits::has_const_iterator_v); + + // 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>); + + // size_type + STATIC_REQUIRE(traits::has_size_type_v); + STATIC_REQUIRE(std::is_unsigned_v); + STATIC_REQUIRE(std::is_integral_v); + STATIC_REQUIRE(std::numeric_limits::max() >= + std::numeric_limits::max()); +} + +TEST_CASE("Collection container members", "[collection][container][members][std]") { + // C() + STATIC_REQUIRE(std::is_default_constructible_v); + REQUIRE(CollectionType().empty() == true); + + // C(a) + DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); + + // C(rv) + STATIC_REQUIRE(std::is_move_constructible_v); + + // a = b + DOCUMENTED_STATIC_FAILURE(std::is_copy_assignable_v); + + // a = rv + STATIC_REQUIRE(std::is_move_assignable_v); + + // a.~C() + STATIC_REQUIRE(std::is_destructible_v); + + // a.begin() + STATIC_REQUIRE(traits::has_begin_v); + STATIC_REQUIRE(std::is_same_v().begin()), CollectionType::iterator>); + STATIC_REQUIRE( + std::is_same_v().begin()), CollectionType::const_iterator>); + + // a.end() + STATIC_REQUIRE(traits::has_end_v); + STATIC_REQUIRE(std::is_same_v().end()), CollectionType::iterator>); + STATIC_REQUIRE(std::is_same_v().end()), CollectionType::const_iterator>); + + // a.cbegin() + STATIC_REQUIRE(traits::has_cbegin_v); + STATIC_REQUIRE(std::is_same_v().cbegin()), CollectionType::const_iterator>); + + // a.cend() + STATIC_REQUIRE(traits::has_cend_v); + STATIC_REQUIRE(std::is_same_v().cend()), CollectionType::const_iterator>); + + // value_type is EqualityComparable + STATIC_REQUIRE(traits::has_equality_comparator_v); + STATIC_REQUIRE( + std::is_convertible_v< + decltype(std::declval() != std::declval()), bool>); + // a == b + DOCUMENTED_STATIC_FAILURE(traits::has_equality_comparator_v); + // STATIC_REQUIRE(std::is_convertible_v()==std::declval()), + // bool>); + + // a != b + DOCUMENTED_STATIC_FAILURE(traits::has_inequality_comparator_v); + // STATIC_REQUIRE(std::is_convertible_v()!=std::declval()), + // bool>); + + // a.swap(b) + DOCUMENTED_STATIC_FAILURE(traits::has_swap_v); + // STATIC_REQUIRE( + // std::is_same_v().swap(std::declval())), void>); + + // swap(a,b) + STATIC_REQUIRE(std::is_swappable_v); + + // a.size() + STATIC_REQUIRE(traits::has_size_v); + STATIC_REQUIRE(std::is_same_v().size()), CollectionType::size_type>); + + // a.max_size()) + STATIC_REQUIRE(traits::has_max_size_v); + STATIC_REQUIRE(std::is_same_v().max_size()), CollectionType::size_type>); + + // a.empty() + STATIC_REQUIRE(traits::has_empty_v); + STATIC_REQUIRE(std::is_convertible_v().empty()), bool>); +} + +TEST_CASE("Collection AllocatorAwareContainer types", "[collection][container][types][std]") { + DOCUMENTED_STATIC_FAILURE(traits::has_allocator_type_v); +} +// 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); + } + 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); + } +#endif +} + +TEST_CASE("Collection iterators", "[collection][container][iterator][std]") { + // the checks are duplicated for iterator and const_iterator as expectations on them are slightly different + + // nested sections as the requirements make a hierarchy + SECTION("LegacyForwardIterator") { + + // LegacyForwardIterator requires LegacyInputIterator + SECTION("LegacyInputIterator") { + + // LegacyInputIterator requires LegacyIterator + SECTION("LegacyIterator") { + + // CopyConstructible + // iterator + DOCUMENTED_STATIC_FAILURE(std::is_move_constructible_v); + DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); + // const_iterator + DOCUMENTED_STATIC_FAILURE(std::is_move_constructible_v); + DOCUMENTED_STATIC_FAILURE(std::is_copy_constructible_v); + + // CopyAssignable + // iterator + DOCUMENTED_STATIC_FAILURE(std::is_move_assignable_v); + DOCUMENTED_STATIC_FAILURE(std::is_copy_assignable_v); + // const_iterator + DOCUMENTED_STATIC_FAILURE(std::is_move_assignable_v); + DOCUMENTED_STATIC_FAILURE(std::is_copy_assignable_v); + + // Destructible + // iterator + STATIC_REQUIRE(std::is_nothrow_destructible_v); + // const_iterator + STATIC_REQUIRE(std::is_nothrow_destructible_v); + + // Swappable + // iterator + DOCUMENTED_STATIC_FAILURE(std::is_swappable_v); + // const_iterator + DOCUMENTED_STATIC_FAILURE(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>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_value_type_v>); +#endif + // std::iterator_traits::difference_type + // iterator + DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_difference_type_v>); + + // std::iterator_traits::reference + // iterator + DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_reference_v>); + + // std::iterator_traits::pointer + // iterator + DOCUMENTED_STATIC_FAILURE(traits::has_pointer_v>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_pointer_v>); + + // std::iterator_traits::iterator_category + // iterator + DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); + + // *r + // iterator + STATIC_REQUIRE(traits::has_indirection_v); + STATIC_REQUIRE_FALSE(std::is_same_v())>); + // const_iterator + STATIC_REQUIRE(traits::has_indirection_v); + STATIC_REQUIRE_FALSE(std::is_same_v())>); + + // ++r + // iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), iterator&>); + // const_iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), const_iterator&>); + + } // end of LegacyIterator + + // EqualityComparable + // iterator + STATIC_REQUIRE(traits::has_equality_comparator_v); + STATIC_REQUIRE(std::is_convertible_v() != std::declval()), bool>); + // const_iterator + STATIC_REQUIRE(traits::has_equality_comparator_v); + STATIC_REQUIRE( + std::is_convertible_v() != std::declval()), bool>); + + // i != j (contextually convertible) + // iterator + STATIC_REQUIRE(traits::has_inequality_comparator_v); + STATIC_REQUIRE(std::is_constructible_v() != std::declval())>); + // const_ iterator + STATIC_REQUIRE(traits::has_inequality_comparator_v); + STATIC_REQUIRE( + std::is_constructible_v() != std::declval())>); + + // *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>); + // 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>); + + // i->m + // iterator + STATIC_REQUIRE(traits::has_member_of_pointer_v); + STATIC_REQUIRE(traits::has_indirection_v); + STATIC_REQUIRE( + std::is_same_v()->energy()), decltype((*std::declval()).energy())>); + // const_iterator + STATIC_REQUIRE(traits::has_member_of_pointer_v); + STATIC_REQUIRE(traits::has_indirection_v); + STATIC_REQUIRE(std::is_same_v()->energy()), + decltype((*std::declval()).energy())>); + + // ++r + // iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), iterator&>); + // const_iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), const_iterator&>); + + // (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()++)>); + // 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()++)>); + + // *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>); + // 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>); + + // 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>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(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&&>); + // 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&&>); + + // (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&&>); + // 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&&>); + + // DefaultConstructible + // iterator + DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); + // const_iterator + DOCUMENTED_STATIC_FAILURE(std::is_default_constructible_v); + + // Multipass guarantee + // iterator + { + CollectionType coll; + for (int i = 0; i < 3; ++i) { + coll.create(); + } + // iterator + auto a = coll.begin(); + auto b = coll.begin(); + REQUIRE(a == b); + 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); + + // const_iterator + auto ca = coll.cbegin(); + auto cb = coll.cbegin(); + REQUIRE(ca == cb); + 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); + } + + // Singular iterators + // iterator + STATIC_REQUIRE(traits::has_equality_comparator_v); + DOCUMENTED_STATIC_FAILURE(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{}); + //} + + // i++ + // iterator + DOCUMENTED_STATIC_FAILURE(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>); + + // *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>); + // 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>); + + // 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>); + // const_iterator + DOCUMENTED_STATIC_FAILURE(traits::has_iterator_category_v>); + // STATIC_REQUIRE(std::is_base_of_v::iterator_category>); + + } // end of LegacyForwardIterator + + SECTION("LegacyOutputIterator") { + + // is class type or pointer type + // iterator + STATIC_REQUIRE(std::is_pointer_v || std::is_class_v); + // const_iterator + STATIC_REQUIRE(std::is_pointer_v || std::is_class_v); + + // *r = o + // iterator + DOCUMENTED_STATIC_FAILURE(traits::has_dereference_assignment_v); + STATIC_REQUIRE(traits::has_dereference_assignment_v); + { + auto coll = CollectionType{}; + auto item = coll.create(13ull, 0., 0., 0., 0.); + REQUIRE(coll.begin()->cellID() == 13ull); + auto new_item = CollectionType::value_type::mutable_type{42ull, 0., 0., 0., 0.}; + *coll.begin() = new_item; + DOCUMENTED_FAILURE(coll.begin()->cellID() == 42ull); + } + // const_iterator + STATIC_REQUIRE(traits::has_dereference_assignment_v); + STATIC_REQUIRE(traits::has_dereference_assignment_v); + { + auto coll = CollectionType{}; + auto item = coll.create(13ull, 0., 0., 0., 0.); + REQUIRE(coll.cbegin()->cellID() == 13ull); + auto new_item = CollectionType::value_type::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); + } + + // ++r + // iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), iterator&>); + // const_iterator + STATIC_REQUIRE(traits::has_preincrement_v); + STATIC_REQUIRE(std::is_same_v()), const_iterator&>); + + // r++ + // iterator + DOCUMENTED_STATIC_FAILURE(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&>); + + // *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 + // 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 + + // 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>); + // 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>); + + } // 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>); +#if (__cplusplus >= 202002L) + DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); +#endif + // 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>); +#if (__cplusplus >= 202002L) + DOCUMENTED_STATIC_FAILURE(std::bidirectional_iterator); +#endif + // TODO add runtime checks here + } + SECTION("Back inserter") { + DOCUMENTED_STATIC_FAILURE(traits::has_const_reference_v); + // STATIC_REQUIRE(traits::has_push_back_v); + + STATIC_REQUIRE(traits::has_value_type_v); + STATIC_REQUIRE(traits::has_push_back_v); + STATIC_REQUIRE(traits::has_push_back_v); + + auto it = std::back_inserter(coll); + // insert immutable to not-SubsetCollection + REQUIRE_THROWS_AS(it = CollectionType::value_type{}, std::invalid_argument); + // insert mutable (implicit cast to immutable) to not-SubsetCollection + REQUIRE_THROWS_AS(it = CollectionType::value_type::mutable_type{}, std::invalid_argument); + auto subColl = CollectionType{}; + subColl.setSubsetCollection(true); + auto subIt = std::back_inserter(subColl); + auto val = coll.create(); + val.cellID(42); + // insert immutable to SubsetCollection + REQUIRE_NOTHROW(subIt = val); + REQUIRE(subColl.begin()->cellID() == 42); + } + + SECTION("Front inserter") { + DOCUMENTED_STATIC_FAILURE(traits::has_const_reference_v); + // STATIC_REQUIRE(traits::has_push_front_v); + + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE(traits::has_push_front_v); + DOCUMENTED_STATIC_FAILURE(traits::has_push_front_v); + // TODO add runtime checks here + } + + SECTION("Inserter") { + STATIC_REQUIRE(traits::has_iterator_v); + DOCUMENTED_STATIC_FAILURE(traits::has_const_reference_v); + // STATIC_REQUIRE( + // traits::has_insert_v); + + STATIC_REQUIRE(traits::has_value_type_v); + DOCUMENTED_STATIC_FAILURE( + traits::has_insert_v); + DOCUMENTED_STATIC_FAILURE( + traits::has_insert_v); + // TODO add runtime checks here + } + + SECTION("Const iterator") { + // C++23 required + DOCUMENTED_STATIC_FAILURE((__cplusplus >= 202302L)); + } + + 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>); +#if (__cplusplus >= 202002L) + DOCUMENTED_STATIC_FAILURE(std::input_iterator); +#endif + // TODO add more checks here + // 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>); +#if (__cplusplus >= 202002L) + DOCUMENTED_STATIC_FAILURE(std::input_iterator); +#endif + // TODO add more checks here + } + +#if (__cplusplus >= 202002L) + SECTION("Counted iterator") { + // iterator + DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); + // TODO add runtime checks + // const_iterator + DOCUMENTED_STATIC_FAILURE(std::input_or_output_iterator); + // TODO add runtime checks + } +#endif +} + +#undef DOCUMENTED_STATIC_FAILURE +#undef DOCUMENTED_FAILURE \ No newline at end of file