std::variant 存储多个字符串类型并区分它们



在C++17中,我正在实现一个玩具虚拟机。我正在使用 std::variant(( 作为虚拟机堆栈的元素。我需要将字符串推送到表示不同类型操作数的堆栈上,即:

  • 变量名称
  • 标识符
  • 带引号的字符串

所有 3 种类型的操作数都可以是 std::string_view 类型。变体定义如下:

std::variant<bool, int, double, std::string_view>;

为了区分字符串视图的实际类型,做这样的事情是正确的方法吗?

enum StringKind { Symbol, String, Var };
using Stringy = std::tuple<StringKind, std::string_view>; // SV can be symbol, var, string
std::variant<bool, int, double, Stringy>;

或者是否可以在变体处对字符串类型进行编码。在变体级别执行此操作的好处之一是可以通过调用 std::variant::index(( 来获得类型。否则,我必须检查 .index(( == 3, std::get<3>(var(,然后检查类型以查看其字符串、符号还是变量。

我刚刚尝试了这个,我喜欢它:

enum StackStringType {};
enum StackSymbolType {}
enum StackVarType {};
using StackString = std::tuple<StackStringType, std::string_view>;
using StackSymbol = std::tuple<StackSymbolType, std::string_view>;
using StackVar    = std::tuple<StackVarType, std::string_view>;
using StackType = std::variant<bool, StackString, StackSymbol, StackVar>;

一种替代方法是创建一个模板化类来"标记"各种枚举类型。这避免了为每个枚举值使用"using"语句的需要,因此您可以减少重复。

enum class StringKind { Symbol, String, Var };
template <StringKind T> struct TaggedString { std::string_view value; };
using StackType = std::variant<bool,
                               int,
                               double,
                               TaggedString<StringKind::Symbol>,
                               TaggedString<StringKind::String>,
                               TaggedString<StringKind::Var>>;

另外,您提到使用 std::variant::index() 来获取变体中包含的类型。执行此操作的另一种方法是通过以下方式使用 std::visit() 以提高类型安全性:

#include <iostream>
#include <string_view>
#include <variant>
template<typename T> struct always_false : std::false_type { };
enum class StringKind { Symbol, String, Var };
template <StringKind T> struct TaggedString { std::string_view value; };
using StackType = std::variant<bool,
                               int,
                               double,
                               TaggedString<StringKind::Symbol>,
                               TaggedString<StringKind::String>,
                               TaggedString<StringKind::Var>>;
std::ostream& operator<<(std::ostream& stream, const StackType& st) {
    std::visit([&stream](auto&& var) {
        using T = std::decay_t<decltype(var)>;
        if constexpr (std::is_same_v<T, bool>) {
            stream << "Bool(" << var << ")";
        } else if constexpr (std::is_same_v<T, int>) {
            stream << "Int(" << var << ")";
        } else if constexpr (std::is_same_v<T, double>) {
            stream << "Double(" << var << ")";
        } else if constexpr (std::is_same_v<T, TaggedString<StringKind::Symbol>>) {
            stream << "Symbol(" << var.value << ")";
        } else if constexpr (std::is_same_v<T, TaggedString<StringKind::String>>) {
            stream << "String(" << var.value << ")";
        } else if constexpr (std::is_same_v<T, TaggedString<StringKind::Var>>) {
            stream << "Var(" << var.value << ")";
        } else {
            static_assert(always_false<T>::value, "non-exhaustive visitor!");
        }
    }, st);
    return stream;
}
int main(int, char**) {
    StackType t_bool   = true;
    StackType t_int    = 3;
    StackType t_double = 3.0;
    StackType t_symbol = TaggedString<StringKind::Symbol>{"Foo"};
    StackType t_string = TaggedString<StringKind::String>{"Bar"};
    StackType t_var    = TaggedString<StringKind::Var>{"Baz"};
    std::cout << t_bool   << std::endl
              << t_int    << std::endl
              << t_double << std::endl
              << t_symbol << std::endl
              << t_string << std::endl
              << t_var    << std::endl;
}

此程序输出:

Bool(1)
Int(3)
Double(3)
Symbol(Foo)
String(Bar)
Var(Baz)

这个怎么样?

struct StackString : std::string_view {};
struct StackSymbol : std::string_view {};
struct StackVar : std::string_view {};
using StackType = std::variant<bool, int, double, StackString, StackSymbol, StackVar>;

相关内容

  • 没有找到相关文章

最新更新