能否用std::visit处理包含重复类型的std::variant?
按索引用std::visit处理含重复类型的std::variant
你当前的Token定义中,std::variant<int, std::string, std::string>包含两个相同类型的变体,仅靠索引区分数字、关键字、变量。常规std::visit因类型重复无法直接区分分支,但可以通过绑定索引信息的方式实现按索引调用处理函数,无需额外包装类,以下是两种可行方案:
方案一:带索引标签的访问器
通过自定义visit_by_index函数,将变体的索引包装为std::integral_constant传递给访问器,利用编译期constexpr if匹配索引并调用对应处理函数:
#include <variant> #include <string> #include <iostream> #include <type_traits> enum TokenType : std::size_t { Number = 0, Keyword = 1, Variable = 2, }; using Token = std::variant<int, std::string, std::string>; // 示例处理函数 void do_sth_with_number(int num) { std::cout << "Number: " << num << "\n"; } void do_sth_with_keyword(const std::string& kw) { std::cout << "Keyword: " << kw << "\n"; } void do_sth_with_variable(const std::string& var) { std::cout << "Variable: " << var << "\n"; } // 接收索引标签和对应值的访问器 struct TokenVisitor { template<std::size_t I> void operator()(std::integral_constant<std::size_t, I>, const typename std::variant_alternative<I, Token>::type& val) const { if constexpr (I == TokenType::Number) { do_sth_with_number(val); } else if constexpr (I == TokenType::Keyword) { do_sth_with_keyword(val); } else if constexpr (I == TokenType::Variable) { do_sth_with_variable(val); } } }; // 包装std::visit,传递索引标签 template<typename Variant, typename Visitor> void visit_by_index(Variant&& var, Visitor&& visitor) { std::visit( [&](auto&& val) { using VariantType = std::decay_t<Variant>; using ValType = std::decay_t<decltype(val)>; constexpr std::size_t Index = std::variant_index<VariantType, ValType>::value; visitor(std::integral_constant<std::size_t, Index>{}, std::forward<decltype(val)>(val)); }, std::forward<Variant>(var) ); } int main() { Token num_token(std::in_place_index<TokenType::Number>, 42); Token kw_token(std::in_place_index<TokenType::Keyword>, "if"); Token var_token(std::in_place_index<TokenType::Variable>, "x"); visit_by_index(num_token, TokenVisitor{}); // 输出:Number: 42 visit_by_index(kw_token, TokenVisitor{}); // 输出:Keyword: if visit_by_index(var_token, TokenVisitor{}); // 输出:Variable: x return 0; }
方案二:利用std::index_sequence遍历索引
通过std::apply和std::index_sequence遍历变体的所有可能索引,匹配当前变体的索引后直接调用对应处理函数,写法更紧凑:
#include <variant> #include <string> #include <iostream> #include <utility> enum TokenType : std::size_t { Number = 0, Keyword = 1, Variable = 2, }; using Token = std::variant<int, std::string, std::string>; void do_sth_with_number(int num) { std::cout << "Number: " << num << "\n"; } void do_sth_with_keyword(const std::string& kw) { std::cout << "Keyword: " << kw << "\n"; } void do_sth_with_variable(const std::string& var) { std::cout << "Variable: " << var << "\n"; } int main() { Token var_token(std::in_place_index<TokenType::Variable>, "x"); Token kw_token(std::in_place_index<TokenType::Keyword>, "return"); Token num_token(std::in_place_index<TokenType::Number>, 100); auto visit_token = [](const Token& token) { // 遍历所有可能的索引 std::apply( [&](auto... Is) { ( [&]() { if (token.index() == Is) { if constexpr (Is == TokenType::Number) { do_sth_with_number(std::get<Is>(token)); } else if constexpr (Is == TokenType::Keyword) { do_sth_with_keyword(std::get<Is>(token)); } else if constexpr (Is == TokenType::Variable) { do_sth_with_variable(std::get<Is>(token)); } } }(), ... ); }, std::make_index_sequence<std::variant_size_v<Token>>{} ); }; visit_token(num_token); // 输出:Number: 100 visit_token(kw_token); // 输出:Keyword: return visit_token(var_token); // 输出:Variable: x return 0; }
两种方案均无需修改原Token的定义,完全按照索引分支处理逻辑,和你原来的switch实现效果一致,且保留了std::visit的编译期检查优势。
内容的提问来源于stack exchange,提问作者Jemtaly
相关产品推荐
相关产品推荐

