Const wrapper สำหรับ iterator

Oct 11 2020

แก้ไข : เพิ่มคำชี้แจงว่าเหตุใดฉันจึงต้องการสิ่งนี้และอัปเดตรหัสเนื่องจากฉันยังไม่มีคำตอบ

ฉันมีคลาสเหมือนอาร์เรย์ C ++ 11 ซึ่ง (สามารถเป็น) wrapper รอบ ๆ ตัววนซ้ำการเข้าถึงโดยสุ่ม การเข้าถึงตามดัชนีและ.begin()/ .end()สามารถส่งผ่านไปยังตัววนซ้ำได้ แต่มีความซับซ้อนเล็กน้อยเมื่อวัตถุคือconst:

    template<class Size, typename DataIterator>
    class Storage : public Size {
        DataIterator iterator;
    public:
        Storage(const DataIterator &iterator, const Size &size) : Size(size), iterator(iterator) {}
        
        auto operator[](index_t i)
                -> decltype(iterator[i]) {
            return iterator[i];
        }
        auto operator[](index_t i) const
                -> MakeConst<decltype(iterator[i])> {
            return iterator[i];
        }
        
        DataIterator begin() {return iterator;}
        ConstWrapper<DataIterator> begin() const {return iterator;}
        DataIterator end() {return iterator + this->size();}
        ConstWrapper<DataIterator> end() const {return iterator + this->size();}
    };

หากเราเพิ่งกลับมาDataIteratorจากconstเวอร์ชันของ.begin()และ.end()ผู้ที่ถือStorage const &จะ (ไม่ถูกต้อง!) จะสามารถแก้ไขอาร์เรย์ผ่านตัววนซ้ำนั้นได้

คอนเทนเนอร์เช่นstd::vectorมีตัวทำซ้ำสองตัวแยกกัน ( ::iteratorและ::const_iterator) แต่Storageคลาสของเรามีตัวอ่าน - เขียนเท่านั้นดังนั้นเราจึงสังเคราะห์อันหนึ่งโดยใช้ConstWrapper:

    template <typename Iterator>
    class ConstWrapper {
        Iterator iterator;

        using traits = std::iterator_traits<Iterator>;
    public:
        using difference_type = typename traits::difference_type;
        using value_type = typename traits::value_type;
        using pointer = ConstWrapper;
        using reference = MakeConst<typename traits::reference>;
        using iterator_category = typename traits::iterator_category;
    
        ConstWrapper() {}
        ConstWrapper(const Iterator &iterator) : iterator(iterator) {}
        
        // The problematic cases:
        auto operator[](index_t i) const
                -> MakeConst<decltype(iterator[i])> {
            return iterator[i];
        }
        auto operator*() const
                -> MakeConst<decltype(*iterator)> {
            return *iterator;
        }

        bool operator!= (const ConstWrapper& other) const {
            return iterator != other.iterator;
        }
        /** All the other random-access iterator methods **/
    };
    
    // Specialisation to prevent infinite loops
    template <typename Iterator>
    class ConstWrapper<ConstWrapper<Iterator>> : ConstWrapper<Iterator> {
    public:
        using ConstWrapper<Iterator>::ConstWrapper;
    };

การใช้งานจะส่งต่อวิธีการที่เกี่ยวข้องทั้งหมดและมีความเชี่ยวชาญConstWrapper<ConstWrapper<...>>เพื่อไม่ให้ห่อหุ้มตัวเอง

ส่วนสำคัญคือการMakeConstเปลี่ยนประเภทเป็นconstตัวแปรที่ถูกต้อง(ไม่เหมือนกับการเพิ่มเพียงอย่างconstเดียวซึ่งไม่มีผลต่อการอ้างอิง):

    // Converts (T & -> T const &), and (T -> const T)
    using MakeConst = typename std::conditional<
        std::is_reference<T>::value,
        typename std::remove_reference<T>::type const &,
        const T
    >::type;

