diff --git a/include/podio/Frame.h b/include/podio/Frame.h index adb8b5448..b3503de7a 100644 --- a/include/podio/Frame.h +++ b/include/podio/Frame.h @@ -199,6 +199,29 @@ class Frame { /// if it is not const podio::CollectionBase* get(const std::string& name) const; + /// Get the collection to which the passed object belongs from the Frame. + /// + /// @tparam CollT The type of the desired collection + /// @param object The object for which the collection it belongs to should + /// be retrieved + /// + /// @returns A const reference to the collection if it is available or to + /// an empty (static) collection + template + const CollT& get(const typename CollT::value_type& object) const; + + /// Get the collection pointer to which the passed object belongs from the + /// Frame. + /// + /// @tparam O The type of the object + /// @param object The object for which the collection it belongs to should + /// be retrieved + /// + /// @returns A const pointer to a collection if it is available or a nullptr + /// if it is not + template + inline const typename O::collection_type* get(const O& object) const; + /// (Destructively) move a collection into the Frame and get a reference to /// the inserted collection back for further use. /// @@ -382,10 +405,22 @@ const CollT& Frame::get(const std::string& name) const { return emptyColl; } +template +const CollT& Frame::get(const typename CollT::value_type& object) const { + const auto name = m_self->getIDTable().name(object.id().collectionID); + return get(name.value_or("")); +} + inline const podio::CollectionBase* Frame::get(const std::string& name) const { return m_self->get(name); } +template +inline const typename O::collection_type* Frame::get(const O& object) const { + const auto name = m_self->getIDTable().name(object.id().collectionID); + return dynamic_cast(get(name.value_or(""))); +} + inline void Frame::put(std::unique_ptr coll, const std::string& name) { const auto* retColl = m_self->put(std::move(coll), name); if (!retColl) { diff --git a/include/podio/detail/Link.h b/include/podio/detail/Link.h index 27a25ae1b..62a391531 100644 --- a/include/podio/detail/Link.h +++ b/include/podio/detail/Link.h @@ -57,7 +57,7 @@ class LinkT { public: using mutable_type = podio::MutableLink; - using value_type = podio::Link; + using object_type = podio::Link; using collection_type = podio::LinkCollection; static constexpr std::string_view typeName = diff --git a/include/podio/utilities/TypeHelpers.h b/include/podio/utilities/TypeHelpers.h index f20334d9b..1712ce787 100644 --- a/include/podio/utilities/TypeHelpers.h +++ b/include/podio/utilities/TypeHelpers.h @@ -176,6 +176,12 @@ namespace detail { template using hasObject_t = typename T::object_type; + /// Detector for checking the existence of a value_type member. Necessary to + /// avoid false positives for default handle types from collections, since + /// collections also specify a mutable_type member. + template + using hasValue_t = typename T::value_type; + /// Variable template for determining whether type T is a podio generated /// mutable handle class template @@ -184,7 +190,8 @@ namespace detail { /// Variable template for determining whether type T is a podio generated /// default handle class template - constexpr static bool isDefaultHandleType = det::is_detected_v>; + constexpr static bool isDefaultHandleType = det::is_detected_v> && + !det::is_detected_v>; /// Variable template for obtaining the default handle type from any podio /// generated handle type. @@ -245,6 +252,11 @@ namespace detail { // forward declaration to be able to use it below class CollectionBase; +/// Concept for checking whether a passed type T is (or can be) a podio +/// generated handle class +template +concept ObjectType = detail::isMutableHandleType || detail::isDefaultHandleType; + /// Concept for checking whether a passed type T is a collection template concept CollectionType = !std::is_abstract_v && std::derived_from && diff --git a/python/podio/test_Frame.py b/python/podio/test_Frame.py index 5a8d9a7c6..6250b44cd 100644 --- a/python/podio/test_Frame.py +++ b/python/podio/test_Frame.py @@ -152,6 +152,20 @@ def test_frame_empty_parameters(self): vals = frame.get_parameter("empty_param") self.assertEqual(len(vals), 0) + def test_frame_get_collection_from_object(self): + """Check that using an object (handle) to get a collection works""" + frame = Frame() + hits = ExampleHitCollection() + hit = hits.create() + + with self.assertRaises(ReferenceError): + invalidHits = frame.get(hit) + _ = invalidHits[0] + + hits = frame.put(hits, "hits") + hitsFromHit = frame.get(hit) + self.assertEqual(hit, hitsFromHit[0]) + class FrameReadTest(unittest.TestCase): """Unit tests for the Frame python bindings for Frames read from file. diff --git a/tests/unittests/frame.cpp b/tests/unittests/frame.cpp index dc02d5de7..5938b6174 100644 --- a/tests/unittests/frame.cpp +++ b/tests/unittests/frame.cpp @@ -428,6 +428,27 @@ TEST_CASE("Frame getName", "[frame][basics]") { REQUIRE_FALSE(frame.getName(0xfffffff).has_value()); } +TEST_CASE("Frame get from object", "[frame][basics]") { + auto event = podio::Frame{}; + + auto clusters = ExampleClusterCollection{}; + auto cluster = clusters.create(3.14f); + + REQUIRE(event.get(cluster) == nullptr); + + const auto& invalidClusters = event.get(cluster); + REQUIRE_FALSE(invalidClusters.isValid()); + + const auto& frameClusters = event.put(std::move(clusters), "clusters"); + const auto& objectClusters = event.get(cluster); + REQUIRE(frameClusters[0] == objectClusters[0]); + + const auto* collPtr = event.get(cluster); + STATIC_REQUIRE(std::is_same_v); + REQUIRE(collPtr != nullptr); + REQUIRE((*collPtr)[0] == cluster); +} + TEST_CASE("EIC-Jana2 cleanup use case", "[memory-management][492][174]") { // Test case that only triggers in ASan builds if memory-management / cleanup // has a bug diff --git a/tests/unittests/links.cpp b/tests/unittests/links.cpp index 2ffc96916..c0cb7d609 100644 --- a/tests/unittests/links.cpp +++ b/tests/unittests/links.cpp @@ -298,8 +298,12 @@ TEST_CASE("Links templated accessors", "[links]") { } } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) -TEST_CASE("LinkCollection collection concept", "[links][concepts]") { + +TEST_CASE("Link concept checks", "[links][concepts]") { STATIC_REQUIRE(podio::CollectionType); + STATIC_REQUIRE(podio::ObjectType); + STATIC_REQUIRE(podio::ObjectType); + STATIC_REQUIRE_FALSE(podio::ObjectType); } TEST_CASE("LinkCollection constness", "[links][static-checks][const-correctness]") { diff --git a/tests/unittests/unittest.cpp b/tests/unittests/unittest.cpp index ef96af9b3..803d3336b 100644 --- a/tests/unittests/unittest.cpp +++ b/tests/unittests/unittest.cpp @@ -63,6 +63,7 @@ #include "extension_model/extension_model.h" #include "podio/UserDataCollection.h" +#include "podio/utilities/TypeHelpers.h" TEST_CASE("AutoDelete", "[basics][memory-management]") { auto coll = EventInfoCollection(); @@ -126,6 +127,11 @@ TEST_CASE("Component", "[basics]") { REQUIRE(3 == info.component().data.x); } +TEST_CASE("ObjectType concept", "[concepts]") { + STATIC_REQUIRE(podio::ObjectType); + STATIC_REQUIRE_FALSE(podio::ObjectType); +} + TEST_CASE("makeEmpty", "[basics]") { auto hit = ExampleHit::makeEmpty(); // Any access to such a handle is a crash