Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range-based loop over an input stream

To iterate over an input stream, we would usually use a std::istream_iterator like so:

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
}

It'd be nice if we could use the range-based for statement to iterate over input streams. However, for objects of class type, range-based for requires the object to have begin() and end() member functions (§6.5.4, bold emphasis added):

  • if _RangeT is an array type, begin-expr and end-expr are __range and __range + __bound, respectively, where __bound is the array bound. If _RangeT is an array of unknown size or an array of incomplete type, the program is ill-formed;

  • if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, begin-expr and end-expr are __range.begin() and __range.end(), respectively;

  • otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up with argument-dependent lookup (3.4.2). For the purposes of this name lookup, namespace std is an associated namespace.

The input streams don't have these member functions (they are not Containers) and so range-based for won't work on them. This makes sense anyway because you would need some way to specify the type to extract (std::string in the case above).

But if we know what we want to extract, is it possible to define our own begin() and end() functions (perhaps specializations or overloads of std::begin() and std::end()) for input streams such that they would be found by class member access lookup as described above?

It's unclear (at least to me) from §6.5.4 whether the functions will then be looked up with argument-dependent lookup if the previous lookup fails. Another thing to consider is that std::ios_base and its derivatives already have a member called end which is a flag for seeking.

Here's the intended result:

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

Or:

std::ifstream file("myfile");
for (auto i = begin(file); i != end(file); i++) {
  // Here, *i denotes each element extracted from the file
}
like image 622
Joseph Mansfield Avatar asked Oct 23 '12 19:10

Joseph Mansfield


2 Answers

An obvious approach is to use a simple decorator for your stream providing the type and the necessary interface. Here is how this could look like:

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)) {
    ...
}
like image 93
Dietmar Kühl Avatar answered Oct 14 '22 00:10

Dietmar Kühl


It doesn't matter whether they will be found by argument-dependent lookup, because you are allowed to put specializations of classes and functions in the std namespace.

like image 38
Seth Carnegie Avatar answered Oct 14 '22 00:10

Seth Carnegie