日志打印中对容器(包括多级容器)的通用输出

在日志打印中,往往有打印一个数组、集合等容器中的每个元素的需求,这些容器甚至可能嵌套起来,如果每个地方都用for循环打印,将会特别麻烦。基于这种需求,作者尝试实现一个通用的打印函数SeqToStr(),将容器序列化。

通用打印函数

首先这个函数需要接收这个容器arr,然后接收一个打印函数的回调func,形式如下:

cpp 复制代码
template <typename Iterable, typename PrintFunc>
std::string SeqToStr(const Iterable& arr, PrintFunc&& func) {
    std::stringstream ss;
    ss << '[';
    for (auto&& i : arr) func(ss, i) << ",";
    auto ret_str = ss.str();
    if (ret_str.length() == 1) {
        return std::move(ret_str += ']');
    } else {
        ret_str.back() = ']';
        return std::move(ret_str);
    }
}

每个元素被逗号隔开,所有元素被方括号框住。

一级容器

当然,每次调用都写一个func也是很难用的,所以对于一级容器,可以写一个重载,用默认的打印函数:

cpp 复制代码
inline std::string SeqToStr(const Iterable& arr) {
    return SeqToStr(
        arr, [](auto& ss, auto&& ele) -> decltype(auto) { return ss << ele; });
}
// p

ss是一个支持流操作符<<(stringstream,basic_ostream等)的参数,返回值decltype(auto)防止<<返回的引用被当做值传回,实际上basic_ostream不支持复制,所以不显式指定返回值类型推导的话,编都编不过。

多级容器

然后对于多级容器,还需要一个递归的重载:

cpp 复制代码
inline std::string SeqToStr(const Iterable& arr) {
    return SeqToStr(arr, [](auto&& ss, auto&& arr) -> decltype(auto) {
        return ss << SeqToStr(arr);
        });
}

最后就是需要区分一级和多级容器这两种情况。

自动区分级别

C++14实现

对于C++14,只能用SFINAE特性了,要知道容器里元素的类型,以及元素类型是否可直接打印。获得容器的元素类型,只需要获得std::begin(arr)的返回值类型即可,这样写避免某些自定义的容器不支持默认构造,或者没有begin()成员函数。

cpp 复制代码
template <typename T>
struct ValueType {
  using type = std::decay_t<decltype(*std::begin(*static_cast<T*>(nullptr)))>;
};
template <typename T, std::size_t Size>
struct ValueType<T[Size]> {
  using type = T;
};
template <typename T>
using value_type_t = typename ValueType<T>::type;

然后就是判断元素是否可以打印,这里以是否能被cout<<接收为标准,同时,考虑到原生数组可以以指针的形式被打印,所以判断的时候,需要排除这种情况:

cpp 复制代码
template <typename T>
class Printable {
  template <typename U>
  static std::true_type test(decltype(std::cout << U{}, int{})*);
  template <typename U>
  static std::false_type test(...);

 public:
  static constexpr bool value =
      !std::is_array<T>::value &&
      std::is_same<decltype(test<T>(nullptr)), std::true_type>::value;
};

最终,上面一级和多级的重载,可以加上条件模板了:

cpp 复制代码
// print an iterable struct(vector<int>, set<int>, int[3], array<int,3>, etc)
template <typename Iterable,
          std::enable_if_t<Printable<value_type_t<Iterable>>::value, int> = 0>
std::string SeqToStr(const Iterable& arr) {
  return SeqToStr(
      arr, [](auto& ss, auto&& ele) -> decltype(auto) { return ss << ele; });
}
// print an multi-dimensional struct(int[3][3], set<vector<set<int>>>, etc)
template <typename Iterable,
          std::enable_if_t<!Printable<value_type_t<Iterable>>::value, int> = 0>
std::string SeqToStr(const Iterable& arr) {
  return SeqToStr(arr, [](auto&& ss, auto&& arr) -> decltype(auto) {
    return ss << SeqToStr(arr);
  });
}

C++20实现

对于C++20,可以用concept来代替上面的Printable、一级和多级容器的重载,大意一样,但更为简洁:

cpp 复制代码
template <typename Iterable>
std::string SeqToStr(const Iterable& arr) {
    using ele_type = value_type_t<Iterable>;
    if constexpr (requires{std::cout << ele_type{}; } && 
                  !std::is_array_v<ele_type>) {
        return SeqToStr(
            arr, [](auto& ss, auto&& ele) -> decltype(auto) { return ss << ele; });
    } else {
        return SeqToStr(arr, [](auto&& ss, auto&& arr) -> decltype(auto) {
            return ss << SeqToStr(arr);
            });
    }

}

