Основанный на диапазоне цикл по входному потоку

Чтобы перебрать поток ввода, мы обычно используем std::istream_iterator вот так:

typedef std::istream_iterator<std::string> input_iterator;

std::ifstream file("myfile");
for (input_iterator i(file); i != input_iterator(); i++) {
// Here, *i denotes each element extracted from the file
}

Было бы хорошо, если бы мы могли использовать основанный на диапазоне for оператор для перебора входных потоков. Однако для объектов типа класса на основе диапазона for требует, чтобы объект имел begin() а также end() функции-члены (§6.5.4, выделение жирным шрифтом добавлено):

  • если _RangeT тип массива, начать-выр а также конец выраж являются __range а также __range + __boundсоответственно где __bound это связанный массив Если _RangeT массив неизвестного размера или массив неполного типа, программа некорректно сформирована;

  • если _RangeT это тип класса, неквалифицированные идентификаторы begin а также end ищутся в рамках класса _RangeT как если бы путем поиска доступа к члену класса (3.4.5), и если либо (или оба) находит хотя бы одно объявление, начать-выр а также конец выраж являются __range.begin() а также __range.end()соответственно;

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

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

Но если мы знаем, что мы хотим извлечь, можно ли определить наши собственные begin() а также end() функции (возможно, специализации или перегрузки std::begin() а также std::end()) для входных потоков, чтобы они были найдены при поиске доступа к членам класса, как описано выше?

Из §6.5.4 (по крайней мере для меня) неясно, будут ли функции затем искать с помощью аргументно-зависимого поиска, если предыдущий поиск завершится неудачно. Еще одна вещь, которую следует учитывать, это то, что std::ios_base и его производные уже имеют член под названием end который является флагом для поиска.

Вот предполагаемый результат:

std::ifstream file("myfile");
for (const std::string& str : file) {
// Here, str denotes each element extracted from the file
}

Или же:

std::ifstream file("myfile");
for (auto i = begin(file); i != end(file); i++) {
// Here, *i denotes each element extracted from the file
}

6

Решение

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

template <typename T>
struct irange
{
irange(std::istream& in): d_in(in) {}
std::istream& d_in;
};
template <typename T>
std::istream_iterator<T> begin(irange<T> r) {
return std::istream_iterator<T>(r.d_in);
}
template <typename T>
std::istream_iterator<T> end(irange<T>) {
return std::istream_iterator<T>();
}

for (auto const& x: irange<std::string>(std::ifstream("file") >> std::skipws)) {
...
}
4

Другие решения

Не имеет значения, будут ли они найдены с помощью аргументно-зависимого поиска, потому что вам разрешено помещать специализации классов и функций в std Пространство имен.

1

Вот одно из возможных решений. К сожалению, это требует дополнительной структуры:

#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <string>

struct S {
std::istream& is;
typedef std::istream_iterator<std::string> It;
S(std::istream& is) : is(is) {}
It begin() { return It(is); }
It end() { return It(); }
};

int main () {
std::ifstream file("myfile");
for(auto& string : S(file)) {
std::cout << string << "\n";
}
}

Другое решение состоит в том, чтобы извлечь из std::ifstream:

#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <string>struct ifstream : std::ifstream {
// using std::ifstream::ifstream; I wish g++4.7 supported inheriting constructors!
ifstream(const char* fn) : std::ifstream(fn) {}
typedef std::istream_iterator<std::string> It;
It begin() { return It(*this); }
It end() { return It(); }
};

int main () {
ifstream file("myfile");
for(auto& string : file) {
std::cout << string << "\n";
}
}
1

Я попытался реализовать идею специализации std::begin а также std::end для классов, полученных из std::basic_istream (Я не очень хорош в этом бизнесе метапрограммирования шаблонов):

namespace std
{
template <typename C>
typename
std::enable_if<
std::is_base_of<std::basic_istream<typename C::char_type>, C>::value,
std::istream_iterator<std::string>>::type
begin(C& c)
{
return {c};
}

template <typename C>
typename
std::enable_if<
std::is_base_of<std::basic_istream<typename C::char_type>, C>::value,
std::istream_iterator<std::string>>::type
end(C& c)
{
return {};
}
}

На самом деле, это работает довольно хорошо. Я не создавал версии, которые принимают const C& потому что я не думаю, что имеет смысл извлекать из потока const (и у меня были ошибки, когда я пытался это сделать). Я также не уверен, смогу ли я сделать это более дружелюбным. Так что теперь я могу распечатать содержимое myfile вот так::

std::ifstream file("myfile");
std::copy(begin(file), end(file), std::ostream_iterator<std::string>(std::cout, " "));

Так что эти begin а также end функции работают как положено. Тем не менее, он не работает при использовании в диапазоне на основе for петля. std::basic_istream классы являются производными от std::ios_base который уже имеет член под названием end (это флаг для поиска в потоке). Когда-то на основе диапазона for цикл находит это, он просто сдается, потому что не может найти соответствующий begin (не говоря уже о том, что end это не правильный вид сущности):

main.cpp: 35: 33: ошибка: основанное на диапазоне выражение типа «для» типа «std :: basic_ifstream» имеет элемент «конец», но не «начало»

Как уже упоминалось, единственная альтернатива, которая работает в обеих ситуациях, — это создание объекта-оболочки. К сожалению это end член в std::ios_base полностью разрушает любой шанс реализовать это хорошим способом.

0
По вопросам рекламы [email protected]