如何在 std::variant 中存储 std::string 或 std::string_view?

How to store either std::string or std::string_view in a std::variant?

提问人:kteperin 提问时间:8/15/2023 最后编辑:Jan Schultkekteperin 更新时间:8/15/2023 访问量:182

问:

我正在研究词法分析器。我有一个结构,看起来像这样:Token

struct Token {
    enum class Type { ... };
    
    Type type;
    std::string_view lexeme;
}

的只是完整源代码的一小部分视图(顺便说一句,这也是)。Tokenlexemestd::string_view

问题是我需要重新映射特殊字符(例如,)。按原样存储它们不是一个好的解决方案。'\n'

我试过用 替换 的类型,但它很快就变成了意大利面条代码,因为每次我想读取词素(例如,检查类型是否是和词素是)时,这都是一个很大的痛苦。lexemestd::variant<std::string, std::string_view>Bool"true"

存储为所属字符串并不能解决问题。lexeme

顺便说一下,我使用 C++20;也许有一个很好的解决方案?

C++ C++20 stdstring 字符串视图 std-variant

评论

1赞 Sam Varshavchik 8/15/2023
不幸的是,C++并没有以友善而闻名。可以有各种解决方案,但它们将高度依赖于上下文,并针对代码的其余部分进行定制。
1赞 Jarod42 8/15/2023
由于具有类似的界面,因此代码似乎很简单......std::stringstd::string_viewstd::visit
1赞 user17732522 8/15/2023
请举例说明哪里给您带来了麻烦。假设拥有/非拥有组合适用于您的用例,我不明白为什么这会导致特别复杂的代码。当然,使用远不如在具有适当和类型的语言中那么好。std::variant<std::string, std::string_view>std::variant
0赞 Paul Sanders 8/15/2023
就性能而言,在这里使用是否值得痛苦(也许还有内存开销,以及实际上悬空指针的风险)?也许 SSO(短字符串优化)会帮到你。std::string_view
1赞 MSalters 8/15/2023
我会用另一种方式解决这个问题:总是同时拥有 a an .string_view可以指向原始源文本或令牌自己的文本;如果不需要,令牌中的“std::string”为空。std::string_viewstd::stringstd::string

答:

2赞 Homer512 8/15/2023 #1

在我看来,您所需要的只是封装变体,以便为两者提供统一的接口。由于将 an 转换为 an 非常便宜,复制 an 同样便宜,您可以为此创建一个方法并访问这样的内容。std::stringstd::string_viewstd::string_view

struct OptOwnString
{
    using variant_t = std::variant<std::string, std::string_view>;
    variant_t value;

    std::string_view view() const noexcept
    {
        /**
         * Note: noexcept since it is effectively impossible to
         * make this particular variant valueless_by_exception
         */
        return std::visit([](auto const& v) {
              return std::string_view(v); }, value);
    }
};

int main()
{
    OptOwnString owning { std::string("foo") };
    std::cout << owning.view() << '\n';
    OptOwnString borrowed { owning.view() };
    std::cout << borrowed.view() << '\n';
}

评论

1赞 Jan Schultke 8/15/2023
view()可以是 ,并且为了减少发出的代码大小,可以使用 。const noexcept*std::get_if<std::string_view>(value)
1赞 Barry 8/15/2023
或者,更短的实现(这也更能适应未来的变化):如果你碰巧有一个函数对象,可以读得更好:viewreturn std::visit([](auto const& v) { return std::string_view(v); }, value);static_caststd::visit(static_cast_<std::string_view>, value)
1赞 Jan Schultke 8/15/2023 #2

你可以直接使用std::string

首先,a 可以和 .这可能没有您想象的那么昂贵,因为在所有 C++ 标准库中都有 SSO(小字符串优化)。std::stringTokenstd::string_viewstd::string

这意味着短令牌不会在堆上分配;这些字符将直接存储在容器中。在讨论 和 之前,您可能希望衡量分配是否甚至是一个性能问题。否则,这是一个过早优化的情况。"const"std::string_viewstd::variant

如果你坚持......std::variant

用户@Homer512已经提供了一个可靠的解决方案。您可以围绕它创建一个包装器,而不是直接使用 ,它为 和 提供类似字符串的接口。std::variantstd::stringstd::string_view

这很容易做到,因为大多数成员函数的名称和含义对于这两个类都是相同的。这也使它们易于通过 std::visit 使用。

struct MaybeOwningString
{
    using variant_type = std::variant<std::string, std::string_view>;
    using size_type = std::string_view::size_type;

    variant_type v;

    // main member function which grants access to either alternative as a view
    std::string_view view() const noexcept {
        return std::visit([](const auto& str) -> std::string_view {
            return str;
        }, v);
    }

    // various helper functions which expose commonly used member functions
    bool empty() const noexcept {
        // helper functions can be implemented with std::visit, but this is verbose
        return std::visit([](const auto& str) {
            return str.empty();
        }, v);
    }

    size_type size() const noexcept {
        // helper functions can also be implemented by using view()
        return view().size();
    }

    // ...
};