c++ - 如何创建浮点数的“范围”类可迭代对象?
floating-point iterator (3)
我想在
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
s上表现得像一个正确的for循环?
有没有一种 简单的方法 来创建一个可迭代的对象,它在
float
s上表现得像一个正确的for循环?
最简单的hack
†
将使用特征
std::is_floating_point
在
operator!=
overload中提供不同的返回(即
iter <= end
)。
( 见直播 )
#include <type_traits>
bool operator!=(const iterator& other)
{
if constexpr (std::is_floating_point_v<T>) return current <= other.current;
return !(*this == other);
}
†
警告:即使这样做,它也会破坏
operator!=
的意思
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
}
您可以使用生成器(使用
co_yield
的协程)代替范围对象。
尽管它不符合标准(但计划用于C ++ 20),但一些编译器已经实现了它。
请参阅: https://en.cppreference.com/w/cpp/language/coroutines : 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
}
这是我的尝试,它不会妨碍迭代器的语义。 现在,每个迭代器都知道它的停止值,它会在超过它时自行设置。 所有范围的结束迭代器都等于因此比较相等。
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
。
但是,在基于范围的情况下,
它被定义
。
如果你在其他方面使用这个装置,我建议采用上述方法。
或者,如果
sizeof(T) > sizeof(void*)
,则存储指向原始
range
实例的指针是有意义的(在范围的情况下 - 持续到结束)并使用它来引用单个
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
的。