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-idsbegin
andend
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)
andend(__range)
, respectively, wherebegin
andend
are looked up with argument-dependent lookup (3.4.2). For the purposes of this name lookup, namespacestd
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
}
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)) {
...
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With