Skip to content

Commit

Permalink
feat: Delegate construction with tag type (#2668)
Browse files Browse the repository at this point in the history
This uses an empty tag struct to add constructors from free and member function pointers. The member function pointer constructor right now only supports non-owning delegates.

This also fixes a potential memory leak when moving a unique_ptr into a non-owning delegate. I don't think this was used anywhere, however.
  • Loading branch information
paulgessinger authored Nov 15, 2023
1 parent bd74d01 commit 1db9cee
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 15 deletions.
42 changes: 32 additions & 10 deletions Core/include/Acts/Utilities/Delegate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ namespace Acts {
/// Ownership enum for @c Delegate
enum class DelegateType { Owning, NonOwning };

template <auto C>
struct DelegateFuncTag {
explicit constexpr DelegateFuncTag() = default;
};

// Specialization needed for defaulting ownership and for R(Args...) syntax
template <typename, typename H = void, DelegateType = DelegateType::NonOwning>
class Delegate;
Expand Down Expand Up @@ -91,6 +96,26 @@ class Delegate<R(Args...), H, O> {
connect(callable);
}

/// Constructor with a compile-time free function pointer
/// @tparam Callable The compile-time free function pointer
/// @note @c DelegateFuncTag is used to communicate the callable type
template <auto Callable>
Delegate(DelegateFuncTag<Callable> /*tag*/) {
connect<Callable>();
}

/// Constructor with a compile-time member function pointer and instance
/// @tparam Callable The compile-time member function pointer
/// @tparam Type The type of the instance the member function should be called on
/// @param instance The instance on which the member function pointer should be called on
/// @note @c Delegate does not assume owner ship over @p instance.
/// @note @c DelegateFuncTag is used to communicate the callable type
template <auto Callable, typename Type, DelegateType T = kOwnership,
typename = std::enable_if_t<T == DelegateType::NonOwning>>
Delegate(DelegateFuncTag<Callable> /*tag*/, const Type *instance) {
connect<Callable>(instance);
}

/// Constructor from rvalue reference is deleted, should catch construction
/// with temporary objects and thus invalid pointers
template <typename Callable, typename = isNoFunPtr<Callable>>
Expand Down Expand Up @@ -195,23 +220,20 @@ class Delegate<R(Args...), H, O> {
/// @tparam Type The type of the instance the member function should be called on
/// @param instance The instance on which the member function pointer should be called on
/// @note @c Delegate assumes owner ship over @p instance.
template <auto Callable, typename Type>
template <auto Callable, typename Type, DelegateType T = kOwnership,
typename = std::enable_if_t<T == DelegateType::Owning>>
void connect(std::unique_ptr<const Type> instance) {
using member_ptr_type = return_type (Type::*)(Args...) const;
static_assert(Concepts::is_detected<isSignatureCompatible, member_ptr_type,
decltype(Callable)>::value,
"Callable given does not correspond exactly to required call "
"signature");

if constexpr (kOwnership == DelegateType::Owning) {
m_payload.payload = std::unique_ptr<const holder_type, deleter_type>(
instance.release(), [](const holder_type *payload) {
const auto *concretePayload = static_cast<const Type *>(payload);
delete concretePayload;
});
} else {
m_payload.payload = instance.release();
}
m_payload.payload = std::unique_ptr<const holder_type, deleter_type>(
instance.release(), [](const holder_type *payload) {
const auto *concretePayload = static_cast<const Type *>(payload);
delete concretePayload;
});

m_function = [](const holder_type *payload, Args... args) -> return_type {
assert(payload != nullptr && "Payload is required, but not set");
Expand Down
23 changes: 18 additions & 5 deletions Tests/UnitTests/Core/Utilities/DelegateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ BOOST_AUTO_TEST_CASE(ConnectRuntime) {
}
}

BOOST_AUTO_TEST_CASE(ConnectConstructFuncPtr) {
Delegate<int(int, int)> add{DelegateFuncTag<&sumImpl>{}};
BOOST_CHECK(add);
BOOST_CHECK(add.connected());
BOOST_CHECK_EQUAL(add(4, 4), 8);

Subtractor s{18};
Delegate<int(int)> sub{DelegateFuncTag<&Subtractor::execute>{}, &s};

BOOST_CHECK(sub);
BOOST_CHECK(sub.connected());

BOOST_CHECK_EQUAL(sub(7), 7 - 18);
}

void modify(int& v, int a) {
v = a;
}
Expand Down Expand Up @@ -223,11 +238,9 @@ BOOST_AUTO_TEST_CASE(OwningDelegateTest) {
{
auto s = std::make_unique<const SignatureTest>();
Delegate<void(int&, int)> d;
d.connect<&SignatureTest::modify>(std::move(s));

int v = 0;
d(v, 42);
BOOST_CHECK_EQUAL(v, 42);
(void)d;
// This should not compile, as it would be a memory leak
// d.connect<&SignatureTest::modify>(std::move(s));
}

{
Expand Down

0 comments on commit 1db9cee

Please sign in to comment.