3

I am trying to overload operator<<() (i.e. inserter operator) for stl containers (e.g vector, list, array) (i.e. any container that support range-based for loop and whose value_type also has overload for operator<<()). I have written the following template function

template <template <class...> class TT, class ...T>
ostream& operator<<(ostream& out, const TT<T...>& c)
{
    out << "[ ";

    for(auto x : c)
    {
        out << x << " ";
    }

    out << "]";
}

It works for vector and list. But it gives error when I try to call it for array

int main()
{
    vector<int> v{1, 2, 3};
    list<double> ld = {1.2, 2.5, 3.3};
    array<int, 3> aa = {1, 2, 3};

    cout << v << endl;  // okay
    cout << ld << endl; // okay
    cout << aa << endl; // error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
                        // cout << aa << endl;
                        //         ^
}

Why it does not work for array? Is there any work around to overcome this problem?

I have searched in the internet and SO to find if there is a way to overload operator<<() for stl containers. I have read answers in overloading << operator for c++ stl containers, but it does not answer my question. And answers in Pretty-print C++ STL containers seems complecated to me.

g++ version: g++ (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4

Compile command: g++ -std=c++11

army007
  • 551
  • 4
  • 20
  • 3
    Your operator fails to return a value despite not returning void. – eerorika Jul 26 '17 at 12:15
  • Well, you either go with the complicated answers from the question you linked (complication is required) or you write different overloads for different containers. There are only a handful containers after all. – DeiDei Jul 26 '17 at 12:15

2 Answers2

4

Look at template <class...> class TT. This parameter is for a template, any template that accepts any number of type parameters.

Now look at std::array<class T, std::size_t N>. This template doesn't accept a type as the second parameter. It accepts an integral constant.

So it cannot be an argument to the template function you defined, and template argument deduction fails on account of it.

As for making it work, the simplest way is to provide a (templated) overload that accepts only std::array. The template parameters will be the (deduced) array parameters.

template<typename T, std::size_t N>
ostream& operator<<(ostream& out, std::array<T, N> const& a)
{
    out << "[ ";

    for(auto x : a)
    {
        out << x << " ";
    }

    out << "]";
    return out;
}
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • With this overload it works with `std::array`. But does not with C style array. Do I need another overload for that? – army007 Jul 26 '17 at 12:26
  • 1
    @army007 - Sadly, yes. `(ostream& out, T (&a)[N])`. At this point, you may want to move the printing logic to a template that accepts iterators. And turn the *many* overloads to simple wrappers which forward to it. – StoryTeller - Unslander Monica Jul 26 '17 at 12:28
3

Your overload will not work for std::array because it does not match the template that std::array uses. Unlike all the other standard containers std::array has a non type template parameter in its template parameter list. It is defined like

template<typename T, std::size_t N>
struct array;

This will not work with template <template <class...> class TT, class ...T> as that only uses types and does not allow for the non type, std::size_t N, part.

If you add this overload it should work

template <class T, std::size_t N>
ostream& operator<<(ostream& out, std::array<T, N>& c)
{
    out << "[ ";

    for(auto x : c)
    {
        out << x << " ";
    }

    out << "]";

    return out;
}

Also note that you are not returning out in your overload. This is undefined behavior as you tell the compiler you are returning something but you fail to do so.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402