Printing elements of vector or array

Working with vectors it’s nice to have a quick way to print the values contained. Something like:

std::vector<int> vec{1,2,3,4,5};
std::cout << vec << '\n';
//[1, 2, 3, 4, 5]

Unfortunately this doesn’t work out of the box with C++… we first need to overload the << operator for vector.

std::ostream &operator<<(std::ostream &os, const std::vector<int> &vec) {
    auto it = vec.cbegin();
    os << '[' << *it++;
    while (it != vec.cend())
        std::cout << ", " << *it++;
    return std::cout << ']';
}

Now the code in the first block works fine, but only for vectors of int. Wouldn’t it be great if we could make the code a bit more general? As a first step we can template the value type of the vector.

template<typename T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &vec) {
    auto it = vec.cbegin();
    os << '[' << *it++;
    while (it != vec.cend())
        std::cout << ", " << *it++;
    return std::cout << ']';
}

Which is rather straight forward, and if all you work with is vectors then it’s ok. However, we can get better code reuse if the function also in addition to vectors accepted arrays, lists, and other containers.

Replacing the std::vector<T> with T wont work since our template would then match every type T, making many other overloads ambiguous. For this we need to find a way to enable the template only for the types we are interested in. Something that leads us down the dark path of template meta programming. In the end the idea we have is to enable the function only for containers. Something that could be expressed using enable_if in this way:

template <typename Container>
auto operator<<(std::ostream &os, const Container &con) ->
    typename std::enable_if<is_container<Container>::value,
                            std::ostream &>::type {
    if (con.empty())
        return os << "[]";
    auto it = con.cbegin();
    os << '[' << *it++;
    while (it != con.cend())
        os << ", " << *it++;
    return os << ']';
}

Except that there isn’t any is_container helper in std::type_traits. With more than some inspiration from this answer (https://stackoverflow.com/a/31207079) we can try to write our own version and also make it C++11 compatible.

There will be a lot to take in, in the following lines but we start with the false case, and a helper class with a variadic template.

template <typename T, typename _ = void>
struct is_container : std::false_type {};

template <typename... Ts> struct is_container_helper {};

Having this in place we can then get on with the logic of testing if our type T is a container or not using std::conditional. You can adapt the members and methods that you want to test according to your use case.

template <typename T>
struct is_container<
    T, typename std::conditional<
           false,
           is_container_helper<typename T::value_type, typename T::size_type,
                               typename T::iterator, typename T::const_iterator,
                               decltype(std::declval<T>().size()),
                               decltype(std::declval<T>().begin()),
                               decltype(std::declval<T>().end()),
                               decltype(std::declval<T>().cbegin()),
                               decltype(std::declval<T>().cend())>,
           void>::type> : public std::true_type {};

The full source code with a working example is available at: https://github.com/erikfrojdh/article-support/blob/master/print_container.cpp

More reading:

Update: Added tests in the git repository and checking for empty container when printing

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.