TL;DR
clang++
を使用して constexpr
を多用するコードをコンパイルするときに、式評価回数の限界に引っかかった場合は、clang++
オプションに-fconstexpr-depth=-1 -fconstexpr-steps=-1
を追加すれば良い。それでもエラーが出る場合はClangにパッチを当てれば良い。
発端
C++17の constexpr
を使用してニューラルネットワークの誤差逆伝播を行うプログラムを作成した際に、clang++
のデフォルトの設定ではコンパイル時に十分な回数 式評価が行われず、以下のようなエラーが出た。
constexpr evaluation exceeded maximum depth
constexpr evaluation hit maximum step limit
constexpr evaluation hit maximum call limit
これらのような制限を取り除くための簡単な調査をした。
過程
まずcpprefjpの constexpr
の項によると、コンパイル時の再帰回数は -fconstexpr-depth=
オプションによって指定でき、デフォルト値は512回である。これを正の方向に増大させると、その最大値が 2147483647
であることが分かる。これは内部的にこの値が unsigned int
で保持されていることによる。 1
また、コンパイル時に評価されるステップ数を -fconstexpr-steps=
オプションで指定することができ、これの最大値も同様に 2147483647
である。
ところが、Clangのコマンドラインオプション解析においては、オプションの整数値は (signed) int
として評価され、それが整数の型変換によって unsigned int
に変換されている。そのため -fconstexpr-depth=
と-fconstexpr-steps=
に -1
を指定すると 4294967295
を指定したことになる。デフォルトのClangではこれがおそらく最大値である。
問題
C++14以降の constexpr
の制限緩和によって for
や if
をそのまま書くことができるようになった。そのため、コンパイル時の再帰回数については以上の解決方法でおそらく問題ない。
一方で最大ステップ数は比較的容易に上限に達しうる。また、コンパイル時の式評価のためにClang内部で用いられている CallStackFrame
のインデックスもまた unsigned int
で管理されているため、それ以上の関数呼び出しを一関数内で行うことはできない。2
これらの問題を解決するにはClangのソースコードを改変する他にない。
Clangにパッチを当てる
以上の問題を解決するために、2種類のコード改変を行う。
- ステップ数の管理をしている変数の型を
unsigned int
からint
に変更し、これが負の場合は、処理のステップ数のカウントをやめる。これによって-fconstexpr-steps=-1
と指定すれば、無制限にステップを重ねることができるようになる。 -
CallStackFrame
のインデックスの型をunsigned int
からsize_t
に変更する。これによって扱うことができるインデックスの値を $2^{64} - 1$ まで増やすことができる。3
これらの改変を施すパッチがこれである。使用するためには、Clangの公式ドキュメントを参考にしながら、途中で svn patch clang.diff
を行えば良い。以下に典型的な手順を載せる。
cd where-you-want-llvm-to-live
svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
cd llvm/tools
svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
svn patch where-you-cloned-constexpr-nn/clang.diff
cd ../..
mkdir build
cd build
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ../llvm
make -j6
C++の規格
上のパッチに関して、C++の規格上どのような扱いになるのか、気になる向きもあるかもしれない。C++17の規格には4 "Annex B" として "Implementation quantities" という章があり、そこにコンパイル時の再帰回数と評価されるべき式の数の最小値が記載されている。5引用すると
(2.38) Recursive constexpr function invocations [512].
(2.39) Full-expressions evaluated within a core constant expression [1 048 576].
先述のパッチはこれらの制限を満たすため、規格上も問題ないと思われる。
終わりに
C++初心者なので、なにか間違い等あればコメント欄などでマサカリを投げて頂けると幸いです。血を流して喜びます。