是否可以在多次保存相同类型的变体上使用 std::visit ?

Jem*_*aly 6 c++ stl c++17 std-variant

例如,我尝试使用 实现 AST std::variant,其中 Token 可以是数字、关键字或变量。其中数字由 表示int,关键字和变量由 表示std::string

enum TokenType : std::size_t {
    Number = 0, Keyword = 1, Variable = 2,
};
using Token = std::variant<int, std::string, std::string>;
Token token(std::in_place_index<TokenType::Variable>, "x");
Run Code Online (Sandbox Code Playgroud)

当我想用 做某事时token,我可以首先确定它的类型token.index(),然后决定要做什么:

switch (token.index()) {
case TokenType::Number:
    return do_sth_with_number(std::get<TokenType::Number>(token));
case TokenType::Keyword:
    return do_sth_with_keyword(std::get<TokenType::Keyword>(token));
case TokenType::Variable:
    return do_sth_with_variable(std::get<TokenType::Variable>(token));
}
Run Code Online (Sandbox Code Playgroud)

不过,我不知道是否可以使用它std::visit来达到同样的效果。我只知道它可以根据变体中的数据类型调用特定函数,但我不知道是否可以根据索引来执行此操作。

我知道我可以通过将关键字和变量包装在两个不同的类中来实现我的目标,但我想知道是否有更好的方法来做到这一点,因为据我了解,在变体中,决定使用哪个函数应该更直接基于索引而不是类型使用。

康桓瑋*_*康桓瑋 2

可以根据 的variant索引而不是替代类型来分派不同的函数。

可以让访问者接受一个额外的索引常量来捕获当前替代品的索引信息,然后根据大小创建一个函数表,将variant不同的索引常量和对应的替代品转发给访问者

template<class Visitor, class Variant>
constexpr decltype(auto)
visit_i(Visitor&& vis, Variant&& var) {
  constexpr auto N = std::variant_size_v<std::remove_cvref_t<Variant>>;
  constexpr auto func_ptrs = []<std::size_t... Is>(std::index_sequence<Is...>) {
    return std::array{
      +[](Visitor& vis, Variant& var) -> decltype(auto) {
        return std::invoke(std::forward<Visitor>(vis), 
                           std::integral_constant<std::size_t, Is>{}, 
                           std::get<Is>(std::forward<Variant>(var)));
      }...
    };
  }(std::make_index_sequence<N>{});
  return func_ptrs[var.index()](vis, var);
}
Run Code Online (Sandbox Code Playgroud)

然后您可以variant使用接受索引常量的自定义访问者来访问

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);
void do_sth_with_keyword(std::string);
void do_sth_with_variable(std::string);

auto visitor = [](auto index, auto&& token) {
  if constexpr (index == TokenType::Number)
    return do_sth_with_number(token);
  else if constexpr (index == TokenType::Keyword)
    return do_sth_with_keyword(token);
  else if constexpr (index == TokenType::Variable)
    return do_sth_with_variable(token);
};

int main() {
  Token token(std::in_place_index<TokenType::Variable>, "x");
  visit_i(visitor, token);
}
Run Code Online (Sandbox Code Playgroud)

演示