C++コミュニティの中心で
     C++をDISる
    田中英行
自己紹介
•   田中 英行 (@tanakh, id:tanakh)
•   (株)Preferred Infrastructure 勤務
•   Haskell好き/C++嫌い
•   C++歴13年
    – 主にWindowsでゲームを作ったりしていました
    – 最近は仕事でコンソールアプリ書いてます
あらかじめおことわり
• Boost.勉強会ということですが
 – Boost出てきません
 – むしろBoost良くわからないので教えてください!
突然ですが
• 今日はC++をDISる機会をいただきました!
• (通称)C++闇の軍団なる皆様方に於かれまし
  ては釈迦に説法以外の何物でもありません
  が…
• 僭越ながら、C++をDISらせて頂きます
ご存知でしたか?

           C++が今年の
“Discriminating Hackersが選ぶ言語”
  に選ばれていたという事実を・・・
Discriminating Hackersの選ぶ言語
               とは?
• ICFP という関数プログラミングの学会が
• 毎年プログラミングコンテストを開催しています



• 優勝チームの使っていた言語が、
  “Discriminating Hackersの選ぶ言語”
  として讃えられ、無制限に自慢できる権利を与
  えられます
前回の優勝者&言語
よりにもよって、
          手続き型の、
         それもC++が、
関数プログラミングの学会主催の
          コンテストで、
 Discriminating 言語だなんて!
しかし、ルールはルール
• C++に逆らえない日々…
• 何故か仕事でもC++ばかり…
• 何とかせねば!
そして今年(去年)のICFP Contest




                    私の参加した
                    チーム
やりました!
• 優勝者になって、好きな言語を選べる!
 – しかし、ものすごくモメる
• とりあえず使った言語列挙する
 – C++、Haskell、Python
 – すごくオーソドックスな組み合わせですね!


• ということで、今年(9月頃まで?)はくれぐれ
  もこれらの言語の悪口は謹んで頂くように!
しかし。
• 私はC++が好きではない
 – (C++を使ったのは主に私なんですが…)
• 私が選んだのだから(Discriminating Hackerな
  のだから)その効力は私には及ぶまい(と勝
  手にルールを解釈)
• そこで・・・
今年唯一(?)大手を振って
 C++の悪口を言える私が
   思う存分C++をDISる
という
• ごく個人的な発表です。
概要
• 大きく3つのカテゴリに分けて、C++をDISる
 –型
 – テンプレート
 – 関数プログラミング


• お手柔らかにお願いします
Part 1: Type System

その1:型
C++の型システム
• Very Irritating!
• C++のストレスの約8割はこいつの所為
同じ型を何度も書かされる
• (int, int) -> int なる map を扱うとき

 map<int, map<int, int> > mm;
 for (map<int, map<int, int> >::iterator p = mm.begin();
      p != mm.end(); p++){
   map<int, int> &r = p->second;
   for (map<int, int>::iterator q = r.begin();
        q != r.end(); q++){
     ....
   }
 }
これが良くないところ
• 単純に面倒
 – Do not repeat yourself!
• 変更に弱い
 – 何箇所も変更しないといけない
例
• int -> double に変更

map<int, map<int, double> > mm;
for (map<int, map<int, double> >::iterator p = mm.begin();
     p != mm.end(); p++){
  map<int, double> &r = p->second;
  for (map<int, double>::iterator q = r.begin();
       q != r.end(); q++){
    ....
  }
}
typedef 使え

typedef map<int, double> ogram;
typedef map<int, ogram> tgram;

tgram mm;
for (tgram::iterator p = mm.begin();
     p != mm.end(); p++){
  ogram &r = p->second;
  for (ogram::iterator q = r.begin();
       q != r.end(); q++){
    ....
  }
}
だが断る
• 付けるべきではない名前を付けないといけない
 – しかも今回の場合2つ
• なぜ名前をつけるべきではないのか?
 – 1.名前に意味が無い
 – 2.意味のない名前を考えるのは大変
 – 3.(今回の場合)本質的には繰り返しではない
0x使え


map<int, map<int, double> > mm;
for (auto p = mm.begin();p != mm.end(); p++){
  auto r = p->second;
  for (auto q = r.begin();q != r.end(); q++){
    ....
  }
}
0x使わせて
 (´;ω;`)
読ませる気のないコンパイルエラー
• 行番号しか参考になりません!
• gcc 4.5でデフォルト型引数の省略などが入っ
  たが…
 – そもそもの型が大きければ意味がない
 – これはC++ライブラリの構造上の問題か
型推論が非力
• そもそも型推論とは何ぞや?
 – 型なしプログラムから型ありプログラムの再構築
   (Type Reconstruction)
• C++0xでは、すべての型を推論できるわけではな
  い
 –   ○ローカル変数(auto)
 –   ○ラムダ式の返り値
 –   ×関数の引数
 –   ×クラスメンバ
• Hindly-Milner型の型推論ができないものか
Part 2: template in C++

その2:テンプレート
テンプレート
•   みんな大好きテンプレート
•   だがしかし!
•   テンプレートおかしいよテンプレート
•   C++のストレスの約8割はこいつの所為
テンプレートの型
• templateの型パラメータは、forallではない
• たとえばこれは?
   template <class T>
   T id(T v){ return v; }


 – ∀a => a -> a だろうか?
 – 実は違う
テンプレートの型
• なぜなら、引数に対して任意の操作が可能だ
  から
  template <class T>
  void id(T v){ v.foo(); }


 – どういう型が付くのが妥当なのだろうか?

 – 実際にはC++では、テンプレート関数(クラス)
   自体には型はつかない(つけない)
そもそもC++テンプレートとは…?
• C++のテンプレートは何なの?
 – 見た目StructualなSub-Typingを実現している
 – Sub-Type relationの解決をインスタンス時に各々
   行う
 – コンパイル時に型レベルでコードを実行するよう
   なもの
その代償
• C++では、テンプレート関数を宣言時ではなく、
  使用時にインスタンス化し、それに対して型
  付けを行う
• これはどういう事を招くか?
 – テンプレート関数(クラス)を分割コンパイルでき
   ない
 – テンプレート関数自体に対しては型が付かない
 – (得られるコンパイルエラーが往々にして不可解)
分割コンパイルできない
• テンプレート関数を使用している箇所すべて
  に対して、その完全な定義が必要
 – 基本、ヘッダファイルに全部書くことになる
• これは何を招くのか?
 – コンパイル速度の低下
 – ファイル間の依存の増加
コンパイル速度
• 最悪の場合、コンパイル時間がファイル数の
  2乗のオーダに

                  ヘッダファイル




                  ソースファイル
ファイル間の依存性
• インターフェースが変わらなくても、実装が変
  わればコンパイルし直さなければならない
• pimpl のようなバッドノウハウ
 – 実行速度低下
 – コードがまどろっこしく
ヘッダに書くことによる問題
• namespace地獄
std::map<int, std::map<int, int> > mm;
for (std::map<int,std::map<int, int> >::iterator
     p = mm.begin();
     p != mm.end(); p++){
  std::map<int, int> &r = p->second;
  for (std::map<int, int>::iterator q = r.begin();
       q != r.end(); q++){
    ....
  }
}

 – using namespace させて…
テンプレートに型がつかない
• テンプレート関数は定義のみではコンパイル
  されない
 – つまり型がつかない
• 一方、型は非常に良いドキュメントになる
 – 型の重要なメリットの一つ
• C++のテンプレートからは、これがすべて失わ
  れている
例
• std::transform()
      template <class A, class B, class C>
      B transform(A, A, B, C);


  – これでは何のことやら
例(2)
• もちろん、人がある程度注釈することはできる
  template <class InputIterator,
            class OutputIterator,
            class UnaryOperator>
  OutputIterator transform(InputIterator first1,
                           InputIterator last1,
                           OutputIterator result,
                           UnaryOperator op);

• しかし、これでは不十分
 – Input/OutputIteratorが満たす要件は?
 – UnaryOperatorとはなんなのか?
 – これらはドキュメントに書くしかない
   • それは何の保証もない
パラメトリック多相の場合
• 何を渡さなければならないか一目瞭然
• どういう処理がされるかすら分かる場合も多い

     map :: (a -> b) -> [a] -> [b]
     map f xs = ...
今は亡きコンセプトさん
                  /二二ヽ
                  | 今 |
                  | 瀬 |
                  | 腑 |
                  | 戸 |
                  | 之 |
               __| 墓 |__
          / └──┘ \
         |´ ̄ ̄ ̄ ̄ ̄ ̄| ソ
         ソ::::::::::::::::::::::::::::::::ソソ
  / ソ ̄|;;;;;;;lll;;;;;;;| ̄ソ \
  |´ ̄ ̄ |. [廿] .|´ ̄ ̄.|
  |:::::::::::::::|              |::::::::::::::|
  |:::::::::::::::| ̄ ̄ ̄|::::::::::::::|
  . ̄ ̄ ̄|_______|´ ̄ ̄゛
コンセプトがあれば…
• テンプレート引数への要件の明示による
  ドキュメント性の改善
• インスタンス化→型チェックによらずに型
  チェックができるようになり、コンパイルエラー
  の改善

• コンセプトさえあれば…
Part 3: Functional Programming

その3:関数プログラミング
Haskell愛好家としては…
• C++/C++0xでの関数プログラミングには
  我慢ならない…!
オブジェクト指向+関数型
• すべてのものはオブジェクトであるべき
• (すべてのものは関数であるべき)

• 関数がオブジェクトであるということ
 – 関数に付く方がすごく歪になる
  • 関数型ならシンプルなのに…
ラムダ式
• Haskell
  – ¥x -> x+1
• OCaml
  – fun x -> x
• Scheme
  – (lambda (x) x)
• C++
  – [](int x){ return x+1; }
ラムダ式
• とにかくキモい。

     int main()
     {
       [](){}();
     }
ラムダ式
• とにかくキモすぎる。
 int main()
 {
   [](){[](){[](){[](){[](){[](){}();[](){}();}();
 [](){[](){}();[](){}();}();}();}();[](){[](){[]()
 {[](){}();[](){}();}();[](){[](){}();[](){[](){[]
 (){[](){}();[](){}();}();[](){[](){}();[](){}();}
 ();}();}();}();}();}();}();[](){[](){[](){[](){[]
 (){}();[](){[](){[](){[](){[](){[](){[](){}();[](
 ){}();}();[](){[](){cout<<"やぁ(´・ω・`)"<<endl;}();
 [](){}();}();}();}();[](){}();}();[](){[](){}();[
 ](){}();}();}();}();}();[](){[](){}();[](){}();}(
 );}();}();[](){[](){[](){[](){}();[](){}();}();[]
 (){[](){}();[](){}();}();}();}();}();}();
 }
ラムダ式
• どうしてこうなった
 – 予約語、後方互換…
• まあ、それはおいといて。
ラムダ式の型
• ラムダ式の型がイケてない
cout<<demangle(typeid([](int x){return x+1;}).name())<<endl;




             $ g++ -std=c++0x test.cpp
             $ ./a.out
             main::{lambda(int)#1}


                              ???
ラムダ式の型
• 引数と返り値の型がラムダ式の型に現れない
• そのままでは、引数や返り値の型を利用する
  のが困難
関数の型
• 関数の型を扱いたい時、functionクラスを用
  いると簡単
   – しかし、パフォーマンスの低下
      • インライン展開されないなど
   – 単なるテンプレートパラメータが好まれる
      • 結局ドキュメントとしての型が得られない

template <class A>               template <class F, class A>
A twice(function<A(A)> f, A v)   A twice(F f, A v)
{                                {
  return f(f(v));                  return f(f(v));
}                                }
多相関数
• C++0xのラムダ式は多相にならない
 – 例えば id x = x, id :: forall a . a -> a
 – のようなものが書けない

 – template <class A>[](A x){ return x; }
 – ↑ こういうモノが書けない
テンプレート関数の値
• C++ではテンプレート関数、それ自体は値とし
  て扱えない
 – 具体的な型でインスタンス化しないと値にならない
 – つまり、そのような式に付く型がない

   template <class T>
   T id(T x){ return x; }

   int main()
   {
     // auto f = id; // <- これにつく型は?
     auto g = id<int>; // <- これは int->int
   }
総称型がない
• C++の型には総称型がない
 – すべての型は具体的な型の組み合わせ
 – つまり型パラメータがない
 – 多相関数に付けられる型がないのもまあ頷ける
• ところが、Boost.Lambdaでは多相関数が扱え
  るらしい?
 – 一体どういう事なのか?
Boost.Lambdaでのラムダ式
• 何故かできてしまう
      int main()
      {
        using namespace boost::lambda;
        auto f = _1;         // ¥x -> x
        cout<<f(1)<<endl;    // -> 1
        cout<<f(3.14)<<endl; // -> 3.14
      }


• fの型は何なのか?
 – forall a . a -> a をどうやって表現している?
Boost.Lambdaの型
• 出力してみる
       cout<<demangle(typeid(f).name())<<endl;




       boost::lambda::lambda_functor<boost::la
       mbda::placeholder<1> >

• ??
Boost.Lambdaの型(2)
• もう少し複雑な例
           int main()
           {
             using namespace boost::lambda;
             auto f = cout << _1 << '¥n';
             cout<<demangle(typeid(f).name())<<endl;
           }



 boost::lambda::lambda_functor<boost::lambda::lambda_functor_base<boost::lambda::bitwise_action<boost::
 lambda::leftshift_action>,
 boost::tuples::tuple<boost::lambda::lambda_functor<boost::lambda::lambda_functor_base<boost::lambda::b
 itwise_action<boost::lambda::leftshift_action>, boost::tuples::tuple<std::ostream&,
 boost::lambda::lambda_functor<boost::lambda::placeholder<1> >, boost::tuples::null_type,
 boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type,
 boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type> > >, char const,
 boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type,
 boost::tuples::null_type, boost::tuples::null_type, boost::tuples::null_type,
 boost::tuples::null_type> > >
Boost.Lambdaの型(3)
• 型が式を丸ごと持ってた!
 – Expression Template
• 確かにこれで型は付くのだけどインターフェー
  スとしては…
• 型の型を要求する!
パターンマッチ
• オブジェクト指向言語に関数型が取り込まれ
  るときに無視されるものの筆頭その1
• なんでない?
 – なんでない言語が多い?
• なんでない?
 – 絶対必要だと思うんですけども
パターンマッチのような物…?
• なんか提案されているらしい
 – さすがにこれは…
variant<
    int
  , pair<int,double>
  , pair<int,int>
  , pair<double,int>
> var;

switch_(var)
  |= case_< pair<_1,_1> >(...) // matches pair<int,int>
  |= case_< pair<int,_> >(...) // matches pair<int,*>
  |= case_< pair<_,_> >(...) // matches pair<*,*>
  |= case_< int >(...)
;
カリー化
• オブジェクト指向言語に関数型が取り込まれ
  るときに無視されるものの筆頭その2
• もしかしてカリー化軽視されている?
カリー化の旨み
• 純粋関数的に状態を持ちまわるというのが
  とてもやりやすくなる
      cnt acc i = (acc, cnt (acc+i))

      let   a =   cnt 0 in
      let   (v,   b) = a 1 in –- v = 0
      let   (w,   c) = b 2 in –- w = 1
      let   (x,   d) = c 3 in –- x = 3
      ...

• オブジェクト vs 関数
• 関数プログラミングでは、次の状態として
  関数を返すことがよくある(e.g. Iteratee)
まとめ
まとめ
• まだまだ言いたいことはありますが…
 –   iteratorはクソ
 –   例外周りがいろいろクソ
 –   ADLはクソ
 –   moveはクソ
 –   右辺値参照はクソ
 –   GCないとかクソ
 –   スマポはクソ
 –   …
• C++もいいところはあると思います
 – 適当に書いてもそこそこ速いコードが書ける
 – 0xでそれなりに改善されてる
おわりに
• 私がC++から足を洗えるようになるのは
  いつになりますことやら
• せめて0xを・・・!

C++コミュニティーの中心でC++をDISる