IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

JavaScript 的な考え方で C++ Template に入門してみた

はじめに

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

JavaScriptC++ Template の形式的な書き換え

たとえば、 C++ Template では以下のような違いがある

  • テンプレートが内一級のオブジェクトじゃない(さっき述べた)
  • 引数に型がある
  • return がないけど、呼び出し側からスコープにアクセスできる。

なので

  • JavaScript の関数は、構造体で囲んだテンプレートに変換
  • 数値型もすべて構造体で囲んで、引数の型をすべて typename にする
  • return hoge; は typdef hoge v; に変換する
  • 関数呼び出しは、テンプレートの実体化と ::v に変換する

こういうルールで書き換えれば、式が一つしかなくて整数しか扱わないような JavaScript の関数なら C++ Template のテンプレートに書き換えることができるんじゃないか。

例として JavaScript で書いたチャーチ数を C++ Template に直してみます。
チャーチ数については、以下が詳しいです。
ラムダ計算 - Wikipedia

zero (0)

JavaScript

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 足す)

JavaScript

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 (足し算)

JavaScript

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 (かけ算)

JavaScript

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;
}

実行してみる

$ g++ hoge.cpp && ./a.out
0
1
2
3
4
5
6
7
8
9
10
$ 

おおおおおおお!動いてます!
C++ Template すごい!

最後に

これを学ぶ過程で、光成さんや奥一穂さんが沢山アドバイスをくれました!
ありがとうございます!
本当は、もっといっぱい例を書きたかったのですが、力つきました><