Skip to content

Commit 71f73e3

Browse files
authored
Formalize common interface for collections with a concept (#758)
* add checks to `CollectionType` concept * add `at` and `create` methods to `UserDataCollection` * require collection typeName strings in concept * use same_as for create and element access methods * require to not be copyable * un-nest general concept checks * fix and add comments on stripping references
1 parent 52665af commit 71f73e3

File tree

4 files changed

+105
-2
lines changed

4 files changed

+105
-2
lines changed

include/podio/UserDataCollection.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ class UserDataCollection : public CollectionBase {
206206

207207
// ----- some wrappers for std::vector and access to the complete std::vector (if really needed)
208208

209+
typename std::vector<BasicType>::reference create() {
210+
return _vec.emplace_back();
211+
}
212+
209213
iterator begin() {
210214
return _vec.begin();
211215
}
@@ -251,6 +255,13 @@ class UserDataCollection : public CollectionBase {
251255
return _vec[idx];
252256
}
253257

258+
typename std::vector<BasicType>::reference at(size_t idx) {
259+
return _vec.at(idx);
260+
}
261+
typename std::vector<BasicType>::const_reference at(size_t idx) const {
262+
return _vec.at(idx);
263+
}
264+
254265
void resize(size_t count) {
255266
_vec.resize(count);
256267
}

include/podio/utilities/TypeHelpers.h

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#ifndef PODIO_UTILITIES_TYPEHELPERS_H
22
#define PODIO_UTILITIES_TYPEHELPERS_H
33

4+
#include <concepts>
5+
#include <iterator>
46
#include <map>
7+
#include <ranges>
58
#include <tuple>
69
#include <type_traits>
710
#include <unordered_map>
@@ -242,9 +245,65 @@ namespace detail {
242245
// forward declaration to be able to use it below
243246
class CollectionBase;
244247

245-
/// Concept for checking whether a passed type T inherits from podio::CollectionBase
248+
/// Concept for checking whether a passed type T is a collection
246249
template <typename T>
247-
concept CollectionType = std::is_base_of_v<CollectionBase, T>;
250+
concept CollectionType = !std::is_abstract_v<T> && std::derived_from<T, CollectionBase> &&
251+
std::default_initializable<T> && std::destructible<T> && std::movable<T> && !std::copyable<T> &&
252+
std::ranges::random_access_range<T> && requires(T t, const T ct) {
253+
// typeName's
254+
{ T::typeName } -> std::convertible_to<std::string_view>;
255+
{ std::bool_constant<(T::typeName, true)>() } -> std::same_as<std::true_type>; // ~is annotated with constexpr
256+
{ T::valueTypeName } -> std::convertible_to<std::string_view>;
257+
{
258+
std::bool_constant<(T::valueTypeName, true)>()
259+
} -> std::same_as<std::true_type>; // ~is annotated with constexpr
260+
{ T::dataTypeName } -> std::convertible_to<std::string_view>;
261+
{ std::bool_constant<(T::dataTypeName, true)>() } -> std::same_as<std::true_type>; // ~is annotated with constexpr
262+
// typedefs
263+
typename T::value_type;
264+
typename T::mutable_type;
265+
requires std::convertible_to<typename T::mutable_type, typename T::value_type>;
266+
typename T::difference_type;
267+
requires std::signed_integral<typename T::difference_type>;
268+
typename T::size_type;
269+
requires std::unsigned_integral<typename T::size_type>;
270+
typename T::const_iterator;
271+
requires std::random_access_iterator<typename T::const_iterator>;
272+
typename T::iterator;
273+
requires std::random_access_iterator<typename T::iterator>;
274+
typename T::const_reverse_iterator;
275+
requires std::random_access_iterator<typename T::const_reverse_iterator>;
276+
typename T::reverse_iterator;
277+
requires std::random_access_iterator<typename T::reverse_iterator>;
278+
// member functions
279+
requires std::same_as<std::remove_reference_t<decltype(t.create())>,
280+
typename T::mutable_type>; // UserDataCollection::create() returns reference which has to be
281+
// stripped to be same as expected typedef
282+
{ t.push_back(std::declval<std::add_lvalue_reference_t<std::add_const_t<typename T::mutable_type>>>()) };
283+
{ t.push_back(std::declval<std::add_lvalue_reference_t<std::add_const_t<typename T::value_type>>>()) };
284+
{ t.begin() } -> std::same_as<typename T::iterator>;
285+
{ t.cbegin() } -> std::same_as<typename T::const_iterator>;
286+
{ ct.begin() } -> std::same_as<typename T::const_iterator>;
287+
{ t.end() } -> std::same_as<typename T::iterator>;
288+
{ t.cend() } -> std::same_as<typename T::const_iterator>;
289+
{ ct.end() } -> std::same_as<typename T::const_iterator>;
290+
{ t.rbegin() } -> std::same_as<typename T::reverse_iterator>;
291+
{ t.crbegin() } -> std::same_as<typename T::const_reverse_iterator>;
292+
{ ct.rbegin() } -> std::same_as<typename T::const_reverse_iterator>;
293+
{ t.rend() } -> std::same_as<typename T::reverse_iterator>;
294+
{ t.crend() } -> std::same_as<typename T::const_reverse_iterator>;
295+
{ ct.rend() } -> std::same_as<typename T::const_reverse_iterator>;
296+
// UserDataCollection element access returns by reference or const reference which has to be stripped to be same
297+
// as expected typedef
298+
requires std::same_as<std::remove_reference_t<decltype(t[std::declval<typename T::size_type>()])>,
299+
typename T::mutable_type>;
300+
requires std::same_as<std::remove_cvref_t<decltype(ct[std::declval<typename T::size_type>()])>,
301+
typename T::value_type>;
302+
requires std::same_as<std::remove_reference_t<decltype(t.at(std::declval<typename T::size_type>()))>,
303+
typename T::mutable_type>;
304+
requires std::same_as<std::remove_cvref_t<decltype(ct.at(std::declval<typename T::size_type>()))>,
305+
typename T::value_type>;
306+
};
248307

249308
namespace utils {
250309
template <typename... T>

tests/unittests/links.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "podio/LinkCollection.h"
55
#include "podio/LinkNavigator.h"
6+
#include "podio/utilities/TypeHelpers.h"
67

78
#include "datamodel/ExampleClusterCollection.h"
89
#include "datamodel/ExampleHitCollection.h"
@@ -297,6 +298,9 @@ TEST_CASE("Links templated accessors", "[links]") {
297298
}
298299
}
299300
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
301+
TEST_CASE("LinkCollection collection concept", "[links][concepts]") {
302+
STATIC_REQUIRE(podio::CollectionType<TestLColl>);
303+
}
300304

301305
TEST_CASE("LinkCollection constness", "[links][static-checks][const-correctness]") {
302306
// Test type-aliases in LinkCollection

tests/unittests/unittest.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <type_traits>
1111
#include <unordered_map>
1212
#include <unordered_set>
13+
#include <utility>
1314
#include <vector>
1415

1516
#include "catch2/catch_test_macros.hpp"
@@ -23,6 +24,7 @@
2324
#include "podio/ROOTReader.h"
2425
#include "podio/ROOTWriter.h"
2526
#include "podio/podioVersion.h"
27+
#include "podio/utilities/TypeHelpers.h"
2628

2729
#ifndef PODIO_ENABLE_SIO
2830
#define PODIO_ENABLE_SIO 0
@@ -390,6 +392,15 @@ TEST_CASE("thread-safe prepareForWrite", "[basics][multithread]") {
390392
}
391393
}
392394

395+
TEST_CASE("UserDataCollection collection concept", "[concepts]") {
396+
// check each type in tuple
397+
std::apply(
398+
[]<typename... Ts>(Ts...) {
399+
([]<typename T>(T) { STATIC_REQUIRE(podio::CollectionType<podio::UserDataCollection<T>>); }(Ts{}), ...);
400+
},
401+
podio::SupportedUserDataTypes{});
402+
}
403+
393404
TEST_CASE("UserDataCollection print", "[basics]") {
394405
auto coll = podio::UserDataCollection<int32_t>();
395406
coll.push_back(1);
@@ -402,6 +413,19 @@ TEST_CASE("UserDataCollection print", "[basics]") {
402413
REQUIRE(sstr.str() == "[1, 2, 3]");
403414
}
404415

416+
TEST_CASE("UserDataCollection access", "[basics]") {
417+
auto coll = podio::UserDataCollection<int32_t>();
418+
auto& x = coll.create();
419+
x = 42;
420+
REQUIRE(coll.size() == 1);
421+
REQUIRE(coll.at(0) == 42);
422+
coll.at(0) = 43;
423+
REQUIRE(coll[0] == 43);
424+
coll[0] = 44;
425+
REQUIRE(std::as_const(coll).at(0) == 44);
426+
REQUIRE(std::as_const(coll)[0] == 44);
427+
}
428+
405429
/*
406430
TEST_CASE("Arrays") {
407431
auto obj = ExampleWithArray();
@@ -609,6 +633,11 @@ TEST_CASE("UserInitialization", "[basics][code-gen]") {
609633
REQUIRE(ex.comp().arr[1] == 3.4);
610634
}
611635

636+
TEST_CASE("Collection concepts", "[collections][concepts]") {
637+
STATIC_REQUIRE(podio::CollectionType<ExampleClusterCollection>);
638+
STATIC_REQUIRE(podio::CollectionType<ExampleHitCollection>);
639+
}
640+
612641
TEST_CASE("Collection size and empty", "[basics][collections]") {
613642
ExampleClusterCollection coll{};
614643
REQUIRE(coll.empty());

0 commit comments

Comments
 (0)