输入流上基于范围的循环

时间:2012-10-23 19:10:29

标签: c++ c++11 iterator inputstream

要迭代输入流,我们通常会使用std::istream_iterator,如此:

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
}

如果我们可以使用基于范围的for语句来迭代输入流,那就太好了。但是,对于类类型的对象,基于范围的for要求对象具有begin()end()成员函数(第6.5.4节,添加了粗体强调):

  
      
  • 如果_RangeT是数组类型,则 begin-expr end-expr __range__range + __bound分别是__bound是数组绑定的地方。如果_RangeT是未知大小的数组或不完整类型的数组,则该程序格式不正确;

  •   
  • 如果_RangeT是班级类型,则会在 unqualified-ids beginend中查找类_RangeT的范围,如同类成员访问查找(3.4.5),如果其中任何一个(或两者)找到至少一个声明, begin-expr 结束-expr 分别为__range.begin()__range.end();

  •   
  • 否则, begin-expr end-expr 分别为begin(__range)end(__range),其中begin并且使用参数依赖查找查找end(3.4.2)。出于此名称查找的目的,名称空间std是关联的名称空间。

  •   

输入流没有这些成员函数(它们不是容器),因此基于范围的for将无法使用它们。这是有道理的,因为您需要某种方式来指定要提取的类型(在上面的例子中为std::string)。

但是,如果我们知道要提取的内容,是否可以定义我们自己的begin()end()函数(可能是std::begin()std::end()的特化或重载对于输入流,如上所述,它们将通过类成员访问查找找到?

目前还不清楚(至少对我而言)§6.5.4如果先前的查找失败,是否会使用依赖于参数的查找来查找函数。另一件需要考虑的事情是std::ios_base及其衍生物已经有一个名为end的成员,这是一个寻求的标志。

这是预期的结果:

std::ifstream file("myfile");
for (const std::string& str : file) {
  // Here, str denotes each element extracted from the file
}

或者:

std::ifstream file("myfile");
for (auto i = begin(file); i != end(file); i++) {
  // Here, *i denotes each element extracted from the file
}

4 个答案:

答案 0 :(得分:4)

一种显而易见的方法是为您的流使用简单的装饰器,提供类型和必要的接口。这是这样的:

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)) {
    ...
}

答案 1 :(得分:1)

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

答案 2 :(得分: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";
  }
}

答案 3 :(得分:0)

我试图将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:错误:基于范围的'for'表达式'std :: basic_ifstream'有一个'end'成员但不是'begin'

正如其他人所提到的,唯一适用于这两种情况的替代方法是创建一个包装器对象。不幸的是,end中的std::ios_base成员完全破坏了以一种很好的方式实现这一点的机会。