TMPの条件分岐に条件演算子が使えても良い気がする
以下のコードはgccでインスタンス化が無限再帰するコードです。
ちなみにclangでは無限再帰しないのではなく、エラーメッセージすら表示出来ずにPLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:を表示してクラッシュします。(まだバグ報告はしていません)
#include <type_traits> template<int i> struct fact { static constexpr auto value = i > 1 ? static_cast<int>(i * fact<i - 1>::value) : 1; // 再帰が止まらない }; int main() { static_assert(fact<6>::value == 720, ""); }
通常このようなTMPでは以下のようにパターンマッチを用いて条件分岐を行います。
以下の実装では無事再帰は停止します。
#include <type_traits> template<int i> struct fact { static constexpr auto value = i * fact<i - 1>::value; }; template<> struct fact<1> { static constexpr auto value = 1; }; int main() { static_assert(fact<6>::value == 720, ""); }
さて、最初のコードに目を移します。
上記のコードは、何故インスタンス化が停止しないのかというと
条件演算子の結果の評価が、ショートサーキットされずに本来必要な側だけでなく、i * fact<i - 1>::valueまでもがi <= 1の場合も評価(インスタンス化)されているから
と考えられます。
何故ショートサーキット出来ないのかというと、
条件演算子が戻り値の型を決めるためには、両辺の型の等価性を確認する(或いはthrow文であることを確認する)必要があるから
だと思われます。(他に、結果によらず両辺ともインスタンス化する必要性がある理由がない気がします)
しかし、今回の場合は
static_cast<int>で明示的に囲っているので
条件式がfalseだった場合に
i * fact<i - 1>::valueをインスタンス化しなくとも、戻り値の型が両辺ともintであることはコンパイラ側にとって技術的には検出可能な筈
です。
実際には、i * fact<i - 1>::valueはintにキャスト出来ないオブジェクトかもしれません。
しかし、それは実際にキャストが必要になった場合に失敗すればいいだけの話なので、
実際に返される側になっていない(条件式の結果使われない側、本来評価しなくても良い側の)限りは、その結果の型情報だけが必要
であり、
実際にインスタンス化やキャストを試みる必要性は無い
はずです。
この考えに基づくと、i * fact<i - 1>::valueはその値自体が不要な場合には、返されるオブジェクトの型さえ分かれば良いので、条件演算子を用いても不要な場合はインスタンス化を回避する事が出来、再帰を停止させられると考えられます。
プログラマが型情報をヒントとして与えられる記述(明示的なキャスト)をして、コンパイラがそれを検出出来るようになれば、冗長なパターンマッチではなく条件演算子でも、この手のメタ関数は実装出来るのではないでしょうか。
D言語のstatic ifには及ばずとも、条件演算子をネストしたり出来るだけでも、十分便利になるはずです。
あとは、C++14ではメタ関数の計算結果をvariable templatesを使ってメタ関数をfact_implのような名前にしておいて
template<int i> constexpr int fact = fact_impl<i>::value;
みたいな感じにして::value部分を書かなくても結果をfact<N>で得られるように出来るようになるのでかなりTMPの使い勝手がよくなる気がします。
といってもこのような限定的なTMPは、constexprを使えば良いという結論に落ち着きますかね。
しかし、型さえ分かれば条件演算子の両辺ともインスタンス化される事態は回避出来るという考えは結構役に立つ気がします。