Jak mogę uniknąć pętli „for” zawierających warunek „if” w C ++?

111

W przypadku prawie całego kodu, który piszę, często mam do czynienia z problemami z redukcją zestawów na kolekcjach, które ostatecznie kończą się naiwnymi warunkami „jeśli” w nich. Oto prosty przykład:

for(int i=0; i<myCollection.size(); i++)
{
     if (myCollection[i] == SOMETHING)
     {
           DoStuff();
     }
}

Dzięki językom funkcjonalnym mogę rozwiązać problem, redukując zbiór do innego zbioru (łatwo), a następnie wykonując wszystkie operacje na moim zredukowanym zestawie. W pseudokodzie:

newCollection <- myCollection where <x=true
map DoStuff newCollection

A w innych wariantach języka C, takich jak C #, mógłbym zredukować za pomocą klauzuli where, takiej jak

foreach (var x in myCollection.Where(c=> c == SOMETHING)) 
{
   DoStuff();
}

Albo lepiej (przynajmniej dla moich oczu)

myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));

Wprawdzie robię dużo mieszania paradygmatów i stylu opartego na subiektywnych / opiniach, ale nie mogę się powstrzymać od poczucia, że ​​brakuje mi czegoś naprawdę podstawowego, co pozwoliłoby mi użyć tej preferowanej techniki w C ++. Czy ktoś mógłby mnie oświecić?

Darkenor
źródło
7
Poza standardową funkcjonalnością biblioteki C ++, możesz spróbować std::copy_if, ale wybory nie są leniwe
milleniumbug
14
Możesz być zainteresowany Range-v3 . Powinien również pojawić się w C ++ jako TS i, miejmy nadzieję, ustandaryzowany w przyszłym wydaniu.
NathanOliver
12
Czuję potrzebę podkreślenia, że ifwnętrze, o forktórym wspominasz, jest nie tylko funkcjonalnie równoważne z innymi przykładami, ale prawdopodobnie byłoby również szybsze w wielu przypadkach. Również dla kogoś, kto twierdzi, że lubi styl funkcjonalny, to, co DoStuffpromujesz, wydaje się być sprzeczne z ukochaną koncepcją czystości programowania funkcjonalnego, ponieważ wyraźnie ma efekty uboczne.
Pharap
60
Nigdy tak naprawdę nie rozumiałem, dlaczego ludzie łączą całą logikę w jednej linii, co sprawia, że ​​wygląda to lepiej lub bardziej czytelnie. Twój fragment kodu w C ++ na samej górze jest dla mnie zdecydowanie najbardziej czytelny ze wszystkich Twoich możliwości. A ponieważ wydajność nie ulegnie zmianie, nie mogę zrozumieć, dlaczego wolisz tego nie pisać, chyba że płacisz za liczbę usuniętych wierszy kodu.
Cody Gray
10
@CodyGray Zgodne: to tylko cukier syntaktyczny. A tytuł pytania jest mylący, ponieważ bardzo różni się od niego unikanie rozgałęziania i ukrywanie go pod abstrakcją.
edmz

Odpowiedzi:

99

IMHO jest prostsze i bardziej czytelne w użyciu pętli for z symbolem if w środku. Jeśli jednak jest to dla Ciebie irytujące, możesz użyć for_each_ifponiższego:

template<typename Iter, typename Pred, typename Op> 
void for_each_if(Iter first, Iter last, Pred p, Op op) {
  while(first != last) {
    if (p(*first)) op(*first);
    ++first;
  }
}

Przypadek użycia:

std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });

Live Demo

