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

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

通用打印函数

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

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) {
        ret_str += ']';
    } else {
        ret_str.back() = ']';
    }
    return ret_str;

}

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

一级容器

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


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不支持复制,所以不显式指定返回值类型推导的话,编都编不过。

多级容器        

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

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

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

指针形式数组

        指针形式的数组需要包装成可迭代对象(C++20开始,#include <span>,然后直接将PtrView替换成std::span),注意第一个参数不要下意识写成const T* arr,以免非常量的指针不能匹配。然后调用上面的函数:

template <typename T>
struct PtrView {
  const T* begin() const { return first; }
  const T* end() const { return last; }
  const T* const first{};
  const T* const last{};
};

// #include <span>
template <typename T, typename Len,
          std::enable_if_t<std::is_integral<Len>::value>* = nullptr>
std::string SeqToStr(T* arr, Len len) {
  return SeqToStr(PtrView<T>{arr, arr + len});
  //return SeqToStr(std::span<T>{arr, arr + len});
}

自动区分级别

C++14实现

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

template <typename T>
using value_type_t = typename std::remove_reference_t<decltype(
    *std::begin(*static_cast<const T*>(nullptr)))>;

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

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

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

// 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来代替上面的using、Printable、一级和多级容器的重载,大意一样,但更为简洁:

template <typename Iterable>
std::string SeqToStr(const Iterable& arr) {
  using ele_type = typename std::remove_reference_t<decltype(
      *std::begin(*static_cast<const Iterable*>(nullptr)))>;
  if constexpr (requires(ele_type e) { std::cout << e; } &&
                !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);
    });
  }
}

测试代码

#include <string>
#include <sstream>
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <unordered_map>
#include <array>

struct Foo {
  char a{};
  float b{};
  const char *c{};
  Foo(char x, float y, const char *z) : a(x), b(y), c(z) {}
};

template <typename T>
auto &&operator<<(T &&ss, const Foo &f) {
  return ss << "{a=" << f.a << ",b=" << f.b << ",c=" << f.c << '}';
}

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}};
  int arr1[] = { 6, 7, 8, 9, 10 };
  const int arr2[3][5] = { {0,1,2,3,4},{5,6,7,8,9},{10,11,12,13,14} };
  const auto * const arr3 =  arr2;
  const int* const arr4 = arr1;
  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'}};
  std::vector<Foo> vecf{Foo('c', 3.3f, "foo"), Foo('d', 4.33f, "bar")};
  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;
  cout << SeqToStr(vecf) << endl;
  cout << SeqToStr(std::vector<bool>{ true, false, false, true }) << endl;
  cout << SeqToStr(arr1 + 1, 3) << endl;
  cout << SeqToStr(arr2, 2) << endl;
  cout << SeqToStr(arr2 + 1, 2) << endl;
  cout << SeqToStr(arr3 + 1, 2) << endl;
  cout << SeqToStr(arr4 + 2, 3) << endl;

}

   期望输出为    

[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}]
[{a=c,b=3.3,c=foo},{a=d,b=4.33,c=bar}]
[1,0,0,1]
[7,8,9]
[[0,1,2,3,4],[5,6,7,8,9]]
[[5,6,7,8,9],[10,11,12,13,14]]

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

#include <bitset>
int main() {
    using std::cout, std::endl;
    auto bs{ std::make_unique<std::bitset<65537>>(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 = std::make_unique<std::bitset<65537>>(*bs);
    bs1->set(8);
    bs1->set(10384);
    cout << SeqToStr(std::vector{ BitsetRange(*bs), BitsetRange(*bs1) }) << endl;
    auto vecb = std::make_unique<std::vector<std::bitset<65537>>>();
    vecb->push_back(*bs1);
    vecb->push_back(*bs);
    cout << SeqToStr(*vecb, [](auto&& ss, auto&& ele) -> decltype(auto) {
        return ss << SeqToStr(BitsetRange(ele));
        }) << endl;

}

期望输出为:

[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,8,63,64,65,264,364,664,6663,10384,64645,65536]]
[[2,3,8,63,64,65,264,364,664,6663,10384,64645,65536],[2,3,63,64,65,264,364,664,6663,64645,65536]]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值