Skip to content

Latest commit

 

History

History
1147 lines (883 loc) · 28.8 KB

025-array-iterator.md

File metadata and controls

1147 lines (883 loc) · 28.8 KB

arrayのイテレーター

イテレーターの中身

自作のarrayをイテレーターに対応させる前に、まず'std::array'のイテレーターについてひと通り調べよう。

イテレーターはstd::begin/std::endで取得する。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto first = std::begin(a) ;
    auto last = std::end(a) ;
}

std::begin/std::endは何をしているのか見てみよう。

namespace std
{
    template < typename C >
    auto begin( C & c )
    { return c.begin() ; }

    template < typename C >
    auto begin( C const & c )
    { return c.begin() ; }

    template < typename C >
    auto end( C & c )
    { return c.end() ;}

    template < typename C >
    auto end( C const & c )
    { return c.end() ;}
}

なんと、単に引数に対してメンバー関数begin/endを呼び出してその結果を返しているだけだ。

さっそく確かめてみよう。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto iter = a.begin() ;
    std::cout << *iter ; // 1
    ++iter ;
    std::cout << *iter ; // 2
}

確かに動くようだ。

すると自作のarrayでイテレーターに対応する方法がわかってきた。

// イテレーターを表現するクラス
struct array_iterator { } ;

template < typename T, std::size_t N >
struct array
{
    // イテレーター型
    using iterator = array_iterator ;

    // イテレーターを返すメンバー関数
    iterator begin() ;
    iterator end() ;

    // その他のメンバー
} ;

イテレーターに対応するには、おおむねこのような実装になるとみていいだろう。おそらく細かい部分で微調整が必要になるが、いまはこれでよしとしよう。ではイテレーターが具体的に何をするかを見ていこう。

すでに学んだように、イテレーターはoperator *で参照する要素の値を取得できる。また書き込みもできる。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto iter = a.begin() ;
    int x = *iter ; // 1
    *iter = 0 ;
    // aは{0,2,3,4,5}
}

問題を簡単にするために、これまでに作った自作のarrayで最初の要素にアクセスする方法を考えてみよう

array<int, 5> a = {1,2,3,4,5} ;
int x = a[0] ; // 1
a[0] = 0 ;

このことから考えると、先頭要素を指すイテレーターはoperator *をオーバーロードして先頭要素をリファレンスで返せばよい。

struct array_iterator_int_5_begin
{
    array<int, 5> & a ;

    array<int, 5>::reference operator *()
    {
        return a[0] ;
    }
} ;

しかし、この実装ではarray<int,5>にしか対応できない。array<int,7>array<double, 10>には対応できない。なぜなら、arrayに渡すテンプレート実引数が違うと、別の型になるからだ。

array_iteratorでさまざまなarrayを扱うにはどうすればいいのか。テンプレートを使う。

template < typename Array >
struct array_iterator_begin
{
    Array & a ;

    array_iterator_begin( Array & a )
        : a( a ) { }

    // エラー
    // Array::referenceは型ではない
    Array::reference operator *()
    {
        return a[0] ;
    }
} ;

しかしなぜかエラーだとコンパイラーに怒られる。この理由を説明するのはとても難しい。気になる読者は近所のC++グルに教えを請おう。ここでは答えだけを教える。

T::Yにおいて、Tがテンプレート引数に依存する名前で、Yがネストされた型名の場合、typenameキーワードを付けなければならない。

template < typename T >
void f()
{
    // typenameが必要
    typename T::Y x = 0 ;
}

struct S
{
    using Y = int ;
} ;

int main()
{
    // T = S
    // T::Y = int
    f<S>() ;
}

わかっただろうか。わからなくても無理はない。この問題を理解するにはテンプレートに対する深い理解が必要だ。理解した暁には読者はC++グルとして崇拝されているだろう。

さしあたって必要なのはArray::referenceの前にtypenameキーワードを付けることだ。

typename Array::reference
array_iterator_begin::operator * ()
{
    return a[0] ;
}

どうやら最初の要素を読み書きするイテレーターはできたようだ。array側も実装して試してみよう。

array側の実装にはまだ現時点では完全に理解できない黒魔術が必要だ。

template < typename T, std::size_t N >
struct array
{
    T storage[N] ;
    // 黒魔術1: array
    using iterator = array_iterator_begin<array> ;
    iterator begin()
    // 黒魔術2: *this
    // 黒魔術3: iterator(*this)
    { return iterator(*this) ; }
} ;

黒魔術1はarray_iterator_begin<array>の中にある。このarrayarray<T,N>と同じ意味になる。つまり全体としては、array_iterator_begin<array<T,N>>と書いたものと同じだ。クラステンプレートの中でクラス名を使うと、テンプレート実引数をそれぞれ指定したものと同じになる。

template < typename A, typename B, typename C >
struct S
{
    void f()
    {
        // S<A,B,C>と同じ
        S s ;
    }
} ;

黒魔術2は*thisだ。*thisはメンバー関数を呼んだクラスのオブジェクトへのリファレンスだ。

struct S
{
    int data {} ;
    // *thisはメンバー関数が呼ばれたSのオブジェクト
    S & THIS() { return *this ; } 
} ;

int main()
{
    S s1 ;
    
    s1.THIS().data = 123 ;
    // 123
    std::cout << s1.data ;

    S s2 ;
    s2.THIS().data = 456 ;
    // 456
    std::cout << s2.data ;
}

クラスのメンバー関数は対応するクラスのオブジェクトに対して呼ばれる。本来ならばクラスのオブジェクトをリファレンスで取るような形になる。

struct S
{
    int data {} ;
    void set(int x)
    {
        data = x ;
    }
} ;

int main()
{
    S object ;
    object.set(42) ;
}

というコードは、ほぼ同じことを以下のようにも書ける。

struct S
{
    int data {} ;
} ;

void set( S & object, int x )
{
    object.data = x ;
}

int main()
{
    S ojbect ;
    set( object, 42 ) ;
}

クラスの意義は変数と関数を結び付けることだ。このように変数と関数がバラバラではわかりにくいので、メンバー関数という形でobject.set(...)のようにわかりやすく呼び出せるし、その際クラスSのオブジェクトは変数objectであることが文法上わかるので、わざわざ関数の実引数の形で書くことは省略できるようにしている。

メンバー関数の中で、メンバー関数が呼ばれているクラスのオブジェクトを参照する方法が*thisだ。

しかしなぜ*thisなのか。もっとわかりやすいキーワードでもいいのではないか。なぜ*が付いているのか。この謎を理解するためには、これまたポインターの理解が必要になるが、それは次の章で学ぶ。

黒魔術3はiterator(*this)だ。クラス名に(){}を続けると、コンストラクターを呼び出した結果のクラスの値を得ることができる。

struct S
{
    S() { }
    S( int ) { }
    S( int, int ) { }
} ;

int main()
{
    S a = S() ;
    S b = S(0) ;
    S c = S(1,2) ;

    S d = S{} ;
    S e = S{0} ;
    S f = S{1,2} ;
}

黒魔術の解説が長くなった。本題に戻ろう。

array_iterator_beginは先頭の要素しか扱えない。イテレーターで先頭以外の別の要素を扱う方法を思い出してみよう。

イテレーターはoperator ++で次の要素を参照する。operator --で前の要素を参照する。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto iter = a.begin() ;
    *iter ; // 1
    ++iter ;
    *iter ; // 2
    --iter ;
    *iter ; // 1
}

このoperator ++operator --はイテレーターへのリファレンスを返す。なぜならば、以下のように書けるからだ。

*++iter ;
*++++iter ;

以上を踏まえて、自作のarray_iteratorの宣言を書いてみよう。

template < typename Array >
struct array_iterator
{
    Array & a ;

    array_iterator( Array & a )
        : a( a ) { }

    // 次の要素を指す
    array_iterator & operator ++() ;
    // 前の要素を指す
    array_iterator & operator --() ;
    
    // いま参照している要素へのリファレンスを返す
    Array::reference operator *() ;
} ;

イテレーターの実装で先頭の要素を参照するのはa[0]だった。その次の要素を参照するにはa[1]だ。その次の要素はa[2]となり、その前の要素はa[1]だ。

array<int, 5> a = {1,2,3,4,5} ;

auto iter = a.begin() ; // 最初の要素
*iter ; // 1
++iter ; // 次の要素
*iter ; // 2
--iter ; // 前の要素、つまり最初の要素
*iter ; // 1

では最初の要素の前の要素や、最後の要素の次の要素を参照しようとするとどうなるのか。

auto first = a.begin() ;
--first ;
*first ; // 最初の前の要素?
auto last = a.end() ;
++last ; //
*last ; // 最後の次の要素?

これはエラーになる。このようなエラーを起こさないように務めるのはユーザーの責任で、イテレーター実装者の責任ではない。しかし、必要であればイテレーターの実装者はこのようなエラーを防ぐような実装もできる。それはあとの章で学ぶ。ここでは、こういう場合が起こることは考えなくてもよいとしよう。

これを考えていくと、イテレーターの実装をどうすればいいのかがわかってくる。

array_iteratoroperator *a[i]を返す。

typename Array::reference array_iterator::operator *()
{
    return a[i] ;
}

istd::size_t型のデータメンバーで、イテレーターが現在参照しているi番目の要素を記録している。

ということは先ほどのarray_iteratorの宣言にはデータメンバーiを追加する修正が必要だ。

template < typename Array >
struct array_iterator
{
    Array & a ;
    std::size_t i ;

    array_iterator( Array & a, std::size_t i )
        : a( a ), i(i) { }

    // いま参照している要素へのリファレンスを返す
    Array::reference operator *()
    {
        return a[i] ;
    }

    // その他のメンバー
} ;

そして、array側にも新しいarray_iteratorへの対応が必要になる。

template < typename T, std::size_t N >
struct array
{
    using iterator = array_iterator<array> ;

    // 先頭要素のイテレーター
    iterator begin()
    {
        return array_iterator( *this, 0 ) ;
    }

    // 最後の次の要素へのイテレーター
    iterator end()
    {
        return array_iterator( *this, N ) ;
    }
} ;

何度も書くように、インデックスは0から始まる。要素が$N$個ある場合、最初の要素は0番目で、最後の要素は$N-1$番目だ。

インクリメント演算子operator ++にも対応しよう。

array_iterator & array_iterator::operator ++()
{
    ++i ;
    return *this ;
}

これで最低限のイテレーターは実装できた。さっそく試してみよう。

int main()
{
    array<int,5> a = {1,2,3,4,5} ;

    auto iter = a.begin() ;

    std::cout << *iter ; // 1
    ++iter ;
    std::cout << *iter ; // 2
}

実はoperator ++は2種類ある。前置演算子と後置演算子だ。

int main()
{
    int i = 0 ;

    // 前置
    std::cout << ++i ;  // 1
    // 後置
    std::cout << i++ ;  // 1
    std::cout << i ;    // 2
}

int型では、前置operator ++はオペランドの値を1加算した値にする。後置operator ++はオペランドの値を1加算するが、式を評価した結果は前のオペランドの値になる。

++i ; // i+1
i++ ; // i、ただしiの値はi+1

後置operator ++のオーバーロードは以下のように書く。

struct IntLike
{
    int data {} ;

    // 前置
    IntLike & operator ++()
    {
        ++data ;
        return *this ;
    }
    // 後置
    IntLike operator ++(int)
    {
        IntLike copy = *this ;
        ++*this ;
        return copy ;
    }
} ;

このコードは慣れないとわかりにくいが、妥当な理由のあるコードだ。順番に説明しよう。

まず演算子オーバーロードの宣言だ。

// 前置
IntLike & operator ++() ;
// 後置
IntLike operator ++(int) ;

前置はリファレンスを返す。前置演算子の適用結果はさらに変更できるようにするためだ。

int main()
{
    int i { } ;

    ++++i ;
}

もちろん、リファレンスを返さない実装は可能だ。そもそも何も値を返さないvoidを使うことも可能だ。

struct S
{
    void operator ++() { }
} ;

ただし、その場合operator ++に対して通常期待されるコードが書けなくなる。理由がない限り演算子の自然な挙動を目指すべきだ。

前置と後置は区別できる必要がある。C++はその区別の方法として、int型の仮引数を1つ取るoperator ++を後置演算子だと認識する文法を採用した。このint型の実引数は前置と後置を区別するためだけのもので、値に意味はない。

struct S
{
    void operator ++( int x )
    {
        // 値に意味はない。
        std::cout << x ;
    }
} ;

int main()
{
    S s ;
    // 演算子としての使用
    s++ ;
    // メンバー関数としての使用
    s.operator++(123) ;
}

値に意味はないが、演算子として使用した場合、値は0になるというどうでもいい仕様がある。メンバー関数として使用すると好きな値を渡せるというこれまたどうでもいい仕様がある。テストには出ないので覚える必要はない。

前置は自然な挙動のためにリファレンスを返すが、後置はリファレンスではなくコピーした値を返す。

// 後置
IntLike IntLike::operator ++(int)
{
    // コピーを作る
    IntLike copy = *this ;
    // 演算子が呼ばれたオブジェクトをインクリメントする
    // 前置インクリメント演算子を呼んでいる
    ++*this ;
    // 値が変更されていないコピーを返す
    return copy ;
}

このように実装すると、後置として自然な挙動が実装できる。

++*thisは後置インクリメント演算子が呼ばれたオブジェクトに対して前置インクリメント演算子を使用している。わかりにくければ前置インクリメントと同じ処理を書いてもいい。

IntLike IntLike::operator ++(int)
{
    IntLike copy = *this ;
    // 同じ処理
    ++data ;
    return copy ;
}

IntLikeのように簡単な処理であればこれでもいいが、もっと複雑な何行もある処理の場合は、すでに実装した前置インクリメントを呼び出した方が楽だ。コードの重複を省けるのでインクリメントの処理を変更するときに、2箇所に同じ変更をしなくても済む。

以上を踏まえて、array_iteratorに後置インクリメント演算子を実装しよう。

array_iterator array_iterator::operator ++(int)
{
    array_iterator copy = *this ;
    ++*this ;
    return copy ;
}

デクリメント演算子operator --の実装はインクリメント演算子operator ++と同じだ。ただ処理がインクリメントではなくデクリメントになっているだけだ。

// 前置
array_iterator & array_iterator::operator --()
{
    -- i ;
    return *this ;
}
// 後置
array_iterator array_iterator::operator --(int)
{
    array_iterator copy = *this ;
    --*this ;
    return copy ;
}

ここまでくればイテレーターに必要な操作はあと1つ。比較だ。

イテレーターは同じ要素を指している場合に等しい。つまり、オペレーターabが同じ要素を指しているならば、a == btruea != bfalseだ。違う要素を指しているならばa == bfalsea != btrueだ。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto a = a.begin() ;
    auto b = a.begin() ;

    // true
    bool b1 = (a == b) ;
    // false
    bool b2 = (a != b) ;
    ++a ;
    // false
    bool b3 = (a == b) ;
    // true
    bool b4 = (a != b) ;
}

イテレーターは比較ができるので、イテレーターが終端に到達するまでループを回すことができる。

int main()
{
    std::array<int,5> a = {1,2,3,4,5} ;

            // 変数宣言
    for (   auto iter = std::begin(a),
            last = std::end(a) ;
            // 終了条件
            iter != last ;
            // ループごとの処理
            ++iter )
    {
        std::cout << *iter ;
    }
}

イテレーターは比較ができるので、各種アルゴリズムに渡すことができる。

array_iteratorの比較は、単にデータメンバーiの比較でよい。

bool array_iterator::operator ==( array_iterator const & right )
{
    return i == right.i ;
}
bool array_iterator::operator !=( array_iterator const & right )
{
    return i != right.i ;
}

これで自作のarrayarray_iteratorはアルゴリズムに渡せるようになった。

int main()
{
    array<int, 5> a = {1,2,3,4,5} ;

    std::for_each( std::begin(a), std::end(a),
        [](auto x){ std::cout << x ; } ) ;
}

残りのイテレーターの実装

std::arraystd::vectorのイテレーターはとても柔軟にできている。

例えばイテレーターiの参照する要素を3つ進めたい場合を考えよう。

++i ; // 1
++i ; // 2
++i ; // 3

これは非効率的だ。もっと効率的なイテレーターの進め方として、operator +=がある。

i += 3 ;

i += nはイテレーターin回進める。

operator +もある。

auto j = i + 3 ;

イテレーターjの値はイテレーターiを3つ進めた値になる。イテレーターiの値は変わらない。

実装は簡単だ。データメンバーiに対して同じ計算をする。

template < typename Array >
struct array_iterator
{
    Array & a ;
    std::size_t i ;

    array_iterator & operator += ( std::size_t n )
    {
        i += n ;
        return *this ;
    }

    array_iterator operator + ( std::size_t n ) const
    {
        auto copy = *this ;
        copy += n ;
        return copy ;
    }
} ;

operator +はオペランドの値を変更しないのでconstにできる。

同様に、operator -=operator -もある。上を参考に自分で実装してみよう。

operator +によって任意のn個先の要素を使うことができるようになったので、イテレーターin個先の要素を参照したければ、以下のように*(i+n)も書ける。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    std::cout << a[3] ; // 4

    auto i = a.begin() ;

    std::cout << *(i + 3) ; // 4
}

カッコが必要なのは、演算子の評価順序の都合だ。*i + 3(*i) + 3であり、iの指す要素に対して+3される。*(i+3)iの指す要素の3つ先の要素の値を読む。

イテレーターin個先の要素を読み書きするのにいちいち*(i+n)と書くのは面倒なので、std::arraystd::vectorのイテレーターにはoperator []がある。これを使うとi[n]と書ける。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    std::cout << a[3] ; // 4

    auto i = a.begin() ;

    std::cout << *(i + 3) ; // 4
}

operator []の実装は文字どおり*(i+n)と同じことをするだけでよい。

template < typename Array >
struct array_iterator
{
    typename Array::reference
    operator [] ( std::size_t n ) const
    {
        return *( *this + n ) ;
    }

    // その他のメンバー
} ;

このoperator []は、array_iteratorのデータメンバーを変更しないのでconst修飾できる。

*thisというのはこのイテレーターのオブジェクトなので、それに対してすでに実装済みのoperator +を適用し、その結果にoperator *を適用している。既存の実装を使わない場合、return文は以下のようになる。

return a[i+n] ;

こちらの方が一見簡単なように見えるが、operator +operator *の実装が複雑な場合、この方法では同じコードを複数の箇所に書かなければならず、コードを修正するときは同じ変更を複数の箇所に行わなければならない。すでに実装したメンバー関数は積極的に使って楽をしていこう。

イテレーターは大小比較ができる。

a <  b ;
a <= b ;
a >  b ;
a >= b ;

イテレーターの大小はどういう意味を持つのか。arrayのようにイテレーターが線形に順序のある要素を参照している場合で、前の要素を参照しているイテレーターはあとの要素を参照しているイテレーターより小さい。

int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    auto a = std::begin(a) ;
    auto b = a + 1 ;

    a <  b ; // true
    a <= b ; // true
    a >  b ; // false
    a >= b ; // false
}

自作のarrayの場合、単にデータメンバーiを比較する。

template < typename Array >
struct array_iterator
{
    Array & a ;
    std::size_t i ;

    bool operator < ( array_iterator const & right ) const
    {
        return i < right.i ;
    }
}

残りの演算子も同様に実装できる。

constなイテレーター: const_iterator

std::array<T,N>は通常のイテレーターであるstd::array<T,N>::iteratorのほかに、constなイテレーターであるstd::array<T,N>::const_iteratorを提供している。

int main()
{
    std::array<int,5> a = {1,2,3,4,5} ;

    // iterator
    std::array<int,5>::iterator iter = a.begin() ;
    // const_iterator
    std::array<int,5>::const_iterator const_iter = a.cbegin() ;
}

const_iteratorconst iteratorではない。const_iteratorとはそれ自体が型名だ。constというのは型名を修飾する別の機能だ。

そのため、constの有無の2種類の状態と、iterator, const_iteratorの2つの型を掛け合わせた、以下の型が存在する。

  • iterator
  • const iterator
  • const_iterator
  • const const_iterator
int main()
{
    using Array = std::array<int,5> ;

    // iterator 
    Array::iterator i ;
    // const iterator
    const Array::iterator c_i ;
    // const_iterator
    Array::const_iterator ci ;
    // const const_iterator
    const Array::const_iterator c_ci ;
}

const_iteratoriteratorとは別の型だ。自作のarrayに実装するならば以下のようになる。

template < typename T, std::size_t N >
struct array
{
    using iterator          = array_iterator<array> ;
    using const_iterator   = array_const_iterator<array> ;
} ;

それぞれの型に対して、constキーワードを付けた型とそうでない型が存在する。

const_iteratorを得る方法はいくつかある。

  • constarraybegin/endを呼び出す
int main()
{
    // constなarray
    const std::array<int, 5> a = {1,2,3,4,5} ;

    // const_iterator
    auto i = a.begin() ;
}
  • cbegin/cendを呼び出す
int main()
{
    std::array<int, 5> a = {1,2,3,4,5} ;

    // const_iterator
    auto i = a.cbegin() ;
}
  • iteratorからconst_iteratorへの変換
int main()
{
    using Array = std::array<int,5> ;
    Array a = {1,2,3,4,5} ;

    // iterator
    Array::iterator i = a.begin() ;
    // iteratorからconst_iteratorへの変換
    Array::const_iterator j = i ;
}

