C++20/C++23中是否有更简洁的通用容器打印实现方案?
C++20引入的concept特性完全可以写出更简洁、适配性更强的通用容器打印实现,相比原来的模板模板参数写法,不需要硬编码容器的模板参数结构,还能天然规避和字符串类型输出的冲突。
实现思路
核心是利用标准库<ranges>提供的现成范围概念,自定义一个约束:类型是可遍历的输入范围,同时不属于各类字符串类型,再用这个约束去重载<<运算符即可。
这种写法相比旧的模板模板参数实现有几个明显优势:
- 适配范围更广:不需要关心容器的模板参数数量、是否带分配器/比较器等额外模板参数,所有符合输入范围规范的标准容器、自定义范围类型都能自动支持打印。旧实现只能匹配「元素类型+分配器」双参数模板的容器,对
std::array(带非类型模板参数)、std::set/std::map(带比较器等额外模板参数)这类容器都无法生效。 - 冲突规避逻辑直观:只需要在concept里增加排除条件即可隔离字符串等不需要特殊格式化的类型,不会和标准库已有输出重载产生歧义。
- 编译报错更友好:concept的约束会在模板实例化前做检查,不会出现无约束模板常见的上百行模板实例化错误栈。
C++20 完整实现
#include <iostream> #include <vector> #include <ranges> #include <string> #include <string_view> #include <array> #include <set> #include <map> // 定义可打印范围的concept:可遍历,且排除会产生冲突的字符串类型 template <typename T> concept PrintableRange = std::ranges::input_range<T> && !std::convertible_to<T, std::string_view> && !std::convertible_to<T, const char*>; // 受concept约束的通用输出重载 template <PrintableRange R> std::ostream& operator<<(std::ostream& out, const R& range) { out << "["; bool first_elem = true; for (const auto& elem : range) { if (!first_elem) { out << ", "; } first_elem = false; out << elem; } return out << "]"; } int main() { // 原场景的二维vector输出正常 std::vector<std::vector<int>> v{{1, 2}, {3, 4}}; std::cout << v << "\n"; // 输出 [[1, 2], [3, 4]] // 旧写法适配不了的容器也能直接支持 std::array<int, 3> arr{1, 2, 3}; std::cout << arr << "\n"; // 输出 [1, 2, 3] std::set<int> s{5, 3, 7}; std::cout << s << "\n"; // 输出 [3, 5, 7] std::map<std::string, int> m{{"a", 1}, {"b", 2}}; std::cout << m << "\n"; // 输出 [[a, 1], [b, 2]] // 字符串输出完全不受干扰 std::string str = "hello world"; std::cout << str << "\n"; // 正常输出 hello world const char* cstr = "test"; std::cout << cstr << "\n"; // 正常输出 test return 0; }
C++23 进一步简化
C++23新增的std::views::join_with可以直接处理元素间的分隔符插入逻辑,不需要手动维护首元素标记,代码可以更精简:
#include <ranges> template <PrintableRange R> std::ostream& operator<<(std::ostream& out, const R& range) { out << "["; for (const auto& part : range | std::views::join_with(", ")) { out << part; } return out << "]"; }
这种写法不会和标准库已有operator<<重载产生歧义:对于基本类型、字符串等已经有默认输出实现的类型,编译器会优先选择匹配度更高的标准库重载,只有当类型符合可打印范围约束、且无现成输出实现时,才会调用通用打印逻辑。
内容的提问来源于stack exchange,提问作者Raziman T V
相关产品推荐
相关产品推荐

