To iterate over an input stream, we would usually use a std::istream_iterator
like so:
typedef std::istream_iterator input_iterato
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.
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)) {
...
}
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.
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";
}
}