具有不同参数列表的模板对象的模板静态映射

A template static map of template objects with different parameter lists

本文关键字:静态 映射 对象 列表 参数      更新时间:2023-10-16

我正在询问如何创建不同类型对象的模板映射,观察者示例只是描述问题的一种方便方式。

因此,我有一个观察者模式的简单实现:

template<typename ...A> class Observable
{
typedef void(*EventListener)(A...);  /* Listener callback type */
public:
void addListener(EventListener listener);
void removeListener(EventListener listener);
void fireEvent(A... args);
};

在我的代码中有不同的事件集,每个事件集都由enum表示,即:

enum PCEvents {
JUMPED = 0,       // void (*fn)(int, int)
WALKED = 1,       // void (*fn)(int)
DIED = 2,         // void (*fn)()
DIED_HORRIBLY = 3 // void (*fn)(const std::string&)
};

现在我想要一个伞形类,它封装了基于枚举的所有Observable。我不知道它是如何实现的,所以这里是它想要的实例化:

void gameOver(const std::string& deathCause)
{
std::cout << "You perished because of " << deathCause << std::endl;
}
int main()
{
// parameter list syntax is lax 
PoorGuyObserver<PCEvents, void (*)(int, int), void (*)(int), void (*)(), void (*)(const std::string&)> observer;
observer.addListener(PCEvents::DIED_HORRIBLY, gameOver);
observer.fireEvent(PCEvents::DIED_HORRIBLY, "gazeebo");
}

我对c++14的解决方案很感兴趣。

我对您的问题的解释是,您希望将可观察描述为枚举值到函数签名的映射,并使用可变模板来生成它。

可变模板的一个棘手之处是,不能将常数值与类型名混合使用。尾随位必须是所有类型名称(typename...(或所有常量(auto...(。因此,我的第一步是制作一个助手类型:

template <typename TEnum, TEnum Id, typename... TArgs>
struct Event;

注意:如果您使用的是c++17,您可以将前两个参数组合为auto Id,但您指定了c++14

下一步:为单个Event实现可观测。通常,这将是递归变量模板的一部分,但这是我稍后将描述的一个棘手问题所需要的。

template <typename TEnum, TEnum Id, typename... TArgs>
struct ObservableImpl {
std::vector<std::function<void(TArgs...)>> subscribers;
template <TEnum FireId, typename = std::enable_if_t<FireId == Id>>
void fire(TArgs... args) { 
for (auto subscriber : subscribers) { 
subscriber(args...); 
}
}
template <TEnum SubscribeId, typename = std::enable_if_t<SubscribeId == Id>>
void subscribe(std::function<void (TArgs...)> handler) {
subscribers.push_back(handler);
}
};

然后是递归部分。每个继承层继承自单个事件实现,并提升firesubscribe

template <typename... TEvents>
struct Observable;
// Recursive case, implement the current event, inherit from the next
template <typename TEnum, TEnum Id, typename... TArgs, typename... TEvents>
struct Observable<Event<TEnum, Id, TArgs...>, TEvents...> : public ObservableImpl<TEnum, Id, TArgs...>, Observable<TEvents...> {
using Observable<TEvents...>::subscribe;
using Observable<TEvents...>::fire;
using ObservableImpl<TEnum, Id, TArgs...>::subscribe;
using ObservableImpl<TEnum, Id, TArgs...>::fire;
};
// Terminal case, implement the last event
template <typename TEnum, TEnum Id, typename... TArgs>
struct Observable<Event<TEnum, Id, TArgs...>> : public ObservableImpl<TEnum, Id, TArgs...> {
using ObservableImpl<TEnum, Id, TArgs...>::subscribe;
using ObservableImpl<TEnum, Id, TArgs...>::fire;
};

在许多示例中,终止专门化是空的。但在我们的情况下,因为递归专门化促进了一个基方法,所以终止情况必须具有该基方法。这就是为什么我将单个事件实现拉到第二个类中,以节省一些类型。

之后,它就可以使用了:

enum class Events { 
JUMPED = 0,       // void (*fn)(int, int)
WALKED = 1,       // void (*fn)(int)
DIED = 2,         // void (*fn)()
DIED_HORRIBLY = 3 // void (*fn)(const std::string&)
};
int main(int argc, char** argv) {
Observable<
Event<Events, Events::JUMPED, int, int>,
Event<Events, Events::WALKED, int>,
Event<Events, Events::DIED>,
Event<Events, Events::DIED_HORRIBLY, std::string>> observable;
observable.subscribe<Events::JUMPED>([](int x, int y) { std::cout << "jumped(" << x << ", " << y << ")n"; });
observable.subscribe<Events::WALKED>([](int distance) { std::cout << "walked(" << distance << ")n"; });
observable.subscribe<Events::DIED>([]() { std::cout << "died()n"; });
observable.subscribe<Events::DIED_HORRIBLY>([](std::string how) { std::cout << "died_horribly(" << how << ")n"; });
observable.fire<Events::JUMPED>(1, 2);
observable.fire<Events::WALKED>(42);
observable.fire<Events::DIED>();
observable.fire<Events::DIED_HORRIBLY>("fire");
}

https://godbolt.org/z/VhvGPg