review: delegate/sigh
This commit is contained in:
224
docs/signal.md
224
docs/signal.md
@@ -6,9 +6,8 @@
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Signals](#signals)
|
||||
* [Delegate](#delegate)
|
||||
* [Currying and free functions](#currying-and-free-functions)
|
||||
* [Signals](#signals)
|
||||
* [Event dispatcher](#event-dispatcher)
|
||||
* [Event emitter](#event-emitter)
|
||||
<!--
|
||||
@@ -33,6 +32,102 @@ In case that the flexibility and potential of an `std::function` are not
|
||||
required or where you are looking for something different, `EnTT` offers a full
|
||||
set of classes to solve completely different problems.
|
||||
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as a general purpose invoker with no memory overhead for
|
||||
free functions and members provided along with an instance on which to invoke
|
||||
them.<br/>
|
||||
It does not claim to be a drop-in replacement for an `std::function`, so do not
|
||||
expect to use it whenever an `std::function` fits well. However, it can be used
|
||||
to send opaque delegates around to be used to invoke functions as needed.
|
||||
|
||||
The interface is trivial. It offers a default constructor to create empty
|
||||
delegates:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> delegate{};
|
||||
```
|
||||
|
||||
All what is needed to create an instance is to specify the type of the function
|
||||
the delegate will _contain_, that is the signature of the free function or the
|
||||
member function one wants to assign to it.
|
||||
|
||||
Attempting to use an empty delegate by invoking its function call operator
|
||||
results in undefined behavior or most likely a crash. Before to use a delegate,
|
||||
it must be initialized.<br/>
|
||||
There exists a bunch of overloads of the `connect` member function to do that.
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct my_struct {
|
||||
int f(const int &i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
my_struct instance;
|
||||
delegate.connect<&my_struct::f>(&instance);
|
||||
```
|
||||
|
||||
The delegate class accepts also data members, if needed. In this case, the
|
||||
function type of the delegate is such that the parameter list is empty and the
|
||||
value of the data member is at least convertible to the return type.<br/>
|
||||
Functions having type equivalent to `void(T *, args...)` are accepted as well.
|
||||
In this case, `T *` is considered a payload and the function will receive it
|
||||
back every time it's invoked. In other terms, this works just fine with the
|
||||
above definition:
|
||||
|
||||
```cpp
|
||||
void g(const char *c, int i) { /* ... */ }
|
||||
const char c = 'c';
|
||||
|
||||
delegate.connect<&g>(&c);
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
The function `g` will be invoked with a pointer to `c` and `42`. However, the
|
||||
function type of the delegate is still `void(int)`, mainly because this is also
|
||||
the signature of its function call operator.
|
||||
|
||||
To create and initialize a delegate at once, there are also some specialized
|
||||
constructors. Because of the rules of the language, the listener is provided by
|
||||
means of the `entt::connect_arg` variable template:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> func{entt::connect_arg<&f>};
|
||||
```
|
||||
|
||||
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
|
||||
exists a `reset` member function to use to clear a delegate.<br/>
|
||||
To know if a delegate is empty, it can be used explicitly in every conditional
|
||||
statement:
|
||||
|
||||
```cpp
|
||||
if(delegate) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
usual:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
As shown above, listeners do not have to strictly follow the signature of the
|
||||
delegate. As long as a listener can be invoked with the given arguments to yield
|
||||
a result that is convertible to the given result type, everything works just
|
||||
fine.
|
||||
|
||||
Probably too much small and pretty poor of functionalities, but the delegate
|
||||
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||
within the library.
|
||||
|
||||
# Signals
|
||||
|
||||
Signal handlers work with naked pointers, function pointers and pointers to
|
||||
@@ -143,131 +238,6 @@ to which the return type of the listeners can be converted. Moreover, it has to
|
||||
return a boolean value that is false to stop collecting data, true otherwise.
|
||||
This way one can avoid calling all the listeners in case it isn't necessary.
|
||||
|
||||
# Delegate
|
||||
|
||||
A delegate can be used as general purpose invoker with no memory overhead for
|
||||
free functions, members provided along with an instance on which to invoke them,
|
||||
lambdas and functors in general.<br/>
|
||||
It does not claim to be a drop-in replacement for an `std::function`, so do not
|
||||
expect to use it whenever an `std::function` fits well. However, it can be used
|
||||
to send opaque delegates around to be used to invoke functions as needed.
|
||||
|
||||
The interface is trivial. It offers a default constructor to create empty
|
||||
delegates:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> delegate{};
|
||||
```
|
||||
|
||||
All what is needed to create an instance is to specify the type of the function
|
||||
the delegate will _contain_, that is the signature of the free function or the
|
||||
member function one wants to assign to it.
|
||||
|
||||
Attempting to use an empty delegate by invoking its function call operator
|
||||
results in undefined behavior or most likely a crash. Before to use a delegate,
|
||||
it must be initialized.<br/>
|
||||
There exists a bunch of overloads of the `connect` member function to do that.
|
||||
As an example of use:
|
||||
|
||||
```cpp
|
||||
int f(int i) { return i; }
|
||||
|
||||
struct my_struct {
|
||||
int f(const int &i) { return i }
|
||||
};
|
||||
|
||||
// bind a free function to the delegate
|
||||
delegate.connect<&f>();
|
||||
|
||||
// bind a member function to the delegate
|
||||
my_struct instance;
|
||||
delegate.connect<&my_struct::f>(&instance);
|
||||
```
|
||||
|
||||
The delegate class accepts also data members if required. In this case, the
|
||||
function type of the delegate is such that the parameter list is empty and the
|
||||
value of the data member is at least convertible to the return type.<br/>
|
||||
Moreover, it can work with invokable objects in general (lambdas or functors),
|
||||
as long as they are trivially destructible and the size fits the one of
|
||||
`void *`. As an example, a lambda that captures a pointer or an integer value
|
||||
can be used with a delegate:
|
||||
|
||||
```cpp
|
||||
delegate.connect([value = int_var](int i) { return value * i; });
|
||||
```
|
||||
|
||||
To create and initialize a delegate at once, there are also some specialized
|
||||
constructors. Because of the rules of the language, the listener is provided by
|
||||
means of the `entt::connect_arg` class template:
|
||||
|
||||
```cpp
|
||||
entt::delegate<int(int)> func{entt::connect_arg<&f>};
|
||||
```
|
||||
|
||||
Aside `connect`, a `disconnect` counterpart isn't provided. Instead, there
|
||||
exists a `reset` member function to use to clear a delegate.<br/>
|
||||
To know if it's empty instead, the delegate can be used explicitly in every
|
||||
conditional statement:
|
||||
|
||||
```cpp
|
||||
if(delegate) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to invoke a delegate, the function call operator is the way to go as
|
||||
usual:
|
||||
|
||||
```cpp
|
||||
auto ret = delegate(42);
|
||||
```
|
||||
|
||||
As shown above, listeners do not have to strictly follow the signature of the
|
||||
delegate. As long as a listener can be invoked with the given arguments to yield
|
||||
a result that is convertible to the given result type, everything works just
|
||||
fine.
|
||||
|
||||
Probably too much small and pretty poor of functionalities, but the delegate
|
||||
class can help in a lot of cases and it has shown that it is worth keeping it
|
||||
within the library.
|
||||
|
||||
## Currying and free functions
|
||||
|
||||
The delegate class comes with a rather obscure feature to be considered as an
|
||||
_advanced mode_, that is its limited support for curried free functions.
|
||||
|
||||
Consider the following definition:
|
||||
|
||||
```cpp
|
||||
entt::delegate<void(int)> delegate{};
|
||||
```
|
||||
|
||||
As shown previously, it accepts functions having type `void(int)`. However, we
|
||||
can do something more in this case, because of how the delegate class is
|
||||
implemented internally (that is something that goes beyond the purposes of this
|
||||
document).<br/>
|
||||
In particular, the delegate accepts also functions having type equivalent to
|
||||
`void(T &, int)`, as long as `sizeof(T)` is lower than or equal to
|
||||
`sizeof(void *)`. The first parameter is stored directly by the delegate class
|
||||
and passed to the connected function when needed.
|
||||
|
||||
In other terms, this works as well with the above definition:
|
||||
|
||||
```cpp
|
||||
void g(char c, int i) { /* ... */ }
|
||||
|
||||
delegate.connect<&g>('c');
|
||||
delegate(42);
|
||||
```
|
||||
|
||||
In this case, the function `g` is invoked with parameters `'c'` and `42`.
|
||||
However, the function type of the delegate is still `void(int)`, mainly because
|
||||
this is also the signature of its function call operator.<br/>
|
||||
When the curried function receives the linked parameter by reference, it can
|
||||
modify it and the new value will be stored in place of the previous one. It's
|
||||
highly discouraged to accept the parameter by reference, unless you know exactly
|
||||
what you're doing. Prefer accepting it by value if possible.
|
||||
|
||||
# Event dispatcher
|
||||
|
||||
The event dispatcher class is designed so as to be used in a loop. It allows
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
@@ -25,8 +26,8 @@ template<typename Ret, typename... Args>
|
||||
auto to_function_pointer(Ret(*)(Args...)) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Ret, typename... Args, typename Type, typename Value>
|
||||
auto to_function_pointer(Ret(*)(Type, Args...), Value) -> Ret(*)(Args...);
|
||||
template<typename Ret, typename... Args, typename Type>
|
||||
auto to_function_pointer(Ret(*)(Type *, Args...), Type *) -> Ret(*)(Args...);
|
||||
|
||||
|
||||
template<typename Class, typename Ret, typename... Args>
|
||||
@@ -73,16 +74,15 @@ class delegate;
|
||||
* in charge of disconnecting instances before deleting them.
|
||||
*
|
||||
* A delegate can be used as general purpose invoker with no memory overhead for
|
||||
* free functions and members provided along with an instance on which to invoke
|
||||
* them. It comes also with limited support for curried functions.
|
||||
* free functions (with or without payload) and members provided along with an
|
||||
* instance on which to invoke them.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
*/
|
||||
template<typename Ret, typename... Args>
|
||||
class delegate<Ret(Args...)> {
|
||||
using storage_type = std::aligned_storage_t<sizeof(void *), alignof(void *)>;
|
||||
using proto_fn_type = Ret(storage_type &, Args...);
|
||||
using proto_fn_type = Ret(const void *, Args...);
|
||||
|
||||
public:
|
||||
/*! @brief Function type of the delegate. */
|
||||
@@ -90,10 +90,8 @@ public:
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
delegate() ENTT_NOEXCEPT
|
||||
: storage{}, fn{nullptr}
|
||||
{
|
||||
new (&storage) void *{nullptr};
|
||||
}
|
||||
: fn{nullptr}, data{nullptr}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and connects a free function to it.
|
||||
@@ -108,33 +106,18 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and connects a member for a given instance
|
||||
* or a curried free function to it.
|
||||
* @tparam Candidate Member or curried free function to connect to the
|
||||
* delegate.
|
||||
* @tparam Type Type of class to which the member belongs or type of value
|
||||
* used for currying.
|
||||
* @param value_or_instance A valid pointer to an instance of class type or
|
||||
* the value to use for currying.
|
||||
* or a free function with payload.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type value_or_instance) ENTT_NOEXCEPT
|
||||
delegate(connect_arg_t<Candidate>, Type *value_or_instance) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect<Candidate>(value_or_instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs a delegate and connects a lambda or a functor to it.
|
||||
* @tparam Invokable Type of lambda or functor to connect.
|
||||
* @param invokable A valid instance of the given type.
|
||||
*/
|
||||
template<typename Invokable>
|
||||
delegate(Invokable invokable) ENTT_NOEXCEPT
|
||||
: delegate{}
|
||||
{
|
||||
connect(std::move(invokable));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a free function to a delegate.
|
||||
* @tparam Function A valid free function pointer.
|
||||
@@ -142,64 +125,45 @@ public:
|
||||
template<auto Function>
|
||||
void connect() ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Function), Args...>);
|
||||
new (&storage) void *{nullptr};
|
||||
data = nullptr;
|
||||
|
||||
fn = [](storage_type &, Args... args) -> Ret {
|
||||
return std::invoke(Function, args...);
|
||||
fn = [](const void *, Args... args) -> Ret {
|
||||
// this allows void(...) to eat return values and avoid errors
|
||||
return Ret(std::invoke(Function, args...));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member for a given instance or a curried free function
|
||||
* to a delegate.
|
||||
* @brief Connects a member function for a given instance or a free function
|
||||
* with payload to a delegate.
|
||||
*
|
||||
* When used to connect a member, the delegate isn't responsible for the
|
||||
* connected object. Users must guarantee that the lifetime of the instance
|
||||
* overcomes the one of the delegate.<br/>
|
||||
* When used to connect a curried free function, the linked value must be
|
||||
* both trivially copyable and trivially destructible, other than such that
|
||||
* its size is lower than or equal to the one of a `void *`. It means that
|
||||
* all the primitive types are accepted as well as pointers. Moreover, the
|
||||
* signature of the free function must be such that the value is the first
|
||||
* argument before the ones used to define the delegate itself.
|
||||
* The delegate isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the delegate.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the delegate itself.
|
||||
*
|
||||
* @tparam Candidate Member or curried free function to connect to the
|
||||
* delegate.
|
||||
* @tparam Type Type of class to which the member belongs or type of value
|
||||
* used for currying.
|
||||
* @param value_or_instance A valid pointer to an instance of class type or
|
||||
* the value to use for currying.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type value_or_instance) ENTT_NOEXCEPT {
|
||||
static_assert(sizeof(Type) <= sizeof(void *));
|
||||
static_assert(std::is_trivially_copyable_v<Type>);
|
||||
static_assert(std::is_trivially_destructible_v<Type>);
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type &, Args...>);
|
||||
new (&storage) Type{value_or_instance};
|
||||
void connect(Type *value_or_instance) ENTT_NOEXCEPT {
|
||||
static_assert(std::is_invocable_r_v<Ret, decltype(Candidate), Type *, Args...>);
|
||||
data = value_or_instance;
|
||||
|
||||
fn = [](storage_type &storage, Args... args) -> Ret {
|
||||
Type &value_or_instance = *reinterpret_cast<Type *>(&storage);
|
||||
return std::invoke(Candidate, value_or_instance, args...);
|
||||
};
|
||||
}
|
||||
fn = [](const void *data, Args... args) -> Ret {
|
||||
Type *value_or_instance = nullptr;
|
||||
|
||||
/**
|
||||
* @brief Connects a lambda or a functor to a delegate.
|
||||
* @tparam Invokable Type of lambda or functor to connect.
|
||||
* @param invokable A valid instance of the given type.
|
||||
*/
|
||||
template<typename Invokable>
|
||||
void connect(Invokable invokable) ENTT_NOEXCEPT {
|
||||
static_assert(sizeof(Invokable) <= sizeof(void *));
|
||||
static_assert(std::is_class_v<Invokable>);
|
||||
static_assert(std::is_trivially_destructible_v<Invokable>);
|
||||
static_assert(std::is_invocable_r_v<Ret, Invokable, Args...>);
|
||||
new (&storage) Invokable{std::move(invokable)};
|
||||
if constexpr(std::is_const_v<Type>) {
|
||||
value_or_instance = static_cast<Type *>(data);
|
||||
} else {
|
||||
value_or_instance = static_cast<Type *>(const_cast<void *>(data));
|
||||
}
|
||||
|
||||
fn = [](storage_type &storage, Args... args) -> Ret {
|
||||
Invokable &invokable = *reinterpret_cast<Invokable *>(&storage);
|
||||
return std::invoke(invokable, args...);
|
||||
// this allows void(...) to eat return values and avoid errors
|
||||
return Ret(std::invoke(Candidate, value_or_instance, args...));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -209,21 +173,16 @@ public:
|
||||
* After a reset, a delegate cannot be invoked anymore.
|
||||
*/
|
||||
void reset() ENTT_NOEXCEPT {
|
||||
new (&storage) void *{nullptr};
|
||||
fn = nullptr;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the instance linked to a delegate, if any.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use an instance returned by a delegate that doesn't contain
|
||||
* a pointer to a member results in undefined behavior.
|
||||
*
|
||||
* @return An opaque pointer to the instance linked to the delegate, if any.
|
||||
*/
|
||||
const void * instance() const ENTT_NOEXCEPT {
|
||||
return *reinterpret_cast<const void **>(&storage);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +201,7 @@ public:
|
||||
*/
|
||||
Ret operator()(Args... args) const {
|
||||
assert(fn);
|
||||
return fn(storage, args...);
|
||||
return fn(data, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,8 +216,8 @@ public:
|
||||
/**
|
||||
* @brief Checks if the connected functions differ.
|
||||
*
|
||||
* In case of members, the instances connected to the delegate are not
|
||||
* verified by this operator. Use the `instance` member function instead.
|
||||
* Instances connected to delegates are ignored by this operator. Use the
|
||||
* `instance` member function instead.
|
||||
*
|
||||
* @param other Delegate with which to compare.
|
||||
* @return False if the connected functions differ, true otherwise.
|
||||
@@ -268,16 +227,16 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
mutable storage_type storage;
|
||||
proto_fn_type *fn;
|
||||
const void *data;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Checks if the connected functions differ.
|
||||
*
|
||||
* In case of members, the instances connected to the delegate are not verified
|
||||
* by this operator. Use the `instance` member function instead.
|
||||
* Instances connected to delegates are ignored by this operator. Use the
|
||||
* `instance` member function instead.
|
||||
*
|
||||
* @tparam Ret Return type of a function type.
|
||||
* @tparam Args Types of arguments of a function type.
|
||||
@@ -308,29 +267,14 @@ delegate(connect_arg_t<Function>) ENTT_NOEXCEPT
|
||||
* @brief Deduction guideline.
|
||||
*
|
||||
* It allows to deduce the function type of the delegate directly from a member
|
||||
* or a curried free function provided to the constructor.
|
||||
* or a free function with payload provided to the constructor.
|
||||
*
|
||||
* @tparam Candidate Member or curried free function to connect to the delegate.
|
||||
* @tparam Type Type of class to which the member belongs or type of value used
|
||||
* for currying.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
*/
|
||||
template<auto Candidate, typename Type>
|
||||
delegate(connect_arg_t<Candidate>, Type) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type>()))>>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Deduction guideline.
|
||||
*
|
||||
* It allows to deduce the function type of the delegate directly from a lambda
|
||||
* or a functor provided to the constructor.
|
||||
*
|
||||
* @tparam Invokable Type of lambda or functor to connect.
|
||||
* @param invokable A valid instance of the given type.
|
||||
*/
|
||||
template<typename Invokable>
|
||||
delegate(Invokable invokable) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(&Invokable::operator(), &invokable))>>;
|
||||
delegate(connect_arg_t<Candidate>, Type *) ENTT_NOEXCEPT
|
||||
-> delegate<std::remove_pointer_t<decltype(internal::to_function_pointer(Candidate, std::declval<Type *>()))>>;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -129,6 +129,9 @@ class sink<Ret(Args...)> {
|
||||
template<typename, typename>
|
||||
friend struct sigh;
|
||||
|
||||
template<typename Type>
|
||||
Type * payload_type(Ret(*)(Type *, Args...));
|
||||
|
||||
sink(std::vector<delegate<Ret(Args...)>> *calls) ENTT_NOEXCEPT
|
||||
: calls{calls}
|
||||
{}
|
||||
@@ -159,23 +162,31 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Connects a member function to a signal.
|
||||
* @brief Connects a member function or a free function with payload to a
|
||||
* signal.
|
||||
*
|
||||
* The signal isn't responsible for the connected object. Users must
|
||||
* guarantee that the lifetime of the instance overcomes the one of the
|
||||
* delegate. On the other side, the signal handler performs checks to avoid
|
||||
* multiple connections for the same member function of a given instance.
|
||||
* The signal isn't responsible for the connected object or the payload.
|
||||
* Users must always guarantee that the lifetime of the instance overcomes
|
||||
* the one of the delegate. On the other side, the signal handler performs
|
||||
* checks to avoid multiple connections for the same function.<br/>
|
||||
* When used to connect a free function with payload, its signature must be
|
||||
* such that the instance is the first argument before the ones used to
|
||||
* define the delegate itself.
|
||||
*
|
||||
* @tparam Member Member function to connect to the signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
* @tparam Candidate Member or free function to connect to the delegate.
|
||||
* @tparam Type Type of class or type of payload.
|
||||
* @param value_or_instance A valid pointer that fits the purpose.
|
||||
*/
|
||||
template<auto Member, typename Class>
|
||||
void connect(Class *instance) {
|
||||
static_assert(std::is_member_function_pointer_v<decltype(Member)>);
|
||||
disconnect<Member>(instance);
|
||||
template<auto Candidate, typename Type>
|
||||
void connect(Type *value_or_instance) {
|
||||
if constexpr(std::is_member_function_pointer_v<decltype(Candidate)>) {
|
||||
disconnect<Candidate>(value_or_instance);
|
||||
} else {
|
||||
disconnect<Candidate>();
|
||||
}
|
||||
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Member>(instance);
|
||||
delegate.template connect<Candidate>(value_or_instance);
|
||||
calls->emplace_back(std::move(delegate));
|
||||
}
|
||||
|
||||
@@ -186,12 +197,19 @@ public:
|
||||
template<auto Function>
|
||||
void disconnect() {
|
||||
delegate<Ret(Args...)> delegate{};
|
||||
delegate.template connect<Function>();
|
||||
|
||||
if constexpr(std::is_invocable_r_v<Ret, decltype(Function), Args...>) {
|
||||
delegate.template connect<Function>();
|
||||
} else {
|
||||
decltype(payload_type(Function)) payload = nullptr;
|
||||
delegate.template connect<Function>(payload);
|
||||
}
|
||||
|
||||
calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnects the given member function from a signal.
|
||||
* @brief Disconnects a given member function from a signal.
|
||||
* @tparam Member Member function to disconnect from the signal.
|
||||
* @tparam Class Type of class to which the member function belongs.
|
||||
* @param instance A valid instance of type pointer to `Class`.
|
||||
|
||||
@@ -5,13 +5,8 @@ int delegate_function(const int &i) {
|
||||
return i*i;
|
||||
}
|
||||
|
||||
int curried_function_by_value(int i, int j) {
|
||||
return i+j;
|
||||
}
|
||||
|
||||
int curried_function_by_ref(int &value) {
|
||||
value *= 2;
|
||||
return value;
|
||||
int curried_function(const int *i, int j) {
|
||||
return *i+j;
|
||||
}
|
||||
|
||||
struct delegate_functor {
|
||||
@@ -81,46 +76,18 @@ TEST(Delegate, DataMembers) {
|
||||
ASSERT_EQ(delegate(), 42);
|
||||
}
|
||||
|
||||
TEST(Delegate, LambdaAndFunctor) {
|
||||
entt::delegate<int(int)> non_capturing_delegate;
|
||||
entt::delegate<int(int)> capturing_delegate;
|
||||
entt::delegate<int(int)> functor_delegate;
|
||||
const int value = 5;
|
||||
|
||||
non_capturing_delegate.connect([](int v) { return v*v; });
|
||||
capturing_delegate.connect([val = &value](int v) { return v * *val; });
|
||||
functor_delegate.connect(delegate_functor{});
|
||||
|
||||
ASSERT_EQ(non_capturing_delegate(3), 9);
|
||||
ASSERT_EQ(capturing_delegate(3), 15);
|
||||
ASSERT_EQ(functor_delegate(3), 6);
|
||||
}
|
||||
|
||||
TEST(Delegate, Comparison) {
|
||||
entt::delegate<int(int)> lhs;
|
||||
entt::delegate<int(int)> rhs;
|
||||
delegate_functor functor;
|
||||
delegate_functor other;
|
||||
const int value = 0;
|
||||
|
||||
ASSERT_EQ(lhs, entt::delegate<int(int)>{});
|
||||
ASSERT_FALSE(lhs != rhs);
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
|
||||
lhs.connect(functor);
|
||||
|
||||
ASSERT_EQ(lhs, entt::delegate<int(int)>{functor});
|
||||
ASSERT_TRUE(lhs != rhs);
|
||||
ASSERT_FALSE(lhs == rhs);
|
||||
ASSERT_NE(lhs, rhs);
|
||||
|
||||
rhs.connect<>(functor);
|
||||
|
||||
ASSERT_EQ(rhs, entt::delegate<int(int)>{functor});
|
||||
ASSERT_FALSE(lhs != rhs);
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
|
||||
lhs.connect<&delegate_function>();
|
||||
|
||||
ASSERT_EQ(lhs, entt::delegate<int(int)>{entt::connect_arg<&delegate_function>});
|
||||
@@ -135,16 +102,16 @@ TEST(Delegate, Comparison) {
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
|
||||
lhs.connect<&curried_function_by_value>(0);
|
||||
lhs.connect<&curried_function>(&value);
|
||||
|
||||
ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
|
||||
ASSERT_EQ(lhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function>, &value}));
|
||||
ASSERT_TRUE(lhs != rhs);
|
||||
ASSERT_FALSE(lhs == rhs);
|
||||
ASSERT_NE(lhs, rhs);
|
||||
|
||||
rhs.connect<&curried_function_by_value>(0);
|
||||
rhs.connect<&curried_function>(&value);
|
||||
|
||||
ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function_by_value>, 0}));
|
||||
ASSERT_EQ(rhs, (entt::delegate<int(int)>{entt::connect_arg<&curried_function>, &value}));
|
||||
ASSERT_FALSE(lhs != rhs);
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
@@ -180,7 +147,7 @@ TEST(Delegate, Comparison) {
|
||||
|
||||
rhs.reset();
|
||||
|
||||
ASSERT_EQ(lhs, (entt::delegate<int(int)>{}));
|
||||
ASSERT_EQ(rhs, (entt::delegate<int(int)>{}));
|
||||
ASSERT_FALSE(lhs != rhs);
|
||||
ASSERT_TRUE(lhs == rhs);
|
||||
ASSERT_EQ(lhs, rhs);
|
||||
@@ -207,32 +174,24 @@ TEST(Delegate, ConstNonConstNoExcept) {
|
||||
|
||||
TEST(Delegate, DeducedGuidelines) {
|
||||
const_nonconst_noexcept functor;
|
||||
const int value = 0;
|
||||
|
||||
entt::delegate invokable_functor{delegate_functor{}};
|
||||
entt::delegate invokable_lambda{[value = 2](double v) { return v * value; }};
|
||||
entt::delegate func_deduced{entt::connect_arg<&delegate_function>};
|
||||
entt::delegate curried_func_by_ref_deduced{entt::connect_arg<&curried_function_by_ref>, 0};
|
||||
entt::delegate curried_func_by_value_deduced{entt::connect_arg<&curried_function_by_value>, 0};
|
||||
entt::delegate curried_func_deduced{entt::connect_arg<&curried_function>, &value};
|
||||
entt::delegate member_f_deduced{entt::connect_arg<&const_nonconst_noexcept::f>, &functor};
|
||||
entt::delegate member_g_deduced{entt::connect_arg<&const_nonconst_noexcept::g>, &functor};
|
||||
entt::delegate member_h_deduced{entt::connect_arg<&const_nonconst_noexcept::h>, &functor};
|
||||
entt::delegate member_i_deduced{entt::connect_arg<&const_nonconst_noexcept::i>, &functor};
|
||||
|
||||
static_assert(std::is_same_v<typename decltype(invokable_functor)::function_type, int(int)>);
|
||||
static_assert(std::is_same_v<typename decltype(invokable_lambda)::function_type, double(double)>);
|
||||
static_assert(std::is_same_v<typename decltype(func_deduced)::function_type, int(const int &)>);
|
||||
static_assert(std::is_same_v<typename decltype(curried_func_by_ref_deduced)::function_type, int()>);
|
||||
static_assert(std::is_same_v<typename decltype(curried_func_by_value_deduced)::function_type, int(int)>);
|
||||
static_assert(std::is_same_v<typename decltype(curried_func_deduced)::function_type, int(int)>);
|
||||
static_assert(std::is_same_v<typename decltype(member_f_deduced)::function_type, void()>);
|
||||
static_assert(std::is_same_v<typename decltype(member_g_deduced)::function_type, void()>);
|
||||
static_assert(std::is_same_v<typename decltype(member_h_deduced)::function_type, void()>);
|
||||
static_assert(std::is_same_v<typename decltype(member_i_deduced)::function_type, void()>);
|
||||
|
||||
ASSERT_TRUE(invokable_functor);
|
||||
ASSERT_TRUE(invokable_lambda);
|
||||
ASSERT_TRUE(func_deduced);
|
||||
ASSERT_TRUE(curried_func_by_ref_deduced);
|
||||
ASSERT_TRUE(curried_func_by_value_deduced);
|
||||
ASSERT_TRUE(curried_func_deduced);
|
||||
ASSERT_TRUE(member_f_deduced);
|
||||
ASSERT_TRUE(member_g_deduced);
|
||||
ASSERT_TRUE(member_h_deduced);
|
||||
@@ -256,40 +215,27 @@ TEST(Delegate, ConstInstance) {
|
||||
ASSERT_EQ(delegate, entt::delegate<int(int)>{});
|
||||
}
|
||||
|
||||
TEST(Delegate, CurriedFunctionByValue) {
|
||||
TEST(Delegate, CurriedFunction) {
|
||||
entt::delegate<int(int)> delegate;
|
||||
delegate.connect<&curried_function_by_value>(3);
|
||||
const auto value = 3;
|
||||
|
||||
delegate.connect<&curried_function>(&value);
|
||||
|
||||
ASSERT_TRUE(delegate);
|
||||
ASSERT_EQ(delegate(1), 4);
|
||||
}
|
||||
|
||||
TEST(Delegate, CurriedFunctionByRef) {
|
||||
entt::delegate<int()> delegate;
|
||||
delegate.connect<&curried_function_by_ref>(2);
|
||||
|
||||
ASSERT_TRUE(delegate);
|
||||
ASSERT_EQ(delegate(), 4);
|
||||
ASSERT_EQ(delegate(), 8);
|
||||
}
|
||||
|
||||
TEST(Delegate, Constructors) {
|
||||
delegate_functor functor;
|
||||
const auto value = 2;
|
||||
|
||||
entt::delegate<int(int)> empty{};
|
||||
entt::delegate<int(int)> invokable{functor};
|
||||
entt::delegate<int(int)> lambda{[value = 5](int v) { return value * v; }};
|
||||
entt::delegate<int(int)> func{entt::connect_arg<&delegate_function>};
|
||||
entt::delegate<int(int)> curr{entt::connect_arg<&curried_function_by_value>, 2};
|
||||
entt::delegate<int(int)> curr{entt::connect_arg<&curried_function>, &value};
|
||||
entt::delegate<int(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
|
||||
|
||||
ASSERT_FALSE(empty);
|
||||
|
||||
ASSERT_TRUE(invokable);
|
||||
ASSERT_EQ(6, invokable(3));
|
||||
|
||||
ASSERT_TRUE(lambda);
|
||||
ASSERT_EQ(15, lambda(3));
|
||||
|
||||
ASSERT_TRUE(func);
|
||||
ASSERT_EQ(9, func(3));
|
||||
|
||||
@@ -299,3 +245,15 @@ TEST(Delegate, Constructors) {
|
||||
ASSERT_TRUE(member);
|
||||
ASSERT_EQ(6, member(3));
|
||||
}
|
||||
|
||||
TEST(Delegate, VoidVsNonVoidReturnType) {
|
||||
delegate_functor functor;
|
||||
|
||||
entt::delegate<void(int)> func{entt::connect_arg<&delegate_function>};
|
||||
entt::delegate<void(int)> member{entt::connect_arg<&delegate_functor::operator()>, &functor};
|
||||
entt::delegate<void(int)> cmember{entt::connect_arg<&delegate_functor::identity>, &std::as_const(functor)};
|
||||
|
||||
ASSERT_TRUE(func);
|
||||
ASSERT_TRUE(member);
|
||||
ASSERT_TRUE(cmember);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ struct test_collect_all {
|
||||
template<>
|
||||
struct test_collect_all<void> {
|
||||
std::vector<int> vec{};
|
||||
static void h() {}
|
||||
static void h(const void *) {}
|
||||
bool operator()() noexcept {
|
||||
return true;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ struct test_collect_all<void> {
|
||||
template<typename Ret>
|
||||
struct test_collect_first {
|
||||
std::vector<Ret> vec{};
|
||||
static int f() { return 42; }
|
||||
static int f(const void *) { return 42; }
|
||||
bool operator()(Ret r) noexcept {
|
||||
vec.push_back(r);
|
||||
return false;
|
||||
@@ -151,8 +151,9 @@ TEST(SigH, Members) {
|
||||
|
||||
TEST(SigH, Collector) {
|
||||
entt::sigh<void(), test_collect_all<void>> sigh_void;
|
||||
const void *fake_instance = nullptr;
|
||||
|
||||
sigh_void.sink().connect<&test_collect_all<void>::h>();
|
||||
sigh_void.sink().connect<&test_collect_all<void>::h>(fake_instance);
|
||||
auto collector_void = sigh_void.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_void.empty());
|
||||
@@ -173,8 +174,8 @@ TEST(SigH, Collector) {
|
||||
|
||||
entt::sigh<int(), test_collect_first<int>> sigh_first;
|
||||
|
||||
sigh_first.sink().connect<&test_collect_first<int>::f>();
|
||||
sigh_first.sink().connect<&test_collect_first<int>::f>();
|
||||
sigh_first.sink().connect<&test_collect_first<int>::f>(fake_instance);
|
||||
sigh_first.sink().connect<&test_collect_first<int>::f>(fake_instance);
|
||||
auto collector_first = sigh_first.collect();
|
||||
|
||||
ASSERT_FALSE(sigh_first.empty());
|
||||
|
||||
Reference in New Issue
Block a user