diff --git a/Framework/Core/include/Framework/ASoA.h b/Framework/Core/include/Framework/ASoA.h index 2d880648c42f4..e05ab848ec4e3 100644 --- a/Framework/Core/include/Framework/ASoA.h +++ b/Framework/Core/include/Framework/ASoA.h @@ -131,41 +131,41 @@ struct TableRef { template ar1, std::array ar2> consteval auto merge() { - constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); + constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) consteval { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); std::array out; auto pos = std::copy(ar1.begin(), ar1.end(), out.begin()); - std::copy_if(ar2.begin(), ar2.end(), pos, [&](TableRef const& a) { return std::none_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); + std::copy_if(ar2.begin(), ar2.end(), pos, [&](TableRef const& a) consteval { return std::none_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); return out; } template ar1, std::array ar2, typename L> consteval auto merge_if(L l) { - constexpr const int to_remove = std::ranges::count_if(ar1.begin(), ar1.end(), [&](TableRef const& a) { return !l(a); }); - constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }) || !l(a); }); + constexpr const int to_remove = std::ranges::count_if(ar1.begin(), ar1.end(), [&](TableRef const& a) consteval { return !l(a); }); + constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) consteval { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }) || !l(a); }); std::array out; - auto pos = std::copy_if(ar1.begin(), ar1.end(), out.begin(), [&](TableRef const& a) { return l(a); }); - std::copy_if(ar2.begin(), ar2.end(), pos, [&](TableRef const& a) { return std::none_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }) && l(a); }); + auto pos = std::copy_if(ar1.begin(), ar1.end(), out.begin(), [&](TableRef const& a) consteval { return l(a); }); + std::copy_if(ar2.begin(), ar2.end(), pos, [&](TableRef const& a) consteval { return std::none_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }) && l(a); }); return out; } template ar, typename L> consteval auto remove_if(L l) { - constexpr const int to_remove = std::ranges::count_if(ar.begin(), ar.end(), [&l](TableRef const& e) { return l(e); }); + constexpr const int to_remove = std::ranges::count_if(ar.begin(), ar.end(), [&l](TableRef const& e) consteval { return l(e); }); std::array out; - std::copy_if(ar.begin(), ar.end(), out.begin(), [&l](TableRef const& e) { return !l(e); }); + std::copy_if(ar.begin(), ar.end(), out.begin(), [&l](TableRef const& e) consteval { return !l(e); }); return out; } template ar1, std::array ar2> consteval auto intersect() { - constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); + constexpr const int duplicates = std::ranges::count_if(ar2.begin(), ar2.end(), [&](TableRef const& a) consteval { return std::any_of(ar1.begin(), ar1.end(), [&](TableRef const& e) { return e == a; }); }); std::array out; - std::copy_if(ar1.begin(), ar1.end(), out.begin(), [](TableRef const& a) { return std::find(ar2.begin(), ar2.end(), a) != ar2.end(); }); + std::copy_if(ar1.begin(), ar1.end(), out.begin(), [](TableRef const& a) consteval { return std::find(ar2.begin(), ar2.end(), a) != ar2.end(); }); return out; } @@ -246,7 +246,7 @@ struct TableMetadata { template static consteval std::array getMap(framework::pack) { - return std::array{[]() { + return std::array{[]() consteval { if constexpr (requires { PCs::index_targets.size(); }) { return Key::template isIndexTargetOf(); } else { @@ -286,12 +286,12 @@ struct Hash { template ar, typename Key> consteval auto filterForKey() { - constexpr std::array test = [](std::index_sequence) { + constexpr std::array test = [](std::index_sequence) consteval { return std::array{(Key::template hasOriginal() || (o2::aod::MetadataTrait>::metadata::template getIndexPosToKey() >= 0))...}; }(std::make_index_sequence()); constexpr int correct = std::ranges::count(test.begin(), test.end(), true); std::array out; - std::ranges::copy_if(ar.begin(), ar.end(), out.begin(), [&test](soa::TableRef const& r) { return test[std::distance(ar.begin(), std::find(ar.begin(), ar.end(), r))]; }); + std::ranges::copy_if(ar.begin(), ar.end(), out.begin(), [&test](soa::TableRef const& r) consteval { return test[std::distance(ar.begin(), std::find(ar.begin(), ar.end(), r))]; }); return out; } @@ -1036,7 +1036,7 @@ struct TableIterator : IP, C... { using persistent_columns_t = framework::selected_pack; using external_index_columns_t = framework::selected_pack; using internal_index_columns_t = framework::selected_pack; - using bindings_pack_t = decltype([](framework::pack) -> framework::pack {}(external_index_columns_t{})); // decltype(extractBindings(external_index_columns_t{})); + using bindings_pack_t = decltype([](framework::pack) consteval -> framework::pack {}(external_index_columns_t{})); // decltype(extractBindings(external_index_columns_t{})); TableIterator(arrow::ChunkedArray* columnData[sizeof...(C)], IP&& policy) : IP{policy}, @@ -1274,14 +1274,14 @@ struct ArrowHelpers { template os1, size_t N2, std::array os2> consteval bool is_compatible() { - return [](std::index_sequence) { - return ([](std::index_sequence) { + return [](std::index_sequence) consteval { + return ([](std::index_sequence) consteval { constexpr auto h = os1[Is].desc_hash; using H = o2::aod::Hash; return (((h == os2[Ks].desc_hash) || is_ng_index_equivalent_v>) || ...); - }(std::make_index_sequence()) || + }(std::make_index_sequence()) || ...); - }(std::make_index_sequence()); + }.template operator()(std::make_index_sequence()); } template @@ -1721,7 +1721,7 @@ class Table using table_t = self_t; static constexpr const auto originals = computeOriginals(); - static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) { + static constexpr const auto originalLabels = [] refs, size_t... Is>(std::index_sequence) consteval { return std::array{o2::aod::label()...}; }.template operator()(std::make_index_sequence()); static constexpr const uint32_t binding_origin = originals[0].origin_hash; @@ -1739,7 +1739,7 @@ class Table static consteval auto isIndexTargetOf() { return std::ranges::any_of(self_t::originals, - [](TableRef const& r) { + [](TableRef const& r) consteval { return std::ranges::any_of(bindings, [&r](TableRef const& b) { return b == r; }); }); } @@ -1754,7 +1754,7 @@ class Table template static consteval bool hasOriginal() { - return std::ranges::any_of(originals, [](TableRef const& o) { return o.desc_hash == r.desc_hash; }); + return std::ranges::any_of(originals, [](TableRef const& o) consteval { return o.desc_hash == r.desc_hash; }); } using columns_t = decltype(getColumns()); @@ -1765,11 +1765,11 @@ class Table return hashes; }(columns_t{}); - using persistent_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); - using column_types = decltype([](framework::pack) -> framework::pack {}(persistent_columns_t{})); + using persistent_columns_t = decltype([](framework::pack&&) consteval -> framework::selected_pack {}(columns_t{})); + using column_types = decltype([](framework::pack) consteval -> framework::pack {}(persistent_columns_t{})); - using external_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); - using internal_index_columns_t = decltype([](framework::pack&&) -> framework::selected_pack {}(columns_t{})); + using external_index_columns_t = decltype([](framework::pack&&) consteval -> framework::selected_pack {}(columns_t{})); + using internal_index_columns_t = decltype([](framework::pack&&) consteval -> framework::selected_pack {}(columns_t{})); template using base_iterator = decltype(base_iter(columns_t{})); @@ -1777,7 +1777,7 @@ class Table struct TableIteratorBase : base_iterator { using columns_t = typename Parent::columns_t; using external_index_columns_t = typename Parent::external_index_columns_t; - using bindings_pack_t = decltype([](framework::pack) -> framework::pack {}(external_index_columns_t{})); + using bindings_pack_t = decltype([](framework::pack) consteval -> framework::pack {}(external_index_columns_t{})); static constexpr auto originals = Parent::originals; using policy_t = IP; using parent_t = Parent; @@ -1999,7 +1999,7 @@ class Table template inline arrow::ChunkedArray* getIndexToKey() { - constexpr auto map = [](framework::pack) { + constexpr auto map = [](framework::pack) consteval { return std::array{[]() { if constexpr (requires { Cs::index_targets.size(); }) { return Key::template isIndexTargetOf(); @@ -2370,68 +2370,68 @@ consteval static std::string_view namespace_prefix() }; \ [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } -#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, ...) \ - struct _Name_ : o2::soa::Column { \ - static constexpr const char* mLabel = _Label_; \ - static constexpr const char* query = _CCDBQuery_; \ - static constexpr const uint32_t hash = crc32(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ - static constexpr bool needs_ptr_rec = true; \ - /* Post-deserialisation fixup for objects which are not usable straight out of the ROOT */ \ - /* streamer, e.g. FlatObjects whose internal pointers must be rectified first. Runs on */ \ - /* the receiving device, once per (re)deserialisation, before the object is ever handed */ \ - /* out. Returns the object to cache: a finaliser returning a different instance owns */ \ - /* disposing of the one it was given. */ \ - using finaliser_t = _ConcreteType_* (*)(_ConcreteType_*); \ - static constexpr finaliser_t finalise = __VA_ARGS__; \ - std::function const* ptrRec = nullptr; \ - using base = o2::soa::Column; \ - using type = int64_t[3]; \ - using column_t = _Name_; \ - _Name_(arrow::ChunkedArray const* column) \ - : o2::soa::Column(o2::soa::ColumnIterator(column)) \ - { \ - } \ - \ - _Name_() = default; \ - _Name_(_Name_ const& other) = default; \ - _Name_& operator=(_Name_ const& other) = default; \ - \ - decltype(auto) _Getter_() const \ - { \ - auto& [handle, segment, size] = *mColumnIterator; \ - auto span = std::span{(*ptrRec)(fair::mq::shmem::MetaHeader{ \ - static_cast(size), \ - 0, handle, 0, 0, \ - static_cast(segment), true}), \ - static_cast(size)}; \ - if constexpr (std::same_as<_ConcreteType_, std::span>) { \ - return span; \ - } else { \ - static std::byte* payload = nullptr; \ - static _ConcreteType_* deserialised = nullptr; \ - static TClass* c = TClass::GetClass(#_ConcreteType_); \ - if (payload != (std::byte*)span.data()) { \ - payload = (std::byte*)span.data(); \ - delete deserialised; \ - TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ - auto* streamed = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ - if (!streamed) { \ - LOGP(fatal, \ - "Could not deserialise a {} from the CCDB payload for {} ({} bytes). Check the configured " \ - "path (option \"ccdb:{}\") and that the object exists for this timestamp.", \ - #_ConcreteType_, _CCDBQuery_, span.size(), _Label_); \ - } \ - deserialised = finalise(streamed); \ - } \ - return *deserialised; \ - } \ - } \ - \ - decltype(auto) \ - get() const \ - { \ - return _Getter_(); \ - } \ +#define DECLARE_SOA_CCDB_COLUMN_FULL(_Name_, _Label_, _Getter_, _ConcreteType_, _CCDBQuery_, ...) \ + struct _Name_ : o2::soa::Column { \ + static constexpr const char* mLabel = _Label_; \ + static constexpr const char* query = _CCDBQuery_; \ + static constexpr const uint32_t hash = compile_time_hash(namespace_prefix<_Name_>(), std::string_view{#_Getter_}); \ + static constexpr bool needs_ptr_rec = true; \ + /* Post-deserialisation fixup for objects which are not usable straight out of the ROOT */ \ + /* streamer, e.g. FlatObjects whose internal pointers must be rectified first. Runs on */ \ + /* the receiving device, once per (re)deserialisation, before the object is ever handed */ \ + /* out. Returns the object to cache: a finaliser returning a different instance owns */ \ + /* disposing of the one it was given. */ \ + using finaliser_t = _ConcreteType_* (*)(_ConcreteType_*); \ + static constexpr finaliser_t finalise = __VA_ARGS__; \ + std::function const* ptrRec = nullptr; \ + using base = o2::soa::Column; \ + using type = int64_t[3]; \ + using column_t = _Name_; \ + _Name_(arrow::ChunkedArray const* column) \ + : o2::soa::Column(o2::soa::ColumnIterator(column)) \ + { \ + } \ + \ + _Name_() = default; \ + _Name_(_Name_ const& other) = default; \ + _Name_& operator=(_Name_ const& other) = default; \ + \ + decltype(auto) _Getter_() const \ + { \ + auto& [handle, segment, size] = *mColumnIterator; \ + auto span = std::span{(*ptrRec)(fair::mq::shmem::MetaHeader{ \ + static_cast(size), \ + 0, handle, 0, 0, \ + static_cast(segment), true}), \ + static_cast(size)}; \ + if constexpr (std::same_as<_ConcreteType_, std::span>) { \ + return span; \ + } else { \ + static std::byte* payload = nullptr; \ + static _ConcreteType_* deserialised = nullptr; \ + static TClass* c = TClass::GetClass(#_ConcreteType_); \ + if (payload != (std::byte*)span.data()) { \ + payload = (std::byte*)span.data(); \ + delete deserialised; \ + TBufferFile f(TBufferFile::EMode::kRead, span.size(), (char*)span.data(), kFALSE); \ + auto* streamed = (_ConcreteType_*)soa::extractCCDBPayload((char*)payload, span.size(), c, "ccdb_object"); \ + if (!streamed) { \ + LOGP(fatal, \ + "Could not deserialise a {} from the CCDB payload for {} ({} bytes). Check the configured " \ + "path (option \"ccdb:{}\") and that the object exists for this timestamp.", \ + #_ConcreteType_, _CCDBQuery_, span.size(), _Label_); \ + } \ + deserialised = finalise(streamed); \ + } \ + return *deserialised; \ + } \ + } \ + \ + decltype(auto) \ + get() const \ + { \ + return _Getter_(); \ + } \ }; /* Conventional label, and the object used exactly as the ROOT streamer produced it. Reach @@ -2517,7 +2517,7 @@ consteval static std::string_view namespace_prefix() [[maybe_unused]] static constexpr o2::framework::expressions::BindingNode _Getter_ { _Label_, _Name_::hash, o2::framework::expressions::selectArrowType<_Type_>() } #define DECLARE_SOA_EXPRESSION_COLUMN(_Name_, _Getter_, _Type_, _Expression_) \ - DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, "f" #_Name_, _Expression_); + DECLARE_SOA_EXPRESSION_COLUMN_FULL(_Name_, _Getter_, _Type_, "f" #_Name_, _Expression_) /// A configurable 'expression' column. i.e. a column that can be calculated from other /// columns with gandiva based on dynamically supplied C++ expression or a string definition. @@ -3202,8 +3202,8 @@ consteval auto getIndexTargets() DECLARE_SOA_TABLE_FULL(_Name_, #_Name_, _Origin_, _Desc_, __VA_ARGS__) #define DECLARE_SOA_TABLE_VERSIONED(_Name_, _Origin_, _Desc_, _Version_, ...) \ - O2HASH(#_Name_); \ - DECLARE_SOA_TABLE_METADATA(_Name_, _Desc_, _Version_, __VA_ARGS__); \ + O2HASH(#_Name_) \ + DECLARE_SOA_TABLE_METADATA(_Name_, _Desc_, _Version_, __VA_ARGS__) \ DECLARE_SOA_TABLE_FULL_VERSIONED_(_Name_, #_Name_, _Origin_, _Desc_, _Version_) #define DECLARE_SOA_TABLE_STAGED_VERSIONED(_BaseName_, _Desc_, _Version_, ...) \ @@ -3297,13 +3297,13 @@ consteval auto getIndexTargets() template > \ static consteval auto generateSources() \ { \ - return [](framework::pack) { \ + return [](framework::pack) consteval { \ constexpr auto first = o2::soa::mergeOriginals(); \ constexpr auto second = o2::aod::filterForKey(); \ return o2::aod::replaceOrigin(); \ }(framework::pack<__VA_ARGS__>{}); \ } \ - static constexpr auto N = [](framework::pack) { \ + static constexpr auto N = [](framework::pack) consteval { \ constexpr auto a = o2::soa::mergeOriginals(); \ return o2::aod::filterForKey(); \ }(framework::pack<__VA_ARGS__>{}) \ @@ -3336,53 +3336,53 @@ consteval auto getIndexTargets() // The columns of this table have to be CCDB_COLUMNS so that for each timestamp, we get a row // which points to the specified CCDB objectes described by those columns. #define DECLARE_SOA_TIMESTAMPED_TABLE_FULL(_Name_, _Label_, _TimestampSource_, _TimestampColumn_, _UniformitySource_, _UniformityColumn_, _Version_, _Desc_, ...) \ - O2HASH(_Desc_ "/" #_Version_); \ - template \ - using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ - using _Name_##Timestamp = _Name_##TimestampFrom>; \ - struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ - template > \ - using base_table_t = _TimestampSource_##From; \ - template > \ - using extension_table_t = _Name_##TimestampFrom; \ - static constexpr const auto ccdb_urls = [](framework::pack) { \ - return std::array{Cs::query...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - static constexpr const auto ccdb_bindings = [](framework::pack) { \ - return std::array{Cs::mLabel...}; \ - }(framework::pack<__VA_ARGS__>{}); \ - /* The uniformity column may live in a table other than the timestamp source (the run */ \ - /* number is on aod::BCs, the timestamp on aod::Timestamps). Both are handed to the */ \ - /* fetcher, which reads them positionally — sound because the two are row-aligned. */ \ - /* Row alignment cannot be checked here: ASoA encodes no type-level relation between */ \ - /* two tables that happen to have equal row counts (aod::BCs and aod::Timestamps have */ \ - /* disjoint originals). The CCDB fetcher verifies the lengths match before reading. */ \ - static constexpr auto N = o2::soa::mergeOriginals<_TimestampSource_, _UniformitySource_>().size(); \ - template > \ - static consteval auto generateSources() \ - { \ - return o2::soa::mergeOriginals<_TimestampSource_##From, _UniformitySource_##From>(); \ - } \ - static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ - /* Rows sharing a uniformity value resolve to the same CCDB object, so the fetcher */ \ - /* need only query once per distinct value. Defaults to the timestamp column, i.e. */ \ - /* every distinct timestamp may yield a different object — the pre-existing behaviour.*/ \ - static constexpr auto uniformity_column_label = _UniformityColumn_::mLabel; \ - /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ - }; \ - template <> \ - struct MetadataTrait> { \ - static constexpr void isMetadataTrait() {}; \ - using metadata = _Name_##TimestampMetadata; \ - }; \ - template \ - using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ - using _Name_ = _Name_##From \ + using _Name_##TimestampFrom = soa::Table, o2::aod::Hash<_Desc_ "/" #_Version_ ""_h>, O>; \ + using _Name_##Timestamp = _Name_##TimestampFrom>; \ + struct _Name_##TimestampMetadata : TableMetadata, __VA_ARGS__> { \ + template > \ + using base_table_t = _TimestampSource_##From; \ + template > \ + using extension_table_t = _Name_##TimestampFrom; \ + static constexpr const auto ccdb_urls = [](framework::pack) consteval { \ + return std::array{Cs::query...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + static constexpr const auto ccdb_bindings = [](framework::pack) consteval { \ + return std::array{Cs::mLabel...}; \ + }(framework::pack<__VA_ARGS__>{}); \ + /* The uniformity column may live in a table other than the timestamp source (the run */ \ + /* number is on aod::BCs, the timestamp on aod::Timestamps). Both are handed to the */ \ + /* fetcher, which reads them positionally — sound because the two are row-aligned. */ \ + /* Row alignment cannot be checked here: ASoA encodes no type-level relation between */ \ + /* two tables that happen to have equal row counts (aod::BCs and aod::Timestamps have */ \ + /* disjoint originals). The CCDB fetcher verifies the lengths match before reading. */ \ + static constexpr auto N = o2::soa::mergeOriginals<_TimestampSource_, _UniformitySource_>().size(); \ + template > \ + static consteval auto generateSources() \ + { \ + return o2::soa::mergeOriginals<_TimestampSource_##From, _UniformitySource_##From>(); \ + } \ + static constexpr auto timestamp_column_label = _TimestampColumn_::mLabel; \ + /* Rows sharing a uniformity value resolve to the same CCDB object, so the fetcher */ \ + /* need only query once per distinct value. Defaults to the timestamp column, i.e. */ \ + /* every distinct timestamp may yield a different object — the pre-existing behaviour.*/ \ + static constexpr auto uniformity_column_label = _UniformityColumn_::mLabel; \ + /*static constexpr auto timestampColumn = _TimestampColumn_;*/ \ + }; \ + template <> \ + struct MetadataTrait> { \ + static constexpr void isMetadataTrait() {}; \ + using metadata = _Name_##TimestampMetadata; \ + }; \ + template \ + using _Name_##From = o2::soa::Join<_TimestampSource_, _Name_##TimestampFrom>; \ + using _Name_ = _Name_##From>; /* Uniformity defaults to the timestamp column of the timestamp source: each distinct @@ -3497,8 +3497,8 @@ struct Join : Table, o2::aod::Hash<"JOIN/0"_h>, o2::aod: template static consteval bool contains() { - return [](std::index_sequence) { - return (std::ranges::any_of(originals, [](TableRef const& ref) { return ref.desc_hash == T::originals[Is].desc_hash; }) && ...); + return [](std::index_sequence) constexpr { + return (std::ranges::any_of(originals, [](TableRef const& ref) constexpr { return ref.desc_hash == T::originals[Is].desc_hash; }) && ...); }(std::make_index_sequence()); } }; diff --git a/Framework/Core/test/test_GroupSlicer.cxx b/Framework/Core/test/test_GroupSlicer.cxx index f282dcbc5c33b..c81acb6c7c85c 100644 --- a/Framework/Core/test/test_GroupSlicer.cxx +++ b/Framework/Core/test/test_GroupSlicer.cxx @@ -89,8 +89,8 @@ DECLARE_SOA_TABLE(EventExtra, "AOD", "EVTSXTRA", test::Arr, test::Boo, test::Lst TEST_CASE("RelatedByIndex") { using Trks = soa::Join; - CHECK(soa::relatedByIndex() == true); - CHECK(soa::relatedByIndex() == true); + CHECK(soa::relatedByIndex() == true); + CHECK(soa::relatedByIndex() == true); } TEST_CASE("GroupSlicerOneAssociated")