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
}
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.
Here is one possible solution. Sadly, it does require an extra structure:
#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";
}
}
Another solution is to derive from 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";
}
}
I attempted to pursue the idea of specializing std::begin
and std::end
for classes derived from std::basic_istream
(I'm not so great at this template metaprogramming business):
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 {};
}
}
Actually, it works pretty well. I didn't create versions that take const C&
because I don't think it makes sense to extract from a const stream (and I had errors when I tried to do so). I'm also not sure if I can make this more move friendly. So now I can print out the contents of myfile
like so::
std::ifstream file("myfile");
std::copy(begin(file), end(file), std::ostream_iterator<std::string>(std::cout, " "));
So these begin
and end
functions work as expected. However, it falls flat when used in a range-based for
loop. std::basic_istream
classes are derived from std::ios_base
which already has a member called end
(it's a flag for seeking within a stream). Once the range-based for
loop finds this, it just gives up because it can't find a corresponding begin
(not to mention that end
is not the right kind of entity):
main.cpp:35:33: error: range-based ‘for’ expression of type ‘std::basic_ifstream’ has an ‘end’ member but not a ‘begin’
The only alternative that works in both situations, as others have mentioned, is to create a wrapper object. Unfortunately that end
member in std::ios_base
completely ruins any chance of implementing this in a nice way.