At gøre C ++ 11 rækkebaseret til sløjfer lidt mere nyttigt

C ++ 11 er fantastisk. Sandsynligvis en af de smukkeste funktioner (efter min mening) er den såkaldte 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 sige dette:

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

Dette er meget udtryksfuldt: Vi taler om posten, snarere end den i. position i området eller iteratoren, der peger på en post. Denne syntaks mangler dog evnen til at have underranger (f.eks. Ignorere den sidste post). I en række små hjælperstrukturer / -metoder vil jeg tilføje denne funktionalitet på en ren måde.

Så meget for min motivation 😉 Nu for den virkelige aftale.

I dette indlæg adresserer jeg forholdene på range. Grundlæggende skal hjælperekoden udføre ækvivalenten med

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

men uden dette niveau af indrykning.

Her er koden til 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 hjælpestrukturen til 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); } }; 

Den tilsigtede anvendelse 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: klik her!

Hvad er svagheder ved min tilgang? Med hvilken slags argumenter fejler det, selvom det ikke burde være “i den perfekte verden?

Kommentarer

  • Jeg kan ikke se nogen svagheder, men jeg mener, at du bør overveje at omdøbe makeConditionalRange med noget, der fokuserer på klientkodeudseende, ikke på " gør et betinget interval ". betyder, at klientkode i stedet skal se sådan ud: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Så adskiller sig hensigten her markant fra en Boost filter_iterator ?
  • @JerryCoffin Nej det gør det ikke ' t. Jeg har simpelthen ikke ' t kiggede på Boost 😉
  • Den mest almindelige test for iteratorer operator!= optimer dette over operator==
  • @stefan: At være stump, at ' er fjollet. Det er som at sige, at jeg ikke ' t køber ikke en bil fra standardproducenterne fordi jeg selv vil bygge en der løber af tang for at garantere at den ikke forårsager forurening. Problemet er, at dit sæt biblioteker vil være uendeligt mere buggy end boost, fordi du kun har et sæt øjne, der ser på dem. Boost har tusindvis af mennesker, der kontrollerer og validerer kodefiksering og giver feedback for at sikre, at de korrekte sprogbrug bruges korrekt. Læs: stackoverflow.com/a/149305/14065

Svar

Du kan bruge seler til at vende tilbage fra dine funktioner. På den måde behøver du ikke gentage lange og besværlige typer (TØR, så meget som muligt). For eksempel ConditionalRange::end:

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

Du vil sandsynligvis også have din funktion til at acceptere C-stil arrays. Derfor skal du bruge std::begin og std::end i stedet for at kalde medlemsfunktionerne:

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

Med det (overalt i din kode) og nogle trivielle ændringer, fungerer din kode også for C- stilarrays (testet her , og det fungerer fint).

Navngivning af din funktion

Navnet makeConditionalRange er ret lang. Den funktion, du oprettede, findes allerede i andre biblioteker og programmeringssprog (Python kommer til at tænke mig) under navnet filter. ændre navnet for at mange brugere kan genkende navnet ved første øjekast (desuden bliver det kortere).

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *