Search code examples
c++variadic-templates

Check function invokable from std::variant


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.

https://godbolt.org/z/cnj5vnqv5


Solution

  • 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