【问题标题】:Range-based loop over an input stream输入流上基于范围的循环
【发布时间】:2012-10-13 19:13:27
【问题描述】:

要遍历输入流,我们通常会像这样使用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-exprend-expr分别是__range__range + __bound,其中__bound是数组绑定。如果_RangeT是一个未知大小的数组或一个不完整类型的数组,则程序是非良构的;

  • 如果 _RangeT 是一个类类型unqualified-ids beginend 在类 _RangeT 的范围内查找,好像通过类成员访问查找 (3.4.5),如果任一(或两者)找到至少一个声明,begin-exprend-expr__range.begin() 和分别为__range.end()

  • 否则,begin-exprend-expr 分别是 begin(__range)end(__range),其中 beginend 用参数查找依赖查找(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
}

【问题讨论】:

  • 只是我,还是从规范中看不清楚?似乎 std::begin()std::end() 只有在 _RangeT 不是数组或类类型时才能找到。
  • 是的,这不是最好的措辞,但我认为您应该将其理解为“如果它是一个类并且它具有 .begin 和 .end 那么它将使用那些......否则”,即你可以提供免费的功能。
  • "beginend 在类 _RangeT ... 的范围内查找,如果 其中一个 ... 找到至少一个声明begin-exprend-expr__range.begin()__range.end()" - 因为 std::ios_base::end 确实存在(因此会找到 std::ifstream::end)游戏结束。 .begin() 将找不到,.end() 将是语法错误。
  • FWIW Boost.Range 提供istream_rangeDemo.

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


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 但是,据我所知,您不允许添加重载,并且由于您不能部分特化函数,您要么需要为所有 std::istream_iterator&lt;std::string&gt; 添加完整的特化,要么只为您需要的专业添加专业化。
    • @PeterAlexander 呃,我不确定我是否理解。 “所有std::istream_iterator&lt;std::string&gt; 的完全专业化是什么意思?
    • @PeterAlexander 当然我们需要std::basic_istream的专业化?
    • 忽略我,出于某种原因,我认为begin/end 会占用std::istream_iterator&lt;T&gt;
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      我试图追求将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&amp; 的版本,因为我认为从 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”

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

      【讨论】:

        猜你喜欢
        • 2016-10-31
        • 2021-08-25
        • 2018-12-28
        • 1970-01-01
        • 1970-01-01
        • 2014-12-06
        • 2014-01-11
        • 2019-03-07
        • 2013-04-01
        相关资源
        最近更新 更多