review: delegate/sigh

This commit is contained in:
Michele Caini
2019-02-10 23:07:17 +01:00
parent 54129cecc2
commit 9fbdbc1844
5 changed files with 220 additions and 329 deletions

View File

@@ -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

View File

@@ -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 *>()))>>;
}

View File

@@ -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`.

View File

@@ -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);
}

View File

@@ -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());