c++ - फ़्लोट्स की `रेंज` जैसी चलने योग्य वस्तु कैसे बनाएं?




floating-point iterator (3)

क्या एक चलने योग्य वस्तु बनाने का एक सरल तरीका है जो float s पर लूप के लिए एक सही व्यवहार करेगा?

सबसे सरल हैक std::is_floating_point operator!= भीतर अलग-अलग रिटर्न (यानी iter <= end ) प्रदान करने के लिए लक्षण std::is_floating_point का उपयोग किया जाएगा 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!= के अर्थ को तोड़ देता है 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
}

मैं 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 पर लूप के लिए एक सही व्यवहार करेगा?


एक श्रेणी वस्तु के बजाय आप एक जनरेटर (एक coroutine co_yield का उपयोग 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
}

यहाँ मेरा प्रयास है, जो पुनरावृत्तियों के लिए बाधा नहीं है। परिवर्तन यह है कि अब, प्रत्येक पुनरावृत्तिकर्ता को इसके रुकने का मूल्य पता है, जिससे वह इसे पार करने पर खुद को सेट कर लेगा। एक सीमा के सभी अंत पुनरावृत्तियों के साथ बराबर 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 । लेकिन, श्रेणी-आधारित के मामले में, इसे परिभाषित किया गया है । क्या आपको इस गर्भनिरोधक का उपयोग किसी अन्य संदर्भ में करना चाहिए, मैं उपरोक्त दृष्टिकोण की सलाह देता हूं।

वैकल्पिक रूप से, अगर 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 हो सकता है जो सीधे उस मूल्य की ओर इशारा करता है, यह आप पर निर्भर है।

ओटी: दोनों वर्गों के लिए आंतरिक को private बनाने के लिए मत भूलना।







range