c ++ にrange
- like構成を作成します。これは次のように使用されます。
_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ループのように動作するiterable objectを作成する簡単な方法はありますか?
これが、反復子のセマンティクスを損なわない私の試みです。これで、各反復子はその停止値を知っています。イテレータは、それを超えるとこの値に設定されます。したがって、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::iota
の助けを借りて入力される単純な関数で置き換えることができます標準コンテナstd::vector
。
[〜#〜] 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)
_を上げることができるため)、精度は低くなる可能性があります。
整数反復子を関数と一緒に使用して整数を浮動小数点値に変換することは、浮動小数点値の範囲を反復するためのおそらく最も堅牢な方法です。浮動小数点値を直接繰り返し処理しようとすると、「1つずつ」のエラーが発生する可能性がはるかに高くなります。