Zwiększenie użyteczności pętli for w C ++ 11 w oparciu o zakres

C ++ 11 jest świetny. Prawdopodobnie jedną z najpiękniejszych funkcji (moim zdaniem) jest tak zwana pętla for-based. Zamiast

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

lub

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

lub uproszczone w C ++ 11 auto:

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

możemy to powiedzieć:

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

To jest bardzo wyraziste: rozmawiamy o wpisie, a nie o i-tej pozycji w zakresie lub iterator wskazujący na wpis. Jednak w tej składni brakuje możliwości posiadania podzakresów (np. Ignorowania ostatniego wpisu). W serii małych struktur / metod pomocniczych chcę dodać tę funkcjonalność w przejrzysty sposób.

Tyle mojej motywacji 😉 A teraz do rzeczy.

W tym poście odnoszę się do warunków we wpisach range. Zasadniczo kod pomocniczy powinien wykonywać odpowiednik

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

, ale bez tego poziomu wcięcia.

Oto kod dla 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; }; 

To jest struktura pomocnicza dla iteratora:

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

Zamierzone użycie to:

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: kliknij tutaj!

Co są słabości mojego podejścia? Z jakimi argumentami to się nie udaje, chociaż nie powinno to być w idealnym świecie?

Komentarze

  • Nie widzę słabych punktów, ale myślę, że warto rozważyć zmianę nazwy makeConditionalRange na coś, co koncentruje się na wyglądzie kodu klienta, a nie na " tworzeniu zakresu warunkowego ". W ten sposób oznacza, że zamiast tego kod klienta powinien wyglądać następująco: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Czy więc intencja tutaj różni się znacznie od Boost filter_iterator ?
  • @JerryCoffin Nie, nie ' t. Po prostu nie mam ' nie zajrzałem do Boost 😉
  • Najpopularniejszy test dla iteratorów operator!= zoptymalizuj to ponad operator==
  • @stefan: Mówiąc szczerze, ' głupie. To tak, jakby powiedzieć, że nie ' t nie będzie chciał kupować samochodu od standardowych producentów ponieważ sam chcę zbudować taki, który spływa z wodorostów, aby zagwarantować, że nie spowoduje zanieczyszczenia. Problem polega na tym, że twój zestaw bibliotek będzie nieskończenie bardziej błędny niż boost, ponieważ masz tylko jeden zestaw oczu, który na nie patrzy. Boost ma tysiące ludzi sprawdzających i weryfikujących poprawianie kodu i dostarczających informacji zwrotnych, aby upewnić się, że poprawne idiomy są używane poprawnie. Przeczytaj: stackoverflow.com/a/149305/14065

Odpowiedź

Możesz użyć nawiasów klamrowych, aby powrócić z funkcji. W ten sposób nie będziesz musiał „powtarzać długich i kłopotliwych typów (DRY, jak najwięcej). Na przykład ConditionalRange::end:

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

Ponadto prawdopodobnie chcesz, aby Twoja funkcja akceptowała również tablice w stylu C. Dlatego powinieneś używać std::begin i std::end zamiast wywoływać funkcje składowe:

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

Dzięki temu (wszędzie w kodzie) i kilku drobnym zmianom, twój kod będzie również działał dla C- style (testowane tutaj i działa dobrze).

Nazywanie swojej funkcji

Nazwa jest dość długi. Utworzona przez Ciebie funkcja istnieje już w innych bibliotekach i językach programowania (przychodzi mi na myśl Python) pod nazwą filter. Należy rozważyć zmiana jego nazwy, aby wielu użytkowników rozpoznało nazwę na pierwszy rzut oka (ponadto będzie krótsza).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *