【问题标题】:How to make span of spans如何制作跨度的跨度
【发布时间】:2023-03-25 06:55:01
【问题描述】:

C++20 std::span 是一个非常好的编程接口。但似乎没有一个简单的方法来拥有跨度。这是我想要做的:

#include <iostream>
#include <span>
#include <string>
#include <vector>

void print(std::span<std::span<wchar_t>> matrix) {
  for (auto const& str : matrix) {
    for (auto const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

int main() {
  std::vector<std::wstring> vec = {L"Cool", L"Cool", L"Cool"};
  print(vec);
}

这不会编译。我该怎么做?

【问题讨论】:

  • 编译错误是什么?
  • 为什么不是跨度向量?
  • 您希望引用哪个std::span&lt;wchar_t&gt; 对象数组?听起来您想要一个跨度范围,但那个(外部)范围可能不应该是一个跨度。
  • 我认为跨度的跨度没有任何意义。跨度的数组或向量可以,但不是跨度的跨度。对于什么是跨度,请参阅我的答案或其他答案:What is a "span" and when should I use one?
  • @Ay:实际上,错误是“没有从vector到span的转换”。这完全不足为奇。

标签: c++ c++20 std-span


【解决方案1】:

为什么不改用concept

#include <iostream>
#include <string>
#include <vector>
#include <ranges>

template <class R, class T>
concept Matrix = 
    std::convertible_to<
        std::ranges::range_reference_t<std::ranges::range_reference_t<R>>,
        T>;

void print(Matrix<wchar_t> auto const& matrix) {
    for (auto const& str : matrix) {
        for (auto const ch : str) {
            std::wcout << ch;
        }
        std::wcout << '\n';
    }
}

int main() {
  std::vector<std::wstring> vec = {L"Cool", L"Cool", L"Cool"};
  print(vec);
}

godbolt.org

感谢Barry 使用standard ranges library 提出上述简化概念。

【讨论】:

  • 这个概念不会检查任何东西。或者更确切地说,它要么是true,要么是格式错误的......这并不是你真正想要的概念:godbolt.org/z/oE9secK48
  • @Barry 感谢您指出这一点,在您发现这一点后,我花了一段时间才想出令我满意的东西。以上答案should work.
  • 只需使用std::ranges::range。然后range_reference_t 是一个类型特征,可以为您获取范围的引用类型。不需要自己动手
  • @Barry,非常感谢您的反馈,我不知道标准库定义的 &lt;concepts&gt; 标头之外的概念,我会再次更新我的答案。
  • 你可以依赖 range_reference_t&lt;R&gt; 已经需要 range&lt;R&gt;,所以简称:godbolt.org/z/EqWW1n6M1
【解决方案2】:

只需使用模板打印包含std::wstringstd::wstring_view 的任何容器类型(为了演示,两个任意类型限制;根据您的需要轻松调整或删除这些限制)

我更喜欢坚持使用更普遍可读的代码(C++“概念”非常先进,没有被广泛理解)。为什么不直接使用这个简单的模板?

template <typename T>
void print(const T& matrix) {
  for (auto const& str : matrix) {
    for (auto const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

作为奖励,添加此static_assert 以检查类型并确保仅传入std::wstringstd::wstring_view 字符串类型,例如(根据需要修改或删除静态断言,并根据您的需求):

static_assert(std::is_same_v<decltype(str), const std::wstring&> || 
  std::is_same_v<decltype(str), const std::wstring_view&>,
  "Only strings of `std::wstring` or `std::wstring_view` are "
  "allowed!");

现在您有了这个更好版本的print()函数模板

template <typename T>
void print(const T& matrix) {
  for (auto const& str : matrix) {
    static_assert(std::is_same_v<decltype(str), const std::wstring&> || 
      std::is_same_v<decltype(str), const std::wstring_view&>,
      "Only strings of `std::wstring` or `std::wstring_view` are "
      "allowed!");

    for (auto const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

但是,auto 的第二次使用不是必需的,也没有增加任何价值(它只是混淆事物),所以让我们删除它,并改用它:

for (wchar_t const ch : str) {

auto 的第一次用法很好,因为它是必需的,因为它可能是多种类型。

(注意:如果您确实需要在这里处理其他类型的字符,请忽略我在这里所说的并将wchar_t 改回auto。这由您决定。) em>

现在,我们有了这个最终版本的printf() 函数模板

template <typename T>
void print(const T& matrix) {
  for (auto const& str : matrix) {
    static_assert(std::is_same_v<decltype(str), const std::wstring&> || 
      std::is_same_v<decltype(str), const std::wstring_view&>,
      "Only strings of `std::wstring` or `std::wstring_view` are "
      "allowed!");

    for (wchar_t const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

看来您的目标是能够使用您的自定义 print() 函数打印任何包含宽字符文本的容器类型,不是吗?

您似乎将其称为“矩阵”,其中容器中的外部元素是一个字符串,每个字符串的内部元素是一个宽字符(wchar)。

如果是这样,以下模板可以正常工作。我只是改变了这个:

void print(std::span<std::span<wchar_t>> matrix) {

到这里:

template <typename T>
void print(const T& matrix) {

...然后我补充说:

  1. a static_assert 依赖于 A) std::is_same_v&lt;&gt;(与 std::is_same&lt;&gt;::value 相同)和 B) decltype() 说明符以确保仅传入 std::wstringstd::wstring_view 字符串类型,并且
  2. main() 中的更多测试打印,包括std::vector&lt;std::wstring&gt;std::vector&lt;std::wstring_view&gt; 的测试打印,以及链表:std::list&lt;std::wstring_view&gt; 和无序集(哈希集):std::unordered_set&lt;std::wstring&gt; .

这是完整的代码和print() 函数模板。在线运行此代码:https://godbolt.org/z/TabW43Yjf

#include <iostream>
#include <list> // added for demo purposes to print a linked list in main()
// #include <span> // not needed
#include <string>
#include <type_traits> // added to check types and aid with static asserts
#include <unordered_set>
#include <vector>

template <typename T>
void print(const T& matrix) {
  for (auto const& str : matrix) {
    static_assert(std::is_same_v<decltype(str), const std::wstring&> || 
      std::is_same_v<decltype(str), const std::wstring_view&>,
      "Only strings of `std::wstring` or `std::wstring_view` are "
      "allowed!");

    for (wchar_t const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

int main() {
  std::vector<std::wstring> vec1 = {L"Cool1", L"Cool2", L"Cool3"};
  std::vector<std::wstring_view> vec2 = {L"Hey1", L"Hey2", L"Hey3"};
  std::list<std::wstring_view> list1 = {L"You1", L"You2", L"You3"};
  std::unordered_set<std::wstring> set1 = {L"There1", L"There2", L"There3"};
  print(vec1);
  print(vec2);
  print(list1);
  print(set1);

  // Compile-time error due to the std::is_same_v<> usage in the static_assert 
  // above!
  // std::vector<std::string> vec3 = {"hey", "you"};
  // print(vec3);
}

样本输出:

Cool1
Cool2
Cool3
Hey1
Hey2
Hey3
You1
You2
You3
There3
There2
There1

如果您只想打印 std::vector&lt;std::wstring&gt;std::vector&lt;std::wstring_view&gt; 类型,这里有一个更有限的模板(再次,为了演示,这是两个任意类型限制;您可以根据需要轻松调整或删除这些限制):

只需在我上面的模板中替换它:

template <typename T>
void print(const T& matrix) {

这样,强制它只接受std::vector&lt;&gt; 容器类型(上面的const T&amp; 更改为下面的const std::vector&lt;T&gt;&amp;,就是全部):

template <typename T>
void print(const std::vector<T>& matrix) {

然后,根据需要添加static_assert 以确保向量内的类型为std::wstringstd::wstring_view

下面的完整代码。在此处在线运行:https://godbolt.org/z/qjhqq647M

#include <iostream>
// #include <span> // not needed
#include <string>
#include <type_traits>
#include <vector>

template <typename T>
void print(const std::vector<T>& matrix) {
  static_assert(std::is_same_v<T, std::wstring> || 
    std::is_same_v<T, std::wstring_view>,
    "Only vectors of `std::wstring` or `std::wstring_view` are allowed!");

  for (auto const& str : matrix) {
    for (wchar_t const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

int main() {
  std::vector<std::wstring> vec1 = {L"Cool1", L"Cool2", L"Cool3"};
  std::vector<std::wstring_view> vec2 = {L"Hey1", L"Hey2", L"Hey3"};
  print(vec1);
  print(vec2);

  // Compile-time error due to the std::is_same_v<> usage in the static_assert 
  // above!
  // std::vector<std::string> vec3 = {"hey", "you"};
  // print(vec3);
}

为什么 span 的 span 不起作用:

std::span&lt;T&gt; 本质上只是一个包含指向连续内存块的指针的结构。 Cppreference.com states(强调):

类模板跨度描述了一个对象,该对象可以引用对象的连续序列,其中序列的第一个元素位于零位置

正如我在关于跨度 (What is a "span" and when should I use one?) 的其他答案中解释的那样,它可能看起来像这样:

template <typename T>
struct span
{
    T * ptr_to_array;   // pointer to a contiguous C-style array of data
                        // (which memory is NOT allocated or deallocated 
                        // by the span)
    std::size_t length; // number of elements in the array

    // Plus a bunch of constructors and convenience accessor methods here
}

但是,并非所有 C++ 容器类型都存储在连续内存中,例如链表(std::liststd::forward_list),因此它们不能放入跨度中。

一般来说,span 是 C++ 中的一个包装器,用于包装 C 样式的数组,在一个变量中捕获指向其连续内存块的指针,在另一个变量中捕获它们的长度。这样,您可以用两个输入参数替换函数原型,如下所示:

void do_stuff(T *ptr_to_data, std::size_t num_elements) {}
// OR (the const form)
void do_stuff(const T *ptr_to_data, std::size_t num_elements) {}

带有一个输入参数的原型如下:

void do_stuff(std::span<T> data) {}
// OR (the const form)
void do_stuff(const std::span<T> data) {}

正如@mcilloni 所说的in his comment here

参考资料:

  1. 从头开始工作:
    1. https://godbolt.org/z/s99dnzj8z
    2. https://godbolt.org/z/33vzTM787
  2. [我的回答]What is a "span" and when should I use one?
  3. https://www.learncpp.com/cpp-tutorial/an-introduction-to-stdstring_view/ - 关于什么是 std::string_view、何时以及为何使用它以及如何使用它的优秀读物。它还涵盖了它的一些细微差别、局限性和缺点。
  4. https://en.cppreference.com/w/cpp/container/span
  5. https://en.cppreference.com/w/cpp/types/is_same
  6. https://en.cppreference.com/w/cpp/header/type_traits
  7. *****[我的答案——非常有用——我引用它是为了记住如何在编译时使用static_assert(std::is_same_v&lt;decltype(var), some_type&gt;, "some msg"); 静态检查类型] Use static_assert to check types passed to macro

【讨论】:

  • @PatrickRoberts,很好地抓住了我在print() 的输入参数中缺少const&amp;。那肯定是我的疏忽。我已经修好了。我还解决了关于合理诊断消息的其他问题,通过使用static_assert 检查传递给模板的输入类型,并在用户使用错误类型时输出良好的诊断消息。
  • 感谢您的努力!您的解决方案的问题在于它不是一个可重用的概念。您必须将这些检查分散在代码库中并保持同步。除非您深入阅读实现中的所有断言,否则也不会立即清楚输入类型是什么。最后,它仅限于std::wstringstd::wstring_view,并且不会处理包含wchar_t 的任何其他类型。概念答案可以很容易地转换为使用任何类型的矩阵,只需最少的努力。
  • @AyxanHaqverdili, The problem with your solution is that it's not a reusable concept. You have to scatter these checks all around the code base and keep them in sync. 嗯,这不太正确。你根本不需要支票!但是,是的,如果你想要支票,你必须让它们保持同步。 It's also not immediately clear what the input type is unless you go down and read all the asserts within the implementation. 是的,使用函数的 doxygen 标头轻松解决。但是,我查看了 C++ 的“概念”,却丝毫不知道它在做什么或类型是什么!
  • @AyxanHaqverdili,Finally, it's limited to to std::wstring and std::wstring_view &amp; won't handle any other types that contain wchar_t。这是我强行施加的任意限制,因为在我投反对票的答案下,这是您在评论中说想要的两种类型。要使其处理更多类型,只需删除 static_assert 或在其检查中添加新类型。 The concepts answer can easily be converted to work with a matrix of any type w/minimal effort. 我的回答也可以。我根据你之前的cmets任意限制了输入类型,试图猜测你想要什么。
  • @GabrielStaples:“在我看来,在合理范围内使用的 C++ 功能越少越好。”这也是保持对这些知识缺乏了解的好方法特征。您声称您希望 C++ 重视简单性,但concepts 功能设计 为您提供您渴望的简单性。它旨在让普通程序员轻松使用复杂的 SFINAE 技术。我们为您提供了使语言更简单的功能,但您不会使用它们。我不知道你想从语言中得到什么。
【解决方案3】:

更新:尽管自从看到它之后投了反对票,并且它下面的 cmets 也有价值,但我还是留下了下面的答案,但我认为 here's a different answer I just posted instead 具有价值和优点。


我完全不明白在这里使用跨度的愿望(如果我遗漏了什么,请帮助我理解),如the purpose of a span is to wrap and "C++-itize" (which is sometimes a debatable practice already) a C-style array

为什么不直接改变这个:

void print(std::span<std::span<wchar_t>> matrix) {

这个?:

void print(std::vector<std::wstring> matrix) {

现在代码可以正常工作了 (run on Godbolt):

#include <iostream>
// #include <span> // not needed
#include <string>
#include <vector>

void print(std::vector<std::wstring> matrix) {
  for (auto const& str : matrix) {
    for (auto const ch : str) {
      std::wcout << ch;
    }
    std::wcout << '\n';
  }
}

int main() {
  std::vector<std::wstring> vec = {L"Cool", L"Cool", L"Cool"};
  print(vec);
}

这是输出,如 Godbolt 所示。请注意,文本 (Cool Cool Cool) 打印得很好:

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
Cool
Cool
Cool

【讨论】:

  • 该函数应该能够处理wstrings 的向量和wstring_views 的向量。你的回答在这方面如何公平?
  • @AyxanHaqverdili,我没有意识到这是问题的限制,因为你的问题说明无处。我得跑了。稍后再讨论。
  • std::span&lt;T&gt; 的要点是,您可以使用T 的容器,而无需关心它是向量、列表、双端队列、std::array、raw-array 还是其他东西。它基本上是 STL 的 2 个迭代器习语,但以更好的方式打包。我认为这很明显。
  • @AyxanHaqverdili 不完全是,std::span&lt;T&gt; 需要引用 连续 内存区域,因此它仅限于 C 数组,std::array&lt;T, N&gt;,@ 987654335@ 就是这样 - 它基本上是一个包含指向 T 的指针和一个包含元素数量的可选 std::size_t 值的元组。它的主要目的是提供一个关于传递指针及其大小的常见用法的抽象,将void do_stuff(T *ptr, std::size_t count)替换为void do_stuff(std::span&lt;T&gt; tees)
  • @AyxanHaqverdili:不过,它只能处理 contiguous 容器,所以 std::deque 已经过时了,除非您实际上是通过转换容器的每个元素形成的范围为结果形成另一个连续的容器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 2020-07-23
相关资源
最近更新 更多