あめだまふぁくとりー

Boost.Graphとかできますん

Boost.Asio で read / async_read を使用する際の注意点

1. boost::asio::streambuf と組み合わせる場合

うっかり,

boost::asio::async_read(socket, streambuf, handler);

見たいに書くと, 永遠に handler が起動されない可能性があるので注意しましょう. これは, async_read は指定されたサイズ分読みを行い, デフォルトでは 65535 になっているからです. 少量のデータを送受信する場合は, boost::asio::transfer_at_least 等と一緒に使用しましょう.

// 少なくとも 4 byte 読むが, 一度の read でそれ以上読めたら handler が起動される
boost::asio::async_read(socket, streambuf, boost::asio::transfer_at_least(4), handler);

2. coroutine を使用かつ socket が close された場合

ドキュメンにも記載されていますが, read() / async_read() は, 期待したサイズ分を読む前に socket の終端 (EOF) を読むと, boost::asio::error::eof をエラーコードに指定して終了してしまいます. そのため, 以下のコードのように try-catch でエラーハンドリングすると, 何バイト読んだのかが分からず, 読み込んだデータを処理できない場合があります. 最悪, 読み込んだデータが捨てられてしまいます.

try {
    auto const size = boost::asio::async_read(socket, boost::asio::buffer(buf), yield);
}
catch (boost::system::system_error& e) {
    if (e.code() == boost::asio::error::eof) {
        // 何バイト読めたのか分からない
    }
}

以上を考慮すると, error_code を指定してあげるのが良いと思われます.

auto ec = boost::system::error_code{};
auto const size = boost::asio::async_read(socket, boost::asio::buffer(buf), yield[ec]);
if (ec && ec != boost::asio::error::eof) {
   throw boost::system::system_error{ec};
}

// 何か有益な処理
// ...

if (ec && ec == boost::asio::error::eof) {
    socket.close();
    return;
}

Boost.Test で Test Suite と Test Case の Fixture の併用

Boost.Test では,Test Case に対して Fixture を設定すると,Test Suite レベルの Fixture がその Test Case に対して使用されなくなります. よって,以下のようなコードはコンパイルエラーになってしまいます.

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE fixture_test
#include <boost/test/unit_test.hpp>
#include <iostream>

struct suite_fixture {
    suite_fixture() : suite_fix{1}
    { std::cout << __func__ << std::endl; }
    ~suite_fixture()
    { std::cout << __func__ << std::endl; }
    int suite_fix;
};

struct case_fixture {
    case_fixture() : case_fix{2}
    { std::cout << __func__ << std::endl; }
    ~case_fixture()
    { std::cout << __func__ << std::endl; }
    int case_fix;
};

// set test suite level fixture
BOOST_FIXTURE_TEST_SUITE(suite1, suite_fixture)

BOOST_AUTO_TEST_CASE(case1)
{
    BOOST_CHECK_EQUAL(1, suite_fix); // ok
}

// set test case fixture
BOOST_FIXTURE_TEST_CASE(case2, case_fixture)
{
    BOOST_CHECK_EQUAL(1, suite_fix); // compile error
    BOOST_CHECK_EQUAL(2, case_fix);
}

BOOST_AUTO_TEST_SUITE_END()

Boost.Test の Fixture の仕組みは,各 Test Case は Fixture のクラスを指定された場合は,そのクラスから派生し,そうでない場合はデフォルトのクラス,つまり,Test Suite で指定した Fixture のクラスから派生するといったものになっています.

なので,Test Case に渡すクラスを,指定したい Fixture クラスとデフォルトのクラスのリストにしてあげれば,多重継承になって両方の Fixture を利用できそうです. 以下がそのコードと出力になります.

ソースコード

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE fixture_test
#include <boost/test/unit_test.hpp>
#include <boost/preprocessor/tuple/enum.hpp>
#include <iostream>

