PODIO Collection as a Container
Comparison of the PODIO Collection
s with a C++ named requirement 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. Instead, data layer objects 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 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 |
---|---|---|---|---|
|
|
✔️ yes |
Defined as an immutable user layer object type |
|
|
|
❌ no |
Not defined |
|
|
|
❌ no |
Not defined |
|
|
Iterator whose |
LegacyForwardIterator convertible to |
❌ no |
Defined as podio |
|
Constant iterator whose |
❌ no |
Defined as podio |
|
|
Signed integer |
Must be the same as |
✔️ yes |
|
|
Unsigned integer |
Large enough to represent all positive values of |
✔️ yes |
Container member functions and operators
Expression |
Return type |
Semantics |
Fulfilled by Collection? |
Comment |
---|---|---|---|---|
|
|
Creates an empty container |
✔️ yes |
|
|
|
Creates a copy of |
❌ no |
Not defined, non-copyable by design |
|
|
Moves |
✔️ yes |
|
|
|
Destroys or copy-assigns all elements of |
❌ no |
Not defined, non-copyable by design |
|
|
Destroys or move-assigns all elements of |
✔️ yes |
|
|
|
Destroys all elements of |
✔️ yes |
Invalidates all handles retrieved from this collection |
|
|
Iterator to the first element of |
✔️ yes |
|
|
|
Iterator to one past the last element of |
✔️ yes |
|
|
|
Same as |
✔️ yes |
|
|
|
Same as |
✔️ yes |
|
|
Convertible to |
Same as |
❌ no |
Not defined |
|
Convertible to |
Same as |
❌ no |
Not defined |
|
|
Exchanges the values of |
❌ no |
Not defined |
|
|
Same as |
❌ no |
|
|
|
Same as |
✔️ yes |
|
|
|
|
✔️ yes |
|
|
Convertible to |
Same as |
✔️ yes |
Collection as an AllocatorAwareContainer
The C++ standard specifies 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 |
---|---|---|---|
|
|
❌ no |
|
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 |
|
|
---|---|---|
✔️ yes (see below) |
✔️ yes (see below) |
|
✔️ yes (see below) |
✔️ yes (see below) |
|
❌ no (see below) |
❌ no (see below) |
|
❌ no (see below) |
❌ no (see below) |
|
❌ no |
❌ no |
|
❌ no |
❌ no |
|
❌ no |
❌ no |
Concept |
|
|
---|---|---|
|
✔️ yes |
✔️ yes |
|
❌ no |
❌ no |
|
✔️ yes |
✔️ yes |
|
✔️ yes |
✔️ yes |
|
✔️ yes |
✔️ yes |
|
✔️ yes |
✔️ yes |
|
❌ no |
❌ no |
|
✔️ yes (see note below) |
✔️ yes (see note below) |
|
✔️ yes (see note below) |
✔️ yes (see note below) |
|
✔️ yes (see note below) |
✔️ yes (see note below) |
|
❌ no |
❌ no |
Note
The collections’ iterators fulfil the std::forward_iterator
except that the pointers obtained with ->
remain valid only as long as the iterator itself is valid instead of as long as the range remains valid. In practice, this means a ptr
obtained with auto* ptr = it.operator->();
is valid only as long as it
is valid.
The values obtained immediately through ->
(for instance, auto& e = it->energy();
) behave as expected for std::forward_iterator
, as their validity is tied to the collection’s validity.
Despite the exception, the collection iterators are made to still report themselves as fulfilling the forward iterator requirements since the problematic usage (auto* ptr = it.operator->();
) is rare in normal scenarios.
LegacyIterator
Requirement |
Fulfilled by |
Comment |
---|---|---|
✔️ yes / ✔️ yes |
||
✔️ yes / ✔️ yes |
||
✔️ yes / ✔️ yes |
||
✔️ yes / ✔️ yes |
||
|
✔️ yes / ✔️ yes |
|
|
✔️ yes / ✔️ yes |
|
|
✔️ yes / ✔️ yes |
|
|
✔️ yes / ✔️ yes |
Expression |
Return type |
Semantics |
Fulfilled by |
Comment |
---|---|---|---|---|
|
Unspecified |
✔️ yes / ✔️ yes |
||
|
|
✔️ yes / ✔️ yes |
LegacyInputIterator
Requirement |
Fulfilled by |
Comment |
---|---|---|
✔️ yes / ✔️ yes |
||
✔️ yes / ✔️ yes |
Expression |
Return type |
Semantics |
Fulfilled by |
Comment |
---|---|---|---|---|
|
Contextually convertible to |
Same as |
✔️ yes / ✔️ yes |
|
|
|
✔️ yes / ✔️ yes |
||
|
Same as |
✔️ yes / ✔️ yes |
||
|
|
✔️ yes / ✔️ yes |
||
|
Same as |
✔️ yes / ✔️ yes |
||
|
Convertible to |
Same as |
✔️ 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 |
Comment |
---|---|---|
✔️ yes / ✔️ yes |
||
✔️ yes / ✔️ yes |
||
If mutable iterator then |
❌ no / ❌ no |
|
❌ no / ❌ no |
References from dereferencing equal iterators aren’t bound to the same object |
|
✔️ yes / ✔️ yes |
Expression |
Return type |
Semantics |
Fulfilled by |
Comment |
---|---|---|---|---|
|
|
Same as |
✔️ yes / ✔️ yes |
|
|
|
✔️ yes / ✔️ yes |
LegacyOutputIterator
Requirement |
Fulfilled by |
Comment |
---|---|---|
✔️ yes / ✔️ yes |
||
Is pointer type or class type |
✔️ yes / ✔️ yes |
Expression |
Return type |
Semantics |
Fulfilled by |
Comment |
---|---|---|---|---|
|
❌ no / ❌ no |
Not defined |
||
|
|
✔️ yes / ✔️ yes |
||
|
Convertible to |
Same as |
✔️ yes / ✔️ yes |
|
|
Same as |
❌ no / ❌ no |
Not defined |
Collection iterators and standard iterator adaptors
Adaptor |
Compatible with Collection? |
Comment |
---|---|---|
|
❗ attention |
|
|
❗ attention |
Compatible only with SubsetCollections, otherwise throws |
|
❌ no |
|
|
❌ no |
|
|
❗ attention |
|
|
✔️ yes |
Limited usefulness since dereference returns |
|
❗ attention |
|
Collection as a range
Concept |
Fulfilled by Collection? |
---|---|
|
✔️ yes |
|
❌ no |
|
✔️ yes |
|
✔️ yes |
|
❌ no |
|
✔️ yes |
|
✔️ yes |
|
✔️ yes |
|
❌ no |
|
✔️ yes |
|
✔️ yes |
Collection and standard algorithms
Most of the standard algorithms require iterators to meet specific named requirements, such as LegacyInputIterator, LegacyForwardIterator, or LegacyRandomAccessIterator. 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 named requirement, therefore are guaranteed to work with any algorithm requiring LegacyIterator or LegacyInputIterator.
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, or LegacyOutputIterator or mutable, which are not compatible. In these particular cases, they most likely won’t compile. If they do compile, they will produce incorrect results.
For example:
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
Collection and standard range algorithms
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::random_access_iterator
concept, so range algorithms that require this iterator type will work correctly with PODIO iterators. If an algorithm compiles, it is guaranteed to work as expected.
In particular, the PODIO collections’ iterators do not fulfil the std::output_iterator
concept, and as a result, mutating algorithms relying on this iterator type will not compile.
Similarly the collections themselves model the std::random_access_range
concept and can be used in the range algorithms that require that concept. The algorithms requiring unsupported range concept, such as std::output_range
, won’t compile.
For example:
std::ranges::find_if(collection, predicate ); // requires input_range -> OK
std::ranges::adjacent_find(collection, predicate ); // requires forward_range -> OK
std::ranges::views::reverse(collection); //requires bidirectional_range -> OK
std::ranges::fill(collection, value ); // requires output_range -> won't compile
std::ranges::sort(collection); // requires random_access_range and sortable -> won't compile