(大多数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(...; ...; ...)
风格相比,有什么好处?#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
评论
看看我在klmr / cpp11-range的实现。不用说,我认为我的实现有一些好主意,这使其与其他实现有所不同(自述文件对此进行了解释)。您不必担心添加一个step参数。 Python的范围内建函数提供它已经足够了。
@Morwenn Python与C ++不同,具有命名参数。我觉得没有名字就足以保证这种特殊对待是不直观的。此外,step参数创建了一个单独的类型,这使得优化对于编译器而言更加容易(对于简单情况,它可以使用++而不是+ = var)。
@KonradRudolph您可能是对的。我从未见过有人明确地命名过这种说法(有人可能会这样说,但似乎绝大多数人都没有)。
请注意:boost :: irange也带有一个step参数。