// クラス F とデフォルトの Fixture クラスのリストを BOOST_FIXTURE_TEST_CASE に指定
#define CANARD_FIXTURE_TEST_CASE(test_name, F) \
    BOOST_FIXTURE_TEST_CASE(test_name, BOOST_PP_TUPLE_ENUM(2, (BOOST_AUTO_TEST_CASE_FIXTURE, F)))

struct suite_fixture {
    suite_fixture() : suite_fix{1}
    { std::cout << __func__ << std::endl; }
    ~suite_fixture()
    { std::cout << __func__ << std::endl; }
    int suite_fix;
};

struct case_fixture {
    case_fixture() : case_fix{2}
    { std::cout << __func__ << std::endl; }
    ~case_fixture()
    { std::cout << __func__ << std::endl; }
    int case_fix;
};

BOOST_FIXTURE_TEST_SUITE(suite1, suite_fixture)

CANARD_FIXTURE_TEST_CASE(case2, case_fixture)
{
    BOOST_CHECK_EQUAL(1, suite_fix); // ok
    BOOST_CHECK_EQUAL(2, case_fix);
}

BOOST_AUTO_TEST_SUITE_END()

出力

!!!*** No errors detected!!!
Running 1 test case...
suite_fixture
case_fixture
~case_fixture
~suite_fixture

第一回 グラフ王 決定戦

dijkstra_shortest_paths での比較

Stanford GraphBase と boost::adjacency_list, boost::vector_as_graph を利用した std::vector によるグラフで boost::dijkstra_shortest_paths の実行時間を 適当に 比較してみました. boost::adjacency_list は節点と枝を保持するデータ構造に選択肢が複数あるので, vecSlistS の4通りの組み合わせで計測しました.

以下が計測結果です. 単位はナノ秒, 試行回数は100,000回です. 最適化オプションはO3です. また,計測に使用したグラフは Stanford GraphBase のページ にある knight3.gb を使用しました.

Average:
  SGB:              5209.81
  v=vecS,e=vecS:    3698.48
  v=vecS,e=listS:   4141.79
  v=listS,e=vecS::  6560.22
  v=listS,e=listS:  6675.47
  vector:           3560.44
Max:
  SGB:              64000
  v=vecS,e=vecS:    76000
  v=vecS,e=listS:   115000
  v=listS,e=vecS::  40000
  v=listS,e=listS:  20000
  vector:           18000
Min:
  SGB:              4000
  v=vecS,e=vecS:    3000
  v=vecS,e=listS:   3000
  v=listS,e=vecS::  6000
  v=listS,e=listS:  6000
  vector:           3000

平均値を見てみると, 節点に vecS を指定した boost::adjacency_list はなかなか良い結果を出しています.

また, std::vector<list<size_t>> で表される vector グラフも同等の良い結果が出ています. 専用のグラフクラスを定義しなくても std::vector で大抵は事足りると考えられます. ちなみに, vector グラフの枝の重みのプロパティマップには std::unorderd_map を使用しました.

C 言語実装である SGB(Stanford GraphBase) はこれらより少し遅い結果になりました.

そして最後に, 節点に listS を指定した boost::adjacency_list が最も遅いという結果に今回はなりました. dijkstra_shortest_paths は開始時に節点を総なめしているので, それをしない dijkstra_shortest_paths_no_color_map を使用すると結果は変わってくるかもしれません.

最大値に関しては測定するごとにかなりばらつきがあるので何とも言えません.

計測用のコード

#include <array>
#include <chrono>
#include <iostream>
#include <unordered_map>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/algorithm/max_element.hpp>
#include <boost/range/algorithm/min_element.hpp>
#include <boost/range/numeric.hpp>
#include <boost/graph/stanford_graph.hpp>
#undef ap
#undef upi
#undef abbr
#undef nickname
#undef conference
#undef HOME
#undef NEUTRAL
#undef AWAY
#undef venue
#undef date
#include <boost/functional/hash/hash.hpp>
#include <boost/graph/vector_as_graph.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/copy.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>

