2010-03-16 9 views
4

Comment corrigeriez-vous ce code?Boucler sur une plage fermée

template <typename T> void closed_range(T begin, T end) 
{ 
    for (T i = begin; i <= end; ++i) { 
     // do something 
    } 
} 
  • T est contraint à être un type entier, peut être le plus large de ces types et peut être signé ou non signé

  • begin peut être numeric_limits<T>::min()

  • end peut être numeric_limits<T>::max() (auquel cas ++i débordera dans le code ci-dessus)

J'ai plusieurs façons, mais aucune que j'aime vraiment.

+0

+3

@ralu: tout le point de la question est que c'est <=, pas <. C'est la différence entre une gamme fermée et une gamme semi-ouverte. –

+0

Question intéressante en effet. J'ai eu les mêmes problèmes dans les boucles décroissantes >> 'pour (size_t i = 10; i> 0; - i)' n'a pas de sens puisque '0' est la valeur minimale que' size_t' peut atteindre (étant non signé). –

Répondre

6

Peut-être,

template <typename T> void closed_range(T begin, const T end) 
    if (begin <= end) { 
     do { 
      // do something 
     } while (begin != end && (++begin, true)); 
    } 
} 

Curses, ma première tentative a eu tort, et le correctif ci-dessus ne sont pas aussi jolie que je l'avais espéré. Que diriez-vous:

template <typename T> bool advance(T &value) { ++value; return true; } 

template <typename T> void closed_range(T first, const T last) 
    if (first <= last) { 
     do { 
      // do something 
     } while (first != last && advance(first)); 
    } 
} 

Il n'y a pas d'ambiguïté std::advance même si T est pas un type entier, depuis std::advance prend 2 paramètres. Ainsi, le modèle fonctionnerait également avec, par exemple, un itérateur à accès aléatoire, si pour une raison quelconque vous vouliez une plage fermée de ceux-ci.

Ou que diriez-vous d'un peu de théorie des ensembles? Évidemment, il s'agit d'une surenchère massive si vous n'écrivez qu'une seule boucle sur une plage fermée, mais si c'est quelque chose que vous voulez faire beaucoup, alors cela rend le code de boucle à peu près correct. Vous ne savez pas sur l'efficacité: dans une boucle vraiment serré, vous voudrez peut-être vous assurer que l'appel à endof est hissée:

#include <limits> 
#include <iostream> 

template <typename T> 
struct omega { 
    T val; 
    bool isInfinite; 
    operator T() { return val; } 
    explicit omega(const T &v) : val(v), isInfinite(false) { } 
    omega &operator++() { 
     (val == std::numeric_limits<T>::max()) ? isInfinite = true : ++val; 
     return *this; 
    } 
}; 

template <typename T> 
bool operator==(const omega<T> &lhs, const omega<T> &rhs) { 
    if (lhs.isInfinite) return rhs.isInfinite; 
    return (!rhs.isInfinite) && lhs.val == rhs.val; 
} 
template <typename T> 
bool operator!=(const omega<T> &lhs, const omega<T> &rhs) { 
    return !(lhs == rhs); 
} 

template <typename T> 
omega<T> endof(T val) { 
    omega<T> e(val); 
    return ++e; 
} 

template <typename T> 
void closed_range(T first, T last) { 
    for (omega<T> i(first); i != endof(last); ++i) { 
     // do something 
     std::cout << i << "\n"; 
    } 
} 

int main() { 
    closed_range((short)32765, std::numeric_limits<short>::max()); 
    closed_range((unsigned short)65533, std::numeric_limits<unsigned short>::max()); 
    closed_range(1, 0); 
} 

Sortie:

32765 
32766 
32767 
65533 
65534 
65535 

être prudent en utilisant d'autres opérateurs sur omega<T> objets. J'ai seulement implémenté le minimum absolu pour la démonstration, et omega<T> convertit implicitement en T, donc vous constaterez que vous pouvez écrire des expressions qui peuvent potentiellement jeter l'infinité des objets oméga. Vous pouvez corriger cela en déclarant (sans nécessairement définir) un ensemble complet d'opérateurs arithmétiques; ou en lançant une exception dans la conversion si isInfinite est true; ou tout simplement ne vous inquiétez pas à cause du fait que vous ne pouvez pas accidentellement convertir le résultat en oméga, car le constructeur est explicite. Mais par exemple, omega<int>(2) < endof(2) est vrai, mais omega<int>(INT_MAX) < endof(INT_MAX) est faux.

5

Mon avis:

// Make sure we have at least one iteration 
if (begin <= end) 
{ 
    for (T i = begin; ; ++i) 
    { 
     // do something 

     // Check at the end *before* incrementing so this won't 
     // be affected by overflow 
     if (i == end) 
      break; 
    } 
} 
+1

Je pense que ça a l'air un peu plus propre comme 'assign; while (test) {incrément; code} ' – Cascabel

+1

@Jefromi: assigner quoi, cependant? Vous auriez besoin de 'T i = begin - 1;' qui a toujours des problèmes de débordement dans l'autre sens. –

+0

C'est discutable. Dans ce cas, 'while' accentue la condition de coupure,' for' accentue l'itération. L'une ou l'autre des méthodes nécessite que vous cherchiez l'autre moitié de l'équation [vous voulez que votre incrément soit à la fin de la boucle, d'ailleurs, ou vous aurez changé la sémantique de la boucle]. –

4

Cela fonctionne et est assez claire:

T i = begin; 
do { 
    ... 
} 
while (i++ < end); 

Si vous voulez attraper le cas particulier de begin >= end vous devez ajouter une autre if comme dans la solution de Steve Jessop.

+0

+1: bonne solution propre, et d'ailleurs, il y a une pénurie de boucles à faire dans le monde. – Cascabel

+1

Le problème avec ceci est que dans le cas où 'T' est' int' et 'end' est' INT_MAX', il incrémente 'i' quand la valeur de' i' est INT_MAX. C'est un comportement indéfini, même si la valeur de "i" n'est jamais utilisée à nouveau. –

+1

En toute honnêteté je devrais ajouter, c'est un petit problème comparé au problème que le code du questionneur a, d'être faux sur * toutes * les implémentations ;-) –

0

EDIT: Des éléments retravaillés pour mieux correspondre à l'OP.

#include <iostream> 
using namespace std; 

template<typename T> void closed_range(T begin, T end) 
{ 
    for(bool cont = (begin <= end); cont;) 
    { 
     // do something 
     cout << begin << ", "; 
     if(begin == end) 
      cont = false; 
     else 
      ++begin; 
    } 

    // test - this should return the last element 
    cout << " -- " << begin; 
} 
int main() 
{ 
    closed_range(10, 20); 
    return 0; 
} 

La sortie est la suivante:

10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 20

3
template <typename T, typename F> 
void closed_range(T begin, T end, F functionToPerform) 
{ 
    for (T i = begin; i != end; ++i) { 
     functionToPerform(i); 
    } 
    functionToPerform(end); 
} 
+0

Vous pouvez le voir en action ici: http://codepad.org/4zctR9w5 (harnais de test emprunté à Kirill) – Bill

+0

Et ici quand '' end' est numeric_limits :: max() ': http://codepad.org/6qwwFAVd – Bill

+0

Vraiment, vous n'avez pas besoin de preuve que cela fonctionne. Ce style d'itération semi-ouverte est idiomatique en C++, donc il y a beaucoup moins de place pour la confusion que dans les exemples plus compliqués. –

Questions connexes