本の虫

著者:江添亮
ブログ: http://cpplover.blogspot.jp/
メール: boostcpp@gmail.com
Twitter: https://twitter.com/EzoeRyou
GitHub: https://github.com/EzoeRyou

アマゾンの江添のほしい物リストを著者に送るとブログ記事のネタになる

筆者にブログのネタになる品物を直接送りたい場合、住所をメールで質問してください。

インデックス付きrange-based forに必要なのはネストされた構造化束縛ではなくてif constexprだった

親愛なるC++読者諸君、私がC++17を流暢に使いこなす江添亮である。前回、ネストされた構造化束縛がほしいと書いた。

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

このブログを公開してすぐ、同僚からwith_index側でpairを開けばいいのではないかと言われた。たしかにそのとおりだ。早速実装した。その結果、以下のコードが通るようになった。


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

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

その実装の骨子は以下の通り。


template < typename T >
struct is_pair
    : std::false_type { } ;

template < typename T1, typename T2 >
struct is_pair< std::pair<T1, T2> >
    : std::true_type { } ;

template < typename T >
constexpr bool is_pair_v = is_pair<T>::value ;

template < typename Iterator  >
class with_index_iterator
    : public Iterator
{

public :
    auto operator *() const noexcept
    {   // ここが重要
        if constexpr ( is_pair_v< typename std::iterator_traits<Iterator>::value_type > )
        {
            auto & pair = *static_cast<Iterator const &>(*this) ;
            return std::make_tuple( i, pair.first, pair.second ) ;
        }
        else {
            return std::make_pair( i, *static_cast<Iterator const &>(*this) ) ;
        }
    }
} ;

重要なのはif constexprだ。constexpr ifによって、わざわざクラステンプレートを書かずともコンパイル時条件分岐ができるようになった。これでメタプログラミングがとても書きやすくなった。

なお、この実装では以下のようなコードは通らない。

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

    for ( auto[ i, key, m1, m2 ] : with_index(m) )
    { }
}

再帰的なテンプレートメタプログラミングをすることにより、何段階にネストされようとも開くことができるwith_indexは実装可能だ。その実装は読者への課題とする。

また、構造化束縛がpairの他にも対応しているtupleやtuple_sizeとtuple_elementとgetに対応した型のネストへの対応も、読者への課題とする。

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

    for ( auto[ i, key, m1, m2 ] : with_index(m) )
    { }
}

問題は、構造化束縛が対応しているクラスを開くことができない。


struct user_defined_pair
{
    int x ;
    int y ;
} ;

std::map< int, user_defined_pair > m = { {1,{1,1}}, {2,{2,2}},{3,{3,} } ;

これはどうしようもない。とはいえ、実用上はこれでいいのではないか。

with_indexの完全な実装は以下の通り。

template < typename T >
struct is_pair
    : std::false_type { } ;

template < typename T1, typename T2 >
struct is_pair< std::pair<T1, T2> >
    : std::true_type { } ;

template < typename T >
constexpr bool is_pair_v = is_pair<T>::value ;



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
    {
        if constexpr ( is_pair_v< typename std::iterator_traits<Iterator>::value_type > )
        {
            auto & pair = *static_cast<Iterator const &>(*this) ;
            return std::make_tuple( i, pair.first, pair.second ) ;
        }
        else {
            return std::make_pair( 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