Existe-t-il une classe de plage dans C ++ 11 à utiliser avec des boucles for basées sur une plage?

101

Je me suis retrouvé à écrire ceci il y a juste un peu:

template <long int T_begin, long int T_end>
class range_class {
 public:
   class iterator {
      friend class range_class;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return iterator(T_begin); }
   iterator end() const { return iterator(T_end); }
};

template <long int T_begin, long int T_end>
const range_class<T_begin, T_end>
range()
{
   return range_class<T_begin, T_end>();
}

Et cela me permet d'écrire des choses comme ceci:

for (auto i: range<0, 10>()) {
    // stuff with i
}

Maintenant, je sais que ce que j'ai écrit n'est peut-être pas le meilleur code. Et peut-être y a-t-il un moyen de le rendre plus flexible et utile. Mais il me semble que quelque chose comme ça aurait dû faire partie de la norme.

Donc est-il? Une sorte de nouvelle bibliothèque a-t-elle été ajoutée pour les itérateurs sur une plage d'entiers, ou peut-être une plage générique de valeurs scalaires calculées?

Très varié
la source
17
+1. J'aimerais avoir de telles classes dans mes utilitaires. :-)
Nawaz
2
Au fait, quel est l'intérêt d'écrire une rangefonction de modèle? Cela n'ajoute rien à l'usage dans lequel il range_classest utilisé. Je veux dire, range<0,10>()et c'est range_class<0,10>()exactement la même chose!
Nawaz
2
@Nawaz: Ouais, tu as raison. J'avais une vision étrange que je pourrais faire en sorte que la fonction gère la différenciation entre le cas dynamique et statique, mais je ne pense pas que cela puisse être fait.
Omniprésente
2
@iammilind: Nawaz a posé la même question 35 minutes avant vous;)
Sebastian Mach
3
Pour être pédant, je pense que cette implémentation a un bogue, c'est que vous ne pouvez pas l'utiliser pour itérer sur toute la plage d'entiers. Si vous branchez INT_MIN et INT_MAX comme arguments de modèle, INT_MAX une fois incrémenté débordera de INT_MIN et provoquera des boucles infinies. "end" dans la STL est censé être "un après la fin" qui ne peut pas entrer dans le type entier lui-même, donc je ne sais pas si cela peut réellement être implémenté efficacement pour le type entier le plus large sur une plate-forme donnée. Pour les types d'entiers plus petits, vous pouvez toujours utiliser un type plus large en interne ...
Joseph Garvin

Réponses:

59

La bibliothèque standard C ++ n'en a pas, mais Boost.Range a boost :: counting_range , ce qui est certainement qualifié. Vous pouvez également utiliser boost :: irange , qui est un peu plus ciblé.

La bibliothèque de plages de C ++ 20 vous permettra de le faire via view::iota(start, end).

Nicol Bolas
la source
3
Oui, c'est certainement la nature de ce que je recherche. Je suis content que Boost l'ait fait. Je suis triste que le comité des normes ne l'ait pas inclus pour une raison quelconque. Cela aurait été un excellent complément à la fonction range-base-for.
Omnifarious
Cette réponse répond mieux à ma question directe, donc je la choisirai, même si la réponse de Nawaz est très bonne.
Omnifariable
6
Il y a eu beaucoup de progrès ces derniers temps pour intégrer les gammes dans la norme (N4128). Voir github.com/ericniebler/range-v3 pour une proposition et une implémentation de référence.
Ela782
1
@ Ela782: ... et pourtant il semble que nous ne le verrons pas en C ++ 17, non?
einpoklum
1
@Andreas Oui, les gammes en ont fait un TS il y a quelque temps, mais je ne pense pas qu'il y ait / ait jamais eu une implémentation de référence qui en a fait les principaux compilateurs sous l' std::experimental::rangesespace de noms. range-v3était toujours une sorte d'implémentation de référence, je dirais. Mais maintenant, je crois que les éléments de base de la gamme ont également été récemment adoptés en C ++ 20, nous allons donc les intégrer std::bientôt! :-)
Ela782
47

Autant que je sache, il n'existe pas de classe de ce type dans C ++ 11.

Quoi qu'il en soit, j'ai essayé d'améliorer votre implémentation. Je l'ai fait non-modèle , car je ne vois aucun avantage à en faire un modèle . Au contraire, il présente un inconvénient majeur: vous ne pouvez pas créer la plage au moment de l'exécution, car vous devez connaître les arguments du modèle au moment de la compilation.

//your version
auto x = range<m,n>(); //m and n must be known at compile time

//my version
auto x = range(m,n);  //m and n may be known at runtime as well!

Voici le code:

