あめだまふぁくとりー

Boost.Graphとかできますん

std::pair<Iterator, Iterator>をrange based forで使用する

C++11にはrange based forが加わり,rangeに対する走査処理を書くのが楽になりました.しかしながら,このstd::pair<Iterator, Iterator>で表されるIterator対はこのrangeには含まれません*1. そのため以下のようなコードはコンパイルエラーになります.

#include <iostream>
#include <map>

int main() {
    auto const map = std::multimap<int, int>{{2, 3}, {2, 4}, {2, 5}};
    for (auto const& entry : map.equal_range(2)) { // equal_rangeはIterator対を返す!!
        std::cout << "key: " << entry.first << " value: " << entry.second << std::endl;
    }
}

こういった場合は,Boost.Rangeのfor_eachとlambda式を組み合わせることで解決できます.

#include <iostream>
#include <map>
#include <boost/range/algorithm/for_each.hpp>

int main() {
    auto const map = std::multimap<int, int>{{2, 3}, {2, 4}, {2, 5}};
    boost::for_each(map.equal_range(2), [](std::pair<int, int> const& entry) {
        std::cout << "key: " << entry.first << " value: " << entry.second << std::endl;
    });
}

しかしながら,lambda式の引数の型名が長くなってくると,このやり方は少々面倒なことになります.特にBoost.Graphではそれが顕著になります.

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/range/algorithm/for_each.hpp>

int main() {
    boost::adjacency_list<> graph;
    // lambda式の引数の型名が長い
    boost::for_each(vertices(graph), [&](boost::graph_traits<decltype(graph)>::vertex_descriptor const v) {
        std::cout << "v index: " << get(boost::vertex_index, graph, v) << std::endl;
    });
}

こうなってくると,range based forを通した型推論を使って型名を書くのを避けたくなります.しかしながら,既に述べた通りstd::pairによるIterator対を使うとコンパイルエラーになってしまうため,そのままでは使用できません.自分でstd::pairに対するwrapperを書いてもいいですが,Boost.Rangeにiterator_rangeがあるのでこれを使用します.

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/algorithm/for_each.hpp>

int main() {
    boost::adjacency_list<> graph;
    // autoでOK!!
    for (auto v : boost::make_iterator_range(vertices(graph))) {
        std::cout << "v index: " << get(boost::vertex_index, graph, v) << std::endl;
    };
}

make_iterator_rangeはfiltered等のadaptorとは異なり(adaptorもiterator_rangeを使ってました),Iteratorをコピーするので元のIterator対の寿命を気にする必要がありません(上記ではvertices(graph)が返すIterator対の寿命)*2

まとめ

Generic lambdaサイコー!!

*1:http://stackoverflow.com/questions/6167598/why-was-pair-range-access-removed-from-c11?rq=1

*2:std::vector等のテンポラリオブジェクトを渡すのはダメです