Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I instantiate operator<<(ostream&, vector<T>&) with T=vector<int>?

In thinking about C++ iterator question, I wrote this sample program:

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

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

int main()
{
    std::vector<int> v(3);
    std::vector<std::vector<int> > vv(3, v);
    std::cout << v << "\n"; // this line works
    std::cout << vv << "\n"; // this line produces error
}

I compile this program with gcc and get the typical 100 lines of errors. The relevant part, I believe, is:

it.cc:19: instantiated from here

/usr/include/c++/4.4/bits/stream_iterator.h:191: error: no match for ‘operator<<’ in ‘((std::ostream_iterator >, char, std::char_traits >)this)->std::ostream_iterator >, char, std::char_traits >::_M_stream << __value’

Why does this fail? In my templated operator<<, I try to specify that any vector, regardless of type, is printable. So why doesn't std::vector<std::vector<>> print?

EDIT: Using the following code in the template function makes it work

#if 0
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
#else
    for(typename std::vector<T>::const_iterator it = v.begin();
        it != v.end();
        it++) {
        os<<(*it)<<", ";
    }
#endif
like image 225
Robᵩ Avatar asked Mar 18 '11 16:03

Robᵩ


3 Answers

Two words: name lookup.

Here is a simplified example of what you are trying to do, without any Standard Library headers required:

template <typename T> void f(T) { }

namespace ns {
    class C { };

    void f(int) { }

    void test() { f(C()); } // doesn't work :'(
}

int main() {
    f(ns::C());             // works!  :-D
}

In this example, in main(), the only f that is found during normal name lookup is the function template in the global namespace, and it matches, so main uses it (ns::f is also found during argument-dependent lookup, but it isn't a match so the global f is still selected during overload resolution).

In test, however, the ns::f(int) overload is found and name lookup stops. Namespaces are searched outwards, so ns is searched first, then the global namespace, but name lookup stops once a name is found, so once ns::f(int) is found, name lookup stops. Argument-dependent lookup also takes place and also finds ns::f(int), since C is in namespace ns, then ADL stops searching.

The same is true in your example: in main(), the operator<< overload is found, but inside of the std::ostream_iterator, which is in the std namespace, other << overloads are found, and so your overload is not found.

Your operator<< overload would need to be in the std namespace for it to work, but unfortunately you aren't allowed to add names to the std namespace.

like image 100
James McNellis Avatar answered Nov 09 '22 19:11

James McNellis


Lookup in the instantiation context of a function template only uses ADL. No unqualified lookup. Therefor, you need to rely on ADL. But the ADL lookup for vector<int> does not include the global namespace, hence your operator<< is not found. Try this, which should work fine:

struct A { 
  operator int() const { return 0; }
};

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

int main()
{
    std::vector<A> v(3);
    std::vector< std::vector<A> > vv(3, v);
    std::cout << vv << "\n"; // should work fine
}

This works because the global namespace is associated with the ADL lookup set for std::vector<A> (because A is a template argument), therefor finding the globally declared template when instantiating the respective member functions of std::ostream_iterator<>, which will use your operator<< for T being A, which then in turn will use operator<<(int) of std::ostream.

like image 43
Johannes Schaub - litb Avatar answered Nov 09 '22 21:11

Johannes Schaub - litb


I think since std::copy is defined in different namespace, and the generated code from the function template std::copy and ostream_iterator class template, is unable to find the operator<< defined by you which exists in different namespace.

To solve this problem you've to define operator<< in std namespace as shown below:

namespace std //note the namespace
{
   template <class T>
   std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
   { 
     os<<"(";
      std::copy(v.begin(), v.end(), std::ostream_iterator<T>(os, ", "));
      return os<<")"; 
   }
}

Working code at ideone : http://ideone.com/sFenn

However, I cannot say how good the idea of implementing it is in std namespace!


Alternatively, you can define operator<< as (without using std::copy):

template <class T>
std::ostream& operator<<(std::ostream&os, const std::vector<T>& v) 
{ 
   typedef typename std::vector<T>::const_iterator const_iterator;
   os<<"(";
   for (const_iterator it = v.begin() ; it != v.end() ; ++it )
       os << *it << ", ";
   return os<<")"; 
}

Working code : http://ideone.com/FXWlP

like image 32
Nawaz Avatar answered Nov 09 '22 19:11

Nawaz