I have a template class with two parameters:
template<class TEvent, class TData>
class EventPool {
public:
using EventObserver = std::function<void(const TData &)>;
using EventData = TData;
using EventType = TEvent;
public:
EventObserverId observe(TEvent event, EventObserver &&observer) { ... }
void deleteObserver(EventObserverId observerId) { ... }
void dispatch(TEvent event, const TData &data) { ... }
};
I also have another container class that stores many pools:
class EventSystem {
using CollisionPool = EventPool<CollisionEvent, CollisionData>;
using MouseButtonPool = EventPool<MouseButtonEvent, MouseButtonData>;
using KeyboardPool = EventPool<KeyboardButtonEvent, KeyboardButtonData>;
public:
private:
std::tuple<CollisionPool, MouseButtonPool, KeyboardPool> mPools;
};
Now, I want to create a template based function that automatically infers the correct pool and calls the functions inside the pool. For example:
template<class TEvent>
EventObserverId observe(TEvent event, DeriveThePoolHere::EventObserver &&observer) {
auto &pool = getPoolByFromEventType<TEvent>();
return pool.observe(event, observer);
}
template<class TEvent>
void deleteObserver(TEvent event, EventObserverId observerId) {
auto &pool = getPoolFromEevntType<TEvent>();
pool.deleteObserver(observerId);
}
void dispatch(TEvent event, const DeriveThePoolHere::EventData &data) {
auto &pool = getPoolFromEventType<TEvent>();
pool.dispatch(event, data);
}
If I can somehow create a type map that maps an event to the event data, I can solve this problem but I am not sure how to do this in C .
CodePudding user response:
With help of if constexpr
and std::apply
(c 17 needed)
Maybe like this:
template<typename TPool, typename TEvent, typename TData>
void dispatchIfMatch(TPool& pool, TEvent event, const TData& data) {
if constexpr(std::is_same<TEvent, typename TPool::EventType>::value) {
pool.dispatch(event, data);
}
}
template<typename TEvent, typename TData>
void dispatch(EventSystem& esys, TEvent event, const TData& data) {
std::apply(
[&](auto&&... args) {
((dispatchIfMatch(args, event, data)), ...);
}, esys.mPools);
}
test it here: http://coliru.stacked-crooked.com/a/2c2231c860d8023c
CodePudding user response:
I was able to solve this issue using SFINAE (is that what it is called?). I created an empty struct and specialized it with different events:
namespace detail {
template<class TEvent>
struct GetEventData {};
template<>
struct GetEventData<CollisionEvent> {
using Data = CollisionData;
};
template<>
struct GetEventData<MouseButtonEvent> {
using Data = MouseButtonData;
};
}
In order to make my life easier, I created another alias inside my class (note that this type of specialization cannot be inside the class):
class EventSystem {
template<class TEvent>
using GetEventPool = EventPool<TEvent, detail::GetEventData<TEvent>;
public:
// explained later
private:
std::tuple<GetEventPool<CollisionEvent>, GetEventPool<MouseButtonEvent>> mPools;
};
Since GetEventPool
is a type alias, I have type aliases inside EventPool
, and tuple item can be retrieved by type, I was able to combine these three things together:
template<class TEvent>
EventObserverId observe(TEvent event, typename GetEventPool<TEvent>::EventObserver &&observer) {
using CurrentPool = GetEventPool<TEvent>;
return std::get<CurrentPool>(mPools).observe(event, observer);
}
template<class TEvent>
void deleteObserver(TEvent event, typename EventObserverId observerId) {
using CurrentPool = GetEventPool<TEvent>;
std::get<CurrentPool>(mPools).deleteObserver(observerId);
}
template<class TEvent>
void dispatch(TEvent event, const typename GetEventPool<TEvent>::EventData &data) {
using CurrentPool = GetEventPool<TEvent>;
std::get<CurrentPool>(mPools).dispatch(event, data);
}
This works very well and also shows compiler error if incorrect event or event type is being passed. There is going to be some redundancy in terms of data types; so, one thing that I am still trying to do is simplify the tuple somehow:
PoolTuple<CollisionEvent, MouseButtonEvent, ...> mPools;