测试代码

cpp 复制代码
#include <string>
#include <sstream>
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <unordered_map>
#include <array>

int main() {
  using std::cout, std::endl;
  std::initializer_list<int> list{1, 2, 3, 4, 5};
  int arr[][2]{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}};
  std::vector<int> vec{2, 4, 6, 8, 0};
  std::array<std::vector<int>, 2> arr_vec{std::vector<int>{1, 2, 3, 4, 5},
                                          std::vector<int>{11, 22, 33, 44, 55}};
  std::set<int> set{1, 3, 5, 7};
  std::unordered_map<int, char> umap{{1, 'a'}, {2, 'b'}, {3, 'c'}};
  cout << SeqToStr(std::initializer_list{1, 2, 3, 4, 6}) << endl;
  cout << SeqToStr(list) << endl;
  cout << SeqToStr(arr) << endl;
  cout << SeqToStr(vec) << endl;
  cout << SeqToStr(arr_vec) << endl;
  cout << SeqToStr(set) << endl;
  cout << SeqToStr(umap, [](auto&& ss, auto&& ele) -> decltype(auto) {
    return ss << "{key=" << ele.first << ",value=" << ele.second << '}';
  }) << endl;
}

期望输出为

cpp 复制代码
[1,2,3,4,6]
[1,2,3,4,5]
[[1,2],[2,3],[3,4],[4,5],[5,6]]
[2,4,6,8,0]
[[1,2,3,4,5],[11,22,33,44,55]]
[1,3,5,7]
[{key=1,value=a},{key=2,value=b},{key=3,value=c}]

、如果包含了下面这篇文档对bitset遍历的实现,用基于范围的for循环遍历bitset的所有有效位置_bitset 遍历-CSDN博客https://blog.csdn.net/sinat_39088557/article/details/116431911 还可以这么用:

cpp 复制代码
#include <bitset>
int main() {
    using std::cout, std::endl;
    std::bitset<65537> bs{ 0xc };
    bs.set(63);
    bs.set(64);
    bs.set(65);
    bs.set(264);
    bs.set(364);
    bs.set(664);
    bs.set(6663);
    bs.set(64645);
    bs.set(65536);

    cout << SeqToStr(BitsetRange(std::bitset<65537>(0xfabcd0))) << endl;
    cout << SeqToStr(BitsetRange(bs)) << endl;

    auto bs1 = bs;
    bs1.set(7);
    bs1.set(63, false);
    cout << SeqToStr(std::vector{BitsetRange(bs), BitsetRange(bs1)});

}

期望输出为:

cpp 复制代码
[4,6,7,10,11,12,13,15,17,19,20,21,22,23]
[2,3,63,64,65,264,364,664,6663,64645,65536]
[[2,3,63,64,65,264,364,664,6663,64645,65536],[2,3,7,64,65,264,364,664,6663,64645,65536]]
相关推荐
聚美智数32 分钟前
网安查询-备案查询-网络安全查询API接口介绍
网络·数据库·web安全
一个向上的运维者1 小时前
Kubernetes 网络三部曲:CNI、kube-proxy 与 CoreDNS 是如何协作的?
网络·容器·kubernetes
2301_815091012 小时前
TCP 并发服务器模型:4 种 IO 模型完整梳理
服务器·网络·tcp/ip
斑马1392 小时前
Linux软件编程学习笔记(十二)——TCP并发服务器模型
java·服务器·网络
学着改变2752 小时前
2026便携式超声波流量计性能白皮书 户外巡检适用性横评
大数据·网络·人工智能·科技·产品运营·量子计算
rcms152702692183 小时前
Novellus Systems 02-033134-00晶圆底座加热器
网络
马立杰3 小时前
IE241210_路由安全技术(三层安全)
网络·安全
马立杰3 小时前
IE241211_路由安全技术(三层安全)
网络·安全·智能路由器
迪康Defender3 小时前
公用电脑责任追溯难?迪康端点安全一体化管理系统用户模式详解
java·运维·开发语言·网络·其他·安全
迪康Defender4 小时前
迪康端点安全一体化管理系统文档安全:构建企业文档资产全链路防护体系
运维·网络·安全·web安全·终端安全管理