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

C++ で継続

C++

C++ で継続を使いたい」と思い、悪戦苦闘した結果を書いておきます。

通常の関数呼び出しでは return で呼び出し元に戻るわけですが、
"この記事でいう"「継続」は、呼び出し元に戻らずに次の関数へ飛ぶようなものです。
(突っ込みどころはあるでしょうがそっとしてやって下さい)

成果物がこれ。

#include "continuation.hpp"
 
using namespace std;
 
// コンストラクタとデストラクタで数値を表示する
struct scope_indicator_
{
    scope_indicator_( int const v ) : v_( v )
        { cout << "begin " << v_ << endl; }
    ~scope_indicator_()
        { cout << "end   " << v_ << endl; }
 
    int const v_;
};
#define indicate_scope scope_indicator_ indicates( n )
 
 
// result に n 階乗を代入する。( 再帰版 )
void factorial_r( int const n, int & result )
{
    indicate_scope;
    
    result *= n;
    if( 1 < n ) {
        // まだ掛け算を続ける。
        factorial_r( n - 1, result );
    }
}
 
// result に n 階乗を代入する。( 継続版 )
void factorial_c( int const n, int & result )
{
    indicate_scope;
 
    result *= n;
    if( 1 < n ) {
        // まだ掛け算を続ける。
        continues( factorial_c, n - 1, boost::ref( result ) );
    }
}
 
int main()
{
    int result;
    
    cout << "再帰で階乗を求める。" << endl;
    result = 1; factorial_r( 3, result );
    cout << result << endl << endl;
 
    cout << "継続で階乗を求める。" << endl;
    result = 1; continuation_call( factorial_c, 3, boost::ref( result ) );
    cout << result << endl;
 
}

結果


再帰で階乗を求める。
begin 3
begin 2
begin 1
end 1
end 2
end 3
6

継続で階乗を求める。
begin 3
end 3
begin 2
end 2
begin 1
end 1
6

 サンプルに factorial を出してくるあたりセンスの無さがにじみ出t(ry
えぇと、factorial は別に再帰でも実現できるわけですが、再帰と違うのは関数の終わり方です。


 再帰だと、
  main から fuctorial_r( 3 ) 呼んで
    その中で fuctorial_r( 2 ) 呼んで
      その中で fuctorial_r( 1 ) を呼ぶ
 ことになります。

 継続だと main から fuctorial_c( 3 ) 呼んで、
 関数が終わってから fuctorial_c( 2 ) 呼んで、
 関数が終わってから fuctorial_c( 1 ) を呼ぶ
 ことになります。

C++ の文法には継続が無いので、何とかしてそれっぽく実現したいと思ったのですが、continuation.hpp はなんともブサイクな仕上がりでした。

continuation.hpp

#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
 
#include <iostream>

// 継続ライブラリ
namespace ns_continuation
{
    typedef boost::function< void () > function_t;
 
    struct holder
    {
        holder( function_t const & f ) : f_( new function_t( f ) ) {}
 
        boost::shared_ptr< function_t > f_;
    };
 
    // 継続呼び出しするマクロ。引数は関数とそのパラメータ。
    #define continues( ... )                                        \
        throw ns_continuation::holder( boost::bind( __VA_ARGS__ ) )
 
    // 継続呼び出しを開始するマクロ。引数は関数とそのパラメータ。
    #define continuation_call( ... )                                \
        try {                                                       \
            continues( __VA_ARGS__ );                               \
        } catch( ns_continuation::holder & c ) {                    \
            for( ;; ) {                                             \
                try { ( * c.f_ )(); break; }                        \
                catch( ns_continuation::holder & c2 ) { c = c2; }   \
            }                                                       \
        }
}

継続にはジャンプが必要ですが、C++ で許されているジャンプの種類には限りがあります。
そのうち、関数の中から外へジャンプできるのは return, throw, longjmp ぐらいしかありません。
この例では throw を使ってます。

しかし C++ である以上、呼び出し元に必ず戻ってくるという縛りは外せない(と思う)。
なので、継続の開始だけは明示してあげないといけないです。

う〜ん、もっとスマートにできんもんでしょうか。