class range {
 public:
   class iterator {
      friend class range;
    public:
      long int operator *() const { return i_; }
      const iterator &operator ++() { ++i_; return *this; }
      iterator operator ++(int) { iterator copy(*this); ++i_; return copy; }

      bool operator ==(const iterator &other) const { return i_ == other.i_; }
      bool operator !=(const iterator &other) const { return i_ != other.i_; }

    protected:
      iterator(long int start) : i_ (start) { }

    private:
      unsigned long i_;
   };

   iterator begin() const { return begin_; }
   iterator end() const { return end_; }
   range(long int  begin, long int end) : begin_(begin), end_(end) {}
private:
   iterator begin_;
   iterator end_;
};

Code de test:

int main() {
      int m, n;
      std::istringstream in("10 20");
      if ( in >> m >> n ) //using in, because std::cin cannot be used at coliru.
      {
        if ( m > n ) std::swap(m,n); 
        for (auto i : range(m,n)) 
        {
             std::cout << i << " ";
        }
      }
      else 
        std::cout <<"invalid input";
}

Production:

10 11 12 13 14 15 16 17 18 19

Démo Onine .

Nawaz
la source
3
Je l'aime. J'ai pensé à une version sans modèle. Et je suppose qu'un bon compilateur l'optimiserait bien dans le cas où les valeurs sont réellement constantes. Je vais devoir tester ça.
Omniprésente
10
@Nawaz: Je encore calibre il, sur le type intégral :) Je propose également à alias iteratorà const_iterator, ont iteratorTIRENT std::iteratoret ont rangemettre en œuvre cbeginet cend. Oh et ... pourquoi iterator::operator++renvoie une référence const ?
Matthieu M.
6
@RedX: Dijkstra a une bonne description des raisons pour lesquelles l'étiquetage de gamme est le meilleur [begin, end). @OP: +1 pour le jeu de mots sur les boucles basées sur une plage qui n'est pas un jeu de mots :-)
Kerrek SB
2
L'avantage de la version sans modèle est que la longueur de vos boucles n'a pas besoin d'être connue au moment de la compilation. Vous pouvez bien sûr créer un modèle de type entier.
CashCow
2
@weeska: Cette surcharge est censée implémenter l'incrément postfix v++qui est censé renvoyer la valeur avant que l'opération d'incrémentation n'ait lieu. Je vous conseille d'explorer la différence entre ++iet i++iest déclaré être int.
Nawaz
13

J'ai écrit une bibliothèque appelée rangeexactement dans le même but, sauf qu'il s'agit d'une plage d'exécution, et l'idée dans mon cas est venue de Python. J'ai envisagé une version au moment de la compilation, mais à mon humble avis, il n'y a aucun avantage réel à obtenir la version au moment de la compilation. Vous pouvez trouver la bibliothèque sur bitbucket, et elle se trouve sous Boost License: Range . C'est une bibliothèque à un en-tête, compatible avec C ++ 03 et fonctionne comme un charme avec des boucles for basées sur une plage en C ++ 11 :)

