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