結構昔に書いていたっぽい下書きがあったので手直しして出しておこう。まあイマドキgcc-4.8.5を使う羽目になっている人はあまりいないだろうが、少なくともIssueが立つ程度には存在するようなので。
広く知られていることっぽいが、GCC 4.8系統の標準ライブラリ実装にはバグがある(他にもいろんなバグがあるが)。古いパッケージを使い続けることを特徴としている一部のLinuxフレーバーでは未だにGCCが 4.8.5だったりするので注意が必要かもしれない。数ヶ月前にIntelコンパイラでビルドするとtoml11が落ちるというIssueが立ったが、手元のicpcでは落ちなかったので、これは環境に入っている標準ライブラリ側の問題だと直感し、GCC 4.8.5で試した所再現したという経緯で発見した。
この問題を紹介しておこうと思う。
背景
バグの内容だけ知りたい人は飛ばしてください。
前提として、toml11ではTOML上での値に対するcomment
を持ったり持たなかったりするために、std::vector<std::string>
をラップした構造体、discard_comments
とpreserve_comments
をテンプレートパラメータで受け取る。双方std::vector<std::string>
と同様のインターフェースを持っているが、discard_comments
は何をしても常に空で、preserve_comments
は普通のvector
のように振る舞う。
すると、comments()
でコメントにアクセスできるというインターフェースは変わらず、コメントを持たないことを選択した場合はコメント追加が無視され、コメント一覧は常に空ということになる。
なんでそんなややこしいことになってしまったかというと、まず第一にはテンプレートを使えばコメントがいらない人は無意味なvector
を持たなくて済むというのがあった。これはまあ仕方ない。続いて、初期にはリードオンリーのコメントアクセスを常に許していたということがあり(これは話が長くなるので別の機会になんでそうなったか書きたいと思う)、そのインターフェースを変更するのはどうかという気持ちがあった。変更しても良かったんじゃないかとは今は少し思っている。あとパーサ側の実装の単純さが大きい。パラメータによらずとにかくコメントをpush_back
しておくと、discard_comments
は勝手にコメントを無視してくれるので、コメントを保存するかどうかのif
ブロックが必要ない。
バグの内容
というわけで、preserve_comments
は中で持っているstd::vector
のインターフェースをそのまま外にリダイレクトしている。当然、insert
やerase
がある。C++11からstd::vector::insert
はconst_iterator
を受け取るようになった。
iterator insert(const_iterator p, const std::string& x) { return comments.insert(p, x); }
だが、gcc 4.8.x系のstd::vector
実装はiterator
を受け取るままになっていた。constな参照やポインタは非constな参照やポインタに変換できない。書き換えられないはずの参照を書き換え可能な参照に簡単に変換できてしまったら意味がないからだ。なので、上の関数はgcc 4.8.xではコンパイルに失敗する。const_iterator
をiterator
に変換しようとしているので。
std::vector
のインターフェースは標準規格で定まっているので、この場合誰が間違えたかははっきりしている。gcc 4.8.xの標準ライブラリ実装が型を変え忘れたのだ。同じことがerase
やあろうことかC++11で追加されたはずのemplace
でも起きている。ということは型の部分はコピペしたんだろうか。GCCみたいな誰もが知ってる巨大プロジェクトでもこういうことが起きるのはちょっとほっこりしますね。いやまあホッコリしてる場合ではないが。
gcc 4.8.xのためだけにこの関数がiterator
を取るように変えるわけにはいかない。そうするとユーザーがconst_iterator
を渡せなくなってしまう。他のコンパイラを使っていてstd::vector
のインターフェースを知っている人を困惑させることになる。それに、そもそもコンパイラがミスってるのにこっちが追従する必要はない。
ワークアラウンド
なんか華麗なワークアラウンドがあるとかではないです。ゴリ押し。
以下はtoml11の該当箇所をコピペしたもの。
// It is known that `std::vector::insert` and `std::vector::erase` in // the standard library implementation included in GCC 4.8.5 takes // `std::vector::iterator` instead of `std::vector::const_iterator`. // Because of the const-correctness, we cannot convert a `const_iterator` to // an `iterator`. It causes compilation error in GCC 4.8.5. #if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) # if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 # define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION # endif #endif
これでGCC 4.8.x以前の場合だけこのマクロが定義される。絶対にミスで使わないようにこの文脈で考え得る限りもっとも長い名前をつけた。
あとはこれを#ifdef
で使ってウオー!ってやって終わり。
結論
頼むからシステムのコンパイラをアップデートしてくれ。
そもそもなんで新しめのコンパイラを使わないという選択肢がありえるのかよくわかってない。OSとかだと無限にテストケースがあって全部やり直すのはだるいとかなんだろうか。