2012-10-23 3 views
6

per iterare su un flusso di input, che di solito usare un std::istream_iterator in questo modo:ciclo Gamma basati su un flusso di input

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 
} 

Sarebbe bello se potessimo utilizzare la gamma a base di for dichiarazione scorrere su flussi di input. Tuttavia, per oggetti di tipo classe, gamma-based for richiede l'oggetto da avere begin() e end() funzioni membro (§6.5.4, enfasi grassetto aggiunto):

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

I flussi di ingresso non hanno queste funzioni membro (non sono contenitori) e pertanto for basato su intervallo non funzionerà su di essi. Ciò ha comunque senso poiché è necessario un modo per specificare il tipo da estrarre (std::string nel caso precedente).

Ma se sappiamo quello che vogliamo estrarre, è possibile definire le nostre proprie begin() e end() funzioni (forse specializzazioni o sovraccarichi di std::begin() e std::end()) per l'input flussi tale che sarebbero stati trovati da accesso ricerca membro della classe come descritto sopra?

Non è chiaro (almeno per me) da §6.5.4 se le funzioni verranno cercate con ricerca dipendente dall'argomento se la precedente ricerca fallisce. Un'altra cosa da considerare è che std::ios_base e i suoi derivati ​​hanno già un membro chiamato end che è una bandiera per la ricerca.

Ecco il risultato desiderato:

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

Oppure:

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

È solo per me, o è piuttosto poco chiaro dalle specifiche? Sembra che 'std :: begin()' e 'std :: end()' vengano trovati solo se '_RangeT' non è di tipo array o classe. –

+0

Sì, non è la migliore espressione, ma penso che tu voglia leggerla come "se è una classe E ha .begin e .end poi userà quelli ... altrimenti", cioè puoi fornire le funzioni libere . –

+1

"*' begin' e 'end' sono ricercati nell'ambito della classe _RangeT ... e se ** uno ... trova almeno una dichiarazione **,' begin-expr' e 'end-expr' sono '__range.begin()' e '__range.end()' * "- poiché' std :: ios_base :: end' esiste (e quindi 'std :: ifstream :: end' sarà trovato) il gioco è attivo . '.begin()' non verrà trovato, e '.end()' sarà un errore di sintassi. –

risposta

1

Non importa se saranno trovati da lookup argomento-dipendente, perché si è permesso di mettere specializzazioni di classi e funzioni nello spazio dei nomi std.

+0

Ma, per quanto ne so, non è consentito aggiungere sovraccarichi e poiché non è possibile specializzare parzialmente le funzioni, è necessario aggiungere specializzazioni complete per tutti 'std :: istream_iterator ' o semplicemente aggiungere specializzazioni per quello (i) di cui hai bisogno. –

+0

@PeterAlexander uh, non sono sicuro di aver capito. Cosa intendi per "specializzazioni complete per tutti' std :: istream_iterator '? –

+0

@PeterAlexander Sicuramente avremo bisogno di una specializzazione per' std :: basic_istream'? –

1

Ecco una possibile soluzione. Purtroppo, richiede una struttura in più:

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

Un'altra soluzione è quella di derivare da 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

Un approccio più ovvio è quello di utilizzare un semplice decoratore per il vostro flusso di fornire il tipo e l'interfaccia necessaria . Ecco come potrebbe sembrare:

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

Si può rimuovere il nome della variabile' r' in 'std :: istream_iterator fine (irange r)' per evitare un avviso di parametro inutilizzato – Mankka

+0

@Mankka: done. Grazie per aver segnalato il problema. –

0

ho cercato di perseguire l'idea di specializzarsi std::begin e std::end per le classi derivate da std::basic_istream (io non sono così grande a questo modello di business metaprogrammazione):

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 {}; 
    } 
} 

In realtà funziona abbastanza bene. Non ho creato versioni che prendono const C& perché non penso abbia senso estrarre da un flusso const (e ho avuto errori quando ho provato a farlo). Inoltre, non sono sicuro di poter rendere questo più amichevole.Così ora posso stampare il contenuto della myfile in questo modo ::

std::ifstream file("myfile"); 
std::copy(begin(file), end(file), std::ostream_iterator<std::string>(std::cout, " ")); 

Così questi begin e end funzioni di lavoro come previsto. Tuttavia, si riduce quando utilizzato in un ciclo for basato su intervallo. Le classi std::basic_istream derivano da std::ios_base che ha già un membro chiamato end (è una bandiera per la ricerca all'interno di un flusso). Una volta che il ciclo for gamma basata trova questo, si dà solo su perché non riesce a trovare un corrispondente begin (per non parlare che end non è il giusto tipo di entità):

main.cpp:35:33: error: range-based ‘for’ expression of type ‘std::basic_ifstream’ has an ‘end’ member but not a ‘begin’

L'unica alternativa che funziona in entrambe le situazioni, come altri hanno menzionato, è quello di creare un oggetto wrapper. Sfortunatamente il membro end in std::ios_base rovina completamente ogni possibilità di implementarlo in un modo piacevole.