Pembungkus konstan untuk iterator

Oct 11 2020

Sunting : menambahkan klarifikasi mengapa saya menginginkan ini, dan memperbarui kode karena saya belum memiliki jawaban

Saya memiliki kelas mirip array C ++ 11 yang (bisa) menjadi pembungkus di sekitar iterator akses-acak. Akses berbasis indeks dan .begin()/ .end()bisa langsung melewati iterator, tetapi ada sedikit kerumitan saat objeknya 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();}
    };

Jika kita hanya kembali DataIteratordari constversi .begin()dan .end(), maka orang yang memiliki keinginan Storage const &(salah!) Dapat memodifikasi array melalui iterator itu.

Penampung seperti std::vectormemiliki dua iterator terpisah ( ::iteratordan ::const_iterator), tetapi Storagekelas kami hanya memiliki satu iterator baca-tulis, jadi kami menyintesisnya menggunakan 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;
    };

Implementasinya meneruskan semua metode yang relevan, dan memiliki spesialisasi ConstWrapper<ConstWrapper<...>>sehingga tidak dapat membungkus dirinya sendiri.

Bagian kuncinya adalah MakeConst, yang mengubah jenis menjadi constvarian yang benar (tidak seperti sekadar menambahkan const, yang tidak berpengaruh pada referensi):

    // 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;

Apakah ini masuk akal, dan apakah ini OK? Apakah ada hal lain yang bisa saya lakukan yang lebih mudah dibaca / efisien / dll.?

Terima kasih!

Jawaban

2 Quuxplusone Oct 25 2020 at 06:30

Menurut saya spesialisasi Anda tidak melakukan apa yang menurut Anda dilakukannya. https://godbolt.org/z/Gfh7sv

Anda tidak ingin ConstWrapper<ConstWrapper<Iterator>>untuk mewarisi dari ConstWrapper<Iterator> ; itu berarti bahwa CW<CW<I>>"adalah-jenis-dari" CW<I>, yang tidak benar. Anda tidak ingin ada hubungan warisan di sini. Apa yang Anda inginkan adalah hanya untuk spesialisasi dari Storageuntuk tidak membungkus DataIterator s yang kebetulan spesialisasi dari ConstWrapperyang sudah. Cara Anda melakukannya adalah dengan lapisan tambahan dari alias indirection:

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;
    ~~~
};

Sekarang, jika DataIteratorsudah menjadi ConstWrapper, maka keduanya iteratordan const_iteratorakan menjadi tipe yang sama ... yang Anda inginkan.


Anda MakeConsttampaknya didasarkan pada gagasan meragukan yang const _Bit_referencetidak akan dapat dialihkan. Itu mungkin berubah di C ++ 23. Lihathttps://stackoverflow.com/questions/63412623/should-c20-stdrangessort-not-need-to-support-stdvectorbool untuk beberapa konteks.


Anda mengulangi beberapa metaprogramming dalam jenis kembalian operator*dan operator[]. Mereka seharusnya hanya mengembalikan jenis referenceyang telah Anda hitung di atas.

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

Kebetulan, saya mengubah nama anggota data Anda dari iteratormenjadi it_, karena saya curiga ini adalah ide yang buruk, dari segi konsep, memiliki tipe kelas container-ish dengan nama iteratoranggota yang anggotanya (A) bukan publik, dan (B ) bukan tipe.


Anda operator!=mungkin harus menggunakan idiom teman yang tersembunyi:

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

Dalam C ++ 20 Anda secara teoritis dapat menghilangkan operator!=dan menyediakan operator==saja. Saya belum tahu apakah itu ide yang bagus.

Melewati ConstWrapperoleh nilai harus baik-baik, karena hanya berlaku iterator, dan iterator yang murah untuk menyalin.