Bucle en una gama cerrada
-
20-09-2019 - |
Pregunta
¿Cómo solucionar este código?
template <typename T> void closed_range(T begin, T end)
{
for (T i = begin; i <= end; ++i) {
// do something
}
}
-
T está restringido a ser un tipo entero, puede ser el más amplio de tales tipos y pueden ser con o sin signo
-
begin
puedenumeric_limits<T>::min()
-
end
puedenumeric_limits<T>::max()
(en cuyo caso++i
se desbordará en el código anterior)
Tengo varias maneras, pero ninguna me gusta mucho.
Solución
Tal vez,
template <typename T> void closed_range(T begin, const T end)
if (begin <= end) {
do {
// do something
} while (begin != end && (++begin, true));
}
}
Las maldiciones, mi primer intento fue mal, y la revisión anterior no es tan bonita como yo esperaba. ¿Qué hay de:
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));
}
}
No hay ambigüedad con std::advance
incluso si T no es un tipo entero, ya que std::advance
toma 2 parámetros. Por lo que la plantilla también trabajaría con, por ejemplo, un iterador de acceso aleatorio, si por alguna razón quería un rango cerrado de ellos.
O qué tal un poco de la teoría de conjuntos? Obviamente, esto es un exceso masivo si sólo está escribiendo un bucle en un rango cerrado, pero si es algo que usted quiere hacer mucho, entonces tiene el código de bucle sobre la derecha. No está seguro acerca de la eficiencia: en un bucle muy apretado es posible que desee asegurarse de que la llamada a endof
se iza:
#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);
}
Salida:
32765
32766
32767
65533
65534
65535
ser un poco cuidadoso uso de otros operadores en objetos omega<T>
. Sólo he implementado el mínimo absoluto para la demostración, y omega<T>
conversos implícitamente a T
, por lo que encontrará que se puede escribir expresiones que potencialmente tirar el "infinito" de objetos omega. Se podría arreglar eso declarando (no necesariamente definir) un conjunto completo de operadores aritméticos; o por lanzar una excepción en la conversión si isInfinite es cierto; o simplemente no se preocupe por motivos que no se puede convertir accidentalmente el resultado de nuevo a un omega, porque el constructor es explícito. Pero por ejemplo, omega<int>(2) < endof(2)
es cierto, pero omega<int>(INT_MAX) < endof(INT_MAX)
es falso.
Otros consejos
Mi opinión:
// 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;
}
}
Esto funciona y es bastante claro:
T i = begin;
do {
...
}
while (i++ < end);
Si se desea capturar el caso especial de begin >= end
es necesario agregar otra if
como en la solución de Steve Jessop.
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);
}
Edit: Reworked cosas que se asemejan más a la 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 salida es:
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 20