web-dev-qa-db-ja.com

テンプレート特殊化なしのテンプレート階乗関数

以下の動作がわかりません。

コンパイル時に階乗を計算することを目的とした次のコードは、コンパイルすらできません。

#include <iostream>
using namespace std;
template<int N>
int f() {
  if (N == 1) return 1; // we exit the recursion at 1 instead of 0
  return N*f<N-1>();
}
int main() {
  cout << f<5>() << endl;
  return 0;
}

そして次のエラーを投げます:

...$ g++ factorial.cpp && ./a.out 
factorial.cpp: In instantiation of ‘int f() [with int N = -894]’:
factorial.cpp:7:18:   recursively required from ‘int f() [with int N = 4]’
factorial.cpp:7:18:   required from ‘int f() [with int N = 5]’
factorial.cpp:15:16:   required from here
factorial.cpp:7:18: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)
    7 |   return N*f<N-1>();
      |            ~~~~~~^~
compilation terminated.

一方、N == 0の特殊化(上記のテンプレートでは対応できない)を追加すると、

template<>
int f<0>() {
  cout << "Hello, I'm the specialization.\n";
  return 1;
}

特殊化が使用されない場合でも、コードがコンパイルされ、正しい出力が得られます。

...$ g++ factorial.cpp && ./a.out 
120

ここでの問題は、ifステートメントが実行時の構成であることです。あなたが持っているとき

_int f() {
  if (N == 1) return 1; // we exit the recursion at 1 instead of 0
  return N*f<N-1>();
}
_

_f<N-1>_は、呼び出されるときにインスタンス化されます。 if条件によって_f<0>_の呼び出しが停止されても、関数の一部であるため、コンパイラーはそれをインスタンス化する必要があります。つまり、これは_f<4>_をインスタンス化し、これは_f<3>_をインスタンス化し、_f<2>_をインスタンス化します。

これを停止するC++ 17より前の方法は、_0_の特殊化を使用して、そのチェーンを切断することです。 C++ 17で constexpr if を使用して開始すると、これは不要になりました。使用する

_int f() {
  if constexpr (N == 1) return 1; // we exit the recursion at 1 instead of 0
  else return N*f<N-1>();
}
_

_1_の場合でもreturn N*f<N-1>();が存在しないことを保証するため、インスタンス化のラビットホールを降り続ける必要はありません。

22
NathanOliver

問題はf<N>()alwaysがブランチをとるかどうかに関係なくf<N-1>()をインスタンス化することです。適切に終了しない限り、コンパイル時に無限再帰が発生します(つまり、F<0>、次にf<-1>、次にf<-2>などのインスタンス化が試行されます)。明らかに、その再帰を何らかの形で終了する必要があります。

NathanOliverによって提案されたconstexprソリューションと特殊化は別として、再帰を明示的に終了することができます:

template <int N>
inline int f()
{
    if (N <= 1)
        return 1;
    return N * f<(N <= 1) ? N : N - 1>();
}

心に、この解決策はかなり貧弱です(同じ最終条件を2回繰り返す必要があります)。この答えは、問題を解決する方法が常に他にもあることを示すためだけに書いています:-)

5
Igor G