int main()
{
    using namespace boost;
    sgb_graph_ptr sgb_graph = restore_graph(const_cast<char*>("knight3.gb"));

    using result_type = std::array<uint64_t, 100000>;
    {
        dijkstra_shortest_paths(sgb_graph, *vertices(sgb_graph).first
                , weight_map(get(edge_length_t{}, sgb_graph))
                . distance_inf(std::numeric_limits<long>::max()));
    }
    auto sgb_result = result_type{};
    for (auto& result : sgb_result)
    {
        auto const start = std::chrono::system_clock::now();
        dijkstra_shortest_paths(sgb_graph, *vertices(sgb_graph).first
                , weight_map(get(edge_length_t{}, sgb_graph))
                . distance_inf(std::numeric_limits<long>::max()));
        auto const end = std::chrono::system_clock::now();
        result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
    }

    auto vecSvecS_result = result_type{};
    {
        using BoostGraph = adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, long>>;
        auto boost_graph = BoostGraph{};
        copy_graph(sgb_graph, boost_graph
                , vertex_copy([&](auto sgb_v, auto boost_v){
                })
                . edge_copy([&](auto sgb_e, auto boost_e){
                    put(edge_weight, boost_graph, boost_e, get(edge_length_t{}, sgb_graph, sgb_e));
                }));

        {
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
        }
        for (auto& result : vecSvecS_result)
        {
            auto const start = std::chrono::system_clock::now();
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
            auto const end = std::chrono::system_clock::now();
            result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        }
    }

    auto vecSlistS_result = result_type{};
    {
        using BoostGraph = adjacency_list<listS, vecS, directedS, no_property, property<edge_weight_t, long>>;
        auto boost_graph = BoostGraph{};
        copy_graph(sgb_graph, boost_graph
                , vertex_copy([&](auto sgb_v, auto boost_v){
                })
                . edge_copy([&](auto sgb_e, auto boost_e){
                    put(edge_weight, boost_graph, boost_e, get(edge_length_t{}, sgb_graph, sgb_e));
                }));

        {
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
        }
        for (auto& result : vecSlistS_result)
        {
            auto const start = std::chrono::system_clock::now();
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
            auto const end = std::chrono::system_clock::now();
            result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        }
    }

    auto listSvecS_result = result_type{};
    {
        using BoostGraph = adjacency_list<vecS, listS, directedS, property<vertex_index_t, size_t>, property<edge_weight_t, long>>;
        auto boost_graph = BoostGraph{};
        copy_graph(sgb_graph, boost_graph
                , vertex_copy([&](auto sgb_v, auto boost_v){
                    put(vertex_index, boost_graph, boost_v, get(get(vertex_index, sgb_graph), sgb_v));
                })
                . edge_copy([&](auto sgb_e, auto boost_e){
                    put(edge_weight, boost_graph, boost_e, get(edge_length_t{}, sgb_graph, sgb_e));
                }));

        {
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
        }
        for (auto& result : listSvecS_result)
        {
            auto const start = std::chrono::system_clock::now();
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
            auto const end = std::chrono::system_clock::now();
            result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        }
    }

    auto listSlistS_result = result_type{};
    {
        using BoostGraph = adjacency_list<listS, listS, directedS, property<vertex_index_t, size_t>, property<edge_weight_t, long>>;
        auto boost_graph = BoostGraph{};
        copy_graph(sgb_graph, boost_graph
                , vertex_copy([&](auto sgb_v, auto boost_v){
                    put(vertex_index, boost_graph, boost_v, get(get(vertex_index, sgb_graph), sgb_v));
                })
                . edge_copy([&](auto sgb_e, auto boost_e){
                    put(edge_weight, boost_graph, boost_e, get(edge_length_t{}, sgb_graph, sgb_e));
                }));

        {
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
        }
        for (auto& result : listSlistS_result)
        {
            auto const start = std::chrono::system_clock::now();
            dijkstra_shortest_paths(boost_graph, *vertices(boost_graph).first
                    , weight_map(get(edge_weight_t{}, boost_graph))
                    . distance_inf(std::numeric_limits<long>::max()));
            auto const end = std::chrono::system_clock::now();
            result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        }
    }

    auto vector_result = result_type{};
    {
        using VectorGraph = std::vector<std::list<size_t>>;
        auto vector_graph = VectorGraph{};
        using edge_descriptor = graph_traits<VectorGraph>::edge_descriptor;
        auto wmap = std::unordered_map<edge_descriptor, long, boost::hash<edge_descriptor>>{};
        copy_graph(sgb_graph, vector_graph
                , vertex_copy([&](auto, auto){
                })
                . edge_copy([&](auto sgb_e, auto& vector_e){
                    wmap[vector_e] = get(edge_length_t{}, sgb_graph, sgb_e);
                }));
        {
            dijkstra_shortest_paths(vector_graph, *vertices(vector_graph).first
                    , weight_map(make_assoc_property_map(wmap))
                    . distance_inf(std::numeric_limits<long>::max()));
        }
        for (auto& result : vector_result)
        {
            auto const start = std::chrono::system_clock::now();
            dijkstra_shortest_paths(vector_graph, *vertices(vector_graph).first
                    , weight_map(make_assoc_property_map(wmap))
                    . distance_inf(std::numeric_limits<long>::max()));
            auto const end = std::chrono::system_clock::now();
            result = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
        }
    }


    std::cout << "Average:" << std::endl;
    std::cout << "  SGB:              " << accumulate(sgb_result, 0.0) / sgb_result.size() << std::endl;
    std::cout << "  v=vecS,e=vecS:    " << accumulate(vecSvecS_result, 0.0) / vecSvecS_result.size() << std::endl;
    std::cout << "  v=vecS,e=listS:   " << accumulate(vecSlistS_result, 0.0) / vecSlistS_result.size() << std::endl;
    std::cout << "  v=listS,e=vecS::  " << accumulate(listSvecS_result, 0.0) / listSvecS_result.size() << std::endl;
    std::cout << "  v=listS,e=listS:  " << accumulate(listSlistS_result, 0.0) / listSlistS_result.size() << std::endl;
    std::cout << "  vector:           " << accumulate(vector_result, 0.0) / vector_result.size() << std::endl;
    std::cout << "Max:" << std::endl;
    std::cout << "  SGB:              " << *max_element(sgb_result) << std::endl;
    std::cout << "  v=vecS,e=vecS:    " << *max_element(vecSvecS_result) << std::endl;
    std::cout << "  v=vecS,e=listS:   " << *max_element(vecSlistS_result) << std::endl;
    std::cout << "  v=listS,e=vecS::  " << *max_element(listSvecS_result) << std::endl;
    std::cout << "  v=listS,e=listS:  " << *max_element(listSlistS_result) << std::endl;
    std::cout << "  vector:           " << *max_element(vector_result) << std::endl;
    std::cout << "Min:" << std::endl;
    std::cout << "  SGB:              " << *min_element(sgb_result) << std::endl;
    std::cout << "  v=vecS,e=vecS:    " << *min_element(vecSvecS_result) << std::endl;
    std::cout << "  v=vecS,e=listS:   " << *min_element(vecSlistS_result) << std::endl;
    std::cout << "  v=listS,e=vecS::  " << *min_element(listSvecS_result) << std::endl;
    std::cout << "  v=listS,e=listS:  " << *min_element(listSlistS_result) << std::endl;
    std::cout << "  vector:           " << *min_element(vector_result) << std::endl;
}