สิ่งนี้สมเหตุสมผลหรือไม่และตกลงหรือไม่? มีอย่างอื่นที่ฉันสามารถทำได้ซึ่งอ่านได้ / มีประสิทธิภาพ / ฯลฯ มากกว่านี้หรือไม่?

ขอบคุณ!

คำตอบ

2 Quuxplusone Oct 25 2020 at 06:30

ฉันไม่คิดว่าความเชี่ยวชาญของคุณกำลังทำในสิ่งที่คุณคิดว่ากำลังทำอยู่ https://godbolt.org/z/Gfh7sv

คุณไม่ต้องการConstWrapper<ConstWrapper<Iterator>>ที่จะรับมรดกจาก ConstWrapper<Iterator> ; นั่นจะหมายความว่าCW<CW<I>>"เป็นแบบของ" CW<I>ซึ่งไม่เป็นความจริง คุณไม่ต้องการความสัมพันธ์ทางมรดกที่นี่ สิ่งที่คุณต้องการเป็นเพียงสำหรับความเชี่ยวชาญที่Storageจะไม่รวม DataIteratorสิ่งที่เกิดขึ้นเป็นความเชี่ยวชาญConstWrapperอยู่แล้ว วิธีที่คุณทำคือการใช้นามแฝงเพิ่มเติม

template<class T> struct maybe_constwrap { using type = ConstWrapper<T>; };
template<class U> struct maybe_constwrap<ConstWrapper<U>> { using type = ConstWrapper<U>; };

template<class Size, typename DataIterator>
class Storage : public Size {
public:
    using iterator = DataIterator;
    using const_iterator = typename maybe_constwrap<DataIterator>::type;
    ~~~
};

ตอนนี้ถ้าDataIteratorเป็นแล้วConstWrapperทั้งสองiteratorและconst_iteratorจะเป็นประเภทเดียวกันอย่างแท้จริง ... ซึ่งเป็นสิ่งที่คุณต้องการ


MakeConstดูเหมือนว่าคุณจะถูกกำหนดไว้ล่วงหน้าเกี่ยวกับความคิดที่น่าสงสัยซึ่งconst _Bit_referenceจะไม่สามารถมอบหมายให้ได้ มีแนวโน้มที่จะเปลี่ยนแปลงใน C ++ 23 ดูhttps://stackoverflow.com/questions/63412623/should-c20-stdrangessort-not-need-to-support-stdvectorbool สำหรับบางบริบท


คุณทำซ้ำ metaprogramming บางส่วนในประเภทการกลับมาของและoperator* operator[]พวกเขาควรส่งคืนประเภทreferenceที่คุณได้คำนวณไว้แล้วข้างต้น

reference operator*() const { return *it_; }

อนึ่งฉันกำลังเปลี่ยนชื่อสมาชิกข้อมูลของคุณจากiteratorเป็นit_เพราะฉันสงสัยว่ามันเป็นความคิดที่แย่มากแนวคิดที่ชาญฉลาดที่จะมีประเภทคลาส container-ish ที่มีสมาชิกชื่อiteratorซึ่งสมาชิกเป็น (A) ไม่ใช่สาธารณะและ (B ) ไม่ใช่ประเภท


คุณoperator!=น่าจะใช้สำนวนเพื่อนที่ซ่อนอยู่:

friend bool operator==(ConstWrapper a, ConstWrapper b) { return a.it_ == b.it_; }
friend bool operator!=(ConstWrapper a, ConstWrapper b) { return a.it_ != b.it_; }

ใน C ++ 20 คุณสามารถละเว้นoperator!=และระบุในทางทฤษฎีoperator==เท่านั้น ฉันยังไม่รู้ว่านั่นเป็นความคิดที่ดีหรือเปล่า

การส่งผ่านConstWrapperตามค่าควรจะใช้ได้เนื่องจากมีเพียงตัววนซ้ำและตัวทำซ้ำมีราคาถูกในการคัดลอก