diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index 8817411d90d..a0ef2cf7252 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -44,6 +44,7 @@ class Delegate; /// template class Delegate { + public: static constexpr DelegateType kOwnership = O; /// Alias of the return type @@ -51,11 +52,12 @@ class Delegate { 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 signature_type = R(Args...); using deleter_type = void (*)(const holder_type *); + private: template using isSignatureCompatible = decltype(std::declval() = std::declval()); @@ -157,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( @@ -202,6 +207,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 @@ -210,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 { @@ -234,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); @@ -259,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)...); } @@ -328,6 +338,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/DelegateChainBuilder.hpp b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp new file mode 100644 index 00000000000..6e4f2c89650 --- /dev/null +++ b/Core/include/Acts/Utilities/DelegateChainBuilder.hpp @@ -0,0 +1,159 @@ +// 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" +#include "Acts/Utilities/TypeList.hpp" + +#include +#include +#include + +namespace Acts { + +template , auto... Callables> +class DelegateChainBuilder; + +template +class DelegateChainBuilder, + callables...> { + using return_type = + std::conditional_t, void, + std::array>; + using delegate_type = + Delegate; + using tuple_type = std::tuple; + + public: + template + friend class DelegateChainBuilder; + + DelegateChainBuilder() = default; + + template + DelegateChainBuilder(const D& /*unused*/) {} + + template + constexpr auto add(payload_type payload) + requires(std::is_pointer_v) + { + std::tuple payloads = + std::tuple_cat(m_payloads, std::make_tuple(payload)); + + return DelegateChainBuilder, + callables..., Callable>{payloads}; + } + + template + constexpr auto add() { + std::tuple payloads = + std::tuple_cat(m_payloads, std::make_tuple(std::nullptr_t{})); + + return DelegateChainBuilder, + 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)); + } + + private: + DelegateChainBuilder(std::tuple payloads) + : m_payloads(payloads) {} + + 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(); + + 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)...); + } + + } 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)...); + } + } + + 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 { + 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; + } + } + }; + + private: + tuple_type m_payloads{}; +}; + +template +DelegateChainBuilder(const D& /*unused*/) + -> DelegateChainBuilder; + +} // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/CMakeLists.txt b/Tests/UnitTests/Core/Utilities/CMakeLists.txt index 27b91bfbe29..61e927e9edf 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(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/DelegateChainBuilderTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp new file mode 100644 index 00000000000..783eb9a85e2 --- /dev/null +++ b/Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp @@ -0,0 +1,118 @@ +// 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/DelegateChainBuilder.hpp" + +using namespace Acts; + +struct AddTo { + int value = 0; + + void add(int &x) const { x += value; } +}; + +void addFive(int &x) { + x += 5; +} + +BOOST_AUTO_TEST_SUITE(DelegateChainBuilderTests) + +BOOST_AUTO_TEST_CASE(DelegateChainBuilderAdd) { + AddTo a1{1}, a2{2}, a3{3}; + int x = 0; + + // Basic building + OwningDelegate chain = DelegateChainBuilder{} + .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 = DelegateChainBuilder{} + .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 = DelegateChainBuilder{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 + DelegateChainBuilder{}.add<&AddTo::add>(&a1).store(nonOwning); + + x = 0; + nonOwning(x); + BOOST_CHECK_EQUAL(x, 1); +} + +struct GetInt { + int value; + + int get() const { return value; } +}; + +int getSix() { + return 6; +} + +BOOST_AUTO_TEST_CASE(DelegateChainBuilderReturn) { + GetInt g1{1}, g2{2}, g3{3}; + + Delegate(), void, DelegateType::Owning> chain = + DelegateChainBuilder{} + .add<&GetInt::get>(&g1) + .add<&getSix>() + .add<&GetInt::get>(&g2) + .add<&GetInt::get>(&g3) + .build(); + + auto results = chain(); + std::vector expected = {1, 6, 2, 3}; + BOOST_CHECK_EQUAL_COLLECTIONS(results.begin(), results.end(), + expected.begin(), expected.end()); + + Delegate(), void, DelegateType::Owning> delegate; + DelegateChainBuilder{} + .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()