2012-10-23 274 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中的成員完全破壞了以很好的方式實現這一點的任何機會。

相關問題