我想在c++中创建一个类似range
的构造,它将像这样使用:
for (auto i: range(5,9))
cout << i << ' '; // prints 5 6 7 8
for (auto i: range(5.1,9.2))
cout << i << ' '; // prints 5.1 6.1 7.1 8.1 9.1
处理整数情况相对容易:
template<typename T>
struct range
{
T from, to;
range(T from, T to) : from(from), to(to) {}
struct iterator
{
T current;
T operator*() { return current; }
iterator& operator++()
{
++current;
return *this;
}
bool operator==(const iterator& other) { return current == other.current; }
bool operator!=(const iterator& other) { return current != other.current; }
};
iterator begin() const { return iterator{ from }; }
iterator end() const { return iterator{ to }; }
};
但是,这在float
情况下不起作用,因为C++
中基于标准范围的循环会检查是否iter==end
,而不是像您一样检查iter <= end
循环进行。
是否有一种简单的方法来创建可迭代对象,使其在float
上的行为类似于正确的for循环?
答案 0 :(得分:17)
这是我的尝试,它不妨碍迭代器的语义。变化是,现在,每个迭代器都知道其停止值,超过该值便将其设置为自身。因此,to
相等的范围内的所有最终迭代器都比较相等。
template <typename T>
struct range {
T from, to;
range(T from, T to): from(from), to(to) {}
struct iterator {
const T to; // iterator knows its bounds
T current;
T operator*() { return current; }
iterator& operator++() {
++current;
if(current > to)
// make it an end iterator
// (current being exactly equal to 'current' of other end iterators)
current = to;
return *this;
}
bool operator==(const iterator& other) const // OT: note the const
{ return current == other.current; }
// OT: this is how we do !=
bool operator!=(const iterator& other) const { return !(*this == other); }
};
iterator begin() const { return iterator{to, from}; }
iterator end() const { return iterator{to, to}; }
};
@JeJo的解决方案取决于您比较这些迭代器的顺序,即it != end
或end != it
。但是,在基于范围的情况下,it is defined。如果您在其他情况下使用此工具,我建议您采用上述方法。
或者,如果使用sizeof(T) > sizeof(void*)
,则有必要存储指向原始range
实例的指针(在range-for的情况下一直持续到结束),并使用该指针来引用a单个T
值:
template <typename T>
struct range {
T from, to;
range(T from, T to): from(from), to(to) {}
struct iterator {
const range* range;
T current;
iterator& operator++() {
++current;
if(current > range->to)
current = range->to;
return *this;
}
...
};
iterator begin() const { return iterator{this, from}; }
iterator end() const { return iterator{this, to}; }
};
也可能T const* const
直接指向该值,由您决定。
OT:别忘了为这两个类制作内部private
。
答案 1 :(得分:13)
您可以使用生成器(使用co_yield
的协程)代替范围对象。尽管它不在标准中(但计划在C ++ 20中使用),但一些编译器已经实现了它。
请参阅:https://en.cppreference.com/w/cpp/language/coroutines
使用MSVC,它将是:
#include <iostream>
#include <experimental/generator>
std::experimental::generator<double> rangeGenerator(double from, double to) {
for (double x=from;x <= to;x++)
{
co_yield x;
}
}
int main()
{
for (auto i : rangeGenerator(5.1, 9.2))
std::cout << i << ' '; // prints 5.1 6.1 7.1 8.1 9.1
}
答案 2 :(得分:8)
是否有简单方法来创建可重复操作的可迭代对象 像
float
上正确的for循环一样?
最简单的hack † 将使用特征 std::is_floating_point
提供不同的回报(即iter <= end
)在operator!=
超载之内。
(See Live)
#include <type_traits>
bool operator!=(const iterator& other)
{
if constexpr (std::is_floating_point_v<T>) return current <= other.current;
return !(*this == other);
}
† 警告:即使这样做可以破坏operator!=
重载的含义。
整个range
类都可以由一个简单的函数代替,该函数将借助 std::iota
填充范围的值
在标准容器 std::vector
中。
使用 SFINE 来限制仅对有效类型使用该功能。 这样,您可以依靠标准实现,而无需进行重新发明。
(See Live)
#include <iostream>
#include <type_traits>
#include <vector> // std::vector
#include <numeric> // std::iota
#include <cstddef> // std::size_t
#include <cmath> // std::modf
// traits for valid template types(integers and floating points)
template<typename Type>
using is_integers_and_floats = std::conjunction<
std::is_arithmetic<Type>,
std::negation<std::is_same<Type, bool>>,
std::negation<std::is_same<Type, char>>,
std::negation<std::is_same<Type, char16_t>>,
std::negation<std::is_same<Type, char32_t>>,
std::negation<std::is_same<Type, wchar_t>>
/*, std::negation<std::is_same<char8_t, Type>> */ // since C++20
>;
template <typename T>
auto ragesof(const T begin, const T end)
-> std::enable_if_t<is_integers_and_floats<T>::value, std::vector<T>>
{
if (begin >= end) return std::vector<T>{}; // edge case to be considered
// find the number of elements between the range
const std::size_t size = [begin, end]() -> std::size_t
{
const std::size_t diffWhole
= static_cast<std::size_t>(end) - static_cast<std::size_t>(begin);
if constexpr (std::is_floating_point_v<T>) {
double whole; // get the decimal parts of begin and end
const double decimalBegin = std::modf(static_cast<double>(begin), &whole);
const double decimalEnd = std::modf(static_cast<double>(end), &whole);
return decimalBegin <= decimalEnd ? diffWhole + 1 : diffWhole;
}
return diffWhole;
}();
// construct and initialize the `std::vector` with size
std::vector<T> vec(size);
// populates the range from [first, end)
std::iota(std::begin(vec), std::end(vec), begin);
return vec;
}
int main()
{
for (auto i : ragesof( 5, 9 ))
std::cout << i << ' '; // prints 5 6 7 8
std::cout << '\n';
for (auto i : ragesof(5.1, 9.2))
std::cout << i << ' '; // prints 5.1 6.1 7.1 8.1 9.1
}
答案 3 :(得分:5)
浮点循环或迭代器通常应使用整数类型来保存迭代的总数和当前迭代的数目,然后根据这些值和不变的循环来计算循环中使用的“循环索引”值浮点值。
例如:
for (int i=-10; i<=10; i++)
{
double x = i/10.0; // Substituting i*0.1 would be faster but less accurate
}
或
for (int i=0; i<=16; i++)
{
double x = ((startValue*(16-i))+(endValue*i))*(1/16);
}
请注意,舍入误差不可能影响迭代次数。保证后一种计算在端点产生正确的舍入结果;计算startValue+i*(endValue-startValue)
可能会更快(因为可以提升循环不变的(endValue-startValue)
),但准确性可能会降低。
使用整数迭代器以及将整数转换为浮点值的函数可能是在一系列浮点值上进行迭代的最可靠的方法。直接迭代浮点值的尝试更有可能产生“一对一”错误。