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