C ++ 11 je skvělé. Pravděpodobně jednou z nejkrásnějších funkcí (podle mého názoru) je tzv. Range-based-for-loop. Místo
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
nebo
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
nebo zjednodušeno pomocí C ++ 11 auto:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
můžeme říci toto:
for ( auto& entry : range ) { // do something with entry. }
To je velmi expresivní: Mluvíme spíše než o i ^ té pozici v rozsahu nebo iterátor ukazující na položku. Tato syntaxe však postrádá schopnost mít podrozsahy (např. Ignorovat poslední položku). V sérii malých pomocných struktur / metod chci přidat tuto funkčnost čistým způsobem.
Tolik k mé motivaci 😉 Nyní, ke skutečné dohodě.
V tomto příspěvku se věnuji podmínkám u položek range
. V zásadě by měl pomocný kód provádět ekvivalent
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
, ale bez této úrovně odsazení.
Zde je kód pro 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; };
Toto je pomocná struktura pro iterátor:
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); } };
Zamýšlené použití je:
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";
Ukázka: klikněte sem!
Co jsou slabiny mého přístupu? S jakými argumenty selhává, i když by to nemělo být v dokonalém světě?
Komentáře
Odpovědět
K návratu ze svých funkcí můžete použít složené závorky. Tímto způsobem nebudete muset opakovat dlouhé a těžkopádné typy (pokud možno SUCHÉ). Například ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
Pravděpodobně také chcete, aby vaše funkce přijímala i pole ve stylu C. Proto byste měli používat std::begin
a std::end
místo volání členských funkcí:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
S tím (všude ve vašem kódu) a několika triviálními změnami bude váš kód fungovat i pro C- stylová pole (testováno zde a funguje to dobře).
Pojmenování vaší funkce
Název makeConditionalRange
je poměrně dlouhý. Funkce, kterou jste vytvořili, již existuje v jiných knihovnách a programovacích jazycích (Python mě napadá) pod názvem filter
. Měli byste zvážit změna názvu, aby mnoho uživatelů jméno na první pohled poznalo (navíc bude kratší).
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
optimalizovat to přesoperator==