clangでソフトウェアをビルドしC++を知る

 clangというのはllvm向けのC/C++/Obj-Cのためのフロントエンドで、最近はGoogle ChromeとかFirefoxコンパイルできるレベルにまで成熟してきているらしい。
 いくつかのブログで紹介されているのを見ても、ふーん、ぐらいにしか思っていなかったのだが、あんな大規模なソフトウェアがコンパイルできるというのは、考えてみるとすごいことである。大事なことなので強調しておくが、すごいことである。十分に実用的なレベルに到達していることだ。ビルドも早いし生成されたコードもg++と同程度には速いというし、試してみる必要がある。
 という訳で、いくつか実際にソフトウェアをビルドしてみた。試してみた限りでは、

  • libstdc++のtr1/unordered_mapがビルドできない
  • C++のコーナーケースで、clangが許容しないものが多い

 といった問題があったが、割とどれもすんなりとコンパイルが通った。ビルドが通らなかったり、通ってもちゃんと動かなかったりする場合もあるが、私が遭遇した範囲ではどれもコード側の問題で、clangの問題ではなかった。
 ChromeFirefoxがビルドできるのだから当たり前とも言えるが、自分が書いたプログラムがいつもと違うコンパイラでビルドできると、驚いてしまう。clangはAppleがスポンサーについて開発が進んでいるプロジェクトなわけで、然るべき金額をどこかの会社が払って然るべき人間が開発を進めれば実用的なC++コンパイラは作れるのだ、という話でしかないのかもしれないが、それでも驚きである。
 clangはVisual C++やg++と比べてC++の規格への準拠度が高く、大きなソフトウェアになればなるほど、規格上は許されないコードが微妙に混入してしまっており、コンパイルできない場合が多い。以下では、clangを使ったときにハマるであろう典型的な例(というか、私が遭遇した例)をいくつか紹介したい。

clangの使い方

 clangはg++とコマンドラインオプションの互換性があるので、gcc,g++を呼び出しているところをclang, clang++を呼び出すように変えれば良い。
 だいたいのビルドシステムの場合、export CC=clang ; export CXX=clang++とした上でconfigureからやり直すとビルドできるようになるはずである。

constなオブジェクトのデフォルトコンストラクタはユーザーが定義する必要がある

 これはエラーメッセージを見ただけで修正ができる問題であるが、C++の規格上このような制限があるということ自体、知らない人も多いのではないか。私はもちろん知らなかった。

default initialization of an object of const type 'const foo' requires a user-provided default constructor

 規格の文言では、"if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor." と書いてあるみたいだ。

親クラスがテンプレートクラスである場合、そのメソッドを呼ぶときに修飾が必要である

 これはclangのエラーメッセージが親切であるということをありがたく感じると同時に、C++の規格を理解することは(素人には)難しい、と思わされるエラーである。エラー自体は、以下のように非常にシンプルなのだが、その下の方に"note:"としてわざわざ注意書きをつけてくれる。

error: use of undeclared identifier 'foo'

 これに対して以下のようなnoteがつく。

note: must qualify identifier to find this declaration in dependent base class

 エラーメッセージだけでは途方にくれるが、このnoteのおかげでなにをやらないといけないかが分かりやすい。要するに、foo()と書いてあるところをthis->foo()にすればいい。もしくは呼びたいメソッドが定義されている親クラスの名前を明示してもよいが、親クラスが変わった際(ということはさすがに考えにくいが…)に変更しなくて済むので、thisを使った方が良いように思う。
 これは、C++でシンボルからその定義を探す際に、two phase name lookupという名前の解決方法が規格として定められているのが原因であるそうだ。これは以下のような仕組みである。

  • C++ではテンプレート中のシンボルの名前解決を行う2つのフェーズがある。テンプレートの宣言時にはnondependent nameを解決し、テンプレートがインスタンス化された時点でdependent nameを解決する。
  • dependent nameというのは、テンプレートパラメータそのものや、テンプレートパラメータを含む名前などである。Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookupのところにそのような記述がある。
  • テンプレートの宣言時に参照できるのは、nondependent nameだけである。foo()をそのまま呼びだそうとしても、親クラスのfooはdependent nameであるために参照できず、エラーになる。
  • foo()の親クラスの名前を指定して呼びだすと、当たり前ではあるがParent::foo()みたいな形になってdependent nameになるので、名前解決のタイミングがインスタンス化の時点になるので成功する。
  • thisを付けた場合は、this自体がdependent nameとして扱われる(thisはクラスの型情報を含んでいるのでdependentである)ので、上記と同様にインスタンス化の時点で名前解決が行われることになるのでよろしい。

 私が見つけられたわかりやすい情報源は、結局 Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookup だけであった。他のページではそもそもdependent nameが具体的にどのようなものなのかを書いているところがなかった。もっとも、分かりやすく説明しているページはきっと他にもあるだろうし、そもそも規格にはdependent nameの定義は絶対に書いてあるはずである。

stdをつけないといけないところがg++より多い

 関わった中で出てきたエラーとして、

 error: use of undeclared identifier 'make_pair'

 というのがあった。g++だとこのままで通るのだが、clangだとstd::make_pairとしなければならない。undeclared identifierと言われたら、stdをつけないといけないか、もしくは一つ前の問題を疑うべきである。

デフォルト引数は、例え同じ値であってとしても一度しか定義してはならない

 プロトタイプ宣言と関数の宣言の二回でデフォルト引数を定義していると以下のようなコンパイルエラーになる。これもエラーメッセージからすぐにわかる。以下のようなエラーになった。

 error: redefinition of default argument

その他

 g++だと顕在化しなかったバグがclangによって実際に問題になったケースがあった。参照してはいけないオブジェクトを参照しちゃっていて、clangだと落ちるとか。ここら辺はたぶんケースバイケースで、最適化レベルによっても変わったりするだろうし、g++でバグが顕在化する場合もあるだろうから、複数のコンパイラで試すことが望ましいのだと思う。ちょっとの手間で試せるなら、複数のコンパイラでビルドしてユニットテストが通るかぐらいまでは確認しておくと、ソフトウェアの信頼性を上げられるのではないかと感じた。
 その他、現在のところ、wstringをリテラルで書くのが面倒であるという問題があった。gccだとUTF-8でL"あいうえお"とか書けるのだが、clangでは今のところそのような書き方はできない。

まとめ

 clangは実際にいろいろなソフトウェアをビルドできるし、ビルドできない場合にもclangではなくソフトウェア側の問題がほとんどであった。完成度はかなり高い。
 ビルド時間への影響の面では、自分が試した範囲だと、clangとg++では、clangの方がコンパイル速度は早いのだが、1〜2割ぐらいの差しかないようだ。どうも、デバッグビルドだとあんまり差がつかないみたい。もちろん、2割早くなれば嬉しいのは嬉しいのだが、乗り換えるための決定打になるほど速くはなかった。それよりは、エラーメッセージがg++より親切であるとか、規格への準拠度が高いので書いているコードのポータビリティを上げられる、といった点の方が嬉しさは大きい。

参考文献