2012-10-23 277 views
6

遍历的输入流,我们通常会使用一个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,粗体强调):

  • 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-idsbegin 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.

该输入流不具有这些成员函数(他们不是容器),所以基于范围的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

这仅仅是我,还是从规格来说还不清楚?如果'_RangeT'不是数组或类的类型,'std :: begin()'和'std :: end()'似乎就会被找到。 –

+0

是的,这不是最好的措辞,但我认为你打算把它看作是“如果它是一个类,它有.begin和.end那么它将使用那些...否则”,即你可以提供免费的功能。 –

+1

“*'begin'和'end'在类_RangeT ...的范围内查找,如果**发现至少有一个声明**,则”begin-expr“和”end-expr“为'__range.begin()'和'__range.end()'*“ - 因为'std :: ios_base :: end'确实存在(因此会找到'std :: ifstream :: end')游戏结束。 '.begin()'不会被发现,'.end()'将会是一个语法错误。 –

回答

1

不要紧,他们是否会通过参数相关的查找找到,因为你被允许把班特并在std名称空间中起作用。

+0

但是,据我所知,您不允许添加重载,并且由于您不能部分专门化函数,您需要为所有'std :: istream_iterator '添加完整的专业化,或者只需添加专门化你需要的那个。 –

+0

@PeterAlexander恩,我不确定我明白。 “所有'std :: istream_iterator '完全专业化'是什么意思? –

+0

@PeterAlexander当然,我们需要一个专业化的'std :: basic_istream'? –

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"; 
    } 
} 
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)) { 
    ... 
} 
+0

可以在'std :: istream_iterator end(irange r)'中删除变量名'r',以避免未使用的参数警告。 – Mankka

+0

@Mankka:done。感谢您指出问题。 –

0

我试图寻求专业从std::basic_istream派生类std::beginstd::end的想法(我不是在这个模板元编程业务那么大):

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&的版本,因为我认为从常量流中提取并不合理(并且在尝试这样做时出现错误)。我也不确定是否可以让这一举动更加友好。所以,现在我可以打印出的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: error: range-based ‘for’ expression of type ‘std::basic_ifstream’ has an ‘end’ member but not a ‘begin’

唯一的选择,工程在其他情况下,正如其他人所提到的,是创建一个包装对象。不幸的是,endstd::ios_base中的成员完全破坏了以很好的方式实现这一点的任何机会。

相关问题