C ++ 11 range-based for loops een beetje nuttiger maken

C ++ 11 is geweldig. Waarschijnlijk een van de mooiste features (naar mijn mening) is de zogenaamde range-based-for-loop. In plaats van

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

of

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

of vereenvoudigd met C ++ 11 auto:

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

we kunnen dit zeggen:

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

Dit is erg expressief: we praten over het item, in plaats van de i ^ de positie in het bereik of de iterator die naar een item verwijst. Deze syntaxis mist echter de mogelijkheid om subbereiken te hebben (bijv. Negeren van de laatste invoer). In een reeks kleine hulpstructuren / -methoden wil ik deze functionaliteit op een nette manier toevoegen.

Tot zover mijn motivatie 😉 Nu, voor het echte werk.

In dit bericht ga ik in op de voorwaarden voor de vermeldingen van range. In principe zou de helpercode het equivalent moeten uitvoeren van

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

maar zonder dat niveau van inspringing.

Hier is de code voor 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; }; 

Dit is de hulpstructuur voor de 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); } }; 

Het beoogde gebruik is:

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 hier!

Wat zijn zwakke punten van mijn aanpak? Met wat voor argumenten faalt het, hoewel het “niet in de perfecte wereld zou moeten zijn?

Opmerkingen

  • Ik zie geen zwakke punten, maar ik denk dat u zou moeten overwegen om makeConditionalRange te hernoemen met iets dat zich richt op het uiterlijk van de clientcode, niet op " het maken van een voorwaardelijk bereik ". bedoel dat de clientcode er in plaats daarvan als volgt uit zou moeten zien: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Dus verschilt de bedoeling hier significant van een Boost filter_iterator ?
  • @JerryCoffin Nee dat doet het niet ' t. Ik heb gewoon ' t onderzocht Boost 😉
  • De meest gebruikelijke test voor iteratoren operator!= optimaliseer dit via operator==
  • @stefan: Om bot te zijn dat ' stom is. Het is alsof je zegt dat ik niet ' t gewoon om een auto te kopen van de standaardfabrikanten omdat ik er zelf een wil bouwen die van zeewier afvloeit om te garanderen dat het geen vervuiling veroorzaakt. Het probleem is dat je set bibliotheken oneindig veel meer bugs zal bevatten dan boost, omdat je maar één paar ogen hebt die ernaar kijken. Boost heeft duizenden mensen die de codering controleren en valideren en feedback geven om ervoor te zorgen dat de juiste idiomen correct worden gebruikt. Lezen: stackoverflow.com/a/149305/14065

Antwoord

U kunt accolades gebruiken om vanuit uw functies terug te keren. Op die manier hoef je “geen lange en omslachtige typen te herhalen (zo veel mogelijk DROOG). Bijvoorbeeld ConditionalRange::end:

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

U wilt waarschijnlijk ook dat uw functie arrays in C-stijl accepteert. Gebruik daarom std::begin en std::end in plaats van de lidfuncties aan te roepen:

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

Met dat (overal in je code) en enkele triviale wijzigingen, zal je code ook werken voor C- style arrays ( hier getest en het werkt prima).

Uw functie een naam geven

De naam makeConditionalRange is vrij lang. De functie die je hebt gemaakt, bestaat al in andere bibliotheken en programmeertalen (Python komt in mijn gedachten) onder de naam filter. Overweeg het wijzigen van de naam zodat veel gebruikers de naam op het eerste gezicht herkennen (hij zal bovendien korter zijn).

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *