-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(util): Add builder class for "chain" delegates (#3696)
Related to #3502 --- This pull request introduces several enhancements and new features to the `Delegate` class and related utilities, focusing on improving functionality and adding new capabilities. The most important changes include the addition of `requires` clauses for better compile-time checks, new methods for connecting delegates, and the introduction of the `DelegateChainBuilder` class for building delegate chains. ### Enhancements to `Delegate` Class: * Added `requires` clauses to various methods in the `Delegate` class to ensure compile-time checks for function signatures and ownership types. (`Core/include/Acts/Utilities/Delegate.hpp`) [[1]](diffhunk://#diff-62fc5c04840c99ddd78a514b3b33be9001a8b2b2fdb53e5db7e481b115295ca4L160-R165) [[2]](diffhunk://#diff-62fc5c04840c99ddd78a514b3b33be9001a8b2b2fdb53e5db7e481b115295ca4L213-R230) [[3]](diffhunk://#diff-62fc5c04840c99ddd78a514b3b33be9001a8b2b2fdb53e5db7e481b115295ca4L237-L243) [[4]](diffhunk://#diff-62fc5c04840c99ddd78a514b3b33be9001a8b2b2fdb53e5db7e481b115295ca4L262-R272) * Moved the `kOwnership` static member to the public section and added new type aliases for better readability. (`Core/include/Acts/Utilities/Delegate.hpp`) ### New Features: * Introduced the `DelegateChainBuilder` class for constructing chains of delegates, allowing for more flexible and powerful delegate management. (`Core/include/Acts/Utilities/DelegateChainBuilder.hpp`) * Added unit tests for `DelegateChainBuilder` to ensure its functionality and correctness. (`Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp`) * Updated CMakeLists to include the new unit tests for `DelegateChainBuilder`. (`Tests/UnitTests/Core/Utilities/CMakeLists.txt`) ### Enhancements to `OwningDelegate` Class: * Added constructors to the `OwningDelegate` class to support default initialization and move construction from a `Delegate`. (`Core/include/Acts/Utilities/Delegate.hpp`)
- Loading branch information
1 parent
a9ed41f
commit 3d6f703
Showing
4 changed files
with
309 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <tuple> | ||
#include <type_traits> | ||
#include <utility> | ||
|
||
namespace Acts { | ||
|
||
template <typename Fn, typename payload_types = TypeList<>, auto... Callables> | ||
class DelegateChainBuilder; | ||
|
||
template <typename R, typename... payload_types, auto... callables, | ||
typename... callable_args> | ||
class DelegateChainBuilder<R(callable_args...), TypeList<payload_types...>, | ||
callables...> { | ||
using return_type = | ||
std::conditional_t<std::is_same_v<R, void>, void, | ||
std::array<R, sizeof...(payload_types)>>; | ||
using delegate_type = | ||
Delegate<return_type(callable_args...), void, DelegateType::Owning>; | ||
using tuple_type = std::tuple<payload_types...>; | ||
|
||
public: | ||
template <typename, typename Ps, auto... Cs> | ||
friend class DelegateChainBuilder; | ||
|
||
DelegateChainBuilder() = default; | ||
|
||
template <typename D> | ||
DelegateChainBuilder(const D& /*unused*/) {} | ||
|
||
template <auto Callable, typename payload_type> | ||
constexpr auto add(payload_type payload) | ||
requires(std::is_pointer_v<payload_type>) | ||
{ | ||
std::tuple<payload_types..., payload_type> payloads = | ||
std::tuple_cat(m_payloads, std::make_tuple(payload)); | ||
|
||
return DelegateChainBuilder<R(callable_args...), | ||
TypeList<payload_types..., payload_type>, | ||
callables..., Callable>{payloads}; | ||
} | ||
|
||
template <auto Callable> | ||
constexpr auto add() { | ||
std::tuple<payload_types..., std::nullptr_t> payloads = | ||
std::tuple_cat(m_payloads, std::make_tuple(std::nullptr_t{})); | ||
|
||
return DelegateChainBuilder<R(callable_args...), | ||
TypeList<payload_types..., std::nullptr_t>, | ||
callables..., Callable>{payloads}; | ||
} | ||
|
||
delegate_type build() | ||
requires(sizeof...(callables) > 0) | ||
{ | ||
auto block = std::make_unique<const DispatchBlock>(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<const DispatchBlock>(m_payloads); | ||
delegate.template connect<&DispatchBlock::dispatch>(std::move(block)); | ||
} | ||
|
||
void store(Delegate<R(callable_args...)>& delegate) | ||
requires(sizeof...(callables) == 1) | ||
{ | ||
constexpr auto callable = | ||
DispatchBlock::template findCallable<0, 0, callables...>(); | ||
delegate.template connect<callable>(std::get<0>(m_payloads)); | ||
} | ||
|
||
private: | ||
DelegateChainBuilder(std::tuple<payload_types...> payloads) | ||
: m_payloads(payloads) {} | ||
|
||
struct DispatchBlock { | ||
template <std::size_t I, std::size_t J, auto head, auto... tail> | ||
static constexpr auto findCallable() { | ||
if constexpr (I == J) { | ||
return head; | ||
} else { | ||
return findCallable<I, J + 1, tail...>(); | ||
} | ||
} | ||
|
||
template <std::size_t I = 0, typename result_ptr> | ||
static constexpr auto invoke(result_ptr result, const tuple_type* payloads, | ||
callable_args... args) { | ||
const auto& callable = findCallable<I, 0, callables...>(); | ||
|
||
if constexpr (!std::is_same_v<std::tuple_element_t<I, tuple_type>, | ||
std::nullptr_t>) { | ||
auto payload = std::get<I>(*payloads); | ||
|
||
if constexpr (!std::is_same_v<result_ptr, std::nullptr_t>) { | ||
std::get<I>(*result) = std::invoke( | ||
callable, payload, std::forward<callable_args>(args)...); | ||
} else { | ||
std::invoke(callable, payload, std::forward<callable_args>(args)...); | ||
} | ||
|
||
} else { | ||
if constexpr (!std::is_same_v<result_ptr, std::nullptr_t>) { | ||
std::get<I>(*result) = | ||
std::invoke(callable, std::forward<callable_args>(args)...); | ||
} else { | ||
std::invoke(callable, std::forward<callable_args>(args)...); | ||
} | ||
} | ||
|
||
if constexpr (I < sizeof...(payload_types) - 1) { | ||
invoke<I + 1>(result, payloads, std::forward<callable_args>(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<R, void>) { | ||
invoke(nullptr, &m_payloads, std::forward<callable_args>(args)...); | ||
} else { | ||
static_assert( | ||
std::is_same_v<R, void> || std::is_default_constructible_v<R>, | ||
"Delegate chain return type must be void or default constructible"); | ||
return_type result{}; | ||
invoke(&result, &m_payloads, std::forward<callable_args>(args)...); | ||
return result; | ||
} | ||
} | ||
}; | ||
|
||
private: | ||
tuple_type m_payloads{}; | ||
}; | ||
|
||
template <typename D> | ||
DelegateChainBuilder(const D& /*unused*/) | ||
-> DelegateChainBuilder<typename D::signature_type>; | ||
|
||
} // namespace Acts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
Tests/UnitTests/Core/Utilities/DelegateChainBuilderTests.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <boost/test/unit_test.hpp> | ||
#include <boost/test/unit_test_suite.hpp> | ||
|
||
#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<void(int &)> chain = DelegateChainBuilder<void(int &)>{} | ||
.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<void(int &)>{} | ||
.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<void(int &)> nonOwning; | ||
|
||
// In case of a single callable, we can store it in a non-owning delegate | ||
DelegateChainBuilder<void(int &)>{}.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<std::array<int, 4>(), void, DelegateType::Owning> chain = | ||
DelegateChainBuilder<int()>{} | ||
.add<&GetInt::get>(&g1) | ||
.add<&getSix>() | ||
.add<&GetInt::get>(&g2) | ||
.add<&GetInt::get>(&g3) | ||
.build(); | ||
|
||
auto results = chain(); | ||
std::vector<int> expected = {1, 6, 2, 3}; | ||
BOOST_CHECK_EQUAL_COLLECTIONS(results.begin(), results.end(), | ||
expected.begin(), expected.end()); | ||
|
||
Delegate<std::array<int, 3>(), void, DelegateType::Owning> delegate; | ||
DelegateChainBuilder<int()>{} | ||
.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() |