Caractéristiques :

  • Un véritable conteneur à accès aléatoire avec toutes les cloches et sifflets!

  • Les plages peuvent être comparées lexicographiquement.

  • Deux fonctions exist(retourne bool) et find(retourne l'itérateur) pour vérifier l'existence d'un nombre.

  • La bibliothèque est testée à l'unité à l'aide de CATCH .

  • Exemples d'utilisation de base, utilisation de conteneurs standard, utilisation d'algorithmes standard et utilisation de boucles for basées sur une plage.

Voici une introduction d'une minute . Enfin, je me réjouis de toute suggestion concernant cette minuscule bibliothèque.

AraK
la source
L'introduction d'une minute dit que je n'ai pas accès au Wiki. Vous devez rendre votre wiki public.
Nicol Bolas
@Nicol Bolas Je suis vraiment désolé, c'est public maintenant :)
AraK
Merci pour ça, c'est incroyable. J'ai l'impression que plus de gens devraient le savoir.
Rafael Kitover
5

J'ai trouvé que boost::irangec'était beaucoup plus lent que la boucle d'entiers canoniques. J'ai donc opté pour la solution beaucoup plus simple suivante en utilisant une macro de préprocesseur:

#define RANGE(a, b) unsigned a=0; a<b; a++

Ensuite, vous pouvez faire une boucle comme ceci:

for(RANGE(i, n)) {
    // code here
}

Cette plage démarre automatiquement à zéro. Il pourrait être facilement étendu pour commencer à partir d'un nombre donné.

user2664470
la source
7
Notez que for (RANGE(i, flag? n1: n2))cela donnera des résultats surprenants, car vous n'avez pas suivi l'une des règles de base des macros non-maléfiques, qui consiste à mettre entre parenthèses tous vos paramètres (y compris, dans ce cas, b). Votre approche n'apporte pas non plus d'avantages en termes de performances par rapport à l'approche non macro, basée sur les "objets de plage" (par exemple, la réponse de Nawaz ).
Quuxplusone le
2

Voici un formulaire plus simple qui fonctionne bien pour moi. Y a-t-il des risques dans ma démarche?

r_iteratorest un type qui se comporte, autant que possible, comme un long int. Par conséquent, de nombreux opérateurs tels que ==et ++, passent simplement par le long int. J'expose le long int sous-jacent via les conversions operator long intet operator long int &.

#include <iostream>
using namespace std;

struct r_iterator {
        long int value;
        r_iterator(long int _v) : value(_v) {}
        operator long int () const { return value; }
        operator long int& ()      { return value; }
        long int operator* () const { return value; }
};
template <long int _begin, long int _end>
struct range {
        static r_iterator begin() {return _begin;}
        static r_iterator end  () {return _end;}
};
int main() {
        for(auto i: range<0,10>()) { cout << i << endl; }
        return 0;
}

( Edit: - nous pouvons rendre les méthodes rangestatiques au lieu de const.)

Aaron McDaid
la source
1

C'est peut-être un peu tard mais je viens de voir cette question et j'utilise cette classe depuis un moment maintenant:

#include <iostream>
#include <utility>
#include <stdexcept>

template<typename T, bool reverse = false> struct Range final {
    struct Iterator final{
        T value;
        Iterator(const T & v) : value(v) {}
        const Iterator & operator++() { reverse ? --value : ++value; return *this; }
        bool operator!=(const Iterator & o) { return o.value != value; }
        T operator*() const { return value; }
    };
    T begin_, end_;
    Range(const T & b, const T & e)  : begin_(b), end_(e) {
        if(b > e) throw std::out_of_range("begin > end");
    }

    Iterator begin() const { return reverse ? end_ -1 : begin_; }
    Iterator end() const { return reverse ? begin_ - 1: end_; }

    Range() = delete;
    Range(const Range &) = delete;
};

using UIntRange = Range<unsigned, false>;
using RUIntRange = Range<unsigned, true>;

Utilisation:

int main() {
    std::cout << "Reverse : ";
    for(auto i : RUIntRange(0, 10)) std::cout << i << ' ';
    std::cout << std::endl << "Normal : ";
    for(auto i : UIntRange(0u, 10u)) std::cout << i << ' ';
    std::cout << std::endl;
}
OneOfOne
la source
0

as-tu essayé d'utiliser

template <class InputIterator, class Function>
   Function for_each (InputIterator first, InputIterator last, Function f);

La plupart du temps correspond à la facture.

Par exemple

template<class T> void printInt(T i) {cout<<i<<endl;}
void test()
{
 int arr[] = {1,5,7};
 vector v(arr,arr+3);

 for_each(v.begin(),v.end(),printInt);

}

Notez que printInt peut être remplacé par OFC par un lambda en C ++ 0x. Une autre petite variation de cette utilisation pourrait également être (strictement pour random_iterator)

 for_each(v.begin()+5,v.begin()+10,printInt);

Pour l'itérateur Fwd uniquement

 for_each(advance(v.begin(),5),advance(v.begin(),10),printInt);
Ajeet Ganga
la source
Comment utiliseriez-vous cela? Je suppose que vous utiliseriez un lambda pour la fonction, mais je ne suis pas sûr.
Omnifarious
1
Je vous le dirais, mais vous aurez accepté la réponse si vous pensez que c'est la bonne façon de l'utiliser. : P Je plaisante. Posté déjà l'exemple.
Ajeet Ganga
Vous pouvez utiliser un lambda ici donc auto range = myMultiMap.equal_range (key); for_each (range.first, range.second, [&] (decltype (* range.first) const & item) {// le code va ici});
CashCow
-3

Vous pouvez facilement générer une séquence croissante en C ++ 11 en utilisant std :: iota ():

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

template<typename T>
std::vector<T> range(T start, T end)
{
  std::vector<T> r(end+1-start, T(0));
  std::iota(r.begin(), r.end(), T(start));//increasing sequence
  return r;
}

int main(int argc, const char * argv[])
{
  for(auto i:range<int>(-3,5))
    std::cout<<i<<std::endl;

  return 0;
}
scorpion bleu
la source
3
La rangeclasse doit modéliser la plage. Cependant, vous le construisez littéralement. C'est un gaspillage de mémoire et d'accès à la mémoire. La solution est très redondante, car le vecteur ne contient aucune information réelle à l'exception du nombre d'éléments et de la valeur du premier élément (s'il existe).
not-a-user
Oui, c'est très inefficace.
Omnifarious