Шаблонная функция шаблона без специализации шаблона
Я не понимаю следующее поведение.
Следующий код, предназначенный для вычисления факториала во время компиляции, даже не компилируется:
#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
Ответы
Ответ 1
Проблема здесь в том, что ваш оператор 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>
, и так далее, и так далее.
Pre С++ 17 способ остановить это - использовать специализацию для 0
, которая разрывает эту цепочку. Начиная с С++ 17 с constexpr, если, это больше не требуется. Использование
int f() {
if constexpr (N == 1) return 1; // we exit the recursion at 1 instead of 0
else return N*f<N-1>();
}
гарантирует, что return N*f<N-1>();
даже не будет существовать в случае 1
, поэтому вы не будете продолжать спускаться по кроличьей норе.
Ответ 2
Проблема в том, что f<N>()
всегда создает экземпляр f<N-1>()
, независимо от того, берется ветвь или нет. Если не завершено должным образом, это создаст бесконечную рекурсию во время компиляции (то есть он попытается создать экземпляр F<0>
, затем f<-1>
, затем f<-2>
и так далее). Очевидно, вы должны как-то прекратить эту рекурсию.
Помимо решения и специализации constexpr
, предложенных NathanOliver, вы можете явно прекратить рекурсию:
template <int N>
inline int f()
{
if (N <= 1)
return 1;
return N * f<(N <= 1) ? N : N - 1>();
}
Имейте в виду, это решение довольно плохое (одно и то же условие терминала должно быть повторено дважды), я пишу этот ответ просто для того, чтобы показать, что всегда есть больше способов решить проблему: -)