Boost.Graph property to Boost.Fusion

Boost.Graph のグラフ構造体で使用される property を fusion::for_each で使用できるようにしてみました. ソースは こちら にあげています.

property に bundle (ユーザが定義した構造体)が含まれる場合, bundle は fusion::for_each 上には現れないので注意です.

以下は使用例で, グラフを graphviz の dot 言語に書き出す際に node attributes を fusion::for_each で出力しています(attribute の名前は面倒なので typeinfo::name に使用しています).

使用例:

#include <iostream>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/container/map.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include "canard/property_sequence.hpp"

// 長い property のリスト
using vertex_property
    = boost::property<
        boost::vertex_index_t, std::size_t
    , boost::property<
        boost::vertex_color_t, std::string
    , boost::property<
        boost::vertex_name_t, std::string
    , boost::property<
        boost::vertex_rank_t, std::size_t
    >>>>;
using graph = boost::adjacency_list<boost::listS, boost::listS, boost::directedS, vertex_property>;

struct print_property
{
    explicit print_property(std::ostream& out)
        : out{out}
    {
    }

    template <class Tag, class T, class Base>
    void operator()(boost::property<Tag, T, Base>& p) const
    {
        (is_first ? out : (out << ", ")) << typeid(Tag).name() << "=" << p.m_value;
        is_first = false;
    }

