在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>;