如何打印n维C++ STL嵌套容器,实现类似numpy的输出格式?
C++递归打印任意维度嵌套容器(模拟numpy风格输出)
我需要将以vector嵌套vector、std::array嵌套std::array形式表示的张量按照numpy的打印风格输出。我正在学习C++元编程,想通过函数模板递归遍历任意维度的嵌套容器,返回可直接用cout输出的字符串。
numpy输出示例如下:
>>> np.ones([2,2]) array([[1., 1.], [1., 1.]]) >>> np.ones([2,2,4]) array([[[1., 1., 1., 1.], [1., 1., 1., 1.]], [[1., 1., 1., 1.], [1., 1., 1., 1.]]]) >>> np.ones(4) array([1., 1., 1., 1.]) >>>
我此前参考了Stack Overflow上的问题《Print simply STL vectors of vectors recursively in C++》的已采纳回答,该方案对二维vector可以正常工作,但我将printContainerV2内部调用的printContainer改为printContainerV2以尝试支持三维vector时,出现编译报错。
问题代码
#include <iostream> #include <iterator> #include <vector> template <typename Iter, typename Cont> bool isLast(Iter iter, const Cont& cont) { return (iter != cont.end()) && (next(iter) == cont.end()); } template <typename T> struct is_cont { static const bool value = false; }; template <typename T,typename Alloc> struct is_cont<std::vector<T,Alloc> > { static const bool value = true; }; template <typename T> std::string printContainer(T const& container) { std::string str = "{"; for (auto it = std::begin(container); it != std::end(container); ++ it) if (isLast(it, container)) str = str + std::to_string(*it) + "}"; else str = str + std::to_string(*it) + ","; return str; } template<typename T> using if_not_cont = std::enable_if<!is_cont<T>::value, T>; template<typename T> using if_cont = std::enable_if<is_cont<T>::value, T>; template <typename T, typename std::enable_if<!is_cont<T>::value, T>::type* = nullptr> std::string printContainerV2(T const& container) { std::string str = "{"; for (auto it = std::begin(container); it != std::end(container); ++ it) if (isLast(it, container)) str = str + std::to_string(*it) + "}"; else str = str + std::to_string(*it) + ","; return str; } template <typename T, typename std::enable_if<is_cont<T>::value, T>::type* = nullptr> std::string printContainerV2(T const& container) { std::string str = "{"; for (auto it = std::begin(container); it != std::end(container); ++ it) if (isLast(it, container)) str = str + printContainerV2(*it) + "}"; else str = str + printContainerV2(*it) + ","; return str; } int main() { std::vector<int> A({2,3,6,8}); std::vector<std::vector<int>> M(2,A); std::vector<std::vector<std::vector<float>>> m3{{{1,2}, {3,4}},{{5,6}, {7,8}},{{1,2}, {5,9}}}; std::cout << is_cont<decltype(A)>::value << std::endl; // returns true ! // for (auto it = std::begin(M); it != std::end(M); ++ it) // { // std::cout << printContainer(*it) << std::endl; // works well std::vector<int> // std::cout << is_cont<decltype(*it)>::value << std::endl; // return false :( // } // Want to use this for printing a std::vector<std::vector<int>> std::cout << printContainerV2(M) << std::endl; // not working ! std::cout << printContainerV2(m3) << std::endl; // not working }
编译命令:clang++ --std=c++17 test.cpp
编译报错信息
test.cpp:45:20: error: no matching function for call to 'begin' for (auto it = std::begin(container); it != std::end(container); ++ it) ^~~~~~~~~~ test.cpp:59:29: note: in instantiation of function template specialization 'printContainerV2<int, nullptr>' requested here str = str + printContainerV2(*it) + "}"; ^ test.cpp:59:29: note: in instantiation of function template specialization 'printContainerV2<std::__1::vector<int, std::__1::allocator<int> >, nullptr>' requested here test.cpp:80:19: note: in instantiation of function template specialization 'printContainerV2<std::__1::vector<std::__1::vector<int, std::__1::allocator<int> >, std::__1::allocator<std::__1::vector<int, std::__1::allocator<int> > > >, nullptr>' requested here std::cout << printContainerV2(M) << std::endl; // not working ! ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/initializer_list:99:1: note: candidate template ignored: could not match 'initializer_list<type-parameter-0-0>' against 'int' begin(initializer_list<_Ep> __il) _NOEXCEPT ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1753:1: note: candidate template ignored: could not match '_Tp [_Np]' against 'const int' begin(_Tp (&__array)[_Np]) ^ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1771:1: note: candidate template ignored: substitution failure [with _Cp = const int]: member reference base type 'const int' is not a structure or union begin(_Cp& __c) -> decltype(__c.begin()) ^ ~ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:1779:1: note: candidate template ignored: substitution failure [with _Cp = int]: member reference base type 'const int' is not a structure or union begin(const _Cp& __c) -> decltype(__c.begin()) ^ ~ 1 error generated.
问题修复方案
错误根因
你的SFINAE分支逻辑写反了:
!is_cont<T>::value匹配的是int/float这类基础数值类型,不是容器,你在这个分支里调用std::begin遍历当然会报错,int本身没有迭代器接口。- 另外你现有的
is_cont仅支持vector,还需要补充std::array的特化,才能满足你要支持array嵌套的需求。
修复后的完整代码
#include <iostream> #include <iterator> #include <vector> #include <array> #include <string> #include <type_traits> // 判断是否是容器的最后一个元素 template <typename Iter, typename Cont> bool isLast(Iter iter, const Cont& cont) { return (iter != cont.end()) && (std::next(iter) == cont.end()); } // 类型萃取:判断是否是可遍历容器(vector/array) template <typename T> struct is_cont { static const bool value = false; }; // vector特化 template <typename T,typename Alloc> struct is_cont<std::vector<T,Alloc> > { static const bool value = true; }; // array特化 template <typename T, size_t N> struct is_cont<std::array<T,N> > { static const bool value = true; }; // 分支1:不是容器(基础数值类型),直接转字符串返回 template <typename T, typename std::enable_if<!is_cont<T>::value, T>::type* = nullptr> std::string printContainerV2(T const& val) { return std::to_string(val); } // 分支2:是容器,递归遍历子元素拼接字符串 template <typename T, typename std::enable_if<is_cont<T>::value, T>::type* = nullptr> std::string printContainerV2(T const& container) { std::string str = "["; for (auto it = std::begin(container); it != std::end(container); ++ it) { str += printContainerV2(*it); if (!isLast(it, container)) { str += ", "; } } str += "]"; return str; } // 外层包装,模拟numpy的array前缀 template<typename T> void printNumpyStyle(T const& t) { std::cout << "array(" << printContainerV2(t) << ")" << std::endl; } int main() { std::vector<int> A({2,3,6,8}); std::vector<std::vector<int>> M(2,A); std::vector<std::vector<std::vector<float>>> m3{{{1,2}, {3,4}},{{5,6}, {7,8}},{{1,2}, {5,9}}}; std::array<std::array<int,2>,3> arr = {1,2,3,4,5,6}; printNumpyStyle(A); printNumpyStyle(M); printNumpyStyle(m3); printNumpyStyle(arr); return 0; }
运行输出示例
array([2.000000, 3.000000, 6.000000, 8.000000]) array([[2.000000, 3.000000, 6.000000, 8.000000], [2.000000, 3.000000, 6.000000, 8.000000]]) array([[[1.000000, 2.000000], [3.000000, 4.000000]], [[5.000000, 6.000000], [7.000000, 8.000000]], [[1.000000, 2.000000], [5.000000, 9.000000]]]) array([[1.000000, 2.000000], [3.000000, 4.000000], [5.000000, 6.000000]])
如果需要完全对齐numpy的缩进和换行效果,只需要在递归的时候额外传递当前维度的缩进层级参数,输出时对应补全空格即可,逻辑和上述递归框架完全一致。
内容的提问来源于stack exchange,提问作者patro
相关产品推荐
相关产品推荐

