I am implementing Observator pattern with variadic template.
Subject class has parameter as subscibers and store them in std::vector<std::variant>
. In function notify
I am using std::visit
to call function onMessage
to notify all subscibers.
template<typename...Args>
class SubjectBase {
using ArgsType = std::variant<Args*...>;
std::vector<ArgsType> subscribers_;
public:
SubjectBase(Args*... args): subscribers_{args...} {}
template<typename Msg>
auto notify(const Msg& msg) {
for(auto subscriber: subscribers_) {
std::visit([msg](const auto& x) {
x->onMessage(msg);
}, subscriber);
}
}
private:
};
Subscriber class has parameter as Message Type which it would like to receive (for example, SubscriberOne get message Foo
, Bar
, but SubscriberTwo get only message Foo
)
struct Foo {};
struct Bar {};
template<typename ...Args>
class SubscriberBase {
public:
auto onMessage(const auto& msg);
};
class SubscriberOne: public SubscriberBase<Foo, Bar> {
public:
auto onMessage(const Foo& foo) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
auto onMessage(const Bar& foo) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
class SubscriberTwo: public SubscriberBase<Foo> {
public:
auto onMessage(const Foo& foo) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
My main function, for example.
int main() {
SubscriberOne one;
SubscriberTwo two;
SubjectBase<SubscriberOne, SubscriberTwo> subjectOne(&one, &two);
SubjectBase<SubscriberOne> subjectTwo(&one);
Foo foo;
Bar bar;
subjectOne.notify(foo);
// subjectOne.notify(bar); --> // this line error due to SubcriberTwo has no onMessage(Bar&)
return 0;
}
Can we check function is invokable in std::visit
? For example, use if constexpr
to check type in the variant.
Edit
I update version of CRTP for SubsciberBase as @alager's suggestion.
My guess is you're on C++20. If so, you can use concepts to check if a given type has onMessage function (OK, member but also static, I used simplified approach here) callable with a given type. If you're using some older standard, this is probably also doable but in a bit more convoluted manner.
template<typename T, typename MSG>
concept HasMessageHandler = requires(T obj, const MSG& msg) {
{ obj.onMessage(msg) } ;
};
template<typename...Args>
class SubjectBase {
using ArgsType = std::variant<Args*...>;
std::vector<ArgsType> subscribers_;
public:
SubjectBase(Args*... args): subscribers_{args...} {}
template<typename Msg>
auto notify(const Msg& msg) {
for(auto subscriber: subscribers_) {
std::visit([msg](const auto& x) {
using ObjType = std::remove_pointer_t<std::remove_reference_t<decltype(x)>>;
// uncomment line below to see how it fails
// static_assert(HasMessageHandler<ObjType, decltype(msg)>);
if constexpr(HasMessageHandler<ObjType, decltype(msg)> ) {
x->onMessage(msg);
}
}, subscriber);
}
}
private:
};
Also, didn't you have some sort of CRTP in mind?
Because currently SubscriberBase
is completely unnecessary.
Demo: https://godbolt.org/z/87jcsejTb
With SubscriberBase
gotten rid of:
https://godbolt.org/z/c4cMajzrP