首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在C++20/C++23中是否有更好的通用打印容器?

在C++20/C++23中是否有更好的通用打印容器?
EN

Stack Overflow用户
提问于 2022-06-21 14:40:23
回答 3查看 369关注 0票数 1

我使用以下代码打印通用C++容器并排除字符串(感谢,所以回答)。

代码语言:javascript
复制
#include <iostream>
#include <vector>

template <typename T,
          template <typename ELEM, typename ALLOC = std::allocator<ELEM>>
          class Container>
std::ostream &operator<<(std::ostream &out, const Container<T> &container) {
  out << "[";
  bool first = true;
  for (const auto &elem : container) {
    if (first)
      first = false;
    else
      out << ", ";
    out << elem;
  }
  return out << "]";
}

int main() {
  std::vector<std::vector<int>> v{{1, 2}, {3, 4}};
  std::cout << v << "\n";
  return 0;
}

,它正确地打印2d矢量。

代码语言:javascript
复制
[[1, 2], [3, 4]]

也不会弄乱输出字符串。有清洁剂(概念为基础)吗?用C++20或C++23编写模板的方法?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2022-06-21 15:12:49

对于概念,您可能只需要容器/范围类型和丢弃类似字符串的类型,如下所示:

代码语言:javascript
复制
template <typename Container>
requires std::ranges::input_range<const Container>
     && (!std::convertible_to<const Container, std::string_view>)
std::ostream &operator<<(std::ostream &out, const Container& container)
{
  out << "[";
  const char* sep = "";
  for (const auto &elem : container) {
    out << sep << elem;
    sep = ", ";
  }
  return out << "]";
}

演示

请注意,泛型模板至少应该包含一种用户类型。否则,它可能与将来用于std (或其他库)容器的operator<<冲突.

票数 2
EN

Stack Overflow用户

发布于 2022-06-21 15:16:53

更好的方法是使用{fmt}

代码语言:javascript
复制
#include <fmt/ranges.h>
#include <vector>

int main() {
  std::vector<std::vector<int>> v{{1, 2}, {3, 4}};
  fmt::print("{}\n", v); // prints [[1, 2], [3, 4]]
}

will in C++23将以std::print的形式提供。

实际上,您必须处理字符串的两个问题:

  • 不需要包含字符串文本(如"[")
  • 字符串(如实际的std::string)不需要包括在内

您可以通过拒绝任何可转换为string_view的东西来消除这两种情况,而不是要求容器类型的特定形状,如C<T, A=?> (正如我所提到的,这也消除了std::arraystd::spanstd::map等东西)。这将允许您打印std::arraystd::span,但是对于std::map,您必须处理.如何打印std::pair?然后如何打印一个std::pair,其中一个元素是范围?

那你把这个操作员超载放哪儿了?您不能将它放在std中(这是不允许的)。但是您也不能把它放在std中,因为否则真正的代码可能找不到它。

这是一个复杂的问题,这就是为什么最好让一个好的库(比如{fmt})来处理这个问题。

票数 8
EN

Stack Overflow用户

发布于 2022-06-21 15:49:53

您不应该在您不拥有的类型上重载操作符;您不应该拥有std容器。而且您只应该在关联的命名空间中重载操作符;不允许将重载注入std命名空间。

这意味着你不应该做你正在做的事情。

代码语言:javascript
复制
template<class T>
struct pretty_print {
  T&& t;
};
template<class T>
pretty_print(T&&)->pretty_print<T>;

template<typename T>
concept printable = requires(T t) {
  { std::declval<std::ostream&>() << t } -> std::same_as<std::ostream&>;
};
template<printable T>
std::ostream& operator<<( std::ostream& os, pretty_print<T> pp ) {
  return os << pp.t;
}

这让你可以

代码语言:javascript
复制
void test1() {
  int x=42;
  std::cout << pretty_print{x} << "\n";
}

对于任何可打印类型的x。然而,我们现在可以为不可打印的类型添加重载。

代码语言:javascript
复制
template<std::ranges::input_range T>
requires (not printable<T>)
std::ostream& operator<<( std::ostream& os, pretty_print<T> pp ) {
  using std::begin; using std::end;
  os << "[";
  for (auto&& e : pp.t | std::ranges::views::take(1))
  {
    os << pretty_print{e};
  }
  for (auto&& e : pp.t | std::ranges::views::drop(1))
  {
      os << ", ";
      os << pretty_print{e};
  }
  os << "]";
  return os;
}

void test2() {
  std::vector<int> v0 = {1,2,3};
  std::cout << pretty_print{v0} << "\n";
  std::vector<std::vector<int>> v1 = {{1},{1,2},{1,2,3}};
  std::cout << pretty_print{v1} << "\n";
}

现在,如果我们想支持对:

代码语言:javascript
复制
template<typename T>
concept pairlike = requires(T t) {
  { t.first };
  { t.second };
};

template<pairlike P>
std::ostream& operator<<( std::ostream& os, pretty_print<P> pp ) {
  os << "{ ";
  os << pretty_print{pp.t.first} << ", ";
  os << pretty_print{pp.t.second} << " }";
  return os;
}

现在地图开始工作了。

代码语言:javascript
复制
void test3() {
  std::map<int, int> m = {{0,1}, {1,2}, {2,4}};
  std::cout << pretty_print{m}  << "\n";
}

实例化

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72702736

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档