背景
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や使うライブラリ実装に気を使っておけばこう言うことにはならないので、野良ビルドしよう。