A C ++ 11 nagyszerű. Valószínűleg az egyik legszebb funkció (véleményem szerint) az úgynevezett range-based-for-loop.
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
vagy
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
helyett vagy egyszerűsítve a C ++ 11 auto-val:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
ezt mondhatjuk:
for ( auto& entry : range ) { // do something with entry. }
Ez nagyon kifejező: Beszélünk a bejegyzésről, nem pedig a tartomány i-edik pozíciójáról vagy a bejegyzésre mutató iterátorról. Ennek a szintaxisnak azonban hiányzik az altartományok képessége (például figyelmen kívül hagyva az utolsó bejegyzést). Kis segítőstruktúrák / módszerek sorában szeretném tiszta módon hozzáadni ezt a funkciót.
Ennyit a motivációmról 😉 Most az igazi üzletről.
Ebben a bejegyzésben a range
bejegyzéseinek feltételeivel foglalkozom. Alapvetően a segítő kódnak meg kell felelnie a (z)
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
megfelelőnek, de ennek a szintnek a behúzása nélkül.
Itt van a ConditionalRange kódja:
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; };
Ez az iterátor segédstruktúrája:
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); } };
A tervezett felhasználás az:
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";
Bemutató: kattintson ide!
Mi van a megközelítésem gyengeségei? Milyen érvekkel nem sikerül, bár nem kellene a tökéletes világban?
Megjegyzések
Válasz
A zárójelek segítségével visszatérhet a funkcióiból. Így nem kell hosszú és nehézkes típusokat ismételni (amennyire csak lehetséges, SZÁRAZ). Például: ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
Valószínűleg azt is szeretné, hogy a funkciója elfogadja a C stílusú tömböket is. Ezért használja a std::begin
és a std::end
ahelyett, hogy meghívná a tagfüggvényeket:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
Ezzel (mindenhol a kódban) és néhány aprócska változtatással a kódod a C- stílus tömbök ( itt tesztelve és jól működik).
A funkció elnevezése
A név makeConditionalRange
elég hosszú. Az Ön által létrehozott függvény már létezik más könyvtárakban és programozási nyelvekben (a Python jut eszembe) filter
néven. Fontolja meg a név megváltoztatása annak érdekében, hogy sok felhasználó első pillantásra felismerje a nevet (ráadásul rövidebb lesz).
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
ezt optimalizálja aoperator==