From 0ea24b710f0aac689baadae5445378e3b8e495c4 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Mon, 23 Sep 2024 17:03:30 +0200 Subject: [PATCH 01/11] feat(util): Minimal version of a delegate chain --- Core/include/Acts/Utilities/DelegateChain.hpp | 70 +++++++++++++++++++ Tests/UnitTests/Core/Utilities/CMakeLists.txt | 1 + .../Core/Utilities/DelegateChainTests.cpp | 37 ++++++++++ 3 files changed, 108 insertions(+) create mode 100644 Core/include/Acts/Utilities/DelegateChain.hpp create mode 100644 Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp new file mode 100644 index 00000000000..e0d2151560f --- /dev/null +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -0,0 +1,70 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Utilities/Delegate.hpp" + +namespace Acts { + +template +class DelegateChain; + +/// This class is a thin wrapper around a vector of delegates that are supposed +/// to be called in order. +template + requires(std::is_same_v) // Currently does not support return values +class DelegateChain { + using return_type = R; + using holder_type = H; + + public: + static constexpr DelegateType kOwnership = O; + + using DelegateType = Delegate; + + /// Insert a delegate at the end of the chain + /// @param delegate The delegate to append + void push_back(DelegateType&& delegate) { + assert(delegate.connected() && "Delegate is not connected"); + m_delegates.push_back(delegate); + } + + /// Creates a new delegate at the end of the chain and immediately connects it + /// with the given arguments. + /// @tparam Callable The callable to connect + /// @tparam Ts The argument types + /// @param args The arguments to connect the delegate with + template + void connect_back(Ts&&... args) { + auto& delegate = m_delegates.emplace_back(); + delegate.template connect(std::forward(args)...); + } + + /// The number of delegates in the chain + /// @return The number of delegates + std::size_t size() const { return m_delegates.size(); } + + /// Check if the chain is empty + /// @return True if the chain is empty, else false + bool empty() const { return m_delegates.empty(); } + + /// The call operator that exposes the functionality of the @c DelegateChain type. + /// @param args The arguments to call the contained functions with + template + void operator()(Ts&&... args) const { + for (const auto& delegate : m_delegates) { + delegate(std::forward(args)...); + } + } + + private: + std::vector m_delegates; +}; + +} // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/CMakeLists.txt b/Tests/UnitTests/Core/Utilities/CMakeLists.txt index 68b4ba5a655..78e8b459ab7 100644 --- a/Tests/UnitTests/Core/Utilities/CMakeLists.txt +++ b/Tests/UnitTests/Core/Utilities/CMakeLists.txt @@ -35,6 +35,7 @@ add_unittest(Result ResultTests.cpp) add_unittest(TypeList TypeListTests.cpp) add_unittest(UnitVectors UnitVectorsTests.cpp) add_unittest(Delegate DelegateTests.cpp) +add_unittest(DelegateChain DelegateChainTests.cpp) add_unittest(HashedString HashedStringTests.cpp) if(ACTS_BUILD_CUDA_FEATURES) add_unittest(Cuda CudaTests.cu) diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp new file mode 100644 index 00000000000..328cafcfcdf --- /dev/null +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -0,0 +1,37 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include +#include + +#include "Acts/Utilities/DelegateChain.hpp" + +using namespace Acts; + +struct AddTo { + int value = 0; + + void add(int &x) const { x += value; } +}; + +BOOST_AUTO_TEST_SUITE(DelegateChainTests) + +BOOST_AUTO_TEST_CASE(DelegateChainAdd) { + DelegateChain chain; + AddTo a1{1}, a2{2}, a3{3}; + + chain.connect_back<&AddTo::add>(&a1); + chain.connect_back<&AddTo::add>(&a2); + chain.connect_back<&AddTo::add>(&a3); + + int x = 0; + chain(x); + BOOST_CHECK_EQUAL(x, 6); +} + +BOOST_AUTO_TEST_SUITE_END() From 1dcf81c7dc14b6d7e4fc6cf4d8c04ad444393f70 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 24 Sep 2024 15:22:55 +0200 Subject: [PATCH 02/11] feat: DelegateChain builder --- Core/include/Acts/Utilities/Delegate.hpp | 15 +- Core/include/Acts/Utilities/DelegateChain.hpp | 131 +++++++++++++----- .../Core/Utilities/DelegateChainTests.cpp | 19 ++- 3 files changed, 123 insertions(+), 42 deletions(-) diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index 8817411d90d..ca511062789 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -44,18 +44,19 @@ class Delegate; /// template class Delegate { - static constexpr DelegateType kOwnership = O; - + public: /// Alias of the return type using return_type = R; using holder_type = H; /// Alias to the function pointer type this class will store using function_type = return_type (*)(const holder_type *, Args...); - using function_ptr_type = return_type (*)(Args...); using deleter_type = void (*)(const holder_type *); + static constexpr DelegateType kOwnership = O; + + private: template using isSignatureCompatible = decltype(std::declval() = std::declval()); @@ -202,6 +203,14 @@ class Delegate { m_function = callable; } + template + void connect(function_type callable, const Type *instance) + requires(kOwnership == DelegateType::NonOwning) + { + m_payload.payload = instance; + m_function = callable; + } + /// Connect a member function to be called on an instance /// @tparam Callable The compile-time member function pointer /// @tparam Type The type of the instance the member function should be called on diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp index e0d2151560f..74dec054d7a 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -9,62 +9,127 @@ #pragma once #include "Acts/Utilities/Delegate.hpp" +#include "Acts/Utilities/TypeList.hpp" + +#include +#include namespace Acts { -template +template > class DelegateChain; /// This class is a thin wrapper around a vector of delegates that are supposed /// to be called in order. -template +template requires(std::is_same_v) // Currently does not support return values -class DelegateChain { +class DelegateChain> { + public: using return_type = R; - using holder_type = H; + using tuple_type = std::tuple; + using delegate_type = Delegate; + + DelegateChain(std::unique_ptr payloads) + : m_payloads(std::move(payloads)) {} + + delegate_type& delegate() { return m_delegate; } + + /// The call operator that exposes the functionality of the @c DelegateChain type. + /// @param args The arguments to call the contained functions with + template + void operator()(Ts&&... args) const { + m_delegate(std::forward(args)...); + } + + private: + delegate_type m_delegate; + std::unique_ptr m_payloads{}; +}; + +template , auto... Callables> +class DelegateChainFactory; + +struct DelegateNoPayloadTag {}; + +template + requires(std::is_same_v) // Currently does not support return values +class DelegateChainFactory, + callables...> { + using return_type = R; + using chain_type = + DelegateChain>; + using tuple_type = typename chain_type::tuple_type; public: - static constexpr DelegateType kOwnership = O; + DelegateChainFactory() = default; + DelegateChainFactory(std::tuple payloads) + : m_payloads(payloads) {} - using DelegateType = Delegate; + template + auto add(payload_type&& payload) { + std::tuple payloads = + std::tuple_cat(m_payloads, std::make_tuple(payload)); - /// Insert a delegate at the end of the chain - /// @param delegate The delegate to append - void push_back(DelegateType&& delegate) { - assert(delegate.connected() && "Delegate is not connected"); - m_delegates.push_back(delegate); + return DelegateChainFactory, + callables..., Callable>{payloads}; } - /// Creates a new delegate at the end of the chain and immediately connects it - /// with the given arguments. - /// @tparam Callable The callable to connect - /// @tparam Ts The argument types - /// @param args The arguments to connect the delegate with - template - void connect_back(Ts&&... args) { - auto& delegate = m_delegates.emplace_back(); - delegate.template connect(std::forward(args)...); + template + auto add() { + std::tuple payloads = + std::tuple_cat(m_payloads, std::make_tuple(DelegateNoPayloadTag{})); + + return DelegateChainFactory< + R(callable_args...), TypeList, + callables..., Callable>{payloads}; } - /// The number of delegates in the chain - /// @return The number of delegates - std::size_t size() const { return m_delegates.size(); } + template + static constexpr auto findCallable() { + if constexpr (I == J) { + return head; + } else { + return findCallable(); + } + } - /// Check if the chain is empty - /// @return True if the chain is empty, else false - bool empty() const { return m_delegates.empty(); } + template + static constexpr auto invoke(const tuple_type* payloads, + callable_args... args) { + const auto& callable = findCallable(); - /// The call operator that exposes the functionality of the @c DelegateChain type. - /// @param args The arguments to call the contained functions with - template - void operator()(Ts&&... args) const { - for (const auto& delegate : m_delegates) { - delegate(std::forward(args)...); + if constexpr (!std::is_same_v, + DelegateNoPayloadTag>) { + auto payload = std::get(*payloads); + std::invoke(callable, payload, std::forward(args)...); + } else { + std::invoke(callable, std::forward(args)...); + } + + if constexpr (I < sizeof...(payload_types) - 1) { + invoke(payloads, std::forward(args)...); } } + chain_type build() { + auto payloads = std::make_unique(m_payloads); + const tuple_type* payloadsPtr = payloads.get(); + chain_type chain{std::move(payloads)}; + + typename chain_type::delegate_type::function_type function = + [](const void* payloadPack, callable_args... args) { + const auto* tuplePtr = static_cast(payloadPack); + invoke(tuplePtr, std::forward(args)...); + }; + + chain.delegate().connect(function, payloadsPtr); + return chain; + } + private: - std::vector m_delegates; + tuple_type m_payloads{}; }; } // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp index 328cafcfcdf..50532f0b293 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -10,6 +10,7 @@ #include #include "Acts/Utilities/DelegateChain.hpp" +#include "Acts/Utilities/TypeList.hpp" using namespace Acts; @@ -19,19 +20,25 @@ struct AddTo { void add(int &x) const { x += value; } }; +void addFive(int &x) { + x += 5; +} + BOOST_AUTO_TEST_SUITE(DelegateChainTests) BOOST_AUTO_TEST_CASE(DelegateChainAdd) { - DelegateChain chain; AddTo a1{1}, a2{2}, a3{3}; + int x = 0; - chain.connect_back<&AddTo::add>(&a1); - chain.connect_back<&AddTo::add>(&a2); - chain.connect_back<&AddTo::add>(&a3); + auto chain = DelegateChainFactory{} + .add<&AddTo::add>(&a1) + .add<&addFive>() + .add<&AddTo::add>(&a2) + .add<&AddTo::add>(&a3) + .build(); - int x = 0; chain(x); - BOOST_CHECK_EQUAL(x, 6); + BOOST_CHECK_EQUAL(x, 11); } BOOST_AUTO_TEST_SUITE_END() From a26c82979f75d4e460ea75c61670e8b626c4f961 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 24 Sep 2024 16:47:10 +0200 Subject: [PATCH 03/11] feat: Add delegate chain test with return type --- Core/include/Acts/Utilities/DelegateChain.hpp | 47 ++++++++++++++----- .../Core/Utilities/DelegateChainTests.cpp | 26 ++++++++++ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp index 74dec054d7a..8474cc01c17 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -11,6 +11,7 @@ #include "Acts/Utilities/Delegate.hpp" #include "Acts/Utilities/TypeList.hpp" +#include #include #include @@ -22,12 +23,13 @@ class DelegateChain; /// This class is a thin wrapper around a vector of delegates that are supposed /// to be called in order. template - requires(std::is_same_v) // Currently does not support return values class DelegateChain> { public: - using return_type = R; + using return_type = + std::conditional_t, void, + std::array>; using tuple_type = std::tuple; - using delegate_type = Delegate; + using delegate_type = Delegate; DelegateChain(std::unique_ptr payloads) : m_payloads(std::move(payloads)) {} @@ -37,8 +39,8 @@ class DelegateChain> { /// The call operator that exposes the functionality of the @c DelegateChain type. /// @param args The arguments to call the contained functions with template - void operator()(Ts&&... args) const { - m_delegate(std::forward(args)...); + auto operator()(Ts&&... args) const { + return m_delegate(std::forward(args)...); } private: @@ -51,9 +53,9 @@ class DelegateChainFactory; struct DelegateNoPayloadTag {}; +// @TODO: Maybe add concept requirement for default initialization of R template - requires(std::is_same_v) // Currently does not support return values class DelegateChainFactory, callables...> { using return_type = R; @@ -95,21 +97,33 @@ class DelegateChainFactory, } } - template - static constexpr auto invoke(const tuple_type* payloads, + template + static constexpr auto invoke(result_ptr result, const tuple_type* payloads, callable_args... args) { const auto& callable = findCallable(); if constexpr (!std::is_same_v, DelegateNoPayloadTag>) { auto payload = std::get(*payloads); - std::invoke(callable, payload, std::forward(args)...); + + if constexpr (!std::is_same_v) { + std::get(*result) = std::invoke( + callable, payload, std::forward(args)...); + } else { + std::invoke(callable, payload, std::forward(args)...); + } + } else { - std::invoke(callable, std::forward(args)...); + if constexpr (!std::is_same_v) { + std::get(*result) = + std::invoke(callable, std::forward(args)...); + } else { + std::invoke(callable, std::forward(args)...); + } } if constexpr (I < sizeof...(payload_types) - 1) { - invoke(payloads, std::forward(args)...); + invoke(result, payloads, std::forward(args)...); } } @@ -119,9 +133,16 @@ class DelegateChainFactory, chain_type chain{std::move(payloads)}; typename chain_type::delegate_type::function_type function = - [](const void* payloadPack, callable_args... args) { + [](const void* payloadPack, callable_args... args) -> + typename chain_type::return_type { const auto* tuplePtr = static_cast(payloadPack); - invoke(tuplePtr, std::forward(args)...); + if constexpr (std::is_same_v) { + invoke(nullptr, tuplePtr, std::forward(args)...); + } else { + typename chain_type::return_type result{}; + invoke(&result, tuplePtr, std::forward(args)...); + return result; + } }; chain.delegate().connect(function, payloadsPtr); diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp index 50532f0b293..f2995c0302e 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -41,4 +41,30 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { BOOST_CHECK_EQUAL(x, 11); } +struct GetInt { + int value; + + int get() const { return value; } +}; + +int getSix() { + return 6; +} + +BOOST_AUTO_TEST_CASE(DelegateChainReturn) { + GetInt g1{1}, g2{2}, g3{3}; + + auto chain = DelegateChainFactory{} + .add<&GetInt::get>(&g1) + .add<&getSix>() + .add<&GetInt::get>(&g2) + .add<&GetInt::get>(&g3) + .build(); + + auto results = chain(); + std::array expected = {1, 6, 2, 3}; + BOOST_CHECK_EQUAL_COLLECTIONS(results.begin(), results.end(), + expected.begin(), expected.end()); +} + BOOST_AUTO_TEST_SUITE_END() From 1e97616d2eb87b8ad1d1036d540fe799d79aa83d Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 24 Sep 2024 20:47:08 +0200 Subject: [PATCH 04/11] refactor: Remove extra delegate chain, use owning delegate --- Core/include/Acts/Utilities/Delegate.hpp | 7 +- Core/include/Acts/Utilities/DelegateChain.hpp | 151 ++++++++---------- .../Core/Utilities/DelegateChainTests.cpp | 39 +++-- 3 files changed, 95 insertions(+), 102 deletions(-) diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index ca511062789..442e0114983 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -337,6 +337,11 @@ class OwningDelegate; /// Alias for an owning delegate template class OwningDelegate - : public Delegate {}; + : public Delegate { + public: + OwningDelegate() = default; + OwningDelegate(Delegate &&delegate) + : Delegate(std::move(delegate)) {} +}; } // namespace Acts diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp index 8474cc01c17..8a3274b7a3e 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -17,51 +17,20 @@ namespace Acts { -template > -class DelegateChain; - -/// This class is a thin wrapper around a vector of delegates that are supposed -/// to be called in order. -template -class DelegateChain> { - public: - using return_type = - std::conditional_t, void, - std::array>; - using tuple_type = std::tuple; - using delegate_type = Delegate; - - DelegateChain(std::unique_ptr payloads) - : m_payloads(std::move(payloads)) {} - - delegate_type& delegate() { return m_delegate; } - - /// The call operator that exposes the functionality of the @c DelegateChain type. - /// @param args The arguments to call the contained functions with - template - auto operator()(Ts&&... args) const { - return m_delegate(std::forward(args)...); - } - - private: - delegate_type m_delegate; - std::unique_ptr m_payloads{}; -}; - template , auto... Callables> class DelegateChainFactory; -struct DelegateNoPayloadTag {}; - // @TODO: Maybe add concept requirement for default initialization of R template class DelegateChainFactory, callables...> { - using return_type = R; - using chain_type = - DelegateChain>; - using tuple_type = typename chain_type::tuple_type; + using return_type = + std::conditional_t, void, + std::array>; + using delegate_type = + Delegate; + using tuple_type = std::tuple; public: DelegateChainFactory() = default; @@ -80,73 +49,79 @@ class DelegateChainFactory, template auto add() { - std::tuple payloads = - std::tuple_cat(m_payloads, std::make_tuple(DelegateNoPayloadTag{})); + std::tuple payloads = + std::tuple_cat(m_payloads, std::make_tuple(std::nullptr_t{})); - return DelegateChainFactory< - R(callable_args...), TypeList, - callables..., Callable>{payloads}; + return DelegateChainFactory, + callables..., Callable>{payloads}; } - template - static constexpr auto findCallable() { - if constexpr (I == J) { - return head; - } else { - return findCallable(); + struct DispatchBlock { + template + static constexpr auto findCallable() { + if constexpr (I == J) { + return head; + } else { + return findCallable(); + } } - } - template - static constexpr auto invoke(result_ptr result, const tuple_type* payloads, - callable_args... args) { - const auto& callable = findCallable(); + template + static constexpr auto invoke(result_ptr result, const tuple_type* payloads, + callable_args... args) { + const auto& callable = findCallable(); - if constexpr (!std::is_same_v, - DelegateNoPayloadTag>) { - auto payload = std::get(*payloads); + if constexpr (!std::is_same_v, + std::nullptr_t>) { + auto payload = std::get(*payloads); + + if constexpr (!std::is_same_v) { + std::get(*result) = std::invoke( + callable, payload, std::forward(args)...); + } else { + std::invoke(callable, payload, std::forward(args)...); + } - if constexpr (!std::is_same_v) { - std::get(*result) = std::invoke( - callable, payload, std::forward(args)...); } else { - std::invoke(callable, payload, std::forward(args)...); + if constexpr (!std::is_same_v) { + std::get(*result) = + std::invoke(callable, std::forward(args)...); + } else { + std::invoke(callable, std::forward(args)...); + } } - } else { - if constexpr (!std::is_same_v) { - std::get(*result) = - std::invoke(callable, std::forward(args)...); - } else { - std::invoke(callable, std::forward(args)...); + if constexpr (I < sizeof...(payload_types) - 1) { + invoke(result, payloads, std::forward(args)...); } } - if constexpr (I < sizeof...(payload_types) - 1) { - invoke(result, payloads, std::forward(args)...); + DispatchBlock(tuple_type payloads) : m_payloads(std::move(payloads)) {} + + tuple_type m_payloads{}; + + auto dispatch(callable_args... args) const { + if constexpr (std::is_same_v) { + invoke(nullptr, &m_payloads, std::forward(args)...); + } else { + return_type result{}; + invoke(&result, &m_payloads, std::forward(args)...); + return result; + } } + }; + + delegate_type build() { + auto block = std::make_unique(m_payloads); + delegate_type delegate; + delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); + return delegate; } - chain_type build() { - auto payloads = std::make_unique(m_payloads); - const tuple_type* payloadsPtr = payloads.get(); - chain_type chain{std::move(payloads)}; - - typename chain_type::delegate_type::function_type function = - [](const void* payloadPack, callable_args... args) -> - typename chain_type::return_type { - const auto* tuplePtr = static_cast(payloadPack); - if constexpr (std::is_same_v) { - invoke(nullptr, tuplePtr, std::forward(args)...); - } else { - typename chain_type::return_type result{}; - invoke(&result, tuplePtr, std::forward(args)...); - return result; - } - }; - - chain.delegate().connect(function, payloadsPtr); - return chain; + void store(delegate_type& delegate) { + auto block = std::make_unique(m_payloads); + delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); } private: diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp index f2995c0302e..9dda3e7fe1b 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -30,12 +30,13 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { AddTo a1{1}, a2{2}, a3{3}; int x = 0; - auto chain = DelegateChainFactory{} - .add<&AddTo::add>(&a1) - .add<&addFive>() - .add<&AddTo::add>(&a2) - .add<&AddTo::add>(&a3) - .build(); + // Delegate chain = + OwningDelegate chain = DelegateChainFactory{} + .add<&AddTo::add>(&a1) + .add<&addFive>() + .add<&AddTo::add>(&a2) + .add<&AddTo::add>(&a3) + .build(); chain(x); BOOST_CHECK_EQUAL(x, 11); @@ -54,17 +55,29 @@ int getSix() { BOOST_AUTO_TEST_CASE(DelegateChainReturn) { GetInt g1{1}, g2{2}, g3{3}; - auto chain = DelegateChainFactory{} - .add<&GetInt::get>(&g1) - .add<&getSix>() - .add<&GetInt::get>(&g2) - .add<&GetInt::get>(&g3) - .build(); + Delegate(), void, DelegateType::Owning> chain = + DelegateChainFactory{} + .add<&GetInt::get>(&g1) + .add<&getSix>() + .add<&GetInt::get>(&g2) + .add<&GetInt::get>(&g3) + .build(); auto results = chain(); - std::array expected = {1, 6, 2, 3}; + std::vector expected = {1, 6, 2, 3}; BOOST_CHECK_EQUAL_COLLECTIONS(results.begin(), results.end(), expected.begin(), expected.end()); + + Delegate(), void, DelegateType::Owning> delegate; + DelegateChainFactory{} + .add<&GetInt::get>(&g1) + .add<&getSix>() + .add<&GetInt::get>(&g3) + .store(delegate); + auto results2 = delegate(); + expected = {1, 6, 3}; + BOOST_CHECK_EQUAL_COLLECTIONS(results2.begin(), results2.end(), + expected.begin(), expected.end()); } BOOST_AUTO_TEST_SUITE_END() From 0168ac25e1367bb8cee176b5d4e42c75576af8a3 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 26 Sep 2024 11:51:48 +0200 Subject: [PATCH 05/11] refactor: Delegate + Chain improvements --- Core/include/Acts/Utilities/Delegate.hpp | 29 +++++---- Core/include/Acts/Utilities/DelegateChain.hpp | 64 ++++++++++++++----- .../Core/Utilities/DelegateChainTests.cpp | 53 ++++++++++++++- 3 files changed, 113 insertions(+), 33 deletions(-) diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index 442e0114983..a02787d97ab 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -51,6 +51,7 @@ class Delegate { /// Alias to the function pointer type this class will store using function_type = return_type (*)(const holder_type *, Args...); using function_ptr_type = return_type (*)(Args...); + using signature_type = R(Args...); using deleter_type = void (*)(const holder_type *); @@ -158,7 +159,10 @@ class Delegate { /// @note The function pointer must be ``constexpr`` for @c Delegate to accept it /// @tparam Callable The compile-time free function pointer template - void connect() { + void connect() + requires( + Concepts::invocable_and_returns) + { m_payload.payload = nullptr; static_assert( @@ -219,13 +223,11 @@ class Delegate { /// it's lifetime is longer than that of @c Delegate. template void connect(const Type *instance) - requires(kOwnership == DelegateType::NonOwning) - { - static_assert(Concepts::invocable_and_returns, - "Callable given does not correspond exactly to required call " - "signature"); + requires(kOwnership == DelegateType::NonOwning && + Concepts::invocable_and_returns) + { m_payload.payload = instance; m_function = [](const holder_type *payload, Args... args) -> return_type { @@ -243,13 +245,10 @@ class Delegate { /// @note @c Delegate assumes owner ship over @p instance. template void connect(std::unique_ptr instance) - requires(kOwnership == DelegateType::Owning) + requires(kOwnership == DelegateType::Owning && + Concepts::invocable_and_returns) { - static_assert(Concepts::invocable_and_returns, - "Callable given does not correspond exactly to required call " - "signature"); - m_payload.payload = std::unique_ptr( instance.release(), [](const holder_type *payload) { const auto *concretePayload = static_cast(payload); @@ -268,7 +267,9 @@ class Delegate { /// @param args The arguments to call the contained function with /// @return Return value of the contained function template - return_type operator()(Ts &&...args) const { + return_type operator()(Ts &&...args) const + requires(std::is_invocable_v) + { assert(connected() && "Delegate is not connected"); return std::invoke(m_function, m_payload.ptr(), std::forward(args)...); } diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp index 8a3274b7a3e..bfefde6a638 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -10,9 +10,10 @@ #include "Acts/Utilities/Delegate.hpp" #include "Acts/Utilities/TypeList.hpp" +#include "Acts/Utilities/TypeTag.hpp" -#include #include +#include #include namespace Acts { @@ -33,12 +34,18 @@ class DelegateChainFactory, using tuple_type = std::tuple; public: + template + friend class DelegateChainFactory; + DelegateChainFactory() = default; - DelegateChainFactory(std::tuple payloads) - : m_payloads(payloads) {} + + template + DelegateChainFactory(TypeTag /*unused*/) {} template - auto add(payload_type&& payload) { + constexpr auto add(payload_type payload) + requires(std::is_pointer_v) + { std::tuple payloads = std::tuple_cat(m_payloads, std::make_tuple(payload)); @@ -48,7 +55,7 @@ class DelegateChainFactory, } template - auto add() { + constexpr auto add() { std::tuple payloads = std::tuple_cat(m_payloads, std::make_tuple(std::nullptr_t{})); @@ -57,6 +64,37 @@ class DelegateChainFactory, callables..., Callable>{payloads}; } + delegate_type build() + requires(sizeof...(callables) > 0) + { + auto block = std::make_unique(m_payloads); + delegate_type delegate; + delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); + return delegate; + } + + void store(delegate_type& delegate) + requires(sizeof...(callables) > 0) + { + auto block = std::make_unique(m_payloads); + delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); + } + + void store(Delegate& delegate) + requires(sizeof...(callables) == 1) + { + constexpr auto callable = + DispatchBlock::template findCallable<0, 0, callables...>(); + delegate.template connect(std::get<0>(m_payloads)); + + // auto block = std::make_unique(m_payloads); + // delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); + } + + private: + DelegateChainFactory(std::tuple payloads) + : m_payloads(payloads) {} + struct DispatchBlock { template static constexpr auto findCallable() { @@ -112,20 +150,12 @@ class DelegateChainFactory, } }; - delegate_type build() { - auto block = std::make_unique(m_payloads); - delegate_type delegate; - delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); - return delegate; - } - - void store(delegate_type& delegate) { - auto block = std::make_unique(m_payloads); - delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); - } - private: tuple_type m_payloads{}; }; +template +DelegateChainFactory(TypeTag /*unused*/) + -> DelegateChainFactory; + } // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp index 9dda3e7fe1b..fb566ad4dfe 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -11,6 +11,7 @@ #include "Acts/Utilities/DelegateChain.hpp" #include "Acts/Utilities/TypeList.hpp" +#include "Acts/Utilities/TypeTag.hpp" using namespace Acts; @@ -30,16 +31,63 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { AddTo a1{1}, a2{2}, a3{3}; int x = 0; - // Delegate chain = + // Basic building OwningDelegate chain = DelegateChainFactory{} .add<&AddTo::add>(&a1) .add<&addFive>() .add<&AddTo::add>(&a2) .add<&AddTo::add>(&a3) .build(); - chain(x); BOOST_CHECK_EQUAL(x, 11); + + chain.disconnect(); + + // In case of no return types, we can rebind the owning delegate with a chain + // of different size + chain = DelegateChainFactory{} + .add<&AddTo::add>(&a1) + .add<&addFive>() + .add<&AddTo::add>(&a3) + .build(); + + x = 0; + + chain(x); + BOOST_CHECK_EQUAL(x, 9); + + // CTAD helper from delegate type + chain = DelegateChainFactory{Type} + .add<&AddTo::add>(&a1) + .add<&addFive>() + .add<&AddTo::add>(&a3) + .build(); + + x = 0; + + chain(x); + BOOST_CHECK_EQUAL(x, 9); + + // CTAD helper from delegate type + chain = DelegateChainFactory{TypeTag{chain}} + .add<&AddTo::add>(&a1) + .add<&addFive>() + .add<&AddTo::add>(&a3) + .build(); + + x = 0; + + chain(x); + BOOST_CHECK_EQUAL(x, 9); + + Delegate nonOwning; + + // In case of a single callable, we can store it in a non-owning delegate + DelegateChainFactory{}.add<&AddTo::add>(&a1).store(nonOwning); + + x = 0; + nonOwning(x); + BOOST_CHECK_EQUAL(x, 1); } struct GetInt { @@ -74,6 +122,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainReturn) { .add<&getSix>() .add<&GetInt::get>(&g3) .store(delegate); + auto results2 = delegate(); expected = {1, 6, 3}; BOOST_CHECK_EQUAL_COLLECTIONS(results2.begin(), results2.end(), From c8a8f1216af621ea57ae19b3bc36a92548446945 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 26 Sep 2024 13:11:34 +0200 Subject: [PATCH 06/11] refactor: Delegate chain does not need typetag --- Core/include/Acts/Utilities/DelegateChain.hpp | 4 ++-- .../Core/Utilities/DelegateChainTests.cpp | 16 +--------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChain.hpp index bfefde6a638..da1492991ef 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChain.hpp @@ -40,7 +40,7 @@ class DelegateChainFactory, DelegateChainFactory() = default; template - DelegateChainFactory(TypeTag /*unused*/) {} + DelegateChainFactory(const D& /*unused*/) {} template constexpr auto add(payload_type payload) @@ -155,7 +155,7 @@ class DelegateChainFactory, }; template -DelegateChainFactory(TypeTag /*unused*/) +DelegateChainFactory(const D& /*unused*/) -> DelegateChainFactory; } // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp index fb566ad4dfe..745b1033e22 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp @@ -10,8 +10,6 @@ #include #include "Acts/Utilities/DelegateChain.hpp" -#include "Acts/Utilities/TypeList.hpp" -#include "Acts/Utilities/TypeTag.hpp" using namespace Acts; @@ -57,19 +55,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { BOOST_CHECK_EQUAL(x, 9); // CTAD helper from delegate type - chain = DelegateChainFactory{Type} - .add<&AddTo::add>(&a1) - .add<&addFive>() - .add<&AddTo::add>(&a3) - .build(); - - x = 0; - - chain(x); - BOOST_CHECK_EQUAL(x, 9); - - // CTAD helper from delegate type - chain = DelegateChainFactory{TypeTag{chain}} + chain = DelegateChainFactory{chain} .add<&AddTo::add>(&a1) .add<&addFive>() .add<&AddTo::add>(&a3) From 1cf5af85278c926e0c4769b7c6ecf5238df5595e Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Mon, 7 Oct 2024 09:17:42 +0200 Subject: [PATCH 07/11] refactor: Rename DelegateChainFactory to DelegateChainBuilder --- ...gateChain.hpp => DelegateChainBuilder.hpp} | 20 +++++++++---------- Tests/UnitTests/Core/Utilities/CMakeLists.txt | 2 +- ...ests.cpp => DelegateChainBuilderTests.cpp} | 20 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) rename Core/include/Acts/Utilities/{DelegateChain.hpp => DelegateChainBuilder.hpp} (91%) rename Tests/UnitTests/Core/Utilities/{DelegateChainTests.cpp => DelegateChainBuilderTests.cpp} (84%) diff --git a/Core/include/Acts/Utilities/DelegateChain.hpp b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp similarity index 91% rename from Core/include/Acts/Utilities/DelegateChain.hpp rename to Core/include/Acts/Utilities/DelegateChainBuilder.hpp index da1492991ef..23062156894 100644 --- a/Core/include/Acts/Utilities/DelegateChain.hpp +++ b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp @@ -19,12 +19,12 @@ namespace Acts { template , auto... Callables> -class DelegateChainFactory; +class DelegateChainBuilder; // @TODO: Maybe add concept requirement for default initialization of R template -class DelegateChainFactory, +class DelegateChainBuilder, callables...> { using return_type = std::conditional_t, void, @@ -35,12 +35,12 @@ class DelegateChainFactory, public: template - friend class DelegateChainFactory; + friend class DelegateChainBuilder; - DelegateChainFactory() = default; + DelegateChainBuilder() = default; template - DelegateChainFactory(const D& /*unused*/) {} + DelegateChainBuilder(const D& /*unused*/) {} template constexpr auto add(payload_type payload) @@ -49,7 +49,7 @@ class DelegateChainFactory, std::tuple payloads = std::tuple_cat(m_payloads, std::make_tuple(payload)); - return DelegateChainFactory, callables..., Callable>{payloads}; } @@ -59,7 +59,7 @@ class DelegateChainFactory, std::tuple payloads = std::tuple_cat(m_payloads, std::make_tuple(std::nullptr_t{})); - return DelegateChainFactory, callables..., Callable>{payloads}; } @@ -92,7 +92,7 @@ class DelegateChainFactory, } private: - DelegateChainFactory(std::tuple payloads) + DelegateChainBuilder(std::tuple payloads) : m_payloads(payloads) {} struct DispatchBlock { @@ -155,7 +155,7 @@ class DelegateChainFactory, }; template -DelegateChainFactory(const D& /*unused*/) - -> DelegateChainFactory; +DelegateChainBuilder(const D& /*unused*/) + -> DelegateChainBuilder; } // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/CMakeLists.txt b/Tests/UnitTests/Core/Utilities/CMakeLists.txt index 78e8b459ab7..2823e275b38 100644 --- a/Tests/UnitTests/Core/Utilities/CMakeLists.txt +++ b/Tests/UnitTests/Core/Utilities/CMakeLists.txt @@ -35,7 +35,7 @@ add_unittest(Result ResultTests.cpp) add_unittest(TypeList TypeListTests.cpp) add_unittest(UnitVectors UnitVectorsTests.cpp) add_unittest(Delegate DelegateTests.cpp) -add_unittest(DelegateChain DelegateChainTests.cpp) +add_unittest(DelegateChainBuilder DelegateChainBuilderTests.cpp) add_unittest(HashedString HashedStringTests.cpp) if(ACTS_BUILD_CUDA_FEATURES) add_unittest(Cuda CudaTests.cu) diff --git a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp similarity index 84% rename from Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp rename to Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp index 745b1033e22..783eb9a85e2 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateChainTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp @@ -9,7 +9,7 @@ #include #include -#include "Acts/Utilities/DelegateChain.hpp" +#include "Acts/Utilities/DelegateChainBuilder.hpp" using namespace Acts; @@ -23,14 +23,14 @@ void addFive(int &x) { x += 5; } -BOOST_AUTO_TEST_SUITE(DelegateChainTests) +BOOST_AUTO_TEST_SUITE(DelegateChainBuilderTests) -BOOST_AUTO_TEST_CASE(DelegateChainAdd) { +BOOST_AUTO_TEST_CASE(DelegateChainBuilderAdd) { AddTo a1{1}, a2{2}, a3{3}; int x = 0; // Basic building - OwningDelegate chain = DelegateChainFactory{} + OwningDelegate chain = DelegateChainBuilder{} .add<&AddTo::add>(&a1) .add<&addFive>() .add<&AddTo::add>(&a2) @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { // In case of no return types, we can rebind the owning delegate with a chain // of different size - chain = DelegateChainFactory{} + chain = DelegateChainBuilder{} .add<&AddTo::add>(&a1) .add<&addFive>() .add<&AddTo::add>(&a3) @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { BOOST_CHECK_EQUAL(x, 9); // CTAD helper from delegate type - chain = DelegateChainFactory{chain} + chain = DelegateChainBuilder{chain} .add<&AddTo::add>(&a1) .add<&addFive>() .add<&AddTo::add>(&a3) @@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainAdd) { Delegate nonOwning; // In case of a single callable, we can store it in a non-owning delegate - DelegateChainFactory{}.add<&AddTo::add>(&a1).store(nonOwning); + DelegateChainBuilder{}.add<&AddTo::add>(&a1).store(nonOwning); x = 0; nonOwning(x); @@ -86,11 +86,11 @@ int getSix() { return 6; } -BOOST_AUTO_TEST_CASE(DelegateChainReturn) { +BOOST_AUTO_TEST_CASE(DelegateChainBuilderReturn) { GetInt g1{1}, g2{2}, g3{3}; Delegate(), void, DelegateType::Owning> chain = - DelegateChainFactory{} + DelegateChainBuilder{} .add<&GetInt::get>(&g1) .add<&getSix>() .add<&GetInt::get>(&g2) @@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(DelegateChainReturn) { expected.begin(), expected.end()); Delegate(), void, DelegateType::Owning> delegate; - DelegateChainFactory{} + DelegateChainBuilder{} .add<&GetInt::get>(&g1) .add<&getSix>() .add<&GetInt::get>(&g3) From fe3ab049745f5e38df0b10b1bf3c44b473536e49 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Mon, 7 Oct 2024 09:35:51 +0200 Subject: [PATCH 08/11] small reordering --- Core/include/Acts/Utilities/Delegate.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index a02787d97ab..a0ef2cf7252 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -45,6 +45,8 @@ class Delegate; template class Delegate { public: + static constexpr DelegateType kOwnership = O; + /// Alias of the return type using return_type = R; using holder_type = H; @@ -55,8 +57,6 @@ class Delegate { using deleter_type = void (*)(const holder_type *); - static constexpr DelegateType kOwnership = O; - private: template using isSignatureCompatible = From 83fece5987be4e879230de6ca804206b77c9f85e Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Wed, 9 Oct 2024 11:23:07 +0200 Subject: [PATCH 09/11] add type check on the return type --- Core/include/Acts/Utilities/DelegateChainBuilder.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp index 23062156894..b217cc520ad 100644 --- a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp +++ b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp @@ -10,7 +10,6 @@ #include "Acts/Utilities/Delegate.hpp" #include "Acts/Utilities/TypeList.hpp" -#include "Acts/Utilities/TypeTag.hpp" #include #include @@ -18,12 +17,14 @@ namespace Acts { -template , auto... Callables> -class DelegateChainBuilder; +template , auto... Callables> +class DelegateChainBuilder { + static_assert(false, "Fn must be a valid function type"); +}; -// @TODO: Maybe add concept requirement for default initialization of R template + requires(std::is_same_v || std::is_default_constructible_v) class DelegateChainBuilder, callables...> { using return_type = @@ -86,9 +87,6 @@ class DelegateChainBuilder, constexpr auto callable = DispatchBlock::template findCallable<0, 0, callables...>(); delegate.template connect(std::get<0>(m_payloads)); - - // auto block = std::make_unique(m_payloads); - // delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); } private: From 94b10784e2bb25052774edf01c13b582a86e03bc Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Wed, 9 Oct 2024 11:27:23 +0200 Subject: [PATCH 10/11] improve error message --- Core/include/Acts/Utilities/DelegateChainBuilder.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp index b217cc520ad..efcd117df4e 100644 --- a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp +++ b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp @@ -19,7 +19,9 @@ namespace Acts { template , auto... Callables> class DelegateChainBuilder { - static_assert(false, "Fn must be a valid function type"); + static_assert( + false, + "Delegate chain return type must be void or default constructible"); }; template Date: Wed, 9 Oct 2024 11:35:56 +0200 Subject: [PATCH 11/11] move static_assert --- Core/include/Acts/Utilities/DelegateChainBuilder.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp index efcd117df4e..6e4f2c89650 100644 --- a/Core/include/Acts/Utilities/DelegateChainBuilder.hpp +++ b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp @@ -18,15 +18,10 @@ namespace Acts { template , auto... Callables> -class DelegateChainBuilder { - static_assert( - false, - "Delegate chain return type must be void or default constructible"); -}; +class DelegateChainBuilder; template - requires(std::is_same_v || std::is_default_constructible_v) class DelegateChainBuilder, callables...> { using return_type = @@ -143,6 +138,9 @@ class DelegateChainBuilder, if constexpr (std::is_same_v) { invoke(nullptr, &m_payloads, std::forward(args)...); } else { + static_assert( + std::is_same_v || std::is_default_constructible_v, + "Delegate chain return type must be void or default constructible"); return_type result{}; invoke(&result, &m_payloads, std::forward(args)...); return result;