ゲーム作りは楽しい

なんか書く

C++ 範囲ベースforに自分で作ったclassを対応させる

はじめに

NITMic Advent Calender 25日目 担当のマホウです。

メリークリスマス!

僕はサンタさんに免許更新のお金をもらいました! ラッキーなことに初回の更新なのに講習を地元でうけれることになりました。

今日は C++の範囲ベースfor

//こーゆーの
for(auto&& elm:v)
{}

に自分の作ったクラスを対応させる方法を紹介します。

範囲ベースfor とは

そもそも範囲ベースfor(range-based for)とは 配列とかコンテナとかの要素を全て回したりするときに、とても使える構文で C#だとforeachってキーワードがあったりするあれみたいなものです。

例えば、こんなコード

int main()
{
    int ar[] = { 1,2,3 };
    for (size_t i = 0; i < std::size(ar); ++i)
    {
        std::cout << ar[i] << std::endl;
    }

    return 0;
}

int main()
{
    int ar[] = { 1,2,3 };
    for (int elm : ar)
    {
        std::cout << elm << std::endl;
    }

    return 0;
}

のようにかけます。

int elmの部分はコピーが発生するので、値を書き換えたいときやコピーのコストがかかるものは(というかclassは全部といってもいい) auto& elmのような参照にしておく必要があります。
書き換えないときはconst auto&にするとよいでしょう
ユニヴァーサル参照auto&&とかもあり(詳しいことは自分で調べて)

予断ですが普通forの時にint iとsize等の比較は符号有り無し比較で警告が出るのでsize_t等unsignedにする癖をつけといたほうがいいかもね

Range Concept

さっそくですが ある要件さえ満たしていれば範囲ベースforで使うことができます。

その要件は

  • begin()とend()メソッドを持っている(正確にはstd::begin/endをオーバーロードしてもいいのだがとてもオススメはしない)
  • begin()とend()の返り値の型は
    • operator *
    • operator ++(前置)
    • operator != を持つ

これだけです。 C++17ではない場合begin()とend()の返り値の型は同じでなければなりません。

ここなんか読むと範囲forがどう展開されるか書いてあるので、わかりやすいです。 cpprefjp.github.io

class Hoge 
{
    int m_ar[3];

public:
    Hoge():
        m_ar{1,2,3}
    {}
    /*
   ポインタ型は++ * !=をもつ
   */
    int* begin()
    {
        return &m_ar[0];
    }
    int* end()
    {
        return &m_ar[3];
    }

};
int main()
{

    Hoge hoge;

    for (int elm : hoge)
    {
        std::cout << elm << std::endl;
    }

    return 0;
}

応用例 逆順範囲for

ほしいと思ったことありませんか? 要件をしった僕達ならもう実現できちゃいます。

#include<iterator>

namespace range
{
    template<class Range>
    using range_value_t = typename std::iterator_traits<decltype(std::begin(std::declval<Range&>()))>::value_type;

    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
    {
        template<class Range>
        class ReverseRange
        {
            using iterator = std::reverse_iterator<range_iterator_t<Range>>;
            using const_iterator = std::reverse_iterator<range_const_iterator_t<Range>>;

        private:
            Range m_range;
        public:
            ReverseRange(Range&& range) :
                m_range(std::forward<Range>(range))
            {}

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

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


    //逆順
    constexpr struct Reverse_OP
    {
        template<class Range>
        detail::ReverseRange<Range> operator ()(Range&& v)const
        {
            return detail::ReverseRange<Range>(std::forward<Range>(v));
        }

        template<class Range>
        friend auto operator -(Range&& v, Reverse_OP op)
        {
            return op(std::forward<Range>(v));
        }

    }reverse;
}
int main()
{

    int ar[] = { 1,2,3 };

    for (int elm : ar - range::reverse)
    {
        std::cout << elm << std::endl;
    }

    return 0;
}

最後に

25日間みなさんお疲れ様でした。 いろんな記事がよめて、特に自分があまり触れていない分野なんかは本当に勉強になりました。 今年はためしにやってみた感じですが、手ごたえはどうだったでしょうか?

いいね!って感じたならまた来年以降もやっていってくれたら良いかなと思います。