Att göra C ++ 11-intervallbaserad för loopar lite mer användbar

C ++ 11 är jättebra. Förmodligen en av de vackraste funktionerna (enligt min mening) är den så kallade range-based-for-loop. I stället för

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 förenklat med C ++ 11 auto:

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

vi kan säga detta:

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

Detta är väldigt uttrycksfullt: Vi pratar om posten, snarare än den i ^ e positionen i intervallet eller iteratorn som pekar på en post. Denna syntax saknar dock förmågan att ha underområden (t.ex. ignorera den senaste posten). I en serie små hjälparstrukturer / metoder vill jag lägga till den här funktionen på ett rent sätt.

Så mycket för min motivation 😉 Nu, för den verkliga affären.

I det här inlägget adresserar jag villkoren för posterna för range. I grund och botten ska hjälpkoden utföra motsvarande

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

men utan den indragningsnivån.

Här är koden för 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; }; 

Detta är hjälpstrukturen för iteratorn:

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 avsedda användningen är:

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: klicka här!

Vad är svagheter i mitt tillvägagångssätt? Med vilken typ av argument misslyckas det, även om det inte borde vara ”i den perfekta världen?

Kommentarer

  • Jag ser inga svagheter, men jag tycker att du bör överväga att byta namn på makeConditionalRange med något som fokuserar på klientkodsutseende, inte på " vilket gör ett villkorligt intervall ". menar att klientkoden ska se ut så här istället: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Så skiljer sig avsikten här avsevärt från en Boost filter_iterator ?
  • @JerryCoffin Nej det gör det inte ' t. Jag har helt enkelt inte ' t tittade på Boost 😉
  • Det vanligaste testet för iteratorer operator!= optimera detta över operator==
  • @stefan: Att vara trubbig att ' är dumt. Det är som att jag inte ' t kommer inte att köpa en bil från standardtillverkarna eftersom jag själv vill bygga en som rinner av tång för att garantera att den inte orsakar föroreningar. Problemet är att din uppsättning bibliotek kommer att bli oändligt mer buggy än boost eftersom du bara har en uppsättning ögon som tittar på dem. Boost har tusentals människor som kontrollerar och validerar kodfixningen och ger feedback för att säkerställa att rätta idiom används korrekt. Läs: stackoverflow.com/a/149305/14065

Svar

Du kan använda hängslen för att återvända från dina funktioner. På så sätt behöver du inte upprepa långa och besvärliga typer (TORR, så mycket som möjligt). Till exempel ConditionalRange::end:

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

Dessutom vill du antagligen att din funktion ska acceptera C-stilmatriser. Därför bör du använda std::begin och std::end istället för att ringa medlemsfunktionerna:

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

Med det (överallt i din kod) och några triviala ändringar fungerar din kod också för C- stilmatriser (testat här och det fungerar bra).

Namnge din funktion

Namnet makeConditionalRange är ganska lång. Funktionen du skapade finns redan i andra bibliotek och programmeringsspråk (Python kommer till mig) under namnet filter. ändra namn för att många användare ska känna igen namnet vid första anblicken (dessutom blir det kortare).

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *