イテレータのconstラッパー

Oct 11 2020

編集:これが必要な理由の説明を追加し、まだ回答がないためコードを更新しました

ランダムアクセスイテレータのラッパーである(可能性がある)C ++ 11配列のようなクラスがあります。インデックスベースのアクセスおよび.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は2つの別々のイテレータ(::iteratorと::const_iterator)がありますが、Storageクラスには読み取り/書き込みのイテレータしかないため、次を使用して1つを合成し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しない ための専門化のためです。これを行う方法は、エイリアス間接参照の追加レイヤーを使用することです。DataIteratorConstWrapper

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 いくつかのコンテキストのために。


あなたはの戻り値の型にいくつかのメタプログラミングを繰り返しoperator*とoperator[]。reference上で計算したタイプを返すだけです。

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

ちなみに、データメンバーの名前をからiteratorに変更します。it_これは、概念的に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値の受け渡しは、イテレータのみを保持し、イテレータは安価にコピーできるため、問題ありません。