每当我要遍历整数范围时,我真的很厌烦必须键入

(大多数IDE都可以帮助键入,但是看起来仍然很冗长,我将整数命名为3次!)

我想要这样的东西:

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


或者rangeBegin为0(大多数情况)然后是一个简单的

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


我非常简单的实现:

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


我将其命名为LoopRange以使其清晰它是用于循环的,而不是用于相交或建立并集等的一些通用整数范围类。

当然可以用很多方法来概括该类,但是我认为如果需要更复杂的功能(例如自定义步长,双精度值),那么您正在做一些特别的事情,最好写显式的for循环。

您怎么看?
如果我在整个项目中都会用到这样的东西,它会使人们感到困惑和干扰/分散他们的注意力吗?与仅使用经典且冗长的for(...; ...; ...)风格相比,有什么好处?

评论

看看我在klmr / cpp11-range的实现。不用说,我认为我的实现有一些好主意,这使其与其他实现有所不同(自述文件对此进行了解释)。

您不必担心添加一个step参数。 Python的范围内建函数提供它已经足够了。

@Morwenn Python与C ++不同,具有命名参数。我觉得没有名字就足以保证这种特殊对待是不直观的。此外,step参数创建了一个单独的类型,这使得优化对于编译器而言更加容易(对于简单情况,它可以使用++而不是+ = var)。

@KonradRudolph您可能是对的。我从未见过有人明确地命名过这种说法(有人可能会这样说,但似乎绝大多数人都没有)。

请注意:boost :: irange也带有一个step参数。

#1 楼

如果可以使用Boost.Range的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;
}


在线示例,使用Clang 3.4 return-type- C ++ 1y模式下的推论(gcc 4.9和其他编译器也很快支持(对于C ++ 11编译器使用尾随-> decltype(/*statement inside function*/)返回类型)

评论


\ $ \ begingroup \ $
是的,我调查了一下并选择了它。但是我也写了一个包装函数,它接受一个参数(to)并将开始值设置为零,就像在python中一样。
\ $ \ endgroup \ $
–isarandi
2014年6月1日20:10

\ $ \ begingroup \ $
@isarandi是的,那将是一个有用的包装器(类似于std :: iota的行为)
\ $ \ endgroup \ $
–TemplateRex
2014年6月1日20:16在

\ $ \ begingroup \ $
@isarandi已更新为从0开始计数的精简包装。
\ $ \ endgroup \ $
–TemplateRex
2014年6月1日20:25在

\ $ \ begingroup \ $
为什么?因为您:1.必须安装Boost,并且2.必须包括超长且慢编译的Boost标头。
\ $ \ endgroup \ $
– einpoklum
16年1月22日在9:31

\ $ \ begingroup \ $
@einpoklum 1. Boost已安装在大多数开发系统上,如果没有,则负担不大。 2.除了说一些繁重的元编程库(Fusion,MPL,Spirit)外,大多数Boost在编译器上都不比标准库重,这导致3. Boost在历史上很多都被视为“ pre-STL”,高度重视正确性和速度(运行时)。
\ $ \ endgroup \ $
–TemplateRex
16年1月22日在9:35

#2 楼

您可以对类模板进行微不足道的更改(在您的类中添加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),则会累积舍入错误。从基值乘以计算每个值都可以避免这种问题。

评论


\ $ \ begingroup \ $
模板解决方案的问题在于浮点数将需要更多注意。如果您反复向浮点数添加增量,则可能永远不会达到预期的“结束”值。因此,我需要一些技巧(例如,跟踪剩余的迭代次数)。对于一个罕见的用例来说,它将太复杂了。我可能可以使用一些模板魔术将模板实例化限制为整数类型,但是为什么事情变得过于复杂呢?
\ $ \ endgroup \ $
–isarandi
2014年5月23日14:54

\ $ \ begingroup \ $
@qorilla一点static_assert应该可以解决问题:)
\ $ \ endgroup \ $
–莫文
2014年5月23日14:55

\ $ \ begingroup \ $
返回{from}时;较短的类型,我认为这比返回LoopRangeIterator(from)更好;从可读性和可维护性的角度来看。
\ $ \ endgroup \ $
–R Sahu
2015年1月3日,3:23

\ $ \ begingroup \ $
@RSahu最近对此进行了长时间的辩论,即使在标准委员会内部也是如此,因此它仍然是debatabe。我的观点是,当您从单线返回时,没有任何其他含义,而第二次编写返回类型也无济于事。
\ $ \ endgroup \ $
–莫文
2015年1月3日,11:23

#3 楼

如果您具有operator!=,则还应该具有operator==以保持对称性:

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


此外,就operator!=而言,重载==更为常见:

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


评论


\ $ \ begingroup \ $
一般而言是合理的。在这里,我将重点放在for循环中所需的功能上,因为我打算在其他任何地方都不使用该类。
\ $ \ endgroup \ $
–isarandi
2014年5月23日14:03

\ $ \ begingroup \ $
@qorilla:很好。我仍然会遵循此规则,至少在运算符重载方面。当您有一个过载但另一个没有过载时,它似乎也有些俗气。
\ $ \ endgroup \ $
– Jamal♦
2014年5月23日14:06

#4 楼

我非常喜欢MORTAL的代码,所以我对其进行了改进。
第一,我需要它具有模板类型而不是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;
}


我希望这对其他人有用。

#5 楼

本示例使用嵌套的迭代器类,因此您不需要两个暴露的模板类。也许更好-我认为它更整洁

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


#6 楼

预处理程序宏!下面的所有循环将产生相同的结果。不幸的是,宏不能被多次定义,因此您必须给它们指定不同的名称。

#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.以外的东西。

评论


\ $ \ begingroup \ $
嗯,这不像现代的C ++解决方案那么干净。由于您提到的原因以及所有其他原因,我们尝试避免使用宏:/
\ $ \ endgroup \ $
–莫文
2014年11月6日21:01

\ $ \ begingroup \ $
但是我做到了一行。 ;)
\ $ \ endgroup \ $
–是
2014年11月6日在21:04

\ $ \ begingroup \ $
不幸的是,在真实代码中,我们重视实现的用法和质量,而不是其字符数:p
\ $ \ endgroup \ $
–莫文
2014年11月6日在21:20

\ $ \ begingroup \ $
不鼓励使用这种宏。这使静态分析更加困难。
\ $ \ endgroup \ $
– einpoklum
16年1月22日在9:32

\ $ \ begingroup \ $
嗯...宏...
\ $ \ endgroup \ $
–艾米莉·L。
17年11月1日在19:18