Моя интерпретация вашей проблемы состояла в том, что вы хотели описать наблюдаемое как карту значений перечисления для сигнатур функций и использовать вариационный шаблон для его генерации.
Один хитрый момент с вариационными шаблонами заключается в том, что выне может смешивать постоянные значения с именами типов. Конечный бит должен быть либо всеми именами типов (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);
}
};
Затем рекурсивная часть. Каждый уровень наследования наследуется от реализации отдельного события и поддерживает fire
и subscribe
.
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