Boost.Context による単純なファイバー
いわゆる非対称コルーチン。C++03/11 両対応。
https://gist.github.com/3684731
簡単な使い方。
#include <iorate/fiber.hpp> int main() { iorate::fiber f([] { for (auto const c : "Hello, world!\n") { std::cout << c; iorate::fiber::yield(); } }); while (f.alive()) { f.resume(); } }
Boost.Context は trunk に入った当初は高レベル API を提供していたのですが、ある時を境にばっさり削除されてしまったという経緯があります(Boost.Contextの怒涛の変更 - にゃははー に詳しい)。おかげで Boost.Context でググって出てくる記事の多くは今では動かないコードだったり…。
まあ高レベル API は Boost.Coroutine あたりが Context ベースで書き直されて入ると思うので、もう少しの辛抱だと思います。
可変長引数を扱う手段として Fusion を使う
Variadic Template による可変長引数 ...args は f(args)... で f(a1), ..., f(aN) に展開できますが
template <class ...Args> void fg(Args ...args) { f(g(args)...); // f(g(a1), ..., g(aN)) }
少し込み入ったことをする場合は、引数を Boost.Fusion の列にまとめてから操作することができます
例えば (a1, ..., aN) => a1 + ... + aN という関数を書きたい場合
namespace fusion = boost::fusion; using namespace boost::phoenix::placeholders; template <class A1, class ...Args> auto plus(A1 const &a1, Args const &...args) -> decltype(fusion::fold(fusion::vector_tie(args...), a1, _1 + _2)) { return fusion::fold(fusion::vector_tie(args...), a1, _1 + _2); }
fusion::fold を使い、演算子 (+) と初期値 a1 で列 [a2, ..., aN] を畳み込んでいます
(引数の無駄なコピーを防ぐためには *_tie を使うと良いでしょう)
Perfect forwarding を行いたい場合は PP に頼ることになるかもしれませんが
http://ideone.com/sUOIn
あまり書きたくありませんね
Iteratee で progress_display を書いてみた
Iteratee を始めようと思いこちらを読んでいたのですが,
Lazy I/O must go! - Iteratee: 列挙ベースのI/O - 純粋関数型雑記帳
気になるフレーズが.
streamToFile "hoge" `enumPair` throbber とすることにより、ファイル書き込みに簡単に進捗表示をつけることができるようになります。その他、時間のかかるような処理にプログレスバー表示を取り付けたりするのも簡単です。
http://d.hatena.ne.jp/tanakh/20100824#p1
進捗表示?プログレスバー表示?
…
…
…
それは progress_display ではありませんか?
※ progress_display について
書いてみた
Iteratee like なパッケージとしては,見た目に分かりやすかった enumerator を使いました.
Progress.hs
module Boost.Progress (progressDisplay) where progressDisplay :: MonadIO m => Integer -- expected count -> Handle -- output handle -> String -- leading strings -> String -> String -> Iteratee a m ()
progressDisplay に適当なパラメータを与え,他の Iteratee に zip して使います.
Main.hs
import Control.Exception import Control.Monad.IO.Class import qualified Data.Enumerator as E import qualified Data.Enumerator.List as EL import System.IO import Boost.Progress streamToHandle :: MonadIO m => Handle -> E.Iteratee Char m () streamToHandle h = E.continue go where go E.EOF = E.yield () E.EOF go (E.Chunks xs) = do liftIO $ hPutStr h xs E.continue go main :: IO ((), ()) main = bracket (openFile "hoge" WriteMode) hClose $ \h -> E.run_ $ E.enumList 64 (replicate 1000000 '\NUL') E.$$ streamToHandle h `EL.zip` progressDisplay 1000000 stdout "\n" "" ""
出力
0% 10 20 30 40 50 60 70 80 90 100% |----|----|----|----|----|----|----|----|----|----| ***************************************************
実装
boost::progress_display の実装に合わせています.
Progress.hs
module Boost.Progress (progressDisplay) where import Control.Monad import Control.Monad.IO.Class import qualified Data.Enumerator as E import System.IO progressDisplay :: MonadIO m => Integer -- expected count -> Handle -- output handle -> String -- leading strings -> String -> String -> E.Iteratee a m () progressDisplay n h s1 s2 s3 = start >> E.continue (go 0 0) where expcnt = if n <= 0 then 1 else n start = liftIO $ do hPutStrLn h $ s1 ++ "0% 10 20 30 40 50 60 70 80 90 100%\n" ++ s2 ++ "|----|----|----|----|----|----|----|----|----|----|" hFlush h hPutStr h s3 go _ _ E.EOF = E.yield () E.EOF go cnt tic (E.Chunks xs) = do let cnt' = cnt + fromIntegral (length xs) tic' = floor (fromIntegral cnt' / fromIntegral expcnt * 50) liftIO $ do replicateM_ (tic' - tic) $ do hPutChar h '*' hFlush h when (cnt' == expcnt) $ do hPutStrLn h "*" hFlush h if (cnt' < expcnt) then E.continue (go cnt' tic') else E.yield () E.EOF
まとめ
遅くなりましたが,progress_display の追悼記事ということにします.
簡単に並置合成できるという Iteratee の利点も少し分かったような気がしますね.
Boost.Context でジェネレータを作る
知らないうちに Boost.Context が trunk 入りしていたので,それを使って Python のジェネレータのようなものを作る CRTP クラスを書いてみました.
generator.hpp
メンバ関数 generate を実装するとジェネレータを作り上げてくれます.ジェネレータは,遅延評価される range として振る舞います.
以下の例はフィボナッチ数列を返すジェネレータを作るものです.
#include <iostream> #include <tuple> #include "generator.hpp" struct fib : iorate::generator<fib, int> { int max; explicit fib(int max) : max(max) {} template <class Context> void generate(Context &ctx) const { int a = 0, b = 1; while (a < max) { yield(a, ctx); std::tie(a, b) = std::make_tuple(b, a + b); } } }; int main() { for (int i : fib(100)) std::cout << i << ' '; // 0 1 1 2 3 5 8 13 21 34 55 89 }
yield ベースの列挙を自然に書けていると思います.
Boost.Context は夢が広がりますね.
Boost.Context を GCC 4.8.0 20110302 で使うときの問題
上の例は GCC 4.6.1 (i686-pc-mingw32) + Boost Trunk 77256 で動作を確認しましたが,GCC 4.8.0 20120302 だと列挙を中断した場合に std::terminate() で終了してしまいます.
再現条件を探したところ,std::shared_ptr に指定したデリータ内で,完了していないコンテキストの unwind_stack() を呼び出す場合に(典型的には,std::shared_ptr<context> を使っている場合に起こります),std::terminate() が呼び出されるようです*1.
#include <memory> #include <boost/context/all.hpp> #include <boost/shared_ptr.hpp> using namespace boost::contexts; context ctx; void f() { ctx.suspend(); } int main() { ctx = context(f, default_stacksize(), no_stack_unwind, return_to_caller); ctx.start(); // OK // ctx.unwind_stack(); // std::terminate() is called at context_base.hpp:176 std::shared_ptr<void>(nullptr, [](void *) { ctx.unwind_stack(); }); // OK // boost::shared_ptr<void>(static_cast<void *>(nullptr), [](void *) { ctx.unwind_stack(); }); }
std::shared_ptr の排他制御が関係しているのかなと思いますが,よく分かりません.GCC のリリース版でも同じ問題が存在していれば,もう少し調べてみようと思います.
*1:スタック巻き戻しのために forced_unwind 例外が投げられる時点で呼ばれます.
ラムダ式でムーブキャプチャ
ラムダ式での変数のキャプチャはコピーまたは参照で行いますが,ムーブするための標準的な方法はありません.
std::unique_ptr<int> p(new int(23)); auto f = [std::move(p)] { std::cout << *p << '\n'; }; // こんな書き方はない
どうしても変数 p をムーブしたい場合には,別の変数 p_ を用意して,p_ がコピーキャプチャされたときに p がムーブされるようにする方法があります.以下にそのためのクラステンプレート move_capture を示します.
#include <cassert> #include <memory> #include <new> #include <type_traits> #include <utility> template <class T> struct move_capture { private: bool has_obj; union { T *ptr; T obj; }; public: move_capture(T &t) noexcept : has_obj(false), ptr(std::addressof(t)) {} move_capture(move_capture const &x) { construct(x); } move_capture(move_capture &&x) : has_obj(x.has_obj) { if (x.has_obj) ::new(std::addressof(obj)) T(std::move(x.obj)); else ptr = x.ptr; } move_capture &operator=(move_capture const &) = delete; ~move_capture() { if (has_obj) obj.~T(); } T &get() noexcept { assert(has_obj); return obj; } T const &get() const noexcept { assert(has_obj); return obj; } private: template <class U, typename std::enable_if<std::is_copy_constructible<U>::value>::type * = nullptr> void construct(move_capture<U> const &x) { has_obj = true; if (x.has_obj) ::new(std::addressof(obj)) T(x.obj); else ::new(std::addressof(obj)) T(std::move(*x.ptr)); } template <class U, typename std::enable_if<!std::is_copy_constructible<U>::value>::type * = nullptr> void construct(move_capture<U> const &x) { assert(!x.has_obj); has_obj = true; ::new(std::addressof(obj)) T(std::move(*x.ptr)); } }; template <class T> inline move_capture<T> make_move_capture(T &t) { return move_capture<T>(t); }
使い方としては,make_move_capture で p から p_ を作り,それをコピーキャプチャします.
// ポインタの参照先を表示する関数テンプレート template <class P> void print(P const &p) { if (p) std::cout << *p << '\n'; else std::cout << "null\n"; } void example() { std::unique_ptr<int> p(new int(23)); auto p_ = make_move_capture(p); print(p); // 23 auto f = [p_] { // 元の変数の値にアクセスするときは .get() を使う print(p_.get()); }; print(p); // null f(); // 23 auto g = std::move(f); f(); // null g(); // 23 }
もっとも,どうしてもムーブキャプチャしなければならない場面はそんなにないと思います.
Boost.Phoenix で range-based for
phx::for_each より楽に使えるものをと.
https://gist.github.com/1540409
void revival_example() { using boost::phoenix::arg_names::_1; using boost::phoenix::arg_names::_2; using boost::phoenix::local_names::_x; int array[5] = { 1, 2, 3, 4, 5 }; /* for (int &x : array) x *= 2; */ ranged(_x, _1) [ _x *= _2 ] (array, 2); int const result[5] = { 2, 4, 6, 8, 10 }; BOOST_CHECK(boost::equal(array, result)); }
内部では iterator を回しながら phx::let にぶん投げているだけです.
std::common_type の問題点
SFINAE に使えない
http://d.hatena.ne.jp/gintenlabo/20110420/1303288950
メタプログラミングをする上で不便ですね.
必ずしも「引数の型全てが変換される型」を返すとは限らない
struct A { A(int) {} template <class T> A(T) = delete; }; char c = '$'; std::common_type<char, int, A>::type a = c; // エラー,delete されたコンストラクタの呼び出し
std::common_type の特殊化では対応できません (しきれません).
あいまいな特殊化をしている
c++ - Partial specialization of variadic templates - Stack Overflow
14.8.2.5/9 に照らし合わせると,引数2つの特殊化があいまいになってしまいます.
std::common_type<char, int>::type i = c; // エラー,あいまい
規格の defect でしょうか.
Reference を返す場合がある
「や」の字: 【C++11】 decltype, conditional operator, そして common_type
これも defect のような気がしますね.
ではどうすべきか
あんまり使わないのがいいんじゃないでしょうか.