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 suoperator==
- @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).