2010-02-03 9 views
7

Korzystanie zakres oparty na pętli w C++ 0x, wiem, że będziemy w stanie zrobić:Jak wycinać z pętlą dla zakresu? C++ 0x

std::vector<int> numbers = generateNumbers(); 

for(int k : numbers) 
{ 
    processNumber(k); 
} 

(może być jeszcze prostsze napisać z lambda)

Ale jak powinienem zrobić, jeśli chcę tylko zastosować processNumber (k) do części liczb? Na przykład, w jaki sposób powinienem napisać to dla pętli, aby zastosować processNumber() do połowy (głowy lub ogona) liczb? Czy "krojenie" jest dozwolone tak jak w Pythonie czy Ruby?

+4

To prawdopodobnie tylko będzie łatwiej zrobić 'std :: for_each (od, do , [] (int k) {processNumber (k);}); '. Lub musiałbyś podać dla każdego zgodnego podzakresu w tym wektorze. –

+0

Tak, jestem tego świadomy. Po prostu chcę poznać limity pętli for-range w C++ w porównaniu do innych języków, w których cięcie jest "łatwe". – Klaim

+0

Czy powyższe rozwiązanie "for_each" nie jest "łatwe"? – jalf

Odpowiedz

0

Coś takiego może działać (zaznaczone jako nie mam dostępu do kompilatora C++ 0x),

Edit: sprawdzone go na VS10, oczywiście musiałem naprawić błędy numurous ....

Należy zdefiniować klasę, która jest pełnomocnikiem dowolnego kontenera, a której tylko iterator zwraca podzbiór kontenera. Podany przeze mnie przykład jest najprostszym, który podaje pierwszą połowę, ale może być znacznie bardziej ogólny.

template <class Container> 
class head_t { 
    Container& c_; 
public: 
    template <class T> 
    class iter { 
     T curr_; 
     const T& end_; 
     int limit_; // count how many items iterated 
    public: 
     iter(T curr, const T& end) 
      : curr_(curr) 
      , end_(end)    
      , limit_(std::distance(curr_, end_)/2) 
      { } 

     typename Container::value_type operator*() { return *curr_; } 

     // Do the equivilant for for operator++(int) 
     iter& operator++() { 
      if (--limit_ == 0) // finished our slice 
       curr_ = end_; 
      else 
       ++curr_; 
      return *this; 
     } 

     bool operator!=(const iter& i) const { 
      return curr_ != i.curr_; 
     } 
    }; 

    head_t(Container& c) : c_(c) {} 
    iter<typename Container::iterator> begin() { 
     return iter<typename Container::iterator>(c_.begin(), c_.end()); 
    } 

    iter<typename Container::iterator> end() { 
     return iter<typename Container::iterator>(c_.end(), c_.end()); 
    }  
}; 

template <class T> 
head_t<T> head(T& t) { return head_t<T>(t); } 

A następnie używać go w pętli:

for(int k : head(numbers)) 
+0

Dobra odpowiedź. Zwróć uwagę, że nazwy kończące się na '_t' są zarezerwowane: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier –

+0

+1. Kilka sugestii: powinieneś wyprowadzić klasę iter ze std :: iterator_traits .Ale chcesz, aby był to iterator wejściowy, niezależnie od kontenera, więc powinieneś również dodać "typedef std :: input_iterator_tag iterator_category" do treści klasy. Ponadto zapomniałeś zaimplementować operatora ==. – Manuel

+0

To dużo pracy, aby osiągnąć to, co można zrobić z onelinerem 'for_each'. – jalf

3

Jedną z możliwości może być impuls na iterator_range

(nie ma kompilatora, który obsługuje zakres oparte na, używając BOOST_FOREACH zamiast ja. "oczekuje się, że zakres pracy będzie taki sam, o ile pojemnik lub zakres ma metodę początkową i końcową.)

#include <boost/foreach.hpp> 
#include <boost/range/iterator_range.hpp> 
#include <iostream> 
#include <vector> 

int main() 
{ 
    std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 
    BOOST_FOREACH(int n, boost::make_iterator_range(v.begin(), v.begin() + v.size()/2)) { 
     std::cout << n << '\n'; 
    } 
} 

Dla wygody można również utworzyć własną funkcję przekroju, aby przyjmowała indeksy zamiast iteratorów. Znowu, może to być oparte na boost.iterator_range, czy nie:

#include <cstddef> 
#include <iterator> 

template <class Iterator> 
class iter_pair 
{ 
public: 
    typedef Iterator iterator; 
    typedef Iterator const_iterator; //BOOST_FOREACH appears to want this 
    iter_pair(iterator first, iterator last): first(first), last(last) {} 
    iterator begin() const { return first; } 
    iterator end() const { return last; } 
private: 
    iterator first, last; 
}; 

template <class Container> 
struct iterator_type 
{ 
    typedef typename Container::iterator type; 
}; 

template <class Container> 
struct iterator_type<const Container> 
{ 
    typedef typename Container::const_iterator type; 
}; 

template <class Container> 
iter_pair<typename iterator_type<Container>::type> 
    slice(Container& c, size_t i_first, size_t i_last) 
{ 
    typedef typename iterator_type<Container>::type iterator; 
    iterator first = c.begin();   
    std::advance(first, i_first); 
    iterator last = first; 
    std::advance(last, i_last - i_first); 
    return iter_pair<iterator>(first, last); 
} 

template <class Container> 
iter_pair<typename iterator_type<Container>::type> 
    slice(Container& c, size_t i_last) 
{ 
    return slice(c, 0, i_last); 
} 

//could probably also be overloaded for arrays 

#include <cctype> 
#include <string> 
#include <boost/foreach.hpp> 
#include <iostream> 

int main() 
{ 
    std::string s("Hello world, la-la-la!"); 
    BOOST_FOREACH(char& c, slice(s, 2, 11)) { 
     if (c == 'l') 
      c = std::toupper(c); 
    } 
    const std::string& r = s; 
    BOOST_FOREACH(char c, slice(r, r.size() - 1)) { 
     std::cout << c << " "; 
    } 
    std::cout << '\n'; 
} 

Ogólnie jeden prawdopodobnie pracować z iteratorów w pierwszej kolejności, więc to nie może być tak, że użyteczne.

+0

+1 Dobre rozwiązanie, zdecydowanie do zrobienia, jeśli jesteś przyzwyczajony do pracy z zakresami (w przeciwieństwie do pracy z iteratorami). Jedna z optymalizacji sugerowałaby umieszczenie linii "iterator last = first;" poniżej linii, która przesuwa się "pierwszy". Oczywiście oznacza to, że "ostatnia" musi zostać rozwinięta przez "i_last-i_first", a nie "i_last". – Manuel

+0

Tak, to świetnie, ale to, czego szukam, to metoda oparta na pętli for-range. Mam wrażenie, że pętla for-range może być "niekompletna" bez krojenia, ale może po prostu nie wiem, jak kroić tę nową składnię? – Klaim

+1

@Klaim: Czy to nie działa z zakresem? Nie mogę przetestować i nie znam żadnej nowej specjalnej składni krojenia. O ile mi wiadomo, wszystko, co zapewnia metodę begin() i end() zwracającą podobne do iteratora rzeczy. @Manuel: Dzięki, edytowany kod. – UncleBens

11

Można użyć "sliced" range adaptor z biblioteki Boost.Range:

#include <boost/range/adaptor/sliced.hpp> 

using boost::adaptors::sliced; 

...

std::vector<int> numbers = generateNumbers(); 
for(int k : numbers | sliced(0, numbers.size()/2)) 
{ 
    processNumber(k); 
} 
Powiązane problemy