古い標準ライブラリ実装を新しいコンパイラで使って困った話

背景

C++11ではconstexprメンバ関数は暗黙にconst指定される。

template<typename T>
class X
{
  public:
    constexpr T get_member() /* const */ {return member_;}
  private:
    T member_;
};

この仕様はある程度面倒を引き起こしていた(参考:http://boleros.hateblo.jp/entry/20130604/1370364968 )。

しかし、C++14ではめでたくこの仕様はなくなった。

事例

さて、絶妙に古いコンパイラが入っているマシンでC++14コードをコンパイルしようとしたのだ。バージョン番号は忘れてしまったが、GCCではC++14対応がなかったものの、clangは対応していたので、clangで普通にコンパイルすれば通るだろうと思った。実際、簡単なコードは通っていた。

しかし、別のコードをコンパイルしようとすると驚くべきことにstd::complex::real()が呼べないという問題が発生する。Clang曰く、「const std::complex& に対して const 指定されていないメンバを呼び出した」という。std::complex::real()はそもそもconstだ。非const版は引数を一つ取る。

これは何かがおかしいと思い標準の実装を見てみた。すると以下のようになっていた。

# if //C++11以上かどうか確認のマクロ
constexpr T real() {return _M_real;}
# else
...

const指定がない。この標準ライブラリ実装はC++14以前のもので、つまりconstexprなので暗黙にconst指定されていたのだ。なので陽にconst指定されてはいなかった。だがclangはC++14に対応しており、私も-std=c++14を渡していたのでこの関数は非constになっていた。よってconst性をviolateしてしまい、呼べなくなっていたのだ。

標準ライブラリ実装を分ければよかったわけだが、そこを横着したための問題ということになる。

結論

コンパイラ野良ビルドし、環境が用意している古いコンパイラとライブラリ実装は全て無視しよう。

追記(4/23)

📰2018-04-22のニュース - ゆなこん Yuna Computer System で取り上げて頂いたのだが、読み返してみるとこの記事がかなり言葉足らずだったことに気づいたので、少し追記しておこうと思う。

この時、何も考えずにコンパイラだけ変更してコンパイルしたので、古いGCC実装の標準ライブラリが新しめのclangによってコンパイルされると言う状況が完成しており、上記のような問題が起きたのだった。

GCCからしてみればC++14以前なので、constexprメンバは暗黙constでよかった。clangからしてみればC++14対応は済んでいたので、constexprメンバは暗黙constではなかった。誰も悪くなかった。悪かったのは何も気を使わずに横着してその2つを組み合わせた私だったと言うわけである。

と言うわけで、野良ビルドしてPATHや使うライブラリ実装に気を使っておけばこう言うことにはならないので、野良ビルドしよう。