はじめに
id:kazuhooku さんが
Kazuho@Cybozu Labs: なんとなくリフレクション in C++
という記事を書いていて、なんか凄そう!
わくわく!
でも、読めません>< Template 怖い><
という訳で
C++ Template の勉強をしてみよう!
そいえば、ちょっと前に 1000speakers で上野氏に C++ Template はチューリング完全、関数型言語というようなことを聞いたきがする!
じゃあ、いろんなことが出来るはず!
という訳で
JavaScript で出来ることが C++ で出来るかを試してみよう!
まず
関数テンプレートと構造体テンプレートどっちで JavaScript の関数を表現するべきかを考えた。
C++ の文法を考える
// これはできない>< void foo() { void bar() { void baz() { } } }
// これはできる! struct foo { struct bar { struct baz { } } }
C++ の関数はネスト出来ない、でも構造体はネスト出来る!
という訳で、構造体テンプレートで JavaScript の関数に出来ることをやれるかどうかをやってみる。
構造体 = Activation オブジェクト
構造体のメンバのスコープを考える
struct hoge { int fuga; // この fuga はここから : : // この中括弧の終わりまで有効 };
ネストして構造体のメンバのスコープを考える
struct puga { struct hoge { typedef int fuga; // この fuga はここから : : // この中括弧の終わりまで有効 }; struct piyo { typedef int fuga; // この fuga はここから : : // この中括弧の終わりまで有効 }; };
というわけで、 struct 内で宣言されたメンバ変数名やメンバ型(?)名やメンバ構造体(?)名は struct 内のレキシカルスコープで有効である。
JavaScript の Activation オブジェクトは関数が評価されるごとに一つ作られ、そのプロパティ名は関数内のレキシカルスコープの中で変数として使える。
構造体も構造体テンプレートが実体化(という言葉でいいのかな)されるごとに一つ作られ、そのメンバ名は構造体内のレキシカルスコープの中で使える。
つまり、以下のように対応している
JavaScript | C++ Template |
---|---|
値 | 定数、型 |
関数 | 構造体テンプレート |
Activationオブジェクト | 構造体 |
変数名(Activation オブジェクトのプロパティ名) | メンバ名 |
変数への代入 | メンバ型名に型を typedef。メンバ変数に定数を代入 |
return で値を返す | :: で、呼び出した側から値を取る |
返り値に関しては、少しトリッキーですね。
JavaScript では、呼び出された側が返す値を決めるのですが、 C++ では呼び出した(実体化した側、 Hoge
// JS function hoge(x) { return x; } var a = hoge(x);
// C++ template <typename x> struct hoge { typedef x v; // 返す値を適当な変数(メンバ名)に代入(typedef)しておく }; typedef hoge<x>::v a; // hoge を呼び出して返す値をこちらから指定する
テンプレートは C++ Template における、第一級のオブジェクトではない
第一級のオブジェクトとは、変数として名前がつけられて、手続きに引数として渡せて、結果として返せる値のこと。
以下のエントリが詳しい。
http://d.hatena.ne.jp/heppokoprogram/20060201#1138803596
テンプレート(JavaScript でいう関数)が第一級のオブジェクトじゃないので、 JavaScript での以下のようなことができない。
// JS function hoge(f, x) { // 引数に関数を受け取る return f(x); // 受け取った関数を呼び出す }
// C++ // テンプレート hoge template <typename f, int x> struct hoge { // 引数にテンプレートを受け取れない // static const int v = f<x>::v; // f はテンプレートはありえないのでエラーになっちゃう>< };
テンプレートを構造体で囲めばいい
型は C++ Template における第一級のオブジェクトなので引数に渡せる。なので、メンバにテンプレートを一個だけもった構造体を作ればいい。
イメージとしては、 Java でたまに見かけるやつ、メソッドを一個だけ持ったクラスを作ればいい。
// テンプレート hoge template <typename f, int x> struct hoge { // 引数にテンプレートを受け取れない static const int v = f::template _<x>::v; // 呼び出せる!! }; // fuga は型(第一級のオブジェクト) struct fuga { template <int a> struct _ { static const int v = a; }; }; static const int a = hoge<fuga, 10>::v; #include <iostream> using namespace std; int main() { cout << a << endl; } // 10
JavaScript と C++ Template の形式的な書き換え
たとえば、 C++ Template では以下のような違いがある
- テンプレートが内一級のオブジェクトじゃない(さっき述べた)
- 引数に型がある
- return がないけど、呼び出し側からスコープにアクセスできる。
なので
- JavaScript の関数は、構造体で囲んだテンプレートに変換
- 数値型もすべて構造体で囲んで、引数の型をすべて typename にする
- return hoge; は typdef hoge v; に変換する
- 関数呼び出しは、テンプレートの実体化と ::v に変換する
こういうルールで書き換えれば、式が一つしかなくて整数しか扱わないような JavaScript の関数なら C++ Template のテンプレートに書き換えることができるんじゃないか。
例
例として JavaScript で書いたチャーチ数を C++ Template に直してみます。
チャーチ数については、以下が詳しいです。
ラムダ計算 - Wikipedia
zero (0)
function zero(f) { return function(x) { return x; } }
C++ Template
struct zero { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef x v; }; }; }; };
succ (1 足す)
function succ(n) { return function(f) { return function(x) { return f(n(f)(x)); } } }
C++ Template
struct succ { template <typename n> struct _ { struct v { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef typename f::template _< typename n::template _< f >::v::template _< x >::v >::v v; }; }; }; }; }; };
plus (足し算)
function plus(m) { return function(n) { return function(f) { return function(x) { return m(f)(n(f)(x)); } } } }
C++ Template
struct plus_ { template <typename m> struct _ { struct v { template <typename n> struct _ { struct v { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef typename m::template _<f>::v::template _< typename n::template _< f >::v::template _< x >::v >::v v; }; }; }; }; }; }; }; };
mult (かけ算)
function mult(m) { return function(n) { return function(f) { return m(n(f)) } } }
C++ Template
struct mult { template <typename m> struct _ { struct v { template <typename n> struct _ { struct v { template <typename f> struct _ { typedef typename m::template _< typename n::template _< f >::v >::v v; }; }; }; }; }; };
ソース全体
#include <iostream> using namespace std; // チャーチ数の 0 struct zero { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef x v; }; }; }; }; // チャーチ数に 1 を足す struct succ { template <typename n> struct _ { struct v { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef typename f::template _< typename n::template _< f >::v::template _< x >::v >::v v; }; }; }; }; }; }; // チャーチ数にどうしの足し算 struct plus_ { template <typename m> struct _ { struct v { template <typename n> struct _ { struct v { template <typename f> struct _ { struct v { template <typename x> struct _ { typedef typename m::template _<f>::v::template _< typename n::template _< f >::v::template _< x >::v >::v v; }; }; }; }; }; }; }; }; // チャーチ数にどうしのかけ算 struct mult { template <typename m> struct _ { struct v { template <typename n> struct _ { struct v { template <typename f> struct _ { typedef typename m::template _< typename n::template _< f >::v >::v v; }; }; }; }; }; }; // 1 から 10 までのチャーチ数をてきとーに作る typedef succ::_<zero>::v one; // 0+1 typedef succ::_<one>::v two; // 1+1 typedef plus_::_<one>::v::_<two>::v three; // 1+2 typedef mult::_<two>::v::_<two>::v four; // 2*2 typedef plus_::_<two>::v::_<three>::v five; // 2+3 typedef mult::_<two>::v::_<three>::v six; // 2*3 typedef plus_::_<four>::v::_<three>::v seven; // 4+3 typedef mult::_<two>::v::_<four>::v eight; // 2*4 typedef mult::_<three>::v::_<three>::v nine; // 3*3 typedef succ::_<nine>::v ten; // 9+1 // チャーチ数を非 C++ Template の C++ の世界の定数する struct add_real_one { template <typename n> struct _ { struct v { static const int int_val = n::int_val + 1; }; }; }; struct real_zero { static const int int_val = 0; }; #define PUTS(n) cout << n::_<add_real_one>::v::_<real_zero>::v::int_val << endl // メイン int main() { PUTS(zero); PUTS(one); PUTS(two); PUTS(three); PUTS(four); PUTS(five); PUTS(six); PUTS(seven); PUTS(eight); PUTS(nine); PUTS(ten); return 0; }
最後に
これを学ぶ過程で、光成さんや奥一穂さんが沢山アドバイスをくれました!
ありがとうございます!
本当は、もっといっぱい例を書きたかったのですが、力つきました><