Rendere i cicli for basati su intervalli di C ++ 11 un po più utili

C ++ 11 è fantastico. Probabilmente una delle caratteristiche più belle (secondo me) è il cosiddetto range-based-for-loop. Invece di

for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; } 

o

for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

o semplificato con C ++ 11 auto:

for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

possiamo dire questo:

for ( auto& entry : range ) { // do something with entry. } 

Questo è molto espressivo: parliamo sulla voce, piuttosto che sulla i ^ esima posizione nellintervallo o sulliteratore che punta a una voce. Tuttavia, questa sintassi manca della capacità di avere sottointervalli (ad es. Ignorare lultima voce). In una serie di piccoli metodi / strutture di supporto, voglio aggiungere questa funzionalità in modo pulito.

Questo per la mia motivazione 😉 Ora, per il vero affare.

In questo post, affronterò le condizioni sulle voci di range. Fondamentalmente, il codice helper dovrebbe eseguire lequivalente di

for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } } 

ma senza quel livello di rientro.

Ecco il codice per ConditionalRange:

template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; }; 

Questa è la struttura di supporto per literatore:

template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } }; 

Lutilizzo previsto è:

std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n"; 

Demo: fai clic qui!

Cosa sono i punti deboli del mio approccio? Con che tipo di argomenti fallisce, anche se non dovrebbe “t nel mondo perfetto?

Commenti

  • Non vedo punti deboli, ma io penso che dovresti considerare di rinominare makeConditionalRange con qualcosa che si concentri sullaspetto del codice client, non su " che crea un intervallo condizionale ". significa che il codice client dovrebbe invece assomigliare a questo: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Quindi lintento qui differisce in modo significativo da un Boost filter_iterator ?
  • @JerryCoffin No, non ' t. Semplicemente non ho ' t ha esaminato Boost 😉
  • Il test più comune per gli iteratori operator!= ottimizza questo su operator==
  • @stefan: per essere schietto che ' è sciocco. È come dire che non ' non acquisterà unauto dai produttori standard perché voglio costruirne uno che scappi dalle alghe per garantire che non causi inquinamento. Il problema è che il tuo set di librerie sarà infinitamente più difettoso del boost perché hai solo un paio di occhi che le guarda. Boost ha migliaia di persone che controllano e convalidano la correzione del codice e forniscono feedback per assicurarsi che gli idiomi corretti vengano utilizzati correttamente. Lettura: stackoverflow.com/a/149305/14065

Risposta

Puoi usare le parentesi graffe per tornare dalle tue funzioni. In questo modo, non dovrai ripetere tipi lunghi e ingombranti (DRY, il più possibile). Ad esempio, ConditionalRange::end:

iterator_type end() const { return { range.end(), range.end(), condition }; } 

Inoltre, probabilmente vuoi che anche la tua funzione accetti array in stile C. Pertanto, dovresti usare std::begin e std::end invece di chiamare le funzioni membro:

iterator_type end() const { return { std::end(range), std::end(range), condition }; } 

Con questo (ovunque nel codice) e alcune modifiche banali, il codice funzionerà anche per C- array di stile (testato qui e funziona bene).

Assegnazione di un nome alla funzione

Il nome makeConditionalRange è piuttosto lungo. La funzione che hai creato esiste già in altre librerie e linguaggi di programmazione (mi viene in mente Python) con il nome filter. Dovresti prendere in considerazione cambiandone il nome in modo che molti utenti riconoscano il nome a prima vista (inoltre, sarà più breve).

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *