From 2e1397918b2dcf97f6e0810c40469c2fa7e6dc01 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Mon, 22 Jan 2024 13:15:07 +0100 Subject: [PATCH] Add support for interface types (#516) * Make code generation process interfaces * Implement interface types using type-erasure * Add unittests for interface types * Make sure to always store the immutable handle inside * Put all type helpers into the podio namespace Avoid collisions downstream * make equality operators symmetric * Return trivial types by value for consistency * Improve method name to be more interface like * Make interfaces only accept interfaced types * Fix easy to fix python issues * Add documentation for interface types * Make checking for explicit types and if-else chain --- cmake/podioMacros.cmake | 7 +- doc/datamodel_syntax.md | 53 +++++- include/podio/utilities/TypeHelpers.h | 93 +++++++++++ python/podio_gen/cpp_generator.py | 32 +++- python/podio_gen/generator_base.py | 65 ++++++-- python/podio_gen/generator_utils.py | 8 +- python/podio_gen/julia_generator.py | 12 ++ python/podio_gen/podio_config_reader.py | 72 +++++++- .../test_ClassDefinitionValidator.py | 109 ++++++++++-- python/templates/CMakeLists.txt | 2 + python/templates/Interface.h.jinja2 | 157 ++++++++++++++++++ python/templates/macros/collections.jinja2 | 20 +++ python/templates/macros/interface.jinja2 | 48 ++++++ tests/datalayout.yaml | 40 +++++ tests/unittests/CMakeLists.txt | 2 +- tests/unittests/interface_types.cpp | 93 +++++++++++ 16 files changed, 770 insertions(+), 43 deletions(-) create mode 100644 python/templates/Interface.h.jinja2 create mode 100644 python/templates/macros/interface.jinja2 create mode 100644 tests/unittests/interface_types.cpp diff --git a/cmake/podioMacros.cmake b/cmake/podioMacros.cmake index 9ac7bd3af..0fd0bee39 100644 --- a/cmake/podioMacros.cmake +++ b/cmake/podioMacros.cmake @@ -203,8 +203,11 @@ function(PODIO_GENERATE_DATAMODEL datamodel YAML_FILE RETURN_HEADERS RETURN_SOUR ${YAML_FILE} ${PODIO_TEMPLATES} ${podio_PYTHON_DIR}/podio_class_generator.py - ${podio_PYTHON_DIR}/podio/generator_utils.py - ${podio_PYTHON_DIR}/podio/podio_config_reader.py + ${podio_PYTHON_DIR}/podio_gen/generator_utils.py + ${podio_PYTHON_DIR}/podio_gen/podio_config_reader.py + ${podio_PYTHON_DIR}/podio_gen/generator_base.py + ${podio_PYTHON_DIR}/podio_gen/cpp_generator.py + ${podio_PYTHON_DIR}/podio_gen/julia_generator.py ) message(STATUS "Creating '${datamodel}' datamodel") diff --git a/doc/datamodel_syntax.md b/doc/datamodel_syntax.md index 5644d10a6..04e6753bd 100644 --- a/doc/datamodel_syntax.md +++ b/doc/datamodel_syntax.md @@ -9,6 +9,7 @@ PODIO encourages the usage of composition, rather than inheritance. One of the reasons for doing so is the focus on efficiency friendly `plain-old-data`. For this reason, PODIO does not support inheritance within the defined data model. Instead, users can combine multiple `components` to build a to be used `datatype`. +Additionally, if several datatypes should be callable through the same interface, the `interface` category can be used. To allow the datatypes to be real PODs, the data stored within the data model are constrained to be POD-compatible data. Those are @@ -92,7 +93,6 @@ For describing physics quantities it is important to know their units. Thus it i {} [] // ``` - ### Definition of references between objects: There can be one-to-one-relations and one-to-many relations being stored in a particular class. This happens either in the `OneToOneRelations` or `OneToManyRelations` section of the data definition. The definition has again the form: @@ -126,6 +126,57 @@ The `includes` will be add to the header files of the generated classes. The code being provided has to use the macro `{name}` in place of the concrete name of the class. +## Definition of custom interfaces +An interface type can be defined as follows (again using an example from the example datamodel) + +```yaml + interfaces: + TypeWithEnergy: + Description: "A generic interface for types with an energy member" + Author: "Someone" + Types: + - ExampleHit + - ExampleMC + - ExampleCluster + Members: + - double energy // the energy +``` + +This definition will yield a class called `TypeWithEnergy` that has one public +method to get the `energy`. Here the syntax for defining more accessors is +exactly the same as it is for the `datatypes`. Additionally, it is necessary to +define which `Types` can be used with this interface class, in this case the +`ExampleHit`, `ExampleMC` or an `ExampleCluster`. **NOTE: `interface` types do +not allow for mutable access to their data.** They can be used in relations +between objects, just like normal `datatypes`. + +### Assigning to interface types + +Interface types support the same functionality as normal (immutable) datatypes. +The main additional functionality they offer is the possibility to directly +assign to them (if they type is supported) and to query them for the type that +is currently being held internally. + +```cpp +auto energyType = TypeWithEnergy{}; // An empty interface is possible but useless +bool valid = energyType.isValid(); // <-- false + +auto hit = ExampleHit{}; +energyType = hit; // assigning a hit to the interface type +energyType.energy(); // <-- get's the energy from the underlying hit + +auto cluster = ExampleCluster{}; +energyType = cluster; // ra-assigning another object is possible +bool same = (energyType == cluster); // <-- true (comparisons work as expected) + +bool isCluster = energyType.isA(); // <-- true +bool isHit = energyType.isA(); // <-- false + +auto newCluster = energyType.getValue(); // <-- "cast back" to original type + +// "Casting" only works if the types match. Otherwise there will be an exception +auto newHit = energyType.getValue(); // <-- exception +``` ## Global options Some customization of the generated code is possible through flags. These flags are listed in the section `options`: diff --git a/include/podio/utilities/TypeHelpers.h b/include/podio/utilities/TypeHelpers.h index 74d1a4d28..7c794230c 100644 --- a/include/podio/utilities/TypeHelpers.h +++ b/include/podio/utilities/TypeHelpers.h @@ -9,6 +9,45 @@ #include namespace podio { +#if __has_include("experimental/type_traits.h") + #include +namespace det { + using namespace std::experimental; +} // namespace det +#else +// Implement the minimal feature set we need +namespace det { + namespace detail { + template typename Op, typename... Args> + struct detector { + using value_t = std::false_type; + using type = DefT; + }; + + template typename Op, typename... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + } // namespace detail + + struct nonesuch { + ~nonesuch() = delete; + nonesuch(const nonesuch&) = delete; + void operator=(const nonesuch&) = delete; + }; + + template