Почему range-for не находит мои перегрузки begin и end для std::istream_iterator?

у меня такой код

std::ifstream file(filename, std::ios_base::in);
if(file.good())
{
    file.imbue(std::locale(std::locale(), new delimeter_tokens()));
    for(auto& entry : std::istream_iterator<std::string>(file))
    {
        std::cout << entry << std::endl;    
    }
}
file.close();

где std::istream_iterator<std::string> begin() и end() определяются следующим образом

template<class T>
std::istream_iterator<T> begin(std::istream_iterator<T>& stream)
{
    return stream;
}

template<class T>
std::istream_iterator<T> end(std::istream_iterator<T>& stream)
{
    return std::istream_iterator<T>();
}

об этом Марк Нельсон также написал в здесь. Увы, код не компилируется в моей Visual Studio 2012 с сообщениями об ошибках.

ошибка C3312: не найдена вызываемая функция «начало» для типа «std::istream_iterator‹_Ty>»

и

ошибка C3312: не найдена вызываемая функция "конец" для типа "std::istream_iterator‹_Ty>"

Вопрос: Я что-то не заметил, баг в компиляторе (маловероятно, но на всякий случай) или... Ну, есть идеи?


Этот вопрос значительно устранен, как советует Xeo. Чтобы предоставить больше информации и ссылок, это связано с моим другой вопрос в Stackoverflow, мне было интересно, как сделать синтаксический анализ на основе строк чище, чем обычные циклы. Немного кода и проверки из интернета, и у меня был рабочий эскиз следующим образом.

std::ifstream file(filename, std::ios_base::in);
if(file.good())
{               
    file.imbue(std::locale(std::locale(), new delimeter_tokens()));
    for(auto& entry : istream_range<std::string>(file)
    {
        std::cout << entry << std::endl;    
    }
}
file.close();

но была небольшая загвоздка, которую я пытался исправить. Думаю, естественнее было бы написать как в коде, который не компилируется, а не как

for(auto& entry : istream_range<std::string>(file)

Пожалуйста, обратите внимание на другой итератор. delimeter_tokens определяется как Nawaz любезно показал здесь (код не дублируется) и istream_range как в блоге Code Synthesis здесь. Я думаю, что реализации begin и end должны работать, как рекламируется в вышеупомянутом сообщении блога Code Synthesis

Последнее правило (откат к автономным функциям begin() и end()) позволяет нам неинвазивно адаптировать существующий контейнер в интерфейс цикла for на основе диапазона.

Таким образом, мой вопрос со всем (ир) соответствующим фоном.


person Veksi    schedule 01.07.2012    source источник
comment
Я взял на себя смелость изменить название вопроса, чтобы отразить фактически заданный вопрос. Откатитесь или измените при необходимости.   -  person Xeo    schedule 01.07.2012
comment
Думаю, так лучше. Это привлекает внимание тех, кто ищет решение, подобное моему, и тех, кому нужно решить проблему поиска. Как мне сделать ваш пост ответом (или это делает кто-то более опытный)?   -  person Veksi    schedule 01.07.2012


Ответы (2)


Ranged-for полагается на ADL, если специальная обработка собственного массива (T foo[N]) и элемента begin/end не дает никаких результатов.

§6.5.4 [stmt.ranged] p1

  • в противном случае begin-expr и end-expr равны begin(__range) и end(__range) соответственно, где begin и end ищутся с поиском, зависящим от аргумента (3.4.2) . Для целей поиска этого имени пространство имен std является связанным пространством имен.

Ваша проблема в том, что связанное пространство имен std::istream_iterator (очевидно) namespace std, а не глобальное пространство имен.

§3.4.2 [basic.lookup.argdep] p2

Для каждого типа аргумента T в вызове функции существует набор из нуля или более связанных пространств имен и набор из нуля или более связанных классов, которые необходимо учитывать. Наборы пространств имен и классов полностью определяются типами аргументов функции [...].

  • Если T является фундаментальным типом, связанные с ним наборы пространств имен и классов пусты.
  • Если T является типом класса (включая объединения), его ассоциированными классами являются: сам класс; класс, членом которого он является, если таковой имеется; и его прямые и косвенные базовые классы. Связанные с ним пространства имен — это пространства имен, членами которых являются связанные с ним классы. Кроме того, если T является специализацией шаблона класса, связанные с ним пространства имен и классы также включают: пространства имен и классы, связанные с типами шаблона аргументы, предоставленные для параметров типа шаблона [...].

Обратите внимание на последнюю (в кавычках) часть второго пункта. В основном это означает, что использование класса, который является членом глобального пространства имен, в качестве аргумента шаблона, заставляет код работать:

#include <iterator>
#include <iostream>

template<class T>
std::istream_iterator<T> begin(std::istream_iterator<T> is){
  return is;
}
template<class T>
std::istream_iterator<T> end(std::istream_iterator<T>){
  return std::istream_iterator<T>();
}

struct foo{};

std::istream& operator>>(std::istream& is, foo){
  return is;
}

int main(){
  for(foo f : std::istream_iterator<foo>(std::cin))
  //                                ^^^
  // make global namespace one of the associated namespaces
    ;
}
person Xeo    schedule 01.07.2012
comment
Я не могу следовать за вами сейчас. Как бы сработал этот трюк с ассоциацией пространства имен, если бы мне пришлось читать токенизированные строки из файлового потока? То есть foo, на которое вы указываете в цикле, в моем случае должны быть строками. - person Veksi; 01.07.2012
comment
@Veksi: По сути, важна только первая часть моего ответа: вы не можете помещать перегрузки для std членов в глобальное пространство имен для ранжирования. Вторая часть была предназначена только для демонстрации причудливости ADL, которая позволила бы ему работать. :П - person Xeo; 01.07.2012
comment
Комментируя эти вопросы и ответы, я узнал о основная проблема 1442, которая немного меняет правила. Пространство имен std больше не является ассоциированным пространством имен и < i>явно указано, что начало и конец просматриваются в соответствующих пространствах имен (3.4.2). [Примечание: Обычный неквалифицированный поиск (3.4.1) не выполняется. — примечание в конце] - person TemplateRex; 19.08.2013

Из-за поиска, зависящего от аргумента, компилятор пытается найти begin() и end() в пространстве имен std. Если вы поместите туда свои функции, код скомпилируется.

Поскольку поиск имени в С++ является сложной проблемой, я не совсем уверен, правильно ли ведет себя компилятор.

person sth    schedule 01.07.2012
comment
Вы не можете помещать перегрузки в namespace std. - person Xeo; 01.07.2012
comment
Но вы можете специализировать шаблоны, не так ли? - person jrok; 01.07.2012
comment
@jrok: Да, но только для пользовательских типов, а не для типов, происходящих из std (IIRC). Кроме того, вы не можете частично специализировать шаблоны функций, а специализация шаблонов функций в целом осуждается. - person Xeo; 01.07.2012