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

在日志打印中,往往有打印一个数组、集合等容器中的每个元素的需求,这些容器甚至可能嵌套起来,如果每个地方都用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]]
相关推荐
jiecy1 小时前
路由器虚拟化之VRF(vpn-instance)和动态路由配置实例
运维·网络
快快小毛毛1 小时前
弹性伸缩高性能计算服务一一黑石裸金属服务器
运维·服务器·网络·数据库·安全
ET、小涵1 小时前
深度学习Week19——学习残差网络和ResNet50V2算法
网络·深度学习·学习
国中之林2 小时前
【qt】如何获取网卡的IP地址?
服务器·c++·qt·网络协议·学习·tcp/ip
SeaBreeze__2 小时前
中小企业适用的HTTPS证书
网络·网络协议·http·https·ssl
节点小宝2 小时前
玩转内网穿透详细教程,收藏这一篇就够了
服务器·网络·科技·物联网·远程工作
Canon_YK3 小时前
源代码防泄漏的制胜法宝——沙箱
网络·安全·web安全
yoloGina3 小时前
机器人外呼相比人工外呼优势有哪些
网络·人工智能·机器人·软件构建·软件需求
xiandong203 小时前
240701_昇思学习打卡-Day13-Vision Transformer图像分类
网络·图像处理·人工智能·深度学习·学习·分类·transformer
阿猿收手吧!3 小时前
【Linux】TCP协议【下二】{流量控制/滑动窗口/延迟应答/捎带应答/拥塞控制}
linux·网络·网络协议·tcp/ip