読者です 読者をやめる 読者になる 読者になる

C++11:範囲for文

C++11では、ある範囲内の全要素の処理を簡潔に書けるようになった。

for ( auto x : 範囲 ) {
    // ...
}

「範囲」は、std::vectorのように反復子を返すメンバー関数begin()/end()が定義されているクラスのオブジェクトでも良いし、組み込み配列のように非メンバー関数begin()/end()を適用できるオブジェクトでも良い。(メンバー関数の方が優先される。)

void func(std::vector<double> &v)
{
    for ( auto x : v ) std::cout << x << "\n"; // 要素はxにコピーされる
    for ( auto &x : v ) ++x;                   // 要素を参照すると更新できる
    for ( const auto x : {1,2,5,13,34} ) std::cout << x << "\n";
}
int v[] = {1,2,5,13,34};
for ( auto x : v ) std::cout << x << "\n";

範囲for文を支援するために、標準ライブラリの<iterator>で範囲アクセス関数std::begin()/std::end()が定義されている。

namespace std {
    // クラス用
    template<typename C>
    auto begin(C &c) -> decltype(c.begin());
    template<typename C>
    auto begin(const C &c) -> decltype(c.begin());
    template<typename C>
    auto end(C &c) -> decltype(c.end());
    template<typename C>
    auto end(const C &c) -> decltype(c.end());
    // 組み込み配列用
    template<typename T, size_t N>
    auto begin(T (&array)[N]) -> T*;
    template<typename T, size_t N>
    auto end(T (&array)[N]) -> T*;
}

つまり、冒頭の範囲for文は以下のコードと等価である。

for ( auto __begin = begin(範囲), __end = end(範囲);
      __begin != __end; ++__begin )
{
    auto x = *__begin;
    // ...
}

範囲アクセス関数を直接使うことはあまりないだろうが、呼び出し方には注意が必要だ。

#include <algorithm>
#include <iterator>

template<typename Range, typename Function>
void for_each_all(Range &range, Function func)
{
    // 組み込み配列へ対応するために、using宣言する必要がある。
    using std::begin;
    using std::end;
    // 非標準の範囲アクセス非メンバー関数へ対応するために、名前空間修飾無しで呼び出す必要がある。
    auto first = begin(range);
    auto last = end(range);
    std::for_each(first, last, func);
}

#include <cstddef>

struct MyContainer
{
    static constexpr std::size_t m_a_size = 3u;
    int m_a[m_a_size];
    MyContainer() : m_a{4, 5, 6} {}
};

int *begin(MyContainer &c) { return c.m_a; }
int *end(MyContainer &c) { return c.m_a + MyContainer::m_a_size; }

#include <iostream>
#include <vector>

void print_line(int i)
{
    std::cout << i << "\n";
}

int main()
{
    std::vector<int> v = {1, 2, 3};
    MyContainer c;
    int a[] = {7, 8, 9};

    for_each_all(v, print_line);
    for_each_all(c, print_line);
    for_each_all(a, print_line);
}

高橋氏の記事の「std名前空間のbegin()/end()をusing宣言しているのは、配列版を考慮するため。」という説明は、よくわからなかった。)

参考