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
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).
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
zoptymalizuj to ponadoperator==