Цикл на закрытом круге - PullRequest
4 голосов
/ 16 марта 2010

Как бы вы исправили этот код?

template <typename T> void closed_range(T begin, T end)
{
    for (T i = begin; i <= end; ++i) {
        // do something
    }
}
  • T является целочисленным типом, может быть шире таких типов и может быть со знаком или без знака

  • begin может быть numeric_limits<T>::min()

  • end может быть numeric_limits<T>::max() (в этом случае ++i переполнится в приведенном выше коде)

У меня есть несколько способов, но ни один из них мне не нравится.

Ответы [ 5 ]

6 голосов
/ 16 марта 2010

Может быть,

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

Проклятия, моя первая попытка была неудачной, и исправление выше не так красиво, как я надеялся. Как насчет:

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

Нет никакой двусмысленности с std::advance, даже если T не является целочисленным типом, поскольку std::advance принимает 2 параметра. Таким образом, шаблон также будет работать, например, с итератором с произвольным доступом, если по какой-то причине вы хотели бы иметь закрытый диапазон из них.

Или как насчет теории множеств? Очевидно, что это является огромным излишним, если вы пишете только один цикл в закрытом диапазоне, но если это то, что вы хотите сделать много, то это делает код цикла правильным. Не уверен насчет эффективности: в очень узком цикле вы можете убедиться, что вызов endof поднят:

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

Выход:

32765
32766
32767
65533
65534
65535

Будьте осторожны, используя другие операторы для omega<T> объектов. Я реализовал только абсолютный минимум для демонстрации, и omega<T> неявно преобразуется в T, так что вы обнаружите, что можете писать выражения, которые потенциально отбрасывают «бесконечность» объектов омега. Вы можете исправить это, объявив (не обязательно определяя) полный набор арифметических операторов; или с помощью исключения в преобразовании, если isInfinite имеет значение true; или просто не беспокойтесь об этом на том основании, что вы не можете случайно преобразовать результат обратно в омегу, потому что конструктор явный. Но, например, omega<int>(2) < endof(2) - это правда, а omega<int>(INT_MAX) < endof(INT_MAX) - это ложь.

5 голосов
/ 16 марта 2010

Мой дубль:

// 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;
    }
}
4 голосов
/ 16 марта 2010

Это работает и довольно ясно:

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

Если вы хотите поймать особый случай begin >= end, вам нужно добавить еще if, как в решении Стива Джессопа.

3 голосов
/ 16 марта 2010
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 голосов
/ 16 марта 2010

РЕДАКТИРОВАТЬ: переработанные вещи, чтобы более точно соответствовать ОП.

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

Вывод:

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

...