web-dev-qa-db-ja.com

フロートの「範囲」のような反復可能なオブジェクトを作成する方法は?

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_かどうかをチェックするためです。

floatsで正しい範囲ベースのforループのように動作するiterable objectを作成する簡単な方法はありますか?

24

これが、反復子のセマンティクスを損なわない私の試みです。これで、各反復子はその停止値を知っています。イテレータは、それを超えるとこの値に設定されます。したがって、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にすることを忘れないでください。

17
LogicStuff

範囲オブジェクトの代わりに、ジェネレータ(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
}
14
P. PICARD

floatsの正しい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
}
9
JeJo

浮動小数点ループまたは反復子は、通常、整数型を使用して、反復の合計数と現在の反復の数を保持し、それらとループ不変の浮動小数点に基づいてループ内で使用される「ループインデックス」値を計算します。値。

例えば:

_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つずつ」のエラーが発生する可能性がはるかに高くなります。

5
supercat