如何在std::visit中检查std::variant成员的函数可调用性?
问题描述
我正用可变参数模板实现观察者模式:
- Subject类接收订阅者作为参数,将其存储在
std::vector<std::variant<...>>中,notify函数通过std::visit调用订阅者的onMessage函数完成通知。 - 订阅者类以期望接收的消息类型为模板参数,比如
SubscriberOne接收Foo、Bar消息,SubscriberTwo仅接收Foo消息。
当前问题:调用subjectOne.notify(bar)时触发报错,原因是SubscriberTwo没有对应的onMessage(Bar&)函数。想知道能否在std::visit里检查订阅者是否可调用对应onMessage函数,比如用if constexpr结合variant中的类型做判断?
注:已基于CRTP更新了SubscriberBase版本。
解决方案
完全可以通过编译期检查实现这种“选择性通知”,核心是用if constexpr结合SFINAE或C++20概念,判断当前variant持有的订阅者是否支持处理目标消息:
1. 定义可调用性检查工具
C++20及以上(用concept)
直接写一个concept来判断订阅者是否拥有对应消息的onMessage函数:
template<typename Subscriber, typename Msg> concept HasOnMessage = requires(Subscriber& sub, Msg& msg) { { sub.onMessage(msg) } -> std::same_as<void>; };
C++17及更早(用SFINAE)
用模板特化实现编译期判断:
template<typename Subscriber, typename Msg, typename = void> struct has_on_message : std::false_type {}; template<typename Subscriber, typename Msg> struct has_on_message<Subscriber, Msg, std::void_t<decltype(std::declval<Subscriber&>().onMessage(std::declval<Msg&>()))>> : std::true_type {}; template<typename Subscriber, typename Msg> constexpr bool has_on_message_v = has_on_message<Subscriber, Msg>::value;
2. 在std::visit中加入条件判断
修改notify函数的访问器逻辑,通过if constexpr做编译期分支,只调用能处理当前消息的订阅者:
template<typename Msg> void notify(Msg& msg) { for (auto& subscriber_variant : subscribers_) { std::visit([&msg](auto& subscriber) { // C++20用concept判断 if constexpr (HasOnMessage<decltype(subscriber), Msg>) { subscriber.onMessage(msg); } // C++17用SFINAE判断 // if constexpr (has_on_message_v<decltype(subscriber), Msg>) { // subscriber.onMessage(msg); // } // 不满足条件时直接跳过,无编译错误 }, subscriber_variant); } }
这样通知Bar类型消息时,SubscriberTwo因不满足可调用约束会被跳过,不会触发编译报错。
3. 结合CRTP基类优化
如果你的SubscriberBase是CRTP实现的,可以把消息类型支持逻辑整合到基类中,确保订阅者只暴露自己支持的onMessage重载,进一步强化编译期检查的可靠性。
内容的提问来源于stack exchange,提问作者GAVD
相关产品推荐
相关产品推荐

