Itérer sur des indices avec les répétitives for sur des intervalles

Dans la nuit du 14 au 15 janvier 2015, je suis tombé sur cette question posée par Herb Sutter sur son blogue : http://herbsutter.com/2015/01/14/reader-qa-auto-and-for-loop-index-variables/

Pour le paraphraser : les répétitives for sur des intervalles proposent un style de programmation moderne et agréable, mais opèrent sur des intervalles définis par des paires d'itérateurs définissant une séquence à demi ouverte de type . Comment pourrait-on utiliser les mêmes répétitives mais itérer avec des indices?

Évidemment, il existe plusieurs solutions à ce problème, mais la question est de trouver la forme la plus pertinente, et le nommage le plus signifiant.

J'ai écrit rapidement ce qui suit (lien direct : http://herbsutter.com/2015/01/14/reader-qa-auto-and-for-loop-index-variables/#comment-35436 pour ce qui a été publié). Ça fonctionne mais je n'ai pas beaucoup réfléchi aux implications ou au nommage, et je ne l'ai pas testé (c'est un truc écrit en dix minutes environ), mais ça fonctionne dans les cas « évidents » (lire : itérer par indice sur un tableau et sur un vecteur).

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

template <class C>
   constexpr auto size(C &&cont) noexcept {
      return cont.size();
   }
template <class T, std::size_t N>
   constexpr auto size(T(&arr)[N]) noexcept {
      return N;
   }
template <class T, std::size_t N>
   constexpr auto size(const T(&arr)[N]) noexcept {
      return N;
   }

template <class T>
   class value_range : public std::iterator<std::bidirectional_iterator_tag, T> {
      T first, last;
   public:
      template <class U>
         class value_iterator {
            U cur;
         public:
            constexpr value_iterator(U init) : cur{ init } {
            }
            constexpr value_iterator& operator++() {
               ++cur;
               return *this;
            }
            constexpr value_iterator operator++(int) {
               auto temp{ *this };
               operator++();
               return temp;
            }
            constexpr value_iterator& operator--() {
               --cur;
               return *this;
            }
            constexpr value_iterator operator--(int) {
               auto temp{ *this };
               operator--();
               return temp;
            }
            constexpr bool operator==(const value_iterator &other) const {
               return cur == other.cur;
            }
            constexpr bool operator!=(const value_iterator &other) const {
               return !(*this == other);
            }
            constexpr U operator*() {
               return cur;
            }
            constexpr U operator*() const {
               return cur;
            }
         };
      using iterator = value_iterator<T>;
      using const_iterator = value_iterator<T>;
      constexpr value_range(T first, T last) : first{ first }, last{ last } {
      }
      constexpr iterator begin() { return first; }
      constexpr iterator end() { return last; }
      constexpr const_iterator begin() const { return first; }
      constexpr const_iterator end() const { return last; }
      constexpr const_iterator cbegin() const { return first; }
      constexpr const_iterator cend() const { return last; }
   };

template <class C>
   auto value_range_from(C &&cont) -> value_range<decltype(size(cont))> {
      return { 0, size(cont) };
   }

int main() {
   int arr[] { 2, 3, 5, 7, 11 };
   vector<int> v{ begin(arr), end(arr) };
   for (const auto &val : arr)
      cout << val << ' ';
   cout << '\n';
   for (const auto &val : v)
      cout << val << ' ';
   cout << '\n';
   for (auto i : value_range_from(arr))
         cout << "arr[" << i << "] == " << arr[i] << "; ";
   cout << '\n';
   for (auto i : value_range_from(v))
      cout << "v[" << i << "] == " << v[i] << "; ";
   cout << '\n';
}

Votre avis?

Raffinement possible avec C++ 17 – Guides de déductions

Avec C++ 17, les guides de déduction (Deduction Guides) permettent d'alléger l'écriture de telles classes, en supprimant les fonctions génératrices comme value_range_from(). Ainsi, si nous déclarons plutôt ce qui suit :

template <lclass T>
   value_range(int first, T last)  -> value_range<T>;

... signifiant « si tu vois le nom value_range utilisé comme une fonction prenant un int et un T, alors appelle plutôt le constructeur de value_range<T> », le code client pourra maintenant s'écrire comme ceci :

int main() {
   int arr[] { 2, 3, 5, 7, 11 };
   vector<int> v{ begin(arr), end(arr) };
   for (const auto &val : arr)
      cout << val << ' ';
   cout << '\n';
   for (const auto &val : v)
      cout << val << ' ';
   cout << '\n';
   for (auto i : value_range(0, size(arr))) // <-- ICI
         cout << "arr[" << i << "] == " << arr[i] << "; ";
   cout << '\n';
   for (auto i : value_range(0, size(v))) // <-- ICI
      cout << "v[" << i << "] == " << v[i] << "; ";
   cout << '\n';
}

... ce qui est plutôt chouette à mon avis.

Liens complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !