4

Please consider the following code. I'm trying to output a vector of vectors to an ostream.

#include <iterator>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

template<typename T>
std::ostream &operator <<(std::ostream &os, const std::vector<T> &v) {
    using namespace std;
    copy(v.begin(), v.end(), ostream_iterator<T>(os, "\n"));
    return os;
}

int main() {
    using namespace std;
    vector<string> v1;
    cout << v1;
    vector<vector<string> > v2;
    cout << v2;
    return 0;
}

The statement where I output a vector of strings works. The one where I output a vector of vectors of strings doesn't. I'm using g++ 4.7.0. I've tried w/ & w/o the -std=c++11 flag. In C++11 mode, it gives me this line in the half-page of errors.

error: cannot bind 'std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::ostream_type {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'

I don't think I understand what it means. Could someone explain to me? I more or less know what an rvalue reference is, but I don't see why std::basic_ostream<char> wouldn't bind to std::basic_ostream<char>&&. Maybe I don't know it well enough. And is there a better way to do this?

Thanks in advance.

Ashley
  • 833
  • 1
  • 5
  • 15

2 Answers2

8

The error you're getting is a bit misleading. When I tried to compile your program I had to dig into the template vomit quite a bit, and I ended up with what I thought was going on:

error: no match for 'operator<<' in '*((std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >*)this)->std::ostream_iterator<std::vector<std::basic_string<char> >, char, std::char_traits<char> >::_M_stream << __value'

Basically, when you called the copy algorithm, it used the output iterator, which used the << operator from within namespace std. Once there, lookup dictates that it try to find an overload for the template vector<> in the std namespace (because that's where IT resides).

So what you need to do is declare your stream operator for the vector template in namespace std. Surround your code with namespace std {} and see what happens...

It should be noted that what you're doing is basically modifying std::vector<> and adding behavior to it that wasn't there before. Doing this is non-standard, undefined, and can easily get in your way. You might consider other options.


I was wrong about this being a koenig lookup thing. It's not, the issue is name hiding similar to what occurs in classes here you declare an overload of something in a base (not an override).

The standard namespace declares several '<<' operators. These are basically functions named operator <<. In essence what you have is this:

void fun(int);

namespace Test {

  void fun() { fun(3); }

}

int main() {
    Test::fun();
}

Note that you can use fun(int) from the global namespace or any namespace that does not have any function named fun in it. You can't use it from the Test namespace.

This is why your use of operator << declared globally works fine from the global namespace but not from within the std namespace. The std namespace already has things named the same thing as the overload you're trying to provide and so that overload is hidden from all things within std. If you could put a using declaration there things would be different.

Edward Strange
  • 38,861
  • 7
  • 65
  • 123
  • Putting things in the std namespace not permitted by the standard is it? Regardless, I highly doubt it would actually cause a problem in any real implementation, and it seems to work for me. – Benjamin Lindley May 08 '12 at 15:41
  • Yup, that worked! Thanks! I don't fully understand, though. If it can find the overload for vector, where T is string, in the global namespace, how come it can't find the overload in the 2nd level of the 2-level lookup? Is the output iterator code in the std namespace written to look for std::operator < – Ashley May 08 '12 at 16:00
  • It's undefined behavior, but you gotta do what you gotta do. If it was defined already it would work already. – Edward Strange May 08 '12 at 16:01
  • @Ashley - has to do with the scope of the call as well. Name lookup is kind of a complicated mess, especially wrt templates. Have a read about koenig lookup and other name lookup rules. – Edward Strange May 08 '12 at 16:02
  • @CrazyEddie, tks. Anyone else, [Wikipedia](http://en.wikipedia.org/wiki/Argument-dependent_name_lookup) has a pretty good explanation. You'll see that the global namespace isn't searched in this case. – Ashley May 09 '12 at 14:33
4

You need this utility library:


If you want to do this yourself (so as to teach yourself), then you need to define two overloads as:

  • For std::vector<T>:

    template<typename T>
    std::ostream &operator <<(std::ostream &os, const std::vector<T> &v) {
       using namespace std;
       copy(v.begin(), v.end(), ostream_iterator<T>(os, "\n"));
       return os;
    }
    
  • For std::vector<std::vector<T>>:

    template<typename T>
    std::ostream &operator <<(std::ostream &os, const std::vector<std::vector<T>> &v) {
       using namespace std;
    
       //NOTE: for some reason std::copy doesn't work here, so I use manual loop
       //copy(v.begin(), v.end(), ostream_iterator<std::vector<T>>(os, "\n"));
    
       for(size_t i = 0 ; i < v.size(); ++i)
            os << v[i] << "\n";
       return os;
    }
    

If you have these overloads, then they together will handle these cases recursively:

std::vector<int>  v;
std::vector<std::vector<int>>  vv;
std::vector<std::vector<std::vector<int>>>  vvv;
std::vector<std::vector<std::vector<std::vector<int>>>>  vvvv;

std::cout << v << std::endl; //ok
std::cout << vv << std::endl; //ok
std::cout << vvv << std::endl; //ok
std::cout << vvvv << std::endl; //ok
Community
  • 1
  • 1
Nawaz
  • 327,095
  • 105
  • 629
  • 812
  • Is there something else you're doing to get this to work? Because it doesn't work for me. http://ideone.com/A2d67 – Benjamin Lindley May 08 '12 at 15:35
  • 1
    @BenjaminLindley: I don't see any reason why it shouldn't work. Maybe a compiler bug? Anyway, the manual loop works fine. – Nawaz May 08 '12 at 15:52
  • The other version didn't work, because the `ostream_iterator` is in the `std` namespace, as is `vector`. As such, ADL fails to find the `operator < – Dave S May 08 '12 at 15:55
  • @DaveS: I am not convinced. Why does the manual loop work then? How does it find `operator< – Nawaz May 08 '12 at 15:57
  • @Nawaz: My guess is that it's because your `operator< – Dave S May 08 '12 at 16:01
  • @Nawaz - if you avoid `std::copy` in the first function, you don't need the second. – Robᵩ May 08 '12 at 16:50
  • I've spent some time looking at the standard and doing some tests. My answer now reflects my findings and explains why one works and the other does not. Actually has nothing to do with ADL. – Edward Strange May 08 '12 at 16:51