Zajistit, aby smyčky založené na rozsahu C ++ 11 pro smyčky byly o něco užitečnější

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

  • Nevidím žádné slabosti, ale já myslím, že byste měli zvážit přejmenování makeConditionalRange na něco, co se zaměřuje na vzhled klientského kódu, ne na " vytvoření podmíněného rozsahu ". Tím jsem znamená, že kód klienta by měl místo toho vypadat takto: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Takže záměr se zde výrazně liší od Boost filter_iterator ?
  • @JerryCoffin Ne, ' t. Prostě nemám ' t se podíval na Boost 😉
  • Nejběžnější test pro iterátory operator!= optimalizovat to přes operator==
  • @stefan: Abych byl upřímný, ' je hloupý. Je to jako říkat, že ne ' zvyklý kupovat auto od standardních výrobců protože chci postavit takový, který sám vytéká z mořských řas, abych zaručil, že nezpůsobí znečištění. Problém je v tom, že vaše sada knihoven bude nekonečně více buggy než boost, protože se na ně dívá pouze jedna sada očí. Boost má tisíce lidí, kteří kontrolují a ověřují opravu kódu a poskytují zpětnou vazbu, aby zajistili správné použití správných idiomů. Číst: stackoverflow.com/a/149305/14065

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ší).

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *