如何创建一个类似"范围"的浮点数可迭代对象?
How to create a `range`-like iterable object of floats?
我想在 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
,就像您在 for 循环中所做的那样。
有没有一种简单的方法来创建一个可迭代的对象,该对象的行为类似于基于float
s 的正确范围的 for 循环?
这是我的尝试,它不会损害迭代器的语义。现在,每个迭代器都知道其停止值。迭代器将在超过此值时将自身设置该值。因此,具有相等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
.但是,在基于范围的 for 的情况下,它是定义的。如果您在其他情况下使用此装置,我建议使用上述方法。
或者,如果sizeof(T) > sizeof(void*)
,则存储指向原始range
实例的指针(在 range-for 的情况下持续到最后)并使用它来引用单个T
值是有意义的:
template <typename T>
struct range {
T from, to;
range(T from, T to): from(from), to(to) {}
struct iterator {
range const* 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
。
您可以使用生成器(使用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
}
有没有一种简单的方法来创建一个可迭代的对象,该对象的行为 就像
float
S 上的正确 for 循环一样?
最简单的技巧†是使用std::is_floating_point
的特征来提供不同的回报(即iter <= end
)在operator!=
内过载。
(观看直播)
#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::vector
中的std::iota
的帮助下填充。
使用SFINE限制仅对有效类型使用函数。 这样,您可以依靠标准实现而忘记重新发明。
(观看直播)
#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
}
浮点循环或迭代器通常应使用整数类型来保存迭代总数和当前迭代次数,然后根据这些值和循环不变浮点值计算循环中使用的"循环索引"值。
例如:
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)
),但可能不太准确。
使用整数迭代器以及函数将整数转换为浮点值可能是迭代一系列浮点值的最可靠方法。 尝试直接迭代浮点值更有可能产生"逐一"错误。
- 为什么在全局范围内使用"extern int a"似乎不行?
- 尝试通过多个向量访问变量时,向量下标超出范围
- 错误:未在此范围内声明'reverse'
- 正在将指针转换为范围
- 使用std::transform将一个范围的元素添加到另一个范围中
- 在基于范围的for循环中使用结构化绑定声明
- 如何计算数据类型的范围,例如int
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 在C++中查找范围的长度
- 如何设置一个范围来提取我想要获得的信息
- 并行用于C++17中数组索引范围内的循环
- 为左值和右值的包装器实现C++范围
- 求出有多少个数字是完美平方,而sqrt()是L,R范围内的素数
- 关于:C++中异常对象的范围:为什么我没有得到副本?
- 超出范围时使用对象
- 不计算一个范围内的完美数
- ";结果类型必须是可从输入范围的值类型""构造的;创建std::vector时
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- 如何访问超出其块范围的对象?
- 错误消息:"jj"的名称查找已更改为ISO"for"范围,(如果您使用"