2010-02-03 4 views
7

utilisant gamme à base de boucles en C++ 0X, je sais que nous serons en mesure de le faire:Comment trancher avec une boucle de gamme? C++ 0x

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

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

(peut-être encore plus simple à écrire avec lambda)

Mais comment devrais-je faire si je veux seulement appliquer processNumber (k) à une partie des nombres? Par exemple, comment dois-je écrire ceci pour la boucle pour appliquer processNumber() à la moitié (tête ou queue) des nombres? Le "découpage" est-il autorisé comme dans Python ou Ruby?

+4

Il serait sans doute plus facile de le faire 'std :: for_each (de, à , [] (int k) {processNumber (k);}); '. Ou vous devrez fournir un pour chaque sous-gamme compatible dans ce vecteur. –

+0

Oui, j'en suis conscient. Je veux juste connaître les limites de la boucle for-range en C++ par rapport à d'autres langages où le découpage est "facile". – Klaim

+0

La solution 'for_each' présentée ci-dessus n'est-elle pas" facile "? – jalf

Répondre

0

Quelque chose comme cela peut fonctionner (sans contrôle car je n'ai pas accès à un compilateur C++ 0x),

Edit: vérifié sur VS10, bien sûr, je devais corriger les erreurs numurous ....

Définir une classe qui est un proxy à un conteneur et dont iterator ne renvoie qu'un sous-ensemble du conteneur. L'exemple que je donne est le plus simple donnant la première moitié mais il peut être rendu beaucoup plus général.

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

Et puis vous l'utilisez dans la boucle:

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

Bonne réponse. Notez que les noms se terminant par «_t» sont réservés: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier –

+0

+1. Juste quelques suggestions: Vous devriez dériver la classe iter de std :: iterator_traits .Mais vous voulez que ce soit un itérateur d'entrée quel que soit le conteneur, donc vous devriez aussi ajouter "typedef std :: input_iterator_tag iterator_category" au corps de la classe. En outre, vous avez oublié d'implémenter l'opérateur ==. – Manuel

+0

C'est beaucoup de travail juste pour réaliser ce que vous pourriez faire avec un oneliner 'for_each'. – jalf

3

Une possibilité pourrait être iterator_range de boost

(Ne pas avoir un compilateur qui prend en charge basé gamme pour, à l'aide BOOST_FOREACH au lieu I. d attend que la plage de valeurs pour le travail soit la même, à condition que le conteneur ou la plage ait la méthode de début et de fin.)

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

Pour plus de commodité, vous pouvez également créer votre propre fonction de découpe, de manière à accepter les indices au lieu des itérateurs. Encore une fois, il pourrait se fonder sur boost.iterator_range, ou non:

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

En général, on serait probablement travailler avec itérateurs en premier lieu, il pourrait ne pas être très utile.

+0

+1 Bonne solution, définitivement le chemin à parcourir si vous avez l'habitude de travailler avec des gammes (par opposition à travailler avec des itérateurs). Une optimisation que je suggère est de placer la ligne "dernier itérateur = premier;" en dessous de la ligne qui avance "en premier". Bien sûr, cela signifie que "last" doit être avancé par "i_last-i_first", pas "i_last". – Manuel

+0

Oui, c'est génial, mais ce que je cherche, c'est une façon de le faire en boucle. Je pense que la boucle for-range peut être "incomplète" sans trancher mais peut-être que je ne sais pas comment faire avec cette nouvelle syntaxe? – Klaim

+1

@Klaim: Ne fonctionne pas avec la gamme-pour? Je ne peux pas tester et je ne suis pas au courant d'une nouvelle syntaxe de découpage spéciale. Pour autant que je sache, tout ce qui fournit une méthode begin() et end() renvoyant des choses semblables à l'itérateur va. @Manuel: Merci, code édité. – UncleBens

11

Vous pouvez utiliser le "sliced" range adaptor de la bibliothèque 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); 
} 
Questions connexes