constキーワードはすでに学んだように、オブジェクトの値を変更できないようにする機能だ。

なぜconst_iteratorが存在するのか。const iteratorではだめなのか。その理由は、const iteratorは値の変更ができないためだ。

int main()
{
    using Array = std::array<int,5> ;
    Array a = {1,2,3,4,5} ;

    // const iterator
    const Array::iterator iter = a.begin() ;

    // エラー
    // constなオブジェクトは変更できない
    ++iter ;

    // Ok
    // iterは変更していない
    auto next_iter = iter + 1 ;
}

const_iteratorならばイテレーター自体の変更はできる。イテレーターが参照する要素の変更はできない。

int main()
{
    using Array = std::array<int,5> ;
    Array a = {1,2,3,4,5} ;

    auto citer = a.cbegin() ;

    // OK
    // イテレーター自体の変更
    ++citer ;

    // OK
    // 要素を変更しない
    std::cout << *citer ;

    // エラー
    // 要素を変更している
    *citer = 0 ;
}

const const_iteratorconst_iteratorconstだ。const const_iteratorconst iteratorと同じく、イテレーター自体の変更ができない。

int main()
{
    using Array = std::array<int,5> ;
    Array a = {1,2,3,4,5} ;

    // const const_iterator
    auto const iter = a.cbegin() ;

    // エラー
    // constなオブジェクトは変更できない
    ++iter ;

    // OK
    // iterは変更していない
    auto next_iter = iter + 1 ;
}

auto constもしくはconst autoを使うと、変数の型を自動で推定してくれるが、constが付くようになる。

const_iteratorはどう実装するのか。まずarrayにネストされた型名const_iteratorを追加する。

template < typename T, std::size_t N >
struct array
{
    using iterator = array_iterator<array> ;
    using const_iterator = array_const_iterator<array> ;
} ;

arrayconst_iteratorを返すcbegin/cendと、const arrayのときにconst_iteratorを返すbegin/endを追加する。

template < typename T, std::size_t N >
struct array
{
    using iterator = array_iterator<array> ;
    using const_iterator = array_const_iterator<array> ;

    // const arrayのときにconst_iteratorを返す
    const_iterator begin() const
    { return const_iterator(*this, 0) ; }
    const_iterator end() const
    { return const_iterator(*this, N) ; }

    // 常にconst_iteratorを返す
    const_iterator cbegin() const
    { return const_iterator(*this, 0) ; }
    const_iterator cend() const
    { return const_iterator(*this, N) ; }

    // その他のメンバー
} ;

あとはarray_const_iterator<array>を実装する。その実装はarray_iterator<array>とほぼ同じだ。

template < typename Array >
struct array_const_iterator
{
    Array const & a ;
    std::size_t i ;

    // コンストラクター
    array_const_iterator( Array const & a, std::size_t i )
        : a(a), i(i) { }
} ;

ただし、const_iteratoriteratorから変換できるので、

int main()
{
    using Array = std::array<int,5> ;
    Array a = {1,2,3,4,5} ;

    // iterator
    auto i = a.begin() ;

    // iteratorからconst_iteratorへの変換
    Array::const_iterator j = i ;
}

これに対応するために、const_iteratorのコンストラクターはiteratorから変換するためのコンストラクターも持つ。

template < typename Array >
struct array_const_iterator
{
    Array const & a ;
    std::size_t i ;

    // array_iteratorからの変換コンストラクター
    array_const_iterator( typename array_iterator<Array>::iterator const & iter )
        : a( iter.a ), i( iter.i ) { }
} ;

残りのメンバー関数はiteratorとほぼ同じだ。

例えばoperator ++は完全に同じだ。

// iterator版
array_iterator & array_iterator::operator++()
{
    ++i ;
    return *this ;
}
// const_iterator版
array_const_iterator & array_const_iterator::operator ++()
{
    ++i ;
    return *this ;
}

operator *operator []constなリファレンスを返す。

typename Array::const_reference operator *() const
{
    return a[i] ;
}

typename Array::const_reference operator []( std::size_t i ) const
{
    return *(*this + i) ;
}

このために、arrayクラスにもネストされた型名const_referenceを宣言しておく。

template < typename T, std::size_t N >
struct array
{
    using const_reference = T const & ;
} ;

残りはiteratorの実装を参考に読者が自分で実装してみよう。