11

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
}
4

4 回答 4

7

一个明显的方法是为您的流使用一个简单的装饰器,提供类型和必要的接口。这是它的样子:

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)) {
    ...
}
于 2012-10-23T19:32:55.137 回答
1

是否可以通过依赖参数的查找找到它们并不重要,因为您可以将类和函数的特化放在std命名空间中。

于 2012-10-23T19:15:34.857 回答
1

这是一种可能的解决方案。可悲的是,它确实需要一个额外的结构:

#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";
  }
}

另一种解决方案是从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";
  }
}
于 2012-10-23T19:26:46.830 回答
1

我试图追求专业化std::beginstd::end派生类的想法std::basic_istream(我不擅长这个模板元编程业务):

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 {};
  }
}

实际上,它工作得很好。我没有创建需要的版本,const C&因为我认为从 const 流中提取是没有意义的(当我尝试这样做时出现错误)。我也不确定我是否可以让这个动作更友好。所以现在我可以打印出这样的内容myfile::

std::ifstream file("myfile");
std::copy(begin(file), end(file), std::ostream_iterator<std::string>(std::cout, " "));

所以这些beginend功能按预期工作。但是,当在基于范围的for循环中使用时,它会下降。std::basic_istream类派生自std::ios_base其中已经有一个名为的成员end(它是在流中搜索的标志)。一旦基于范围的for循环找到这个,它就会放弃,因为它找不到对应的begin(更不用说这end不是正确的实体):

main.cpp:35:33:错误:“std::basic_ifstream”类型的基于范围的“for”表达式有一个“end”成员,但没有“begin”</p>

正如其他人所提到的,在这两种情况下都有效的唯一选择是创建一个包装器对象。不幸的是,该end成员std::ios_base完全破坏了以一种好的方式实现这一点的任何机会。

于 2012-10-23T21:44:36.610 回答