Range-based loop over an input stream

前端 未结 4 1291
生来不讨喜
生来不讨喜 2021-02-08 17:06

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

typedef std::istream_iterator input_iterato         


        
相关标签:
4条回答
  • 2021-02-08 17:39

    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)) {
        ...
    }
    
    0 讨论(0)
  • 2021-02-08 17:40

    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";
      }
    }
    
    0 讨论(0)
  • 2021-02-08 17:44

    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.

    0 讨论(0)
  • 2021-02-08 17:44

    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.

    0 讨论(0)
提交回复
热议问题