简体   繁体   中英

How to print a n-dimensional c++ STL container? Container is array of arrays or vectors of vectors

I have a code where I want to display tensor represented as vectors of vectors or std::arrays of std::arrays. The intention is to print them the way numpy prints them. I am still learning meta programming in c++ and wanted to explore how to print the n-dim container using function template that can take this container of containers and iterate through it recursively and return a string which I can later cout.

Numpy example:

>>> 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.])
>>> 

Tried so far : I tried the response accepted here as the response:

Print simply STL vectors of vectors recursively in C++

It did work for me for 2 dim vectors but the compilation failed for me with 3d vectors when I changed the call to printContainer() to printContainerV2() inside printContainerV2() :

Code :

#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
}

Command : clang++ --std=c++17 test.cpp test.cpp is the name of the code above.

I got this error :

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.

For nested containers in C++17, differentiating types, you could do something like:

#include <bits/stdc++.h>
using namespace std;

template <typename, typename = void> constexpr bool is_iterable{};
template <> constexpr bool is_iterable<string> = false;
template <typename T, size_t N> constexpr bool is_iterable<T[N]> = true;
template <typename T> constexpr bool is_iterable<T, void_t<decltype(declval<T>().begin()), decltype(declval<T>().end())>> = true;

template <typename> constexpr bool is_set{};
template <typename T> constexpr bool is_set<set<T>> = true;
template <typename T, typename U> constexpr bool is_set<map<T, U>> = true;

template <typename> constexpr bool is_tuple{};
template <typename... T> constexpr bool is_tuple<tuple<T...>> = true;
template <typename T, typename U> constexpr bool is_tuple<pair<T, U>> = true;

inline string to_string(string s) { return '"' + s + '"'; }
inline string to_string(char c) { return '\'' + string(1, c) + '\''; }

template<typename T>
string print(const T& t, string&& s = string(), size_t depth = 0) {
    constexpr size_t TAB_SPACES = 2;
    if constexpr (!is_tuple<T> and !is_iterable<T>) {
        if (s.back() == '\n') s.pop_back();
        s += to_string(t) + ", ";
    } else {
        pair<string, string> braces = is_tuple<T> ? pair("(", ")") : is_set<T> ? pair("{", "}") : pair("[", "]");
        s += string(TAB_SPACES * depth, ' ') + braces.first + '\n';
        if constexpr (is_tuple<T>)
            if constexpr (tuple_size_v<T> == 0) s.push_back('\0');
            else apply([&s, depth](const auto&... x){ ((s = print(x, move(s), depth + 1)), ...); }, t);
        else if (begin(t) == end(t)) s.push_back('\0');
        else for_each(begin(t), end(t), [&s, depth](const auto& x){ s = print(x, move(s), depth + 1); });
        s.erase(s.length() - 2);
        if (s.back() == ')' or s.back() == '}' or s.back() == ']') s += '\n' + string(TAB_SPACES * depth, ' ');
        s += braces.second + ",\n";
    }
    if (depth == 0) cout << s.erase(s.length() - 2) << endl;
    return move(s);
}

Then:

vector<map<int, string>> ex = {{{1, "a"}, {2, "b"}}};
print(ex);

Will print:

[
  {
    (1, "a"),
    (2, "b")
  }
]

Used for a scripting template where simplicity and no external deps took priority. Production probably better served by a library.

Here is a simple way to print vectors of any dimension:

template<typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T> &vec){
    std::cout << "[ ";
    for(const auto& t: vec){
        std::cout << t << " ";
    }
    std::cout << "] ";
    return out;
}

For any other container, you can do the exact same thing.

Use:

int main()
{
    std::vector<int> v{1,2,3};
    std::cout << v << std::endl;
    std::vector<std::vector<std::vector<int>>> v_ds
        {
            {{{1,2,3},{4,5,6}},{{7,8},{9,10}}, {{1,2,3},{4,5,6}},{{7,8},{9,10}}}
        };
    std::cout << v_ds << std::endl;
    return 0;
}

When your first printContainerV2 is enabled, !is_cont<T>::value is true , which means that your T is no longer a container type at this time. In your example, they are int , so you cannot call std::begin on the int , you should directly return std::to_string(value) .

template <typename T, typename std::enable_if<!is_cont<T>::value, T>::type* = nullptr>
std::string printContainerV2(T const& value)
{
    return std::to_string(value);
}

Demo.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM