在日志打印中,往往有打印一个数组、集合等容器中的每个元素的需求,这些容器甚至可能嵌套起来,如果每个地方都用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博客https://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]]