Чтобы перебрать поток ввода, мы обычно используем 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
}
Очевидным подходом является использование простого декоратора для вашего потока, обеспечивающего тип и необходимый интерфейс. Вот как это может выглядеть:
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)) {
...
}
Не имеет значения, будут ли они найдены с помощью аргументно-зависимого поиска, потому что вам разрешено помещать специализации классов и функций в std
Пространство имен.
Вот одно из возможных решений. К сожалению, это требует дополнительной структуры:
#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";
}
}
Я попытался реализовать идею специализации 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
полностью разрушает любой шанс реализовать это хорошим способом.