Почему не диапазон - для поиска моих перегрузок начала и конца для 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>();
}

что и есть Mark Nelson в д-р Добб здесь. Увы, код не скомпилируется на моей Visual Studio 2012 с сообщениями об ошибках

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

и

ошибка C3312: не найдена функция 'end' для вызываемого типа '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 здесь. Я думаю, что начальная и конечная реализации должны работать, как описано в вышеупомянутом блоге блога Code Synthesis.

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

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

Ответ 1

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

Ответ 2

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

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