スタックマシンで学ぶ template expression

例は考えてあったんだった。template expressionを使えるようになりましょうという話。
template expressionが使えるようになれば、今日からあなたも大量のエラーメッセージとお友達になれること間違いなし!
(ネタ以外に、boost::lambdaのエラーメッセージが読めるようになるとかの効果があるかもしれん)

もうちょっとまともなのを読みたい人は http://homepage1.nifty.com/herumi/prog/prog81.html こちらのほうがよいかと思いますよ。

emit_bytecode( ( x + y + 3 ) )

とかやると、

LOAD_VAR 0xnnnnn
LOAD_VAR 0xnnnnn
ADD
LOAD_CONST 3
ADD

とかって出力されるプログラムを作ってみ…と見せかけて、実際には無理なのでちょっと妥協して、

emit_bytecode( var(x) + var(y) + 3 ) ;

こんな感じで。


まず、構文木を表現するデータ型を定義。

template < typename Lhs , typename Rhs >
struct binop {
	Lhs const lhs;
	Rhs const rhs;

	binop ( Lhs const & lhs,
		Rhs const & rhs )
		:lhs(lhs), rhs(rhs)
		{}
};


template < typename Lhs, typename Rhs >
struct add
	:public binop<Lhs,Rhs>
{
	add ( Lhs const & lhs,
	      Rhs const & rhs )
		: binop<Lhs,Rhs>(lhs,rhs)
		{}

};

template < typename Lhs, typename Rhs >
struct sub
	:public binop<Lhs,Rhs>
{
....(続く…mul、div、or…)

基本基本。ここらへんは多分どう書いても一緒になるのでいいや。上のように、LhsとRhsを持つテンプレートを必要なだけ定義。あんまり考えることは無い。


んで、演算子を定義。

template < typename Lhs, typename Rhs >
detail::add< Lhs, Rhs >
operator +( Lhs const &lhs, Rhs const &rhs )
{
	return detail::add<Lhs,Rhs> ( lhs, rhs );
}
template < typename Lhs, typename Rhs >
detail::sub< Lhs, Rhs >
operator -( Lhs const &lhs, Rhs const &rhs )
{
....(続く)

これもあんまり考えることは無い。


んで、構文木の葉を定義。今は変数があればよいか。

namespace detail {
template < typename VarType >
struct var_ {
	VarType &var_ref;
	var_ ( VarType &ref )
		:var_ref(ref) {}
};
}
template < typename T >
detail::var_<T>
var( T &ref ) { return detail::var_<T>(ref); }

(var_はboost::lambdaではidentityとかいう名前だった)
別に var_ とか書いてもいいんだけど…template関数は何故か引数の型を推論してくれるというC++の機能を使う。


ここまでまとめ。構文木作るところ。

  1. データ型作る
  2. operator 書く
  3. var 書く

あんまり考えることは無い。


次に、構文木の評価。
名前は…evalとかでいいんだけど。今回のテーマっぽく、名前はemit_bytecodeにしよう。(文字列出力するんだけど)


これもあんまり考えることは無い(そればっかだな…)


まずは、intの出力。

template < typename OutputStream >
void
emit_bytecode( OutputStream &out, int val )
{
	out << "LOAD_CONST " << val << "\n";
}

で、次は加算でもするか…

template < typename OutputStream, typename Lhs, typename Rhs >
void
emit_bytecode( OutputStream &out, detail::add<Lhs,Rhs> &expr ) /* にばんめの引数がaddね */
{
	emit_bytecode( out, expr.lhs );
	emit_bytecode( out, expr.rhs );

	out << "ADD\n";
}

ほいできた。あんまり考えることはない…

int
main()
{
	emit_bytecode( std::cout, 3 + 4 );
}

ように見せかけて、これが動かないのだった。(LOAD_CONST 7が出力される)


これは、非常に汚い解決法があって、

struct intval
{
	const int val;
	intval( int val )
		:val(val){}
};

こんなのをつくって、

template < typename OutputStream >
void
emit_bytecode( OutputStream &out, intval val )
{
	out << "LOAD_CONST\t" << val.val << "\n";
}

こうして。

int
main()
{
	emit_bytecode( std::cout, intval(3) + 4 );
}

こう!

$ ./stackmachine 
LOAD_CONST 3
LOAD_CONST 4
ADD

ほいできた!
おっと!このぐらいで「C++ってたいへんアレですね」とか思った人はもうだめです。


さて!じゃきじゃきいきましょう。変数出力

template < typename OutputStream, typename T >
void
emit_bytecode( OutputStream &out, detail::var_<T> v )
{
	out << "LOAD_VAR\t" << &v.var_ref << "\n";
}

ほいほい!あっというま!あんまり考えることなんかない!

int
main()
{
	int x, y;
	emit_bytecode( std::cout, var(x) + 4 + var(y));
}
$ ./stackmachine 
LOAD_VAR	0xbf8700cc
LOAD_CONST	4
ADD
LOAD_VAR	0xbf8700c8
ADD

あとは、mulとかsubとかつくって終了!


以下、大体のソース。途中でやめてるけど。


なお、バイトコードを最適化してx86マシン語にしてPEバイナリ生成するのは読者の宿題とする。

続きを読む