ゲームエンジンのkernel部分を作る上で思いついたので書き留めようかと思います。
※追記
rinse_ さん と kazatsuyu さん がコメントにて、条件もループも排除した定数化関数の実装例を挙げています。
下記のアルゴリズムを使用するより、遥かにスマートな方法なので、コメントの実装内容を真似した方が良いと思います。
今回の記事はconstexprの下手な使い方という認識で良いと思います。
必要になった経緯
ある変数で2進数の1になっているビット数が欲しい状況が発生しました。
なので、VC++の組み込み関数 __popcnt を使おうと思ったのですが、使用する箇所が何度も呼ばれる場所だったので何とも定数化したいなと考えました。(コンパイラによる最適化によって定数化するかもしれませんが、そこらへんは検証していないので分かりません)
実装方法
テンプレートで定数化しようかと思いましたが、C++11からコンパイル時にconstexpr(コンパイル時による計算で定数化できる機能)が使えるということで、これを関数に適用させてみました。
結果をまずお見せしますと、以下のようなコードになりました。
constexpr u32 recursivelyScanStandBit(u32 value)
{
return (value & 0x01) ? recursivelyScanStandBit(value >> 1) + 1 : (value == 0 ? 0 : recursivelyScanStandBit(value >> 1));
}
constexpr u32 constStandBits(u32 value)
{
return recursivelyScanStandBit(value);
}
実際に使う関数は constStandBits で、引数には2進数で1になっているビット数を調べたい変数または数値を入れます。
内部では、recursivelyScanStandBit という関数が再帰的に入力された数値を、1か0かのビットとして調べ、1だけのビット数を算出しています。
処理内容
必要ないとは思いますが、再帰処理を念のため説明しますと、手前1ビットが1 or 0なのかを調べ、1だったら右にシフトして次のビットを調べる用意をしつつ、1のビット数をカウント。(再帰関数なので、関数から戻る際に全て加算される)
手前1ビットが0の場合、数値自体が0の場合は0を返して再帰処理を終了させる。数値自体が0でなければ、まだ1のビットが存在するので、右にシフトして次のビットを調べます。(当たり前ですが、手前1ビットは0だったので1のビット数はカウントしないため+1しません)
何故if文やfor分を使用していないかというと、constexprを指定した関数などは内部でreturn文一つのみで実装しなければならない制限があるからです。(コンパイルエラーになります)
なので、唯一使用できる条件分岐が3項演算子なので、これを利用して再帰関数によってループ処理を実現しています。
ここでは、具体的な使用用途を簡単に説明しただけですので、さらに細かい仕様などを知りたい場合はググってください。
※追記
rinse_ さん と kazatsuyu さん がコメントにて、条件もループも排除した定数化関数の実装例を挙げています。
上記のアルゴリズムを使用するより、遥かにスマートな方法なので、コメントの実装内容を真似した方が良いと思います。(自分も真似します)
以上です。お疲れ様でした。