Ошибка компиляции при перегрузке оператора‹‹ аргументами шаблона

Я пытаюсь использовать stl copy() для печати пары ключ-значение на карте. Код выглядит следующим образом:

#include <iterator>
#include <iostream>
#include <algorithm>
#include <map>

using namespace std;
//compile error if I comment out "namespace std"
namespace std {    
template<typename F, typename S> 
ostream& operator<<(ostream& os, const pair<F,S>& p) {
  return os << p.first << "\t" << p.second << endl;
}
}

int main() {
  map<int, int> m;
  fill_n(inserter(m, m.begin()), 10, make_pair(90,120));
  copy(m.begin(), m.end(), ostream_iterator<pair<int,int> >(cout,"\n"));
} 

Я пытаюсь перегрузить оператор‹‹. Проблема в том, что код не скомпилируется, если я не окружу определение перегруженного оператора‹‹ namespace std. Я думаю, это связано с механизмом поиска имени C++, который мне до сих пор трудно понять. Даже если я определяю версию без шаблона следующим образом:

ostream& operator<<(ostream& os, const pair<int,int>& p) {
    return os << p.first << "\t" << p.second << endl;
}

Он все равно не скомпилируется. Кто-нибудь может объяснить, почему?


person rialmat    schedule 19.02.2013    source источник
comment
Пример ошибки   -  person Karthik T    schedule 19.02.2013


Ответы (1)


Ваша проблема связана с поиском имени в зависимости от аргумента (ADL). Компилятор ищет реализацию operator<< в namespace std, поскольку и ostream, и pair находятся в этом пространстве имен. Вы должны сделать оболочку, которая перенаправляет на operator<< из правильного пространства имен:

template<class T>
struct ostreamer {
  ostreamer(const T& value) : reference(value) {}
  const T& reference;
  friend ostream& operator<<(ostream& stream, const ostreamer& value) {
    return stream << value.reference;
  }
};

Тогда просто используйте ostream_iterator<ostreamer<pair<const int, int>>> вместо ostream_iterator<pair<int, int>>. Обратите внимание: поскольку ostreamer хранится по ссылке, а не по значению, вы больше не можете полагаться на неявное преобразование из pair<const int, int> в pair<int, int>. Вы можете изменить ostreamer на сохранение по значению, но так оно и есть, это не имеет накладных расходов, и я думаю, что в любом случае лучше быть явным.

person Jon Purdy    schedule 19.02.2013
comment
На самом деле безопасно перегружать материал в пространстве имен std только в том случае, если он включает пользовательский тип (позже я попытаюсь найти ссылку на спецификацию). В нынешнем виде ни ОП, ни ваш пример этого не делают. - person John5342; 19.02.2013
comment
@Jon Спасибо за ответ! Означает ли это, что компилятор ищет функцию ТОЛЬКО в пространстве имен std и больше нигде? Как насчет других мест, таких как текущая единица перевода? - person rialmat; 19.02.2013
comment
@ John5342 John5342 Я хотел бы знать причину На самом деле безопасно перегружать вещи в пространстве имен std, только если это связано с определенным пользователем типом. Я думаю, что конструкция pair здесь похожа на пользовательский тип, оператор ‹‹ не знает, как выводить пару, точно так же, как он не знает, как выводить пользовательский тип. - person rialmat; 19.02.2013
comment
@rialmat: Да. Когда перегрузки функции (operator<<) существуют в том же пространстве имен, что и класс (std::pair), а сайт вызова находится в пределах области этого пространства имен (std::ostream_iterator), никакие другие кандидаты не рассматриваются. - person Jon Purdy; 19.02.2013
comment
@Jon Ну, во-первых, если все стандарты совершенно ясны, вы не должны ничего определять в пространстве имен std, кроме как в определенных обстоятельствах. Причина заключается в том, чтобы представить себе будущий стандарт, в котором они обеспечивают эту перегрузку. Ваша функция вызовет ошибки компиляции. Если перегрузка включает определенный пользователем тип, она никогда не может конфликтовать. - person John5342; 19.02.2013
comment
@John5342: Хорошо, я удалил эту часть ответа. В любом случае есть альтернатива. - person Jon Purdy; 19.02.2013
comment
Ссылка как и обещала: (17.6.4.2.1/1)The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.. Это С++ 11, но в предыдущем стандарте был аналогичный раздел (17.4.3.1/1?). - person John5342; 19.02.2013
comment
@JonPurdy Обновленный ответ кажется мне немного многословным. Интересно, какой самый идиоматический способ написания перегруженного оператора ‹‹ в этом случае? - person rialmat; 19.02.2013
comment
@rialmat: Извините, я не думаю, что это можно улучшить. Перегрузка должна быть либо в пространстве имен std, что запрещено стандартом, либо перенаправляться в std, что и показано в моем ответе, как это сделать. - person Jon Purdy; 20.02.2013