C ++ 11 este minunat. Probabil una dintre cele mai frumoase caracteristici (după părerea mea) este așa-numita gamă bazată pe buclă. În loc de
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
sau
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
sau simplificat cu C ++ 11 auto:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
putem spune acest lucru:
for ( auto& entry : range ) { // do something with entry. }
Acest lucru este foarte expresiv: vorbim despre intrare, mai degrabă decât poziția i ^ a în interval sau iteratorul care indică o intrare. Cu toate acestea, această sintaxă nu are capacitatea de a avea subintervaluri (de exemplu, ignorarea ultimei intrări). Într-o serie de structuri / metode de ajutor mici, vreau să adaug această funcționalitate într-un mod curat.
Atât de mult pentru motivația mea 😉 Acum, pentru afacerea reală.
În această postare, abordez condițiile de pe intrările range
. Practic, codul de asistență ar trebui să efectueze echivalentul lui
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
dar fără acel nivel de indentare.
Iată codul pentru 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; };
Aceasta este structura de ajutor pentru iterator:
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); } };
Utilizarea intenționată este:
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: faceți clic aici!
Ce sunt punctele slabe ale abordării mele? Cu ce fel de argumente eșuează, deși nu ar trebui să „fie în lumea perfectă?
Comentarii
- Nu văd puncte slabe, dar eu cred că ar trebui să luați în considerare redenumirea makeConditionalRange cu ceva care se concentrează pe aspectul codului clientului, nu pe " realizarea unui interval condițional ". Prin aceasta, eu înseamnă că codul clientului ar trebui să arate astfel:
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
- Deci intenția de aici diferă semnificativ de un Boost
filter_iterator
? - @JerryCoffin Nu, nu ' t. Pur și simplu nu am ' t a analizat Boost 😉
- Cel mai frecvent test pentru iteratori
operator!=
optimizează acest lucru pesteoperator==
- @stefan: să fiu clar că ' este o prostie. Este ca și cum ai spune că nu ' nu obișnuiesc să cumpăr o mașină de la producătorii standard pentru că vreau să construiesc unul care să scape de algele mele pentru a garanta că nu provoacă poluare. Problema este că setul dvs. de biblioteci va fi infinit mai mult buggy decât boost, deoarece aveți un singur set de ochi care le privesc. Boost are mii de oameni care verifică și validează corectarea codului și oferă feedback pentru a se asigura că idiomurile corecte sunt utilizate corect. Citiți: stackoverflow.com/a/149305/14065
Răspuns
Puteți utiliza paranteze pentru a reveni din funcțiile dvs. În acest fel, nu va trebui să repetați tipuri lungi și greoaie (DRY, pe cât posibil). De exemplu, ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
De asemenea, probabil că doriți ca funcția dvs. să accepte și matrici în stil C. Prin urmare, ar trebui să utilizați std::begin
și std::end
în loc să apelați funcțiile de membru:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
Cu asta (oriunde în codul dvs.) și cu unele modificări banale, codul dvs. va funcționa și pentru C- tablouri de stil (testat aici și funcționează bine).
Denumirea funcției dvs.
Numele makeConditionalRange
este destul de lungă. Funcția pe care ați creat-o există deja în alte biblioteci și limbaje de programare (îmi vine în minte Python) sub numele filter
. Ar trebui să luați în considerare schimbându-și numele pentru ca mulți utilizatori să recunoască numele la prima vedere (în plus, va fi mai scurt).