Простой целочисленный диапазон для диапазонов C ++ 11 для циклов

Мне действительно надоело печатать

for (int iSomething = rangeBegin; iSomething < rangeEnd; ++iSomething)
{
   ...
}

всякий раз, когда я хочу итерации по целочисленному диапазону (большинство IDE-файлов помогают при вводе текста, но все же оно выглядит так многословно, именовав целое число 3 раза!)

Мне хотелось что-то вроде этого:

for (int iSomething : LoopRange(rangeBegin, rangeEnd))
{
   ...
}

Или, если rangeBegin равен 0 (большинство случаев), тогда простая

for (int iSomething : LoopRange(rangeEnd))
{
   ...
}

Моя очень простая реализация:

class LoopRangeIterator
{
public:
    LoopRangeIterator(int value_)
        : value(value_){}

    bool operator!=(LoopRangeIterator const& other) const
    {
        return value != other.value;
    }

    int const& operator*() const
    {
        return value;
    }

    LoopRangeIterator& operator++()
    {
        ++value;
        return *this;
    }

private:
    int value;
};

class LoopRange
{
public:
    LoopRange(int from_, int to_)
        : from(from_), to(to_){}

    LoopRange(int to_)
        : from(0), to(to_){}

    LoopRangeIterator begin() const
    {
        return LoopRangeIterator(from);
    }

    LoopRangeIterator end() const
    {
        return LoopRangeIterator(to);
    }

private:
    int const from;
    int const to;
};

Я назвал его LoopRange, чтобы было ясно, что он предназначен для циклов, и это не какой-то общий класс целочисленного диапазона, который вы использовали бы для пересечения или построения объединения и т. д.

Конечно, этот класс можно было бы обобщить разными способами, но я думаю, что если вам нужна более сложная функциональность (например, пользовательские размеры шага, двойные значения), то вы делаете что-то особенное, и вам лучше писать явный цикл цикла.

Что вы думаете об этом? Если я использую такую ​​вещь на протяжении всего моего проекта, будет ли она запутывать и беспокоить /отвлекать людей слишком много по сравнению с просто использованием классического и многословного стиля for(...; ...; ...)

38 голосов | спросил isarandi 23 Maypm14 2014, 17:52:10

7 ответов


14

Зачем писать его самостоятельно, если вы можете использовать Boost. Диапазон irange . Вы можете даже настроить это, чтобы установить начальный индекс 0 и получить поведение типа std::iota (называемый iota_n).

#include <boost/range/irange.hpp>
#include <iostream>

template<class Integer>
decltype(auto) iota_n(Integer last)
{
    return boost::irange(0, last);    
}

template<class Integer, class StepSize>
decltype(auto) iota_n(Integer last, StepSize step_size)
{
    return boost::irange(0, last, step_size);    
}

int main()
{
    for (auto x : iota_n(5)) // 01234
        std::cout << x;
}

Пример Live , используя Clang 3.4 return-type-deduction в C + + 1y (также поддерживается gcc 4.9 и другими компиляторами в ближайшее время) (используйте trailing -> decltype(/*statement inside function*/) возвращаемые типы для компиляторов C ++ 11)

ответил TemplateRex 1 J0000006Europe/Moscow 2014, 23:56:55
17

Вы можете создать шаблон шаблона с тривиальными изменениями (добавьте template<typename T> и измените int на T) в своих классах) затем создайте конструктивную функцию, которая выводит целочисленные типы:

template<typename T>
LoopRange<T> range(T from, T to)
{
    static_assert(std::is_integral<T>::value,
                  "range only accepts integral values");

    return { from, to };
}

Это даже позволит вам явно указать, какое целое число вы хотите использовать, если необходимо:

for (auto i: range<unsigned>(0, 5))
{
    std::cout << i << " ";
}

Если вам нужно генерировать индексы для итерации через std::vector, это может быть полезно, поскольку std::vector<T>::size_type, вероятно, больше чем int. В то время как static_assert избегает некоторых потенциальных проблем с значениями с плавающей запятой, он также запрещает использование целочисленных классов (например, гипотетический класс BigNum).


Вы можете упростить некоторые из своих функций благодаря инициализации списка . Например, используемый в операторе return, он разрешает вам явно не повторять тип возвращаемого значения (если конструктор типа возвращаемого значения не является explicit):

LoopRangeIterator begin() const
{
    return { from };
}

LoopRangeIterator end() const
{
    return { to };
}

На стороне примечания такая утилита range также была бы интересна, если бы она работала с числами с плавающей запятой и, возможно, десятичными числами в будущем (сродни Python numpy.arange ). Тем не менее, если вы хотите избежать проблем, вам придется назначать класс для этих типов, если вы повторно добавляете одну и ту же с плавающей запятой (например, 0.01), вы будете накапливать ошибки округления. Вычисление каждого значения из базового значения с помощью умножения может быть прочь, чтобы обойти эту проблему.

ответил Morwenn 23 Maypm14 2014, 18:45:17
11

Если у вас есть оператор operator!=, у вас также должен быть оператор operator== для симметрии:

bool operator==(LoopRangeIterator const& other) const
{
    return value == other.value;
}

Кроме того, чаще всего перегружается оператор operator!= в терминах ==:

bool operator!=(LoopRangeIterator const& other) const
{
    return !(value == other.value);
}
ответил Jamal 23 Maypm14 2014, 18:00:04
2

Мне очень понравился код MORTAL, я его улучшил. 1-й, мне нужно было иметь тип шаблона, а не int. Во-вторых, мне нужно было запустить /остановить /шаг с помощью оператора ++, прыгающего по шагу. Полученный код (ниже) допускает петли вложенности, такие как:

// range code improved from MORTAL
template <typename T,T iBegin,T iEnd,T iStep=1>
class range {
    public:
        struct iterator {
            T value;
            iterator    (T v) : value(v) {}
            operator T  () const         { return value; }
            operator T& ()               { return value; }
            T operator* () const         { return value; }
            iterator& operator++ ()      { value += iStep; return *this; }
        };
        iterator begin() { return iBegin; }
        iterator end() { return iEnd; }
};

// Sample code using the improved code
for (auto jj: range<size_t,0,256,16>()) {
    for (auto ii: range<size_t,0,16>()) {
        // just show the result as a 16x16 table
        cout << hex << setw(3) << (jj+ii);
    }
    cout << endl;
}

Я надеюсь, что это полезно для других.

ответил jlettvin 1 32017vEurope/Moscow11bEurope/MoscowWed, 01 Nov 2017 22:05:17 +0300 2017, 22:05:17
0

В этом примере используется вложенный класс итератора, поэтому вам не нужно иметь два открытых класса шаблонов. Может быть, это лучше - я думаю, что это более аккуратно

template <class T> class range {
private:
    class iter {
    private:
        T at;
    public:
        iter(T at) : at(at) {}
        bool operator!=(iter const& other) const { return at != other.at; }
        T const& operator*() const { return at; }
        iter& operator++() { ++at; return *this; }
    };

    T begin_val;
    T end_val;
public:
    range(T begin_val, T end_val) :
        begin_val(begin_val), end_val(end_val) { }
    iter begin() { return iter(begin_val); }
    iter end() { return iter(end_val); }
};

используется как:

for (auto i: range<unsigned>(0, 5))
{
    std::cout << i << " ";
}
ответил Neil McGill 22 J000000Sunday18 2018, 02:58:08
-2

Макросы препроцессора! Все приведенные ниже петли приведут к такому же результату. К сожалению, макросы не могут быть определены более одного раза, поэтому вам нужно дать им разные имена.

#include <iostream>

#define irange(i,a,b) int i = (a); i < (b); ++i // if you want to use ints all the time
#define range(i,a,b) i = (a); i < (b); ++i      // if you ever want to use something other than an int
#define zrange(i,b) i = 0; i < (b); ++i       // if you want to start at zero
#define izrange(i,b) int i = 0; i < (b); ++i  // if you want to start at zero and use ints

int main ( )
{
    for ( int num = 0; num < 10; ++num ) std::cout << num << ' ';

    std::cout << '\n';

    for ( irange ( num, 0, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( int range ( num, 0, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( int zrange ( num, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';

    for ( izrange ( num, 10 ) ) std::cout << num << ' ';

    std::cout << '\n';
}

Вы также можете написать больше макросов, чтобы делать такие вещи, как приращение чем-то иным, чем 1.

ответил Yay295 6 42014vEurope/Moscow11bEurope/MoscowThu, 06 Nov 2014 23:31:44 +0300 2014, 23:31:44
-3

явно не лучше вашего, но вы можете рассматривать его как альтернативу. Используя std-вектор, чтобы получить тот же результат

#include <vector>
template <typename T>
std::vector<T> irange(const T& begin, const T& end)
{
    static_assert(std::is_integral<T>::value,
        "range only accepts integral values");
    std::vector<T> v;

    for (auto i = begin; i < end; ++i)
    {
        v.push_back(i);
    }

    return v;
}

или это хорошо работает со мной, я тестировал в VC ++ 2013

template <int begin_,int end_>
struct range {
    struct iterator {
        int value;
        iterator(int v) : value(v) {}
        operator int() const { return value; }
        operator int& ()      { return value; }
        int operator* () const { return value; }
    };
    iterator begin() { return begin_; }
    iterator end() { return end_; }
};

Использование

for (auto i : range<0, 5>())
{
    std::cout << i << ' ';
}
ответил MORTAL 3 Jam1000000amSat, 03 Jan 2015 08:12:53 +030015 2015, 08:12:53

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132