    std::ostream& out;
    mutable bool is_first = true;
};

struct vertex_property_writer
{
    using all_property_map = boost::property_map<graph, boost::vertex_all_t>::type;

    template <class Vertex>
    void operator()(std::ostream& out, Vertex const v) const
    {
        // node attirbutes を出力
        out << "[";
        boost::fusion::for_each(get(all_map, v), print_property{out});
        out << "]";
    }

    all_property_map all_map;
};

int main()
{
    auto g = graph{};
    add_vertex(vertex_property{0, {"blue", {"tokyo", {2}}}}, g);
    add_vertex(vertex_property{1, {"red", {"sapporo", {1}}}}, g);
    boost::write_graphviz(std::cout, g, vertex_property_writer{get(boost::vertex_all, g)});
}

実行結果:

digraph G {
0[N5boost14vertex_index_tE=0, N5boost14vertex_color_tE=blue, N5boost13vertex_name_tE=tokyo, N5boost13vertex_rank_tE=2];
1[N5boost14vertex_index_tE=1, N5boost14vertex_color_tE=red, N5boost13vertex_name_tE=sapporo, N5boost13vertex_rank_tE=1];
}

Boost.Program_optionsで名前を持たないオプションの扱い方

Boost.Program_optionsについて調べてみたので,備忘録として.

名前を持たないオプションの対応

以下が名前を持たないオプション値の例です.オプション値hogeはoptionという名前に関連づけされていますが,huga.txtには関連づけされた名前がありません.

$ command --option=hoge huga.txt

以下のような一般的なProgram_optionsの使用方法ではhuga.txtが手に入りません.

#include <iostream>
#include <boost/program_options.hpp>

using namespace boost::program_options;

int main(int argc, char* argv[])
{
    auto desc = options_description{"options"};
    desc.add_options()
        ("option", value<std::string>(), "some option");
    auto vm = variables_map{};
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);

    if (vm.count("option")) {
        std::cout << vm["option"].as<std::string>() << std::endl;
    }
}

positional_options_descriptionを使用する方法

この場合,huga.txtを扱うには二通り方法があります. 一つ目がpositional_options_descriptionを使用する方法です.これはドキュメントにも載っている方法なので例だけ示します.

auto desc = options_description{"options"};
desc.add_options()
    ("option", value<std::string>(), "some option")
    // 名前をつけないオプションにも名前をつける
    ("unnamed-option", value<std::string>(), "unnamed option")
    ;

auto vm = variables_map{};
store(command_line_parser{argc, argv}
        .options(desc)
        .positional(
            positional_options_description{}
                // 名前の付いていないオプション一つをunnamed-optionで名前づけする
                .add("unnamed-option", 1))
        .run()
    , vm);
notify(vm);

if (vm.count("option")) {
    std::cout << vm["option"].as<std::string>() << std::endl;
}

if (vm.count("unnamed-option")) {
    std::cout << vm["unnamed-option"].as<std::string>() << std::endl;
}

