要迭代输入流,我们通常会使用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-idsbegin
和end
中查找类_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
}
答案 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::begin
和std::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, " "));
因此,这些begin
和end
函数按预期工作。但是,在基于范围的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
成员完全破坏了以一种很好的方式实现这一点的机会。