Почему я не могу создать оператор << (ostream &, vector <T> &) с T = vector <int>?

В мыслях о вопросе итератора С++ я написал эту примерную программу:

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

Я скомпилирую эту программу с помощью gcc и получаю типичные 100 строк ошибок. Я считаю, что важная часть:

it.cc:19: созданный здесь

/usr/include/С++/4.4/bits/stream_iterator.h: 191: ошибка: нет соответствия для 'operator < < < in '((std:: ostream_iterator > , char, std:: char_traits > ) this) → std:: ostream_iterator > , char, std:: char_traits > :: _ M_stream < < __value

Почему это не удается? В моем шаблоне operator<< я пытаюсь указать, что любой вектор, независимо от типа, может быть распечатан. Итак, почему не std::vector<std::vector<>> печатать?

EDIT: использование следующего кода в функции шаблона заставляет его работать

#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

Ответы

Ответ 1

Два слова: поиск имени.

Вот упрощенный пример того, что вы пытаетесь сделать, без каких-либо стандартных заголовков библиотеки:

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
}

В этом примере, в main(), единственный f, который находится во время обычного поиска имени, является шаблоном функции в глобальном пространстве имен, и он соответствует, поэтому main использует его (ns::f также найдено во время зависящего от аргумента поиска, но он не соответствует, поэтому глобальный f все еще выбран во время разрешения перегрузки).

В test, однако, обнаружена перегрузка ns::f(int), и поиск имен прекращается. Пространства имен просматриваются наружу, поэтому сначала выполняется поиск ns, затем глобальное пространство имен, но поиск имени останавливается после того, как имя найдено, поэтому после того, как будет найдено ns::f(int), поиск имен прекратится. Также имеет место зависящий от аргумента поиск, а также находит ns::f(int), так как C находится в пространстве имен ns, тогда ADL останавливает поиск.

То же самое верно в вашем примере: в main() найдена перегрузка operator<<, но внутри std::ostream_iterator, которая находится в пространстве имен std, найдены другие перегрузки << и поэтому ваша перегрузка не найдена.

Ваша перегрузка operator<< должна быть в пространстве имен std для ее работы, но, к сожалению, вам не разрешено добавлять имена в пространство имен std.

Ответ 2

Поиск в контексте создания шаблона функции использует только ADL. Нет безоговорочного поиска. Поэтому вам нужно полагаться на ADL. Но поиск ADL для vector<int> не включает глобальное пространство имен, поэтому ваш operator<< не найден. Попробуйте это, что должно хорошо работать:

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
}

Это работает, потому что глобальное пространство имен связано с набором поиска ADL для std::vector<A> (поскольку A является аргументом шаблона), поэтому поиск объявленного во всем мире шаблона при создании экземпляров соответствующих функций-членов std::ostream_iterator<>, что будет используйте operator<< для T как A, который, в свою очередь, будет использовать operator<<(int) для std::ostream.

Ответ 3

Я думаю, так как std::copy определяется в разных пространствах имен, а сгенерированный код из шаблона шаблона функции std::copy и ostream_iterator не может найти operator<<, определенный вами, который существует в другом пространстве имен.

Чтобы решить эту проблему, вы должны определить operator<< в пространстве имен std, как показано ниже:

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<<")"; 
   }
}

Рабочий код на ideone: http://ideone.com/sFenn

Однако я не могу сказать, насколько хорошо идея его реализации находится в пространстве имен std!


В качестве альтернативы вы можете определить operator<< как (без использования 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<<")"; 
}

Рабочий код: http://ideone.com/FXWlP