ちなみにpositional_options_description::addの第二引数の整数値は,第一引数に指定した名前を持つオプション値の個数を表します. 位置を指定している訳ではありませんので注意してください.-1を指定すると個数は無制限になります.

positional_options_description{}
    .add("unnamed1", 2)
    .add("unnamed2", 1)
    .add("unnamed3", -1)
    .add("unnamed4", 2);

上記のような場合,コマンドライン上に表れる名前を持たないオプション値のうち,左から二つはunnamed1に関連づけられ,その次の一つ,さらにその次の二つはそれぞれunnamed2,unnamed4に関連づけられます.残りのオプション値はunnamed3に関連づけられることになります.

さらに.add("unnamed5", -1)と書くとunnamed3にはオプション値は一つも関連づけられず,代わりにunnamed5に関連づけられます.

collect_unrecognizedを使用する方法

positional_options_description使用する方法の欠点は,名前を必要としないオプション値に対しても名前付けする必要があることです.

名前が不要のオプションに対して名前を付けてしまうと,helpオプション等の出力にoptions_descriptionインスタンスを用いた場合,不要なオプションが見えてしまいます.Program_optionsのtutorialでは,options_descriptionの複数のインスタンスを用いることでこの問題を解決していましたが,出力と実際のparsingで使用するインスタンスが異なるのはあまりよいとは思えません.

ここでは関数collect_unrecognizedを使用して,options_descriptionインスタンスに変更を加えずに名前を持たないオプションを取得します.

collect_unrecognizedは指定したoptions_descriptorインスタンスが知らないオプションを取得する関数です(デフォルトではparserは,'-'で始まるオプションが知らないものだった場合,例外を投げるため,これを許容するようにする必要があります).

collect_unrecognizedの第一引数はstd::vector<basic_option>であり,これはparsing結果から取得できます.

第二引数はcollect_unrecognized_mode型の値であり,include_positionalとexclude_optionalの二つの値が存在します.前者を指定すると'-'から始まらないオプションも取得することができます.

以下が例です.

auto desc = options_description{"options"};
desc.add_options()
    ("option", value<std::string>(), "some option");
auto vm = variables_map{};
// parsingの結果を保持
auto const parsing_result = parse_command_line(argc, argv, desc);
store(parsing_result, vm);
notify(vm);

if (vm.count("option")) {
    std::cout << vm["option"].as<std::string>() << std::endl;
}

std::cout << "unnamed options:" << std::endl;
// parsingの結果からoptions_descriptionに関係しないものを取得
for (auto const& str : collect_unrecognized(parsing_result.options, include_positional)) {
    std::cout << "    " << str << std::endl;
}

先読みiteratorアダプタ作ってみた

指定した値分だけiteratorが進んで見える(つまりstd::advance(it, n))ようなiteratorアダプタを作ってみました.

Boost.Rangeのslicedでも同じようなことができるのですが, このアダプタはbase()では元の場所を指したiteratorを返します. 式で書くと*it == *(it.base() + n)になります.

gist8730956

findやlower_boundなどiteratorを返すアルゴリズムで目的の値の一つ手前の要素が欲しいときに使うことを想定しました.

#include "slide_view_iterator.hpp"
#include <algorithm>
#include <boost/range/irange.hpp>
#include <iostream>

int main()
{
    auto numbers = boost::irange(0, 10);
    {
        auto it = std::lower_bound(
                canard::make_slide_view_iterator(numbers.begin(), 1)
              , canard::make_slide_view_iterator(numbers.end())
              , 9);
        std::cout << *it.base() << " is one step short of " << *it << std::endl;
    }
    {
        auto it = std::find(
                canard::make_slide_view_iterator(numbers.begin(), 2)
              , canard::make_slide_view_iterator(numbers.end())
              , 8);
        std::cout << *it.base() << " is two step short of " << *it << std::endl;
    }
}

iterator_category等のチェックは未実装です.

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等のテンポラリオブジェクトを渡すのはダメです