2017-10-23

インデックス付きRange-based for文を実装したらネストされた構造化束縛が欲しくなった

親愛なるC++14以前の古臭いC++を書く読者諸君。疑う余地なく、私がC++17を華麗に使いこなす江添亮である。

私のC++17参考書も余裕で書き終わり、後は出版を待つだけだ。私の中で、C++というのはもうすっかりC++17になってしまい、以前のC++のことは忘れてしまった。いまさらC++14のような使いづらい時代遅れのC++を書きたくはない。時代はいよいよC++17が当然になったと言えよう。

私のC++17参考書はGPLv3という自由なライセンスで提供される究極の電子書籍だ。これから紙書籍、プロフェッショナルに組版された電子書籍も出版される予定になっているので、読者は読まないという法はない。

EzoeRyou/cpp17book: C++17参考書

ところで、インデックス付きRange-based for文がほしいとさえずっていた。というのも、レンジの要素のイテレートを手で書きたくはないし、インデックスのインクリメントも手で書きたくないからだ。

template < typename Container >
void f( Container & c )
{
    // 要素のイテレートは手動
    // インデックスのインクリメントは手動
    for (   auto i = std::size_t(0),
            iter = std::begin(c), end = std::end(c) ;
            iter != end ; ++iter, ++i )
    {
        // iはインデックス
        // *iterは要素 
    }
}

std::for_eachやRange-based for文を使うことにより、要素のイテレートは手で書かずにすむ。しかし、インデックスのインクリメントは依然として書かなければならない。

template < typename Container >
void f( Container & c )
{
    {
        std::size_t i = 0 ;
        for ( auto element : c )
        {
            // iはインデックス
            // elementは要素
            ++i ; // 手動インクリメント
        }
    }

    std::for_each( std::begin(c), std::end(c),
        [i = std::size_t(0)](auto element) mutable
        {
            // iはインデックス
            // elementは要素
            ++i ; 手動インクリメント
        } ) ;
}

これではfor文より読みやすさ、間違いにくさの点で悪化しているではないか。

ところで、C++17では構造化束縛が追加された。


// クラステンプレートのコンストラクターの実引数からの推定も追加された
std::pair p(1,2) ;
auto [a,b] = p ;
// 以下と同じ
// auto a = p.first ;
// auto b = p.second ;

構造化束縛は、もちろんRange-based for文でも使える。


int main()
{
    std::map<int,int> m = {{1,1},{2,2},{3,3}} ;

    for ( auto [key, mapped] : m )
    {
        std::cout << key << mapped ;
    }
}

このコードは期待通りに動く。この程度のコードは、すでにC++17プログラマーである私にとってごく自然で当然のコードだが、未だに旧態依然のC++14以下にとらわれている読者には垂涎のコードだろう。

ということは、コンテナーをラップして、インデックスカウンターを持ち、operator ++でインデックスをインクリメント、operator *でstd::pair<std::size_t, reference>を返すようなラッパーを書けば、Range-based for文にインデックスが追加できるのではないか。

早速そのようなラッパー、with_indexを書いてみた。


int main()
{
    std::vector v = {1,2,3,4,5} ;

    for ( auto[ i, element ] : with_index(v) )
    {
        std::cout << i << element ;
    }
}

いける。これは使える。

しかし、問題がある。構造化束縛はネストできないので、std::mapに使うと以下のようになってしまう。

int main()
{
    std::map<int,int> m = { {1,1},{2,2},{3,3} } ;

    for ( auto[ i, pair ] : with_index(m) )
    {
        auto [key, mapped] = pair ;
        std::cout << i << key << mapped ;
    }
}

C++にもネストされた構造化束縛がほしい。

// C++ではない
int main()
{
    std::pair< int, std::pair<int,int> > p = { 1, {2,3} } ;

    // これがほしい
    auto [a, [b,c]] = p ;
}

ちなみに、with_indexの実装は以下の通り。

template < typename Iterator  >
class with_index_iterator
    : public Iterator
{
    std::size_t i = 0 ;

public :

    with_index_iterator( Iterator iter )
        : Iterator( iter )
    { }

    auto & operator ++()
    {
        ++i ;
        this->Iterator::operator ++() ;
        return *this ;
    }

    auto operator *() const noexcept
    {
        return std::pair<
            std::size_t,
            typename std::iterator_traits<Iterator>::reference >
        { i, *static_cast<Iterator const &>(*this) } ;
    }

} ;

template < typename Range >
class with_index
{
    Range & range ;

public :
    with_index( Range & range )
        : range(range)
    { }

    auto begin() const
    {
        return with_index_iterator{ std::begin(range) } ;
    }
    auto end() const
    {
        return with_index_iterator{ std::end(range) } ;
    }

} ;

ドワンゴ広告

ドワンゴは本物のC++プログラマーを募集しています。

採用情報|株式会社ドワンゴ

CC BY-ND 4.0: Creative Commons — Attribution-NoDerivatives 4.0 International — CC BY-ND 4.0

No comments: