如何从内部别名中推断出模板参数?

5

我有一个带有两个参数的模板类:

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) { ... }
};

我还有另一个容器类,用于存储多个池:

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

现在,我想创建一个基于模板的函数,可以自动推断正确的池并调用池中的函数。例如:

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

如果我能够创建一个类型映射将事件映射到事件数据,我就可以解决这个问题,但我不确定如何在C++中实现。

2个回答

1

借助 if constexprstd::apply(需要 C++17)
可能是这样的:

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

在这里测试它:http://coliru.stacked-crooked.com/a/2c2231c860d8023c


(注:此为原文,无需翻译)

如果传递了一个不存在的事件,会发生什么?它会抛出错误还是只是忽略调用? - Gasim
@Gasim 这将被忽略。 - sp2danny

1

我能够使用SFINAE(这就是它的名称吗?)解决这个问题。我创建了一个空结构体,并针对不同的事件进行了特化:

namespace detail {

template<class TEvent>
struct GetEventData {};

template<>
struct GetEventData<CollisionEvent> {
  using Data = CollisionData;
};

template<>
struct GetEventData<MouseButtonEvent> {
  using Data = MouseButtonData;
};

}

为了让我的生活更加轻松,我在我的类中创建了另一个别名(请注意,这种类型的专业化不能在类内部完成):
class EventSystem {
  template<class TEvent>
  using GetEventPool = EventPool<TEvent, detail::GetEventData<TEvent>;

public:
  // explained later

private:
  std::tuple<GetEventPool<CollisionEvent>, GetEventPool<MouseButtonEvent>> mPools;
};

由于GetEventPool是一个类型别名,我在EventPool中有类型别名,并且可以通过类型检索元组项,因此我能够将这三个东西结合在一起:

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

这个方法非常有效,如果传递了不正确的事件或事件类型,它也会显示编译器错误。在数据类型方面可能会有一些冗余;所以我仍然在努力简化元组:

PoolTuple<CollisionEvent, MouseButtonEvent, ...> mPools;

编辑:为了简化元组声明,我找到了这个有趣的语法:

template<class... TEvents>
using PoolTuple = std::tuple<GetEventPool<TEvent>...>;

PoolTuple<CollisionEvent, MouseButtonEvent, ...> mPools;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接