2012-10-23 18 views
6

iteracyjne nad strumień wejściowy, chcielibyśmy zazwyczaj użyć std::istream_iterator tak:Zakres oparte pętli nad strumieniem wejściowym

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 
} 

Byłoby miło, gdybyśmy mogli używać zakresie oparte for oświadczenie iteracyjne nad strumieniami wejściowymi. Jednakże, dla obiektów typu klasy zasięg oparte for wymaga przedmiotu mieć begin() i end() metody (§6.5.4 pogrubienie nacisk dodano)

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

Strumienie wejściowe nie mają tych funkcji składowych (nie są to kontenery), więc nie będzie na nich działał profil for bazujący na zasięgach. Ma to sens w każdym razie, ponieważ potrzebowałbyś jakiegoś sposobu na określenie typu do wyodrębnienia (std::string w powyższym przypadku).

Ale jeśli wiemy, co chcemy, aby wyodrębnić, jest to możliwe do zdefiniowania własnych begin() i end() funkcje (może specjalizacji lub przeciążeniem std::begin() i std::end()) dla strumienie wejściowe takie, że zostaną uznane przez dostępu członkiem klasy odnośnika jako opisane powyżej?

Nie jest jasne (przynajmniej dla mnie), od §6.5.4, czy funkcje będą wtedy wyszukiwane za pomocą wyszukiwania zależnego od argumentu, jeśli poprzednie wyszukiwanie zakończy się niepowodzeniem. Inną rzeczą do rozważenia jest to, że std::ios_base i jego pochodne mają już członka o nazwie end, który jest flagą do wyszukiwania.

Oto zamierzony rezultat:

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

Lub:

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

Czy to tylko ja, czy jest to dość niejasne ze specyfikacji? Wygląda na to, że 'std :: begin()' i 'std :: end()' zostaną znalezione tylko wtedy, gdy '_RangeT' nie ma typu tablica lub klasa. –

+0

Tak, to nie jest najlepsze sformułowanie, ale myślę, że masz zamiar przeczytać to jako "jeśli jest to klasa I ma .begin i .end to użyje tych ... inaczej", tzn. Możesz podać darmowe funkcje . –

+1

"*" zacząć "i" koniec "są wyszukiwane w zakresie klasy _RangeT ... i jeśli ** albo ... znajdzie co najmniej jedną deklarację **," begin-expr "i" end-expr "są '__range.begin()' i '__range.end()' * "- ponieważ' std :: ios_base :: end' istnieje (a więc 'std :: ifstream :: end' zostanie znaleziony) gra jest już dostępna . '.begin()' nie zostanie znaleziony, a '.end()' będzie błędem składni. –

Odpowiedz

1

To nie ma znaczenia, czy będzie je znaleźć przez wyszukiwarkę argumentu zależne, ponieważ mogą umieścić specjalizacji klas i funkcje w przestrzeni nazw std.

+0

Ale, o ile mi wiadomo, nie wolno dodawać do przeciążeń, a ponieważ nie można częściowo specjalizują funkcje, to albo trzeba dodać pełne specjalizacje dla wszystkich 'std :: istream_iterator ' lub po prostu dodać do specjalizacji te, których potrzebujesz. –

+0

@PeterAlexander uh, nie jestem pewien, czy rozumiem. Co masz na myśli przez „Full specjalności dla wszystkich' std :: istream_iterator '? –

+0

@PeterAlexander pewnością musimy specjalizację dla' std :: basic_istream'? –

1

Oto jedno z możliwych rozwiązań. Niestety, to nie wymaga dodatkowego strukturę:

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

Innym rozwiązaniem jest czerpanie z 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

Oczywistym rozwiązaniem jest użycie prostego dekoratora dla strumienia zapewniając rodzaj i niezbędną interfejs . Oto jak może to wyglądać tak:

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

Można usunąć nazwę zmiennej' r' na końcu 'std :: istream_iterator '(irange r)' aby uniknąć nieużywanego ostrzeżenia o parametrach – Mankka

+0

@Mankka: done. Dziękuję za wskazanie problemu. –

0

starałem się realizować ideę specjalizujący std::begin i std::end dla klas pochodnych od std::basic_istream (nie jestem tak wielka w tym szablonie metaprogramowanie biznesu):

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

Właściwie działa całkiem dobrze. Nie tworzyłem wersji, które pobierają const C&, ponieważ nie wydaje mi się, że ma sens wyodrębnianie ze strumienia const (i miałem błędy, gdy próbowałem to zrobić). Nie jestem też pewien, czy mogę uczynić to bardziej przyjaznym ruchowi.Więc teraz mogę wydrukować zawartość myfile jak tak ::

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

więc te begin i end funkcje działają zgodnie z oczekiwaniami. Jest jednak płaska, gdy jest używana w pętli for. std::basic_istream klasy pochodzą z std::ios_base, który ma już element o nazwie end (jest to flaga do wyszukiwania w strumieniu). Gdy zakres oparte for pętla znajdzie to, po prostu rezygnuje, ponieważ nie może znaleźć odpowiedniego begin (nie wspominając, że end nie jest odpowiedni rodzaj podmiotu):

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

Jedyną alternatywą, która działa w obu sytuacjach, jak wspomnieli inni, jest stworzenie obiektu otoki. Niestety, end członek w std::ios_base całkowicie rujnuje wszelkie szanse na wdrożenie tego w przyjemny sposób.

Powiązane problemy