3

C++20std::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);
}

这不编译。我该怎么做这样的事情?

4

3 回答 3

5

为什么不使用一个概念呢?

#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);
}

神螺栓.org

感谢Barry建议上面使用标准范围库的简化概念。

于 2021-05-08T16:49:13.467 回答
-1

只需使用模板打印包含std::wstring或的任何容器类型std::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';
  }
}

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

for (wchar_t const ch : str) {

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

(注意:如果您确实需要在这里处理其他类型的字符,请忽略我在此处所说的内容并改wchar_tauto. 这由您决定。)

现在,我们有了函数模板最终版本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. astatic_assert依赖于 A) std::is_same_v<>(与 相同std::is_same<>::value)和 B) 说明decltype()符以确保仅传入std::wstringstd::wstring_view字符串类型,并且
  2. 中的更多测试打印main(),包括 astd::vector<std::wstring>和 a 的测试打印std::vector<std::wstring_view>,以及链表:std::list<std::wstring_view>和无序集(散列集):std::unordered_set<std::wstring>

这是整个代码和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<std::wstring>std::vector<std::wstring_view>输入,这里有一个更有限的模板(同样,为了演示,这是两个任意的类型限制;您可以根据需要轻松调整或删除这些限制):

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

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

有了这个,强制它只接受std::vector<>容器类型(const T&上面对下面的更改const std::vector<T>&,就是全部):

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

然后,根据需要添加 astatic_assert以确保向量内的类型是std::wstringor std::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);
}

为什么跨度跨度不起作用:

std::span<T>本质上只是一个包含指向连续内存块的指针的结构。Cppreference.com 状态(强调添加):

类模板 span 描述了一个对象,该对象可以引用一个连续的对象序列,该序列的第一个元素在位置 0 处

正如我在此处关于跨度的其他答案中解释的那样(什么是“跨度”以及何时应该使用跨度?),它可能看起来像这样:

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在他的评论中所说。

参考:

  1. 刮痧工作:
    1. https://godbolt.org/z/s99dnzj8z
    2. https://godbolt.org/z/33vzTM787
  2. [我的回答]什么是“跨度”,我应该什么时候使用?
  3. https://www.learncpp.com/cpp-tutorial/an-introduction-to-stdstring_view/ - 很好地了解什么是 a 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<decltype(var), some_type>, "some msg");]使用 static_assert 检查传递给宏的类型
于 2021-05-09T05:08:40.280 回答
-6

更新:尽管自从看到它以来遭到了反对,但我还是在下面留下了答案,并且它下面的评论也很有价值,但这是我刚刚发布的另一个答案,我认为它具有价值和优点。


我根本不理解在这里使用跨度的愿望(如果我遗漏了什么,请帮助我理解),因为跨度的目的是包装和“C++-itize”(这有时已经是一种有争议的做法)C 风格的数组

为什么不改变这个:

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

对此?:

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

现在代码工作得很好(在 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
于 2021-05-08T19:05:28.870 回答