C++と色々

主にC++やプログラムに関する記事を投稿します。

find関数で見るC++の変化

STLアルゴリズムはそのアルゴリズムを適用する範囲の先頭と末尾のイテレータのペアを引数に取ります。実際には範囲全体にアルゴリズムを適用させるケースが多く、イテレータのペアを書くのが冗長に感じることがあります。そこでBoost.Rangeには、範囲を引数に渡すことでその範囲全体にSTLアルゴリズムを適用する関数たちがあります。Boost.Rangeのfind関数の実装例を見てC++03/C++11/C++14の比較をしたいと思います。

C++03

まず、範囲なら何でも、コンテナでも組み込み配列でもアルゴリズムに適用できるように、begin関数、end関数を定義します。

namespace ns
{
    //コンテナ版begin/end関数
    //非const、constでオーバーロード

    template <typename Container>
    typename Container::iterator begin(Container& c)
    {
        return c.begin();
    }

    template <typename Container>
    typename Container::const_iterator begin(Container const& c)
    {
        return c.begin();
    }

    template <typename Container>
    typename Container::iterator end(Container& c)
    {
        return c.end();
    }

    template <typename Container>
    typename Container::const_iterator end(Container const& c)
    {
        return c.end();
    }

    //組み込み配列版begin/end関数

    template <typename T, std::size_t N>
    T* begin(T (&ar)[N])
    {
        return ar;
    }

    template <typename T, std::size_t N>
    T* end(T (&ar)[N])
    {
        return ar + N;
    }

コンテナと組み込み配列のオーバーロードは戻り値の型のtypename Container::iteratorあたりでSFINAEしています。

さらに、C++03では関数の戻り値の型推論をする機能はありませんのでfind関数の戻り値の型を求めるためのメタ関数を作成します。

    //コンテナ版range_iterator
    //非const、constで部分特殊化

    template <typename Container>
    struct range_iterator
    {
        typedef typename Container::iterator type;
    };

    template <typename Container>
    struct range_iterator<Container const>
    {
        typedef typename Container::const_iterator type;
    };

    //組み込み配列版range_iterator
    //非const、constで部分特殊化

    template <typename T, std::size_t N>
    struct range_iterator<T[N]>
    {
        typedef T* type;
    };

    template <typename T, std::size_t N>
    struct range_iterator<T const[N]>
    {
        typedef T const* type;
    };

これでfind関数を定義する準備が出来ました。
find関数を実装します。

    template <typename Range, typename T>
    typename range_iterator<Range>::type find(Range& rng, T const& value)
    {
        return std::find(ns::begin(rng), ns::end(rng), value);
    }

    template <typename Range, typename T>
    typename range_iterator<Range>::type find(Range const& rng, T const& value)
    {
        return std::find(ns::begin(rng), ns::end(rng), value);
    }
}

使用します。

#include <vector>

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

    ns::find(v, 2);

    int a[] = {10, 20, 30};
    ns::find(a, 20);
}

C++11

begin/endが標準入りしたので、自分で作成する必要がなくなりました。さらに、戻り値の型を求めるメタ関数も、C++11で戻り値の後方配置と式から型を求めるdecltypeが入りましたので必要無くなります。
以下のコードがC++11のfind関数です。

namespace ns
{
    template <typename Range, typename T>
    auto find(Range& rng, T const& value) -> decltype(std::find(std::begin(rng), std::end(rng), value))
    {
        return std::find(std::begin(rng), std::end(rng), value);
    }

    template <typename Range, typename T>
    auto find(Range const& rng, T const& value) -> decltype(std::find(std::begin(rng), std::end(rng), value))
    {
        return std::find(std::begin(rng), std::end(rng), value);
    }
}

使用コードです。

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

    ns::find(v, 2);

    int a[] = {10, 20, 30};
    ns::find(a, 20);
}

C++14

C++14ではbegin/endだけでなくcbegin/cend関数やrbegin/rend関数、crbegin/crend関数が追加されました。そして、通常関数の戻り値の型推論が追加されました。よってC++14におけるfind関数の実装は以下になります。

namespace ns
{
    template <typename Range, typename T>
    auto find(Range& rng, T const& value)
    {
        return std::find(std::begin(rng), std::end(rng), value);
    }

    template <typename Range, typename T>
    auto find(Range const& rng, T const& value)
    {
        return std::find(std::cbegin(rng), std::cend(rng), value);
    }
}

使用コードはC++11と同様なので割愛します。
C++11と比べてdecltypeに戻り値の式をもう1度書く冗長性がなくなりました。

まとめ

C++が進化するにつれて、より簡単に(それこそ特殊なテクニックを知らずに)よりジェネリックなコードが書けるようになってきました。C++はより簡単により汎用的により安全により効率的にコードが書けるように進化していると私は感じています。