C++の素晴らしさ

SOFSはC++で書いてた。C++といえば、僕の趣味は、ときどき、C++の素晴らしさについて考えることなんだけど、そういう話。


まず、C++に触ったことないのに、どっかの誰かの受け売りで、「C++はキモくてヤバい」というような考えを抱いてる人がいたとすれば、それは、まあ、つまらない人生を送ってるよね、というような話なのである。
確かに、C++の素晴らしさは、大体にして時間の無駄なので、わざわざ時間を割いて勉強するものではないと思うのだけど、C++は、憎むべき点が山のようにあるのと同じように、愛すべき点も他の言語の愛すべき点を++したぐらいはあるのだ!

どっかのだれかの受け売りでC++に全く触らないのというのは、非常に正しいのだけど、間違っているのである!STL極めて、Boost極めて、その後で、「やっぱり、まともな構文とまともなコンパイル時間と、まともなエラー表示が欲しいわ」と、そこまで至ってから、C++を卒業すべきであると思うのだ。
それはちょうど、「全てがリストなのは素晴らしくて、全てが参照透明なのも素晴らしくて、それによって、あらゆるものが、標準ライブラリのリスト操作を結合していくだけで実現できて大変素晴らしい…けど、やっぱり、副作用は欲しいよね」と、そこまで至ってからHaskellを卒業すべきみたいな感じ。(Haskellのそこらへんについては、ずっと書こうと思って書いてないのだけど、そのうち書く←未来への負債)


というわけで、C++の愛すべき点。を、ちょろちょろ。


Haskellは、全部リストなので、色々強力というような話があったと思うのだけど( )、それに対して、C++は、「全部がイテレータになっているので色々強力」というのがあると思う。
これは、実は、「全部リスト」よりも、強力なのである。リストは、中身は何かは問わないけど、コンテナの構造は決められてしまう(方向とか)、けど、C++の「全部イテレータ」というのは、中身も問わないし、コンテナの構造も問わないのである。


C++アルゴリズムは、コンテナの構造を決めない、ということだ。↓どんなコンテナにもアルゴリズムが適用できる。

  template<typename _InputIterator, typename _OutputIterator,
	   typename _UnaryOperation>
    _OutputIterator
    transform(_InputIterator __first, _InputIterator __last,
	      _OutputIterator __result, _UnaryOperation __unary_op)

例えば、リストの中身を変換して、配列の後ろに入れていくとか超普通。

#include <list>
#include <vector>
#include <algorithm>
#include <iostream>
#include <boost/lambda/lambda.hpp>
int main()
{
	using namespace boost::lambda;

	std::list<int> list;
	std::vector<int> v;

	list.push_back( 3);list.push_back(4);list.push_back(5);
	v.push_back( 100 ); v.push_back( 200 );

	std::transform( list.begin(), list.end(),
			std::back_insert_iterator<std::vector<int> >( v ),
			_1 * _1 );

	std::for_each( v.begin(), v.end(), std::cout << _1 << '\n' );
}
$ ./a.out 
100
200
9
16
25

それに対して、どっかのなんとか言語は↓

map :: (a -> b) -> [a] -> [b]

[a] -> [b]て!あなた、どうして、コンテナの中身をマップしたいだけなのに、コンテナの型まで決められないといけないわけ!?mapの型が[a]->[b]で許されるのは、C言語まで!

/* 型安全とか無いけど…。あとテストしてない */
#include <stdlib.h>
struct list {
	struct list *chain;
	void *val;
};

struct list *
map( struct list *l,
     void *(*f)(void*) )
{
	struct list *ret,*cur;
	if ( l == NULL )
		return NULL;

	ret = cur = malloc( sizeof(struct list) );
	cur->val = f( l->val );
	l = l->chain;

	while ( l ) {
		cur->chain = malloc( sizeof(struct list) );
		cur = cur->chain;
		cur->val = f( l->val );
		l = l->chain;
	}

	cur->chain = NULL;
	return ret;
}

というのは全部嘘で、なんとか言語でもFunctorとか使えばいけそうな気がしたのだった。まあ、アレだよ。OCamlとか、配列とリストの違いがキモいとか、そういうの。それよりはC++イテレータのほうがきれいだよね、とかいうような。


というのも全部嘘で、

	std::transform( list.begin(), list.end(),
			std::back_insert_iterator<std::vector<int> >( v ),
			_1 * _2 ); // _1 * _1 を _1 * _2にしてしまった

やっぱり、これだけで山のようにコンパイルエラーが出る言語に比べたら、配列とリストの違いがキモいとか、無視できる問題だと思った。
あと、リスト or イテレータ っていうのは、ただの文化の違いで、OCamlだって、イテレータ実装できると思うし、そこらへんは言語の問題ではないと思うけど、文化の問題は、言語の問題に入るだろうか謎。


結局、C++のヤバいところは、

  • コンパイルエラーやばい
  • コンパイル遅い
  • 文法がヤバい。コンストラクタ呼んだつもりが関数のプロトタイプ宣言になるとかヤバい
  • template使ったらどのぐらいコードサイズ膨れるかわかんない

という話になっていくと思う。ここらへんは、慣れとか根性とかで乗り越えられないんだよなー。


おっと。C++の素晴らしさと言いつつ、結論がC++のヤバいところになってしまってるぜ!


とにかく、オブジェクト指向とかvirtualとか、RTTIとか覚えたC++の人達向けに、C++再入門とかいうような話題があれば面白いと思ったのだけど、それって昔、Cマガかなんかで一回特集やってたよなー。あんまり覚えてないっていうことは、面白くなかったのかも。(それ以前に立ち読みだったのがよくなかったと思う)

なんとか言語とか、Schemeとか、OCamlとかは、仕事で使える可能性は0に限りなく近いので、どうでもいいんだけど、ステキC++は仕事でも使える可能性がそれなりにあるだけに、ドキドキ感は他の言語の比ではなく、まあ、実際仕事でboost::lambdaとか駆使してたら、保守する人大変そうだから使えないだろうけど、ほら、'virtual void OnPaint'とかアリガチなC++コードを書いてるときも、「俺は皆の知らないあの娘の一面を知ってるぜ」的な優越感にひたれて、いいんじゃないでしょうか。でもそれって実際には何の役にも立ってないよね!