使用重载模式调用std::visit失败,求排查嵌套variant分发问题
问题描述
尝试用访问器重载模式处理嵌套的std::variant时,编译报错:
error: no matching function for call to 'visit ...'
对应的代码如下:
#include <variant> #include <cstdio> template <typename... Ts> struct overload : Ts... { using Ts::operator() ...; }; int main() { struct StateA{}; struct StateB{}; struct StateC{}; using state_type = std::variant<StateA, StateB, StateC>; auto state = state_type{StateA{}}; struct EventA{}; struct EventB{}; struct WifiConnectedNote{}; struct ErrorNote{}; using QueueVariantType = std::variant<std::monostate, WifiConnectedNote, ErrorNote>; using event_type = std::variant<std::monostate, QueueVariantType, EventA, EventB>; auto event = event_type{EventA{}}; for (size_t i=0; i < 10; i++) { state = std::visit(overload{ [&](QueueVariantType&& variant, auto&& state_tmp) -> state_type { std::visit(overload{ [&](WifiConnectedNote&&, auto&&){ printf("WifiConnectedNote A\n"); }, [&](ErrorNote&&, auto&&){ printf("ErrorNote A\n"); }, [&](auto&&, auto&&){ printf("Other in Queue variant\n"); }, }, std::forward<decltype(variant)>(variant), std::forward<decltype(state_tmp)>(state_tmp)); return StateC{}; }, [&](auto&&, const StateA&) -> state_type { printf("State A\n"); return StateC{}; }, [&](auto&&, const StateB&) -> state_type { printf("State B\n"); return StateA{}; }, [&](auto&&, const StateC&) -> state_type { printf("State C"); return StateB{}; }, }, std::move(event), state); } }
问题原因及修复方案
编译失败主要是两个核心问题:
1. 参数值类别与引用类型不匹配
外层std::visit里用std::move(event)将event转为右值,但给QueueVariantType的处理函数签名是QueueVariantType&&——可event_type内部存储的QueueVariantType是左值类型,导致无法匹配。同时其他处理函数的第二个参数用了const StateA&这类常量引用,和state的左值传递结合后,重载决议找不到合适的匹配项。
2. 嵌套std::visit参数传递错误
内层std::visit只需要访问QueueVariantType这一个变体,但你传了两个参数(variant和state_tmp),而内层访问器的每个lambda都接收两个参数。std::visit若接收多个参数,要求所有参数都是std::variant类型,但state_tmp是具体的状态类型(不是变体),直接引发匹配错误。
修复后的代码
#include <variant> #include <cstdio> template <typename... Ts> struct overload : Ts... { using Ts::operator() ...; }; int main() { struct StateA{}; struct StateB{}; struct StateC{}; using state_type = std::variant<StateA, StateB, StateC>; auto state = state_type{StateA{}}; struct EventA{}; struct EventB{}; struct WifiConnectedNote{}; struct ErrorNote{}; using QueueVariantType = std::variant<std::monostate, WifiConnectedNote, ErrorNote>; using event_type = std::variant<std::monostate, QueueVariantType, EventA, EventB>; auto event = event_type{EventA{}}; for (size_t i=0; i < 10; i++) { state = std::visit(overload{ // 修改为左值引用,适配variant内部存储的对象类型 [&](QueueVariantType& variant, auto& state_tmp) -> state_type { // 内层visit只处理QueueVariantType,state_tmp直接从捕获获取即可 std::visit(overload{ [&](WifiConnectedNote){ printf("WifiConnectedNote A\n"); }, [&](ErrorNote){ printf("ErrorNote A\n"); }, [&](std::monostate){ printf("Other in Queue variant\n"); }, }, variant); return StateC{}; }, // 统一参数引用类型,去掉不必要的const(也可保留const,只要保持一致) [&](auto&, StateA&) -> state_type { printf("State A\n"); return StateC{}; }, [&](auto&, StateB&) -> state_type { printf("State B\n"); return StateA{}; }, [&](auto&, StateC&) -> state_type { printf("State C\n"); return StateB{}; }, }, event, state); // 除非后续不再使用event,否则不需要std::move } }
额外说明
- 如果确实需要转移
event的所有权,可以把外层访问器的第一个参数改成auto&&适配右值,或者为每种事件类型单独处理右值版本,但一般直接传递左值引用更安全。 - 内层
std::visit如果需要用到state_tmp,直接通过lambda的捕获获取即可,无需作为std::visit的参数传递。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

