C++
C++14
C++17

構造化束縛:範囲ベースforでindexも取得する

More than 1 year has passed since last update.

はじめに

C++17から使える構造化束縛を使えばindex付きの範囲ベースforをいい感じに書けるのでは?って思ったのでまとめとく

10/31追記 constにしたい時はas_constで良かった

範囲ベースでindex取得

もとのイテレーターの値への参照とindexを返すWithIndexIteratorを作成し、
それを返すclassを作る

Range.hpp
#pragma once

#include<iterator>

namespace range
{
    template<class Range>
    using range_iterator_t = decltype(std::begin(std::declval<Range&>()));

    template<class Range>
    using range_const_iterator_t = decltype(std::cbegin(std::declval<Range&>()));

    namespace detail
    {
        //pair(普通のstd::pairでも良い)
        template<class T>
        struct WithIndexPair
        {
            T value;
            const std::size_t index;
        };
        //イテレーター
        template<class It>
        class WithIndexIterator
        {
        public:
            using iterator_category = std::bidirectional_iterator_tag;
            using difference_type = std::size_t;
            using value_type = WithIndexPair<decltype(*std::declval<It>())&>;
            using pointer = value_type*;
            using reference = value_type&;
        private:
            It m_iterator;
            std::size_t m_index;
        public:
            WithIndexIterator(It it, std::size_t index) :
                m_iterator(it),
                m_index(index)
            {}

            value_type operator *()const
            {
                return { *m_iterator, m_index };
            }
            value_type* operator ->()const
            {
                return &*(*this);
            }
            WithIndexIterator& operator ++()
            {
                ++m_iterator;
                ++m_index;
                return *this;
            }
            WithIndexIterator& operator --()
            {
                --m_iterator;
                --m_index;
                return *this;
            }
            bool operator ==(const WithIndexIterator& other)const
            {
                return m_iterator == other.m_iterator;
            }
            bool operator !=(const WithIndexIterator& other)const
            {
                return m_iterator != other.m_iterator;
            }

        };
        //レンジ
        template<class Range>
        class WithIndexRange
        {
            using iterator = WithIndexIterator<range_iterator_t<Range>>;
            using const_iterator = WithIndexIterator<range_const_iterator_t<Range>>;

        private:
            Range m_range;
        public:

            WithIndexRange(Range&& range) :
                m_range(std::forward<Range>(range))
            {}

            iterator begin()
            {
                return { std::begin(m_range),0 };
            }
            iterator end()
            {
                return { std::end(m_range) , std::size(m_range) };
            }

            const_iterator begin()const
            {
                return const_iterator{ std::begin(m_range),0 };
            }
            const_iterator end()const
            {
                return const_iterator{ std::end(m_range) , std::size(m_range) };
            }

            std::size_t size()const
            {
                return std::size(m_range);
            }

        };
    }

    //添え字付き
    constexpr struct _WithIndex_OP
    {
        template<class Range>
        auto operator()(Range&& v)const
        {
            return detail::WithIndexRange<Range>(std::forward<Range>(v));
        }
        template<class Range>
        friend auto operator -(Range&& v, _WithIndex_OP op)
        {
            return op(std::forward<Range>(v));
        }
    }withIndex;


}



以下みたいに値とindexをとることができる。

また、メンバの参照型はconstで受け取っても変更可能なので注意
constにしたい場合はstd::as_constを使うことで使い分けができる(右辺値は無理だが…)

main.cpp
int main()
{
    std::vector<int> v{ 1,2,3 };

    //添え字つき
    for (auto&&elm: v - range::with_index)
    {
        std::cout << "Value :" << elm.value <<
                    " Index :" << elm.index << std::endl;
    }
    //添え字つき const版
    for (auto&&elm : std::as_const(v) - range::with_index)
    {
        //エラー
        //elm.value += 1;
    }
    return 0;
}

ここまではC++17じゃなくてもいい

構造化束縛

C++17から使える構造化束縛が便利

main.cpp
decltype(auto) test1()
{
    return std::make_tuple("Hellow", 1);
}
decltype(auto) test2()
{
    struct
    {
        int m_x;
        int m_y;
    }ret{33,4};
    return ret;
}
decltype(auto) test3()
{
    return std::make_pair(1,0.5);
}


int main()
{

    {
        auto [str, i] = test1();        //tuple
    }
    {
        auto [x, y] = test2();          //struct
    }
    {
        int raw_ar[3] = { 1,2,3 };
        auto[x, y, z] = raw_ar;         //raw array
    }
    {
        std::array<int, 3> ar{ 4,5,6 }; //stl array
        auto [x, y, z] = ar;
    }
    {
        auto [i, d] = test3();          //pair
    }   

    return 0;
}



auto&,const auto&,auto&&などでも受け取れる
std::tieを楽にした感じ

で、この機能と先ほどのindex取得を組み合わせれば
下みたいにかける

main.cpp

int main()
{
    std::vector<int> v{ 1,2,3 };

    //添え字つき
    for (auto&&[elm,i]: v - range::with_index)
    {
        std::cout << "Value :" << elm <<
                    " Index :" << i << std::endl;
    }
    return 0;
}

わりと好み

おまけ

関係ないけど逆順範囲forなども作ってみた

main.cpp
int main()
{

    std::vector<int> v{ 1,2,3 };

    for (auto&& elm: v - range::reverse)
    {
        std::cout << elm;
    }

    return 0;
}

ソースコード

https://gist.github.com/tyanmahou/45341370b330f5cc5cabe850838031af