Å gjøre C ++ 11 rekkevidde-basert for sløyfer litt mer nyttig

C ++ 11 er flott. Sannsynligvis en av de vakreste funksjonene (etter min mening) er den såkalte range-based-for-loop. I stedet for

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

eller

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

eller forenklet med C ++ 11 auto:

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

vi kan si dette:

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

Dette er veldig uttrykksfullt: Vi snakker om oppføringen, i stedet for den i. posisjonen i området eller iteratoren som peker på en oppføring. Imidlertid mangler denne syntaksen muligheten til å ha underordninger (f.eks. Ignorerer den siste oppføringen). I en rekke små hjelperstrukturer / -metoder vil jeg legge til denne funksjonaliteten på en ren måte.

Så mye for motivasjonen min 😉 Nå, for den virkelige avtalen.

I dette innlegget adresserer jeg forholdene til oppføringene til range. I utgangspunktet skal hjelperkoden utføre ekvivalent med

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

men uten dette innrykkingsnivået.

Her er koden for 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; }; 

Dette er hjelperstrukturen for iteratoren:

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

Tiltenkt bruk er:

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: klikk her!

Hva er svakheter ved tilnærmingen min? Med hva slags argumenter feiler det, selv om det ikke burde være «i den perfekte verden?

Kommentarer

  • Jeg ser ingen svakheter, men jeg tror du bør vurdere å gi nytt navn makeConditionalRange med noe som fokuserer på klientkodeutseende, ikke på " å lage et betinget område ". betyr at klientkoden i stedet skal se slik ut: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Så skiller intensjonen her betydelig fra en Boost filter_iterator ?
  • @JerryCoffin Nei det ' t. Jeg har rett og slett ikke ' t så på Boost 😉
  • Den vanligste testen for iteratorer operator!= optimaliser dette over operator==
  • @stefan: Å være sløv at ' er dumt. Det er som å si at jeg ikke ' t vil ikke kjøpe en bil fra standardprodusentene fordi jeg ønsker å bygge en som løper av tang selv for å garantere at den ikke forårsaker forurensning. Problemet er at biblioteket ditt blir uendelig mer buggy enn boost fordi du bare har ett sett med øyne som ser på dem. Boost har tusenvis av mennesker som sjekker og validerer kodefiksingen og gir tilbakemelding for å sikre at de riktige uttrykkene brukes riktig. Les: stackoverflow.com/a/149305/14065

Svar

Du kan bruke bukseseler for å komme tilbake fra funksjonene dine. På den måten trenger du ikke å gjenta lange og tungvint typer (TØRR, så mye som mulig). For eksempel ConditionalRange::end:

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

Du vil sannsynligvis også at funksjonen din skal godta matriser i C-stil. Derfor bør du bruke std::begin og std::end i stedet for å ringe medlemsfunksjonene:

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

Med det (overalt i koden din) og noen trivielle endringer, fungerer koden din også for C- stilmatriser (testet her og det fungerer bra).

Navngi funksjonen din

Navnet makeConditionalRange er ganske lang. Funksjonen du opprettet finnes allerede i andre biblioteker og programmeringsspråk (Python kommer til å tenke meg) under navnet filter. Du bør vurdere endre navnet for at mange brukere skal kjenne igjen navnet ved første øyekast (dessuten vil det være kortere).

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *