? ? ? ? 在日志打印中,往往有打印一個數組、集合等容器中的每個元素的需求,這些容器甚至可能嵌套起來,如果每個地方都用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) {return std::move(ret_str += ']');} else {ret_str.back() = ']';return std::move(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++14實現
????????對于C++14,只能用SFINAE特性了,要知道容器里元素的類型,以及元素類型是否可直接打印。獲得容器的元素類型,只需要獲得std::begin(arr)的返回值類型即可,這樣寫避免某些自定義的容器不支持默認構造,或者沒有begin()成員函數。
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<<接收為標準,同時,考慮到原生數組可以以指針的形式被打印,所以判斷的時候,需要排除這種情況:
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來代替上面的Printable、一級和多級容器的重載,大意一樣,但更為簡潔:
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);});}}
測試代碼
#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;
}
? ?期望輸出為 ? ?
[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? ? ? ? 還可以這么用:
#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)});}
期望輸出為:
[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]]