constexpr を使うべき5の理由 - その3
相変わらず「constexpr を使うべき5の理由」です。
今回は、関数に対する constexpr 指定のもう一つの意味について取り上げます。
3.副作用がないことを保証する
「ねむらなくてもつかれないくすり」に副作用はあるのでしょうか。疲労をポンと感じなくさせてくれるような気はしますが。
ドラえもんがもし「副作用なんてないよ」と言ったとしてもちょっと信用なりません。
物事にはちゃんとした「保証」が必要です。
関数に対する constexpr 指定は、実装に多くの制限を課すことでもある。
constexpr 関数の中では、ローカル変数の定義や、代入などの操作もできない。
基本的に、同じ引数による呼び出しに対しては同じ値を返すような実装しかできない。
そうした制限は、逆に言えばその関数の参照透明性を保証することでもある。
参照透明でない関数は、以下のようにグローバルな状態を変更する可能性がある。
int something_value(); // 常に同じ値を返すだけの関数。しかしこの宣言では参照透明とは限らない /* ... */ int something_value() { _C_drive_format(); return 0; } // C:ドライブをフォーマットする
下手をしたらどこかに、上記のように C:ドライブをフォーマットする実装がなされているかもしれない。
もちろんそんなことはまずありえないが、constexpr 関数であれば、副作用がないことを明示的に保証することができる。
constexpr int something_value(); // 参照透明であることを保証する /* ... */ constexpr int something_value() { return 0; } // Cドライブをフォーマットされることはない
これは例えば、noexcept による無例外保証に似ています。
noexcept 指定は、例外安全において最も強い nothrow 保証を明示的に指定する。
もし指定に反して例外を伝播させようとすれば実行時にプログラムは terminate される。
それと同様に、constexpr 指定は関数に参照透明性を保証する。
もし指定に反して状態を書き換えるようなコードが実行されるならばコンパイル時にエラーになる。
(本当は、実行時にのみ呼び出し可能なコードにおいて副作用を持たせることは実はできるのだが、コンパイル時と同じコードについては確実に参照透明である)
さて、ところで標準ライブラリを見てみよう。
C++11 における標準ライブラリの constexpr の使用は相当保守的であり、constexpr があっておかしくないところに無かったりする。
ところが、一方で驚くほど多くのケースで constexpr が書かれている部分があったりする。デフォルトコンストラクタである。
およそ定数式と縁がないような std::mutex のようなクラスのデフォルトコンストラクタも constexpr だったりする。
デフォルトコンストラクタが constexpr であるとはどういうことか。
それはデフォルトコンストラクタがトリビアルであるか、または、少なくともグローバルな状態を変更しないことを保証している。
これは目的論的にみて、そのクラスがコンパイル時に用いられることを想定するかどうかとは、本質的に無関係である。
このように constexpr 指定は、単にコンパイル時に呼び出すことができるという以上の保証を明示的に表すことができる。
だからあなたは、デフォルトコンストラクタや参照透明であることが自明な関数に対して可能な限り constexpr 指定すべきです。