【问题标题】:Transforming a string_view in-place就地转换 string_view
【发布时间】:2022-01-05 19:39:19
【问题描述】:

std::transform,从 C++20 开始,声明为 constexpr. 我有一堆字符串实用函数,它们接受 std::string 参数,但很多用法最终只是传递了小而短的字符编译时的文字序列。我想我会利用这一事实并声明 constexpr 的版本并采用 std::string_views 而不是创建临时的 std::string 变量只是为了把它们扔掉......

原始STD::STRING 版本:

[[nodiscard]] std::string ToUpperCase(std::string string) noexcept {
    std::transform(string.begin(), string.end(), string.begin(), [](unsigned char c) -> unsigned char { return std::toupper(c, std::locale("")); });
    return string;
}

STD::STRING_VIEW 版本:

[[nodiscard]] constexpr std::string_view ToUpperCase(std::string_view stringview) noexcept {
    std::transform(stringview.begin(), stringview.end(), stringview.begin(), [](unsigned char c) -> unsigned char { return std::toupper(c, std::locale("")); });
    return stringview;
}

但 MSVC 抱怨:

error C3892: '_UDest': you cannot assign to a variable that is const

有没有办法用std::string_view 调用std::transform 并将其放回std::string_view 或者我将不得不创建一个本地字符串并返回它,从而违背了使用std::string_view 的目的首先?

[[nodiscard]] constexpr std::string ToUpperCase(std::string_view stringview) noexcept {
    std::string copy{stringview};
    std::transform(stringview.begin(), stringview.end(), copy.begin(), [](unsigned char c) -> unsigned char { return std::toupper(c, std::locale("")); });
    return copy;
}

【问题讨论】:

  • string_view 是字符序列的不可变视图。它看起来但没有触摸。这就是它不需要复制任何内容来形成const char[] C 样式字符串文字的视图的全部原因。
  • 没有办法避免复制,因为string_view 只提供 const 访问 - 这很好。您希望如何避免在auto upper = ToUpperCase("foo"); 中复制?
  • std::string_view 指的是一个恒定的连续序列。为什么不试试std::span<char>
  • @ecatmur - 因为它会有不同的语义:就地修改。
  • @Eugene 这听起来像是他想要的。他在最后特别问是否有办法修改它。

标签: c++ c++20 string-view toupper tolower


【解决方案1】:

您不能就地转换 std::string_view - 如果它是从 char <em>const</em>* 构造的怎么办?

很多用法最终只是在编译时传递小而短的字符文字序列。

...但是您可以将字符串文字提升到类型级别

namespace impl {
    template<std::size_t n> struct Str {
        std::array<char, n> raw{};
        constexpr Str(char const (&src)[n + 1]) { std::copy_n(src, n, raw.begin()); }
    };
    template<std::size_t n> Str(char const (&)[n]) -> Str<n - 1>;
}
template<char... cs> struct Str { static char constexpr value[]{cs..., '\0'}; };
template<impl::Str s>
auto constexpr str_v = []<std::size_t... is>(std::index_sequence<is...>) {
    return Str<s.raw[is]...>{};
}(std::make_index_sequence<s.raw.size()>{});

...并添加一个特殊情况。一般来说,使用范围/元组多态算法可以避免这种 hack。

[[nodiscard]] constexpr auto ToUpperCase(auto str) {
    for (auto&& c: str) c = ConstexprToUpper(c); // std::toupper doesn't seem constexpr
    return str;
}
template<char... cs> [[nodiscard]] constexpr auto ToUpperCase(Str<cs...>) {
    return Str<ConstexprToUpper(cs)...>{};
}

因此,要使用针对字符文字序列优化的转换,现在写 ToUpperCase(str_v&lt;"abc"&gt;) 而不是 ToUpperCase("abc"sv)。如果您始终希望 string_view 作为输出,请在该重载中返回 std::string_view{Str&lt;ConstexprToUpper(cs)...&gt;::value}

【讨论】:

  • 你能解释一下你取得了什么成就吗? ToUpperCase() 的 2 个重载的使用示例以及它们在哪些方面比 OP 显示的直接实现更好?
  • @Eugene 是的:我的版本提供“使用 std::string_view 调用 std::transform 并将其放回 std::string_view 的方法”而不是“创建本地字符串并返回,从而破坏了首先使用 std::string_view 的目的”。
  • 你能在你的答案中添加一个这样做的例子吗?另外,您是否在编译时创建了大写的字符串文字?
  • @Eugene 我已经编辑了我的答案以显示用法。是的,转换发生在编译时。
  • 谢谢,+1,因为它展示了先进的技术,虽然我不会在生产中使用它。
【解决方案2】:

正如在一条评论中所说,span 是一种更好的词汇类型,因为可以修改单个元素。 另外我不会让它nodiscard,因为即使没有分配结果它也很有用:

#include<algorithm>
#include<cassert>
#include<cctype>
#include<locale>
#include<string_view>
#include<span>

constexpr std::span<char> ToUpperCase(std::span<char> stringview) noexcept {
    std::transform(stringview.begin(), stringview.end(), stringview.begin(), [](unsigned char c) -> unsigned char { return std::toupper(c); });
    return stringview;
}

int main() {
    std::string a = "compiler";
    std::string b = ToUpperCase(a);
    assert( a == "COMPILER");
    assert( b == "COMPILER");
}

https://godbolt.org/z/Toz8Y9bj9


有点偏离你原来的目标... 我认为这更优雅,尽管会出现臃肿和丑陋的编译错误。 在提供的案例中具有相同的效果。

我也不喜欢span(或string_view)的设计

(练习:添加概念)

template<class StringRange>
constexpr StringRange&& ToUpperCase(StringRange&& stringview) noexcept {
    std::transform(stringview.begin(), stringview.end(), stringview.begin(), [](unsigned char c) -> unsigned char { return std::toupper(c); });
    return std::forward<StringRange>(stringview);
}

https://godbolt.org/z/e9aWKMerE

我发现自己最近经常使用这个成语。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-25
    • 1970-01-01
    • 1970-01-01
    • 2017-07-02
    • 2015-08-15
    • 2020-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多