C ++ 11 -sarjapohjaisen tekeminen silmukoille hieman hyödyllisemmäksi

C ++ 11 on hienoa. Todennäköisesti yksi kauneimmista piirteistä (mielestäni) on niin kutsuttu alue-pohjainen silmukalle.

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

tai

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

sijaan yksinkertaistettuna automaattisella C ++ 11: llä:

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

voimme sanoa tämän:

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

Tämä on hyvin ilmeikäs: Puhumme tietoja merkinnästä, ei alueen i ^ sijasta tai merkintää osoittavasta iteraattorista. Tästä syntaksista puuttuu kuitenkin kyky saada alialoja (esim. Jättää huomiotta viimeinen merkintä). Haluan lisätä tämän toiminnon puhtaalla tavalla sarjaan pieniä auttajarakenteita / -menetelmiä.

Paljon motivaatioistani 😉 Nyt, oikeasta kaupasta.

Tässä viestissä käsittelen ehtoja range -merkinnöille. Periaatteessa auttajakoodin tulisi suorittaa vastaava kuin

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

, mutta ilman kyseistä sisennystasoa.

Tässä on ConditionalRange-koodi:

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

Tämä on iteraattorin apurakenne:

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

Tarkoitettu käyttö on:

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"; 

Esittely: napsauta tätä!

Mitä ovatko lähestymistapani heikkouksia? Millaisilla argumenteilla se epäonnistuu, vaikka sen ei pitäisi olla täydellisessä maailmassa?

Kommentit

  • En näe heikkouksia, mutta en luulet sinun harkitsevan makeConditionalRange-nimityksen nimeämistä jollekin, joka keskittyy asiakaskoodin ulkonäköön, ei " ehdollisen alueen tekemiseen ". Tällä tavoin minä tarkoita, että asiakaskoodin pitäisi näyttää tältä: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Eli onko tarkoitus tässä poiketa merkittävästi Boostista filter_iterator ?
  • @JerryCoffin Ei se ole ' t. Minulla ei yksinkertaisesti ole ' ei tutkittu Boostia 😉
  • Yleisin iteraattoritesti operator!= optimoi tämän yli operator==
  • @stefan: olla tylsä, että ' on typerä. Se on kuin sanoisin, etten halua ' t tapana ostaa auto vakiovalmistajilta koska haluan rakentaa sellaisen, joka valuu merilevästä itse, jotta voin taata, ettei se aiheuta pilaantumista. Ongelmana on, että kirjastojesi joukko tulee olemaan äärettömän enemmän buginen kuin tehostettava, koska sinulla on vain yksi silmäjoukko katsomassa niitä. Boostilla tuhannet ihmiset tarkistavat ja vahvistavat koodin korjauksen ja antavat palautetta varmistaakseen, että oikeita idioomeja käytetään oikein. Lue: stackoverflow.com/a/149305/14065

vastaus

Voit palata toiminnoistasi aaltosulkeilla. Tällä tavalla sinun ei tarvitse toistaa pitkiä ja hankalia tyyppejä (DRY, niin paljon kuin mahdollista). Esimerkiksi ConditionalRange::end:

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

Haluat todennäköisesti myös, että toiminto hyväksyy myös C-tyyliset taulukot. Siksi sinun tulisi käyttää std::begin ja std::end jäsenfunktioiden kutsumisen sijaan:

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

Tämän avulla (kaikkialla koodissasi) ja joitain vähäpätöisiä muutoksia koodisi toimii myös C- tyylitaulukot (testattu täällä ja se toimii hyvin).

Funktion nimeäminen

Nimi makeConditionalRange on melko pitkä. Luomasi toiminto on jo olemassa muissa kirjastoissa ja ohjelmointikielissä (Python tulee mieleeni) nimellä filter. Harkitse nimen muuttaminen, jotta monet käyttäjät tunnistavat nimen ensi silmäyksellä (lisäksi se on lyhyempi).

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *