From 86e2ba927063bdf0a19687defd7eb2e9f868439f Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 24 Sep 2024 11:47:22 +0200 Subject: [PATCH] Rework the I/O handling for relations (#673) * Rework the I/O handling for relations This is work that has become necessary for making links work that contain interface types. - Remove differences in handling of interface vs regular types in the jinja2 templates and lift it into a separate function that dispatches depending on the type of the relation - Make the interfaced_types type member publicly available for interface types - Add type helpers to detect whether a type is an interface type * Install implementation headers are installed * Make things work with c++17 * Use more suitable name for header * Remove parts that are only required later * Make things work again (again) with c++17 * Remove check that is not part of this feature * Make lambda capture simpler --- include/podio/detail/RelationIOHelpers.h | 176 +++++++++++++++++++++ include/podio/utilities/TypeHelpers.h | 10 ++ python/templates/CollectionData.cc.jinja2 | 2 + python/templates/Interface.h.jinja2 | 11 +- python/templates/macros/collections.jinja2 | 27 +--- src/CMakeLists.txt | 1 + tests/unittests/interface_types.cpp | 9 +- 7 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 include/podio/detail/RelationIOHelpers.h diff --git a/include/podio/detail/RelationIOHelpers.h b/include/podio/detail/RelationIOHelpers.h new file mode 100644 index 000000000..1ef2ee0ca --- /dev/null +++ b/include/podio/detail/RelationIOHelpers.h @@ -0,0 +1,176 @@ +#ifndef PODIO_DETAIL_RELATIONIOHELPERS_H +#define PODIO_DETAIL_RELATIONIOHELPERS_H + +#include "podio/utilities/TypeHelpers.h" +#include + +#include +#include + +namespace podio::detail { + +/// Function template for handling interface types in OneToMultiRelations +/// +/// Effectively this function checks whether the passed collection can be +/// dynamically cast to the collection type of the concrete type and if that is +/// true uses it to construct an interface type and add it to relElements. The +/// function on its own doesn't do anything too meaningful, it is meant to be +/// used in a call to std::apply that goes over all the interfaced types of an +/// interface type. +/// +/// @tparam T The concrete type inside the interface that should be checked. +/// This effectively is mainly used for tag-dispatch and overload +/// selection in this context +/// @tparam InterfaceType The interface type (that can be used to interface T) +/// +/// @param relElements The vector to which the interface objects should be added +/// @param coll The collection that holds the actual element +/// @param id The ObjectID of the element that we are currently looking for +template +void tryAddTo(T, std::vector& relElements, const podio::CollectionBase* coll, const podio::ObjectID id) { + if (auto typedColl = dynamic_cast(coll)) { + const T tmp = (*typedColl)[id.index]; + relElements.emplace_back(tmp); + } +} + +/// Helper function for handling interface type relations in OneToManyRelations +/// +/// This function tries all types that are interfaced by the InterfaceType and +/// adds the one that matches to the relations. The main work happens in +/// tryAddTo, this simply wraps everything in a std::apply over all +/// interfaced_types. +/// +/// @tparam InterfaceType The interface type of the Relation +/// +/// @param relElements The vector to which the interface objects should be added +/// @param coll The collection that holds the actual element +/// @param id The ObjectID of the element that we are currently looking for +template +void addInterfaceToMultiRelation(std::vector& relElements, const podio::CollectionBase* coll, + const podio::ObjectID id) { + std::apply([&](auto... t) { (tryAddTo(t, relElements, coll, id), ...); }, typename InterfaceType::interfaced_types{}); +} + +/// Helper function for adding an object to the OneToManyRelations container +/// when reading back collections +/// +/// This function does the necessary type casting of the passed collection to +/// retrieve the desired object and also takes care of adding the object to the +/// container that holds them for later usage. It handles relations to regular +/// types as well as interface types. +/// +/// This functionality has been lifted from the jinja2 templates, where we now +/// only call it, because we need a template deduction context for making if +/// constexpr work as expected, such that we can dispatch to different +/// implementatoins depending on whether the relation is to an interface type or +/// to a regular type. +/// +/// @note It is expected that the following pre-conditions are met: +/// - The passed collection is valid (i.e. not a nullptr) +/// - the collectionID of the passed collection is the same as the one in +/// the passed ObjectID +/// - The collection can by casted to the relation type or any of the +/// interfaced types of the relation +/// +/// @tparam RelType The type of the OneToManyRelation +/// +/// @param relElements The container that holds the objects for the relation and +/// which will be used for adding an element from the passed +/// collection +/// @param coll The collection from which the object will be obtained after the +/// necessary type casting +/// @param id The ObjectID of the object that should be retrieved and added. +template +void addMultiRelation(std::vector& relElements, const podio::CollectionBase* coll, const podio::ObjectID id) { + if constexpr (podio::detail::isInterfaceType) { + addInterfaceToMultiRelation(relElements, coll, id); + } else { + const auto* typeColl = static_cast(coll); + relElements.emplace_back((*typeColl)[id.index]); + } +} + +/// Function template for handling interface types in OneToOneRelations +/// +/// Effectively this function checks whether the passed collection can be +/// dynamically cast to the collection type of the concrete type and if that is +/// true uses it to assign to the passed interface object The function on its +/// own doesn't do anything too meaningful, it is meant to be used in a call to +/// std::apply that goes over all the interfaced types of an interface type. +/// +/// @tparam T The concrete type inside the interface that should be checked. +/// This effectively is mainly used for tag-dispatch and overload +/// selection in this context +/// @tparam InterfaceType The interface type (that can be used to interface T) +/// +/// @param relation The object to which the interface object should be assigned +/// to +/// @param coll The collection that holds the actual element +/// @param id The ObjectID of the element that we are currently looking for +template +void tryAssignTo(T, InterfaceType*& relation, const podio::CollectionBase* coll, const podio::ObjectID id) { + if (const auto* typeColl = dynamic_cast(coll)) { + relation = new InterfaceType((*typeColl)[id.index]); + } +} + +/// Helper function for handling interface type relations in OneToOneRelations +/// +/// This function tries all types that are interfaced by the InterfaceType and +/// that assigns the one thta matches to the relation. The main work happens in +/// tryAssignTo, this simply wraps everything in a std::apply over all +/// interfaced_types. +/// +/// @tparam InterfaceType The interface type of the Relation +/// +/// @param relation The object to which the interface object should be assigned +/// to +/// @param coll The collection that holds the actual element +/// @param id The ObjectID of the element that we are currently looking for +template +void addInterfaceToSingleRelation(InterfaceType*& relation, const podio::CollectionBase* coll, + const podio::ObjectID id) { + std::apply([&](auto... t) { (tryAssignTo(t, relation, coll, id), ...); }, typename InterfaceType::interfaced_types{}); +} + +/// Helper function for assigning the related object in a OneToOneRelation +/// +/// This function does the necessary type casting of the passed collection to +/// retrieve the desired object and also takes care of assigning the object to +/// the passed pointer that hold it for later usage. It handles relations +/// to regular types as well as interface types. +/// +/// This functionality has been lifted from the jinja2 templates, where we now +/// only call it, because we need a template deduction context for making if +/// constexpr work as expected, such that we can dispatch to different +/// implementatoins depending on whether the relation is to an interface type or +/// to a regular type. +/// +/// @note It is expected that the following pre-conditions are met: +/// - The passed collection is valid (i.e. not a nullptr) +/// - the collectionID of the passed collection is the same as the one in +/// the passed ObjectID +/// - The collection can by casted to the relation type or any of the +/// interfaced types of the relation +/// +/// @tparam RelType The type of the OneToManyRelation +/// +/// @param relation The pointer to which we should assign the related object +/// that is retrieved from the passed collection +/// @param coll The collection from which the object will be obtained after the +/// necessary type casting +/// @param id The ObjectID of the object that should be retrieved and added. +template +void addSingleRelation(RelType*& relation, const podio::CollectionBase* coll, const podio::ObjectID id) { + if constexpr (podio::detail::isInterfaceType) { + addInterfaceToSingleRelation(relation, coll, id); + } else { + const auto* typeColl = static_cast(coll); + relation = new RelType((*typeColl)[id.index]); + } +} + +} // namespace podio::detail + +#endif // PODIO_DETAIL_RELATIONIOHELPERS_H diff --git a/include/podio/utilities/TypeHelpers.h b/include/podio/utilities/TypeHelpers.h index 6af8a729d..9b5ff4640 100644 --- a/include/podio/utilities/TypeHelpers.h +++ b/include/podio/utilities/TypeHelpers.h @@ -209,6 +209,16 @@ namespace detail { template using TupleOfMutableTypes = typename ToTupleOfTemplateHelper::type; + /// Detector for checking for the existence of an interfaced_type type member + template + using hasInterface_t = typename T::interfaced_types; + + /// Variable template for checking whether the passed type T is an interface + /// type. + /// + /// @note: This simply checks whether T has an interfaced_types type member. + template + constexpr static bool isInterfaceType = det::is_detected_v; } // namespace detail // forward declaration to be able to use it below diff --git a/python/templates/CollectionData.cc.jinja2 b/python/templates/CollectionData.cc.jinja2 index 49acea18a..6cf76fcbe 100644 --- a/python/templates/CollectionData.cc.jinja2 +++ b/python/templates/CollectionData.cc.jinja2 @@ -9,6 +9,8 @@ {{ include }} {% endfor %} +#include + {{ utils.namespace_open(class.namespace) }} {% with class_type = class.bare_type + 'CollectionData' %} diff --git a/python/templates/Interface.h.jinja2 b/python/templates/Interface.h.jinja2 index ef96f601d..299bbfb10 100644 --- a/python/templates/Interface.h.jinja2 +++ b/python/templates/Interface.h.jinja2 @@ -21,16 +21,19 @@ {{ common_macros.class_description(class.bare_type, Description, Author) }} class {{ class.bare_type }} { +public: + /// type alias containing all the types this interface should work for in a + /// tuple + using interfaced_types = std::tuple<{{ Types | join(", ")}}>; - /// type alias containing all the types this interface should work for. - using InterfacedTypes = std::tuple<{{ Types | join(", ")}}>; +private: /// type alias containing all the mutable types that can be used to initialize /// this interface - using InterfacedMutableTypes = podio::detail::TupleOfMutableTypes; + using InterfacedMutableTypes = podio::detail::TupleOfMutableTypes; /// template variable for determining whether type T is a valid interface type template - constexpr static bool isInterfacedType = podio::detail::isInTuple; + constexpr static bool isInterfacedType = podio::detail::isInTuple; /// template variable for determining whether type T can be used to initialize /// this interface diff --git a/python/templates/macros/collections.jinja2 b/python/templates/macros/collections.jinja2 index 1e097cdac..b6fdd3076 100644 --- a/python/templates/macros/collections.jinja2 +++ b/python/templates/macros/collections.jinja2 @@ -71,20 +71,7 @@ std::vector<{{ member.full_type }}> {{ class.bare_type }}Collection::{{ member.n m_rel_{{ relation.name }}->emplace_back({{ relation.full_type }}::makeEmpty()); continue; } -{% if relation.interface_types %} - // We need the concrete collection type to assign it to an InferenceWrapper -{% set else = joiner("else") %} -{% for int_type in relation.interface_types %} - {{ else() }} if (auto {{ int_type.bare_type }}Coll = dynamic_cast<{{ int_type.full_type }}Collection*>(coll)) { - const auto tmp = (*{{ int_type.bare_type }}Coll)[id.index]; - m_rel_{{ relation.name }}->emplace_back(tmp); - } -{% endfor %} -{% else %} - {{ relation.full_type }}Collection* tmp_coll = static_cast<{{ relation.full_type }}Collection*>(coll); - const auto tmp = (*tmp_coll)[id.index]; - m_rel_{{ relation.name }}->emplace_back(tmp); -{% endif %} + podio::detail::addMultiRelation(*m_rel_{{ relation.name }}, coll, id); } else { m_rel_{{ relation.name }}->emplace_back({{ relation.full_type }}::makeEmpty()); } @@ -102,17 +89,7 @@ std::vector<{{ member.full_type }}> {{ class.bare_type }}Collection::{{ member.n entries[i]->m_{{ relation.name }} = nullptr; continue; } -{% if relation.interface_types %} -{% set else = joiner("else") %} -{% for int_type in relation.interface_types %} - {{ else() }} if (auto {{ int_type.bare_type }}Coll = dynamic_cast<{{ int_type.full_type }}Collection*>(coll)) { - entries[i]->m_{{ relation.name }} = new {{ relation.full_type }}((*{{ int_type.bare_type }}Coll)[id.index]); - } -{% endfor %} -{% else %} - {{ relation.full_type }}Collection* tmp_coll = static_cast<{{ relation.full_type }}Collection*>(coll); - entries[i]->m_{{ relation.name }} = new {{ relation.full_type }}((*tmp_coll)[id.index]); -{% endif %} + podio::detail::addSingleRelation(entries[i]->m_{{ relation.name }}, coll, id); } else { entries[i]->m_{{ relation.name }} = nullptr; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f425fe8f..cb3818c3f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -215,6 +215,7 @@ install(FILES ${headers_necessary} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/podio ) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/podio/utilities + ${PROJECT_SOURCE_DIR}/include/podio/detail DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/podio ) diff --git a/tests/unittests/interface_types.cpp b/tests/unittests/interface_types.cpp index a98b55b5d..00fdd0352 100644 --- a/tests/unittests/interface_types.cpp +++ b/tests/unittests/interface_types.cpp @@ -1,11 +1,12 @@ #include "catch2/catch_test_macros.hpp" -#include "podio/ObjectID.h" - #include "datamodel/ExampleHitCollection.h" #include "datamodel/MutableExampleCluster.h" #include "datamodel/TypeWithEnergy.h" +#include "podio/ObjectID.h" +#include "podio/utilities/TypeHelpers.h" + #include #include @@ -45,6 +46,10 @@ TEST_CASE("InterfaceTypes basic functionality", "[interface-types][basics]") { REQUIRE(wrapper1.id() == podio::ObjectID{0, 42}); } +TEST_CASE("InterfaceTypes static checks", "[interface-types][static-checks]") { + STATIC_REQUIRE(podio::detail::isInterfaceType); +} + TEST_CASE("InterfaceTypes STL usage", "[interface-types][basics]") { // Make sure that interface types can be used with STL map and set std::map counterMap{};