101010
źródło
10
To wyjątkowo sprytne. Zgodzę się również, że nie jest to proste i prawdopodobnie użyję warunków if podczas programowania C ++, które są używane przez innych. Ale to jest dokładnie to, czego potrzebuję do własnego użytku! :)
Darkenor
14
@Default Przekazywanie par iteratorów zamiast kontenerów jest zarówno bardziej elastyczne, jak i idiomatyczne w C ++.
Mark B
8
@Slava, ogólnie rzecz biorąc, zakresy nie zmniejszą liczby algorytmów. Na przykład nadal potrzebujesz find_ifi findczy działają one na zakresach lub parach iteratorów. (Jest kilka wyjątków, takich jak for_eachi for_each_n). Sposobem na uniknięcie pisania nowych algorytmów dla każdego kichnięcia jest użycie różnych operacji z istniejącymi algosami, np. Zamiast for_each_ifosadzania warunku w elemencie wywoływanym przekazanym for_eachnp.for_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Jonathan Wakely
9
Muszę się zgodzić z pierwszym zdaniem: standardowe rozwiązanie for-if jest znacznie bardziej czytelne i łatwiejsze w użyciu. Myślę, że składnia lambda i użycie szablonu zdefiniowanego gdzieś indziej tylko do obsługi prostej pętli drażniłyby lub prawdopodobnie wprowadzały w błąd innych programistów. Poświęcasz lokalność i wydajność dla ... co? Umiesz napisać coś w jednej linii?
user1354557
45
Kaszel @Darkenor, generalnie należy unikać " wyjątkowo sprytnego" programowania, ponieważ denerwuje to wszystkich innych, w tym twoje przyszłe ja.
Ryan
48

Zwiększenie zapewnia zakresy, których można używać w oparciu o zakres. Zakresy mają tę zaletę, że nie kopiować leżących u podstaw struktury danych, ale jedynie dostarczenie „Widok” (czyli begin(), end()dla zakresu i operator++(), operator==()na iteracyjnej). To może Cię zainteresować: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html

#include <boost/range/adaptor/filtered.hpp>
#include <iostream>
#include <vector>

struct is_even
{
    bool operator()( int x ) const { return x % 2 == 0; }
};

int main(int argc, const char* argv[])
{
    using namespace boost::adaptors;

    std::vector<int> myCollection{1,2,3,4,5,6,7,8,9};

    for( int i: myCollection | filtered( is_even() ) )
    {
        std::cout << i;
    }
}
lorro
źródło
1
Mogę zasugerować użycie zamiast tego przykładu PO, tj. is_even=> condition, input=> myCollectionItp.
Domyślnie
To całkiem doskonała odpowiedź i zdecydowanie to, czego szukam. Mam zamiar wstrzymać się z akceptacją, chyba że ktoś może wymyślić standardowy zgodny sposób, który wykorzystuje leniwe / odroczone wykonanie. Głosowano za.
Darkenor
5
@Darkenor: Jeśli Boost jest dla Ciebie problemem (np. Masz zakaz korzystania z niego ze względu na politykę firmy i mądrość menedżera), mogę wymyślić filtered()dla Ciebie uproszczoną definicję - to powiedziawszy, lepiej użyć obsługiwana biblioteka niż jakiś kod ad-hoc.
lorro
Zupełnie się z Tobą zgadzam. Zaakceptowałem to, ponieważ sposób zgodny ze standardami był pierwszy, ponieważ pytanie dotyczyło samego C ++, a nie biblioteki boost. Ale to jest naprawdę świetne. Poza tym - tak, niestety pracowałem w wielu miejscach, które zakazały Boost z absurdalnych powodów ...
Darkenor
@LeeClagett:? .
lorro
44

Zamiast tworzyć nowy algorytm, jak to się dzieje w przypadku zaakceptowanej odpowiedzi, możesz użyć istniejącego z funkcją spełniającą warunek:

std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });

Lub jeśli naprawdę potrzebujesz nowego algorytmu, przynajmniej użyj go ponownie for_eachzamiast powielać logikę iteracji:

template<typename Iter, typename Pred, typename Op> 
  void
  for_each_if(Iter first, Iter last, Pred p, Op op) {
    std::for_each(first, last, [&](auto& x) { if (p(x)) op(x); });
  }
Jonathan Wakely
źródło
Znacznie lepsze i bardziej przejrzyste w przypadku korzystania z biblioteki standardowej.
anonimowy
4
Ponieważ std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });jest całkowicie prostsze niż for (Iter x = first; x != last; x++) if (p(x)) op(x);}?
user253751
2
Ponowne użycie standardowej biblioteki @immibis ma inne zalety, takie jak sprawdzanie poprawności iteratora lub (w C ++ 17) znacznie łatwiejsze zrównoleglenie, po prostu poprzez dodanie jeszcze jednego argumentu: std::for_each(std::execution::par, first, last, ...);Jak łatwo jest dodać te rzeczy do odręcznej pętli?
Jonathan Wakely,
1
#pragma omp parallel for
Mark K Cowan
2
@mark przepraszam, jakieś przypadkowe dziwactwo twojego kodu źródłowego lub łańcucha kompilacji sprawiło, że irytująco delikatne równoległe niestandardowe rozszerzenie kompilatora generuje zerowy wzrost wydajności bez żadnej diagnostyki.
Yakk - Adam Nevraumont
21

Idea unikania

for(...)
    if(...)

konstrukty jako anty-wzór jest zbyt szeroki.

Przetwarzanie wielu elementów, które pasują do określonego wyrażenia z wnętrza pętli, jest całkowicie w porządku, a kod nie może być znacznie bardziej przejrzysty. Jeśli przetwarzanie staje się zbyt duże, aby zmieścić się na ekranie, jest to dobry powód, aby użyć podprogramu, ale nadal warunek najlepiej umieścić wewnątrz pętli, tj.

for(...)
    if(...)
        do_process(...);

jest znacznie lepsze niż

for(...)
    maybe_process(...);

Staje się antywzorem, gdy tylko jeden element będzie pasował, ponieważ wtedy byłoby bardziej zrozumiałe, aby najpierw wyszukać element i wykonać przetwarzanie poza pętlą.

for(int i = 0; i < size; ++i)
    if(i == 5)

jest tego skrajnym i oczywistym przykładem. Bardziej subtelny, a przez to bardziej powszechny, jest wzór fabryczny

for(creator &c : creators)
    if(c.name == requested_name)
    {
        unique_ptr<object> obj = c.create_object();
        obj.owner = this;
        return std::move(obj);
    }

Jest to trudne do odczytania, ponieważ nie jest oczywiste, że kod treści zostanie wykonany tylko raz. W takim przypadku lepiej byłoby oddzielić wyszukiwanie:

creator &lookup(string const &requested_name)
{
    for(creator &c : creators)
        if(c.name == requested_name)
            return c;
}

creator &c = lookup(requested_name);
unique_ptr obj = c.create_object();

Wciąż istnieje ifwewnątrz a for, ale z kontekstu staje się jasne, co robi, nie ma potrzeby zmieniać tego kodu, chyba że wyszukiwanie zmieni się (np. Na a map) i od razu widać, że create_object()jest wywoływane tylko raz, ponieważ jest nie wewnątrz pętli.

Simon Richter
źródło
Podoba mi się to jako przemyślany i wyważony przegląd, nawet jeśli w pewnym sensie odmawia odpowiedzi na postawione pytanie. Uważam, że for( range ){ if( condition ){ action } }styl-ułatwia czytanie fragmentów po kawałku i wykorzystuje tylko znajomość podstawowych konstrukcji językowych.
PJTraill
@PJTraill, sposób, w jaki pytanie zostało sformułowane, przypomniał mi o tyradzie Raymonda Chena przeciwko anty-wzorowi for-if , który był kultowy i jakimś cudem stał się absolutem. Całkowicie się zgadzam, że for(...) if(...) { ... }często jest to najlepszy wybór (dlatego zaleciłem podzielenie akcji na podprogram).
Simon Richter,
1
Dziękuję za link, który wyjaśnił mi sprawę: nazwa „ for-if ” jest myląca i powinna brzmieć mniej więcej tak, jak „ for-all-if-one ” lub „ lookup-unikanie ”. Przypomina mi to sposób, w jaki Wikipedia opisała inwersję abstrakcji w 2005 roku, kiedy to „ tworzy się proste konstrukcje na złożonych (jedynkach)” - dopóki ich nie przepisuję! Właściwie nie spieszyłbym się nawet z poprawieniem formy wyszukiwania-procesu-zakończenia, for(…)if(…)…gdyby było to jedyne miejsce wyszukiwania.
PJTraill
17

Oto szybka, stosunkowo minimalna filterfunkcja.

Potrzeba predykatu. Zwraca obiekt funkcji, który przyjmuje iterowalną.

Zwraca iterowalny element, którego można użyć w for(:)pętli.

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }

template<class It, class F>
struct filter_helper:range_t<It> {
  F f;
  void advance() {
    while(true) {
      (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      if (this->empty())
        return;
      if (f(*this->begin()))
        return;
    }
  }
  filter_helper(range_t<It> r, F fin):
    range_t<It>(r), f(std::move(fin))
  {
      while(true)
      {
          if (this->empty()) return;
          if (f(*this->begin())) return;
          (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      }
  }
};

template<class It, class F>
struct filter_psuedo_iterator {
  using iterator_category=std::input_iterator_tag;
  filter_helper<It, F>* helper = nullptr;
  bool m_is_end = true;
  bool is_end() const {
    return m_is_end || !helper || helper->empty();
  }

  void operator++() {
    helper->advance();
  }
  typename std::iterator_traits<It>::reference
  operator*() const {
    return *(helper->begin());
  }
  It base() const {
      if (!helper) return {};
      if (is_end()) return helper->end();
      return helper->begin();
  }
  friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    if (lhs.is_end() && rhs.is_end()) return true;
    if (lhs.is_end() || rhs.is_end()) return false;
    return lhs.helper->begin() == rhs.helper->begin();
  }
  friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    return !(lhs==rhs);
  }
};
template<class It, class F>
struct filter_range:
  private filter_helper<It, F>,
  range_t<filter_psuedo_iterator<It, F>>
{
  using helper=filter_helper<It, F>;
  using range=range_t<filter_psuedo_iterator<It, F>>;

  using range::begin; using range::end; using range::empty;

  filter_range( range_t<It> r, F f ):
    helper{{r}, std::forward<F>(f)},
    range{ {this, false}, {this, true} }
  {}
};

template<class F>
auto filter( F&& f ) {
    return [f=std::forward<F>(f)](auto&& r)
    {
        using std::begin; using std::end;
        using iterator = decltype(begin(r));
        return filter_range<iterator, std::decay_t<decltype(f)>>{
            range(begin(r), end(r)), f
        };
    };
};

Poszedłem na skróty. Prawdziwa biblioteka powinna tworzyć prawdziwe iteratory, a nie for(:)kwalifikujące się pseudo-fasady, które zrobiłem.

W miejscu użytkowania wygląda to tak:

int main()
{
  std::vector<int> test = {1,2,3,4,5};
  for( auto i: filter([](auto x){return x%2;})( test ) )
    std::cout << i << '\n';
}

co jest całkiem ładne i drukuje

1
3
5

Przykład na żywo .

Jest proponowany dodatek do C ++ o nazwie Rangesv3, który robi to i więcej. boostma również dostępne zakresy filtrów / iteratory. boost ma również pomocników, dzięki którym pisanie powyższego jest znacznie krótsze.

Yakk - Adam Nevraumont
źródło
15

Jeden styl, który jest wystarczająco używany, aby go wspomnieć, ale nie został jeszcze wspomniany, to:

for(int i=0; i<myCollection.size(); i++) {
  if (myCollection[i] != SOMETHING)
    continue;

  DoStuff();
}

Zalety:

  • Nie zmienia poziomu wcięcia, DoStuff();gdy zwiększa się złożoność warunku. Logicznie rzecz biorąc, DoStuff();powinien znajdować się na najwyższym poziomie forpętli i tak jest.
  • Natychmiast jasno, że iteracji pętli w ciągu SOMETHINGsekund od zbierania, nie wymagając czytnika w celu sprawdzenia, że nie ma po zamknięciu }części ifbloku.
  • Nie wymaga żadnych bibliotek ani pomocniczych makr ani funkcji.

Niedogodności:

  • continue, podobnie jak inne instrukcje sterujące przepływem, jest nadużywany w sposób, który prowadzi do trudnego do naśladowania kodu do tego stopnia, że ​​niektórzy ludzie sprzeciwiają się jakiemukolwiek ich użyciu: istnieje prawidłowy styl kodowania, który niektórzy przestrzegają, który unika continuei unika breakinnego niż w a switch, to unika returninnego niż na końcu funkcji.

źródło
3
Twierdzę, że w forpętli obejmującej wiele wierszy dwuwierszowe „jeśli nie, kontynuuj” jest znacznie jaśniejsze, logiczne i czytelne. Natychmiastowe powiedzenie „pomiń to, jeśli” po forinstrukcji brzmi dobrze i, jak powiedziałeś, nie powoduje wcięcia pozostałych funkcjonalnych aspektów pętli. Jeśli jednak continuejest dalej niżej, traci się pewną przejrzystość (tj. Jeśli przed ifinstrukcją zawsze będzie wykonywana jakaś operacja ).
anonimowy
11
for(auto const &x: myCollection) if(x == something) doStuff();

Wygląda fordla mnie jak C ++ - specyficzne zrozumienie. Tobie?

bipll
źródło
Nie sądzę, aby słowo kluczowe auto było obecne przed c ++ 11, więc nie powiedziałbym, że jest to bardzo klasyczne c ++. Jeśli mogę zadać pytanie w komentarzu, czy „auto const” powie kompilatorowi, że może zmienić układ wszystkich elementów tak, jak chce? Może kompilatorowi łatwiej będzie zaplanować unikanie rozgałęzień, jeśli tak jest.
mathreadler
1
@mathreadler Im szybciej ludzie przestaną się martwić „klasycznym C ++”, tym lepiej. C ++ 11 był makroewolucyjnym wydarzeniem dla języka i ma 5 lat: to powinno być minimum, do którego dążymy. W każdym razie OP oznaczył to i C ++ 14 (nawet lepiej!). Nie, auto constnie ma żadnego wpływu na kolejność iteracji. Jeśli spojrzysz na oparty na zakresie for, zobaczysz, że w zasadzie wykonuje standardową pętlę od begin()do end()z niejawnym dereferencją. W żaden sposób nie może złamać gwarancji zamówienia (jeśli w ogóle) dla iterowanego kontenera; byłoby to wyśmiewane z powierzchni Ziemi
podkreślenie_d
1
@mathreadler, właściwie to było, po prostu miało zupełnie inne znaczenie. To, czego nie było, to zakres dla ... i każda inna odrębna funkcja C ++ 11. Miałem tutaj na myśli to, że zakres-fors, std::futures, std::functions, nawet te anonimowe domknięcia są bardzo dobrze C ++ ish w składni; każdy język ma swój własny język, a wprowadzając nowe funkcje, stara się naśladować starą dobrze znaną składnię.
bipll
@underscore_d, kompilator może wykonywać dowolne przekształcenia, pod warunkiem przestrzegania reguły as-if, prawda?
bipll
1
Hmmm, a co można przez to rozumieć?
bipll
7

Gdyby DoStuff () w przyszłości zależało od i, to zaproponowałbym ten gwarantowany wariant maskowania bitów bez gałęzi.

unsigned int times = 0;
const int kSize = sizeof(unsigned int)*8;
for(int i = 0; i < myCollection.size()/kSize; i++){
  unsigned int mask = 0;
  for (int j = 0; j<kSize; j++){
    mask |= (myCollection[i*kSize+j]==SOMETHING) << j;
  }
  times+=popcount(mask);
}

for(int i=0;i<times;i++)
   DoStuff();

Gdzie popcount to dowolna funkcja obliczająca populację (liczba bitów = 1). Będzie pewna swoboda w nakładaniu bardziej zaawansowanych ograniczeń na i i ich sąsiadów. Jeśli nie jest to potrzebne, możemy zdjąć pętlę wewnętrzną i przerobić pętlę zewnętrzną

for(int i = 0; i < myCollection.size(); i++)
  times += (myCollection[i]==SOMETHING);

po którym następuje

for(int i=0;i<times;i++)
   DoStuff();
mathreadler
źródło
6

Ponadto, jeśli nie obchodzi Cię zmiana kolejności kolekcji, std :: partition jest tania.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void DoStuff(int i)
{
    std::cout << i << '\n';
}

int main()
{
    using namespace std::placeholders;

    std::vector<int> v {1, 2, 5, 0, 9, 5, 5};
    const int SOMETHING = 5;

    std::for_each(v.begin(),
                  std::partition(v.begin(), v.end(),
                                 std::bind(std::equal_to<int> {}, _1, SOMETHING)), // some condition
                  DoStuff); // action
}
Loreto
źródło
Ale std::partitionzmienia kolejność kontenera.
celtschk
5

Jestem pod wrażeniem złożoności powyższych rozwiązań. Miałem zamiar zasugerować prostą, #define foreach(a,b,c,d) for(a; b; c)if(d)ale ma kilka oczywistych wad, na przykład musisz pamiętać o używaniu przecinków zamiast średników w pętli i nie możesz użyć operatora przecinka w alub c.

#include <list>
#include <iostream>

using namespace std; 

#define foreach(a,b,c,d) for(a; b; c)if(d)

int main(){
  list<int> a;

  for(int i=0; i<10; i++)
    a.push_back(i);

  for(auto i=a.begin(); i!=a.end(); i++)
    if((*i)&1)
      cout << *i << ' ';
  cout << endl;

  foreach(auto i=a.begin(), i!=a.end(), i++, (*i)&1)
    cout << *i << ' ';
  cout << endl;

  return 0;
}
Ian Parberry
źródło
3
Złożoność niektórych odpowiedzi jest wysoka tylko dlatego, że najpierw przedstawiają one ogólną metodę wielokrotnego użytku (którą można wykonać tylko raz), a następnie jej używają. Nieefektywne, jeśli masz jedną pętlę z warunkiem if w całej aplikacji, ale bardzo skuteczne, jeśli zdarza się to tysiąc razy.
gnasher729
1
Podobnie jak większość sugestii, utrudnia to, a nie ułatwia, identyfikację zakresu i warunku wyboru. A użycie makra zwiększa niepewność co do tego, kiedy (i jak często) wyrażenia są oceniane, nawet jeśli nie ma tutaj niespodzianek.
PJTraill
2

Inne rozwiązanie w przypadku, gdy i: s są ważne. Ten tworzy listę, która wypełnia indeksy, dla których należy wywołać funkcję doStuff (). Po raz kolejny głównym celem jest uniknięcie rozgałęzienia i zamiana na możliwe do rurociągu koszty arytmetyczne.

int buffer[someSafeSize];
int cnt = 0; // counter to keep track where we are in list.
for( int i = 0; i < container.size(); i++ ){
   int lDecision = (container[i] == SOMETHING);
   buffer[cnt] = lDecision*i + (1-lDecision)*buffer[cnt];
   cnt += lDecision;
}

for( int i=0; i<cnt; i++ )
   doStuff(buffer[i]); // now we could pass the index or a pointer as an argument.

Linia „magiczna” to linia ładowania bufora, która oblicza arytmetycznie, czy zachować wartość i pozostać na miejscu lub policzyć pozycję i dodać wartość. Więc sprzedajemy potencjalną gałąź na jakąś logikę i arytmetykę, a może trochę trafień w pamięci podręcznej. Typowym scenariuszem, w którym byłoby to przydatne, jest sytuacja, w której doStuff () wykonuje niewielką liczbę potokowych obliczeń i każda gałąź między wywołaniami może przerwać te potoki.

Następnie po prostu przejrzyj bufor i uruchom doStuff (), aż osiągniemy cnt. Tym razem będziemy mieć bieżący i przechowywany w buforze, abyśmy mogli go użyć w wywołaniu doStuff (), jeśli zajdzie taka potrzeba.

mathreadler
źródło
1

Można opisać swój wzorzec kodu jako zastosowanie jakiejś funkcji do podzbioru zakresu lub innymi słowy: zastosowanie jej do wyniku zastosowania filtru do całego zakresu.

Jest to osiągalne w najprostszy sposób dzięki bibliotece range -v3 Erica Neiblera ; chociaż to trochę obrzydliwe, bo chcesz pracować z indeksami:

using namespace ranges;
auto mycollection_has_something = 
    [&](std::size_t i) { return myCollection[i] == SOMETHING };
auto filtered_view = 
    views::iota(std::size_t{0}, myCollection.size()) | 
    views::filter(mycollection_has_something);
for (auto i : filtered_view) { DoStuff(); }

Ale jeśli chcesz zrezygnować z indeksów, otrzymasz:

auto is_something = [&SOMETHING](const decltype(SOMETHING)& x) { return x == SOMETHING };
auto filtered_collection = myCollection | views::filter(is_something);
for (const auto& x : filtered_collection) { DoStuff(); }

co jest ładniejsze IMHO.

PS - Biblioteka zakresów przechodzi głównie do standardu C ++ w C ++ 20.

einpoklum
źródło
0

Wspomnę tylko o Mike'u Actonie, na pewno powiedziałby:

Jeśli musisz to zrobić, masz problem ze swoimi danymi. Sortuj swoje dane!

v.oddou
źródło