По правилам [implimits]
реализации разрешено устанавливать предел глубины рекурсии для constexpr
вычислений.Оба компилятора, которые имеют полные constexpr
реализации (gcc и clang), применяют такое ограничение, используя по умолчанию 512 рекурсивных вызовов, как предложено стандартом.Для обоих этих компиляторов, а также для любой другой реализации, которая следует предложению стандарта, оптимизация хвостовой рекурсии была бы по существу не обнаруживаемой (если в противном случае компилятор не аварийно завершил бы работу, не достигнув своего предела рекурсии).
Вместо этого реализация могла бывыберите для подсчета только те вызовы, для которых он не может применить оптимизацию хвостовой рекурсии в своем пределе глубины рекурсии, или не предоставлять такой предел.Однако такая реализация, вероятно, будет оказывать плохую услугу своим пользователям, поскольку она может либо аварийно завершить работу (из-за переполнения стека), либо не завершится при constexpr
оценках, которые повторяются глубоко или бесконечно.
Что касается того, что происходит при достижении предела глубины рекурсии, пример Пабби поднимает интересный момент.[expr.const]p2
указывает, что
вызов функции constexpr или конструктора constexpr, который превысил бы пределы рекурсии, определенные реализацией (см. Приложение B);
не являетсяпостоянное выражение.Поэтому, если предел рекурсии достигнут в контексте, который требует постоянного выражения, программа является плохо сформированной.Если функция constexpr
вызывается в контексте, который не требует постоянного выражения, реализация, как правило, не обязана пытаться оценить ее во время перевода, но если она выбирает и предел рекурсии достигнут, это необходимовместо этого выполнить вызов во время выполнения.В полной, скомпилированной тестовой программе:
constexpr unsigned long long f(unsigned long long n, unsigned long long s=0) {
return n ? f(n-1,s+n) : s;
}
constexpr unsigned long long k = f(0xffffffff);
GCC говорит:
depthlimit.cpp:4:46: in constexpr expansion of ‘f(4294967295ull, 0ull)’
depthlimit.cpp:2:23: in constexpr expansion of ‘f((n + -1ull), (s + n))’
depthlimit.cpp:2:23: in constexpr expansion of ‘f((n + -1ull), (s + n))’
[... over 500 more copies of the previous message cut ...]
depthlimit.cpp:2:23: in constexpr expansion of ‘f((n + -1ull), (s + n))’
depthlimit.cpp:4:46: error: constexpr evaluation depth exceeds maximum of 512 (use -fconstexpr-depth= to increase the maximum)
, а clang говорит:
depthlimit.cpp:4:30: error: constexpr variable 'k' must be initialized by a constant expression
constexpr unsigned long long k = f(0xffffffff);
^ ~~~~~~~~~~~~~
depthlimit.cpp:2:14: note: constexpr evaluation exceeded maximum depth of 512 calls
return n ? f(n-1,s+n) : s;
^
depthlimit.cpp:2:14: note: in call to 'f(4294966784, 2194728157440)'
depthlimit.cpp:2:14: note: in call to 'f(4294966785, 2190433190655)'
depthlimit.cpp:2:14: note: in call to 'f(4294966786, 2186138223869)'
depthlimit.cpp:2:14: note: in call to 'f(4294966787, 2181843257082)'
depthlimit.cpp:2:14: note: in call to 'f(4294966788, 2177548290294)'
depthlimit.cpp:2:14: note: (skipping 502 calls in backtrace; use -fconstexpr-backtrace-limit=0 to see all)
depthlimit.cpp:2:14: note: in call to 'f(4294967291, 17179869174)'
depthlimit.cpp:2:14: note: in call to 'f(4294967292, 12884901882)'
depthlimit.cpp:2:14: note: in call to 'f(4294967293, 8589934589)'
depthlimit.cpp:2:14: note: in call to 'f(4294967294, 4294967295)'
depthlimit.cpp:4:34: note: in call to 'f(4294967295, 0)'
constexpr unsigned long long k = f(0xffffffff);
^
Если мы изменим код так, чтобыоценка не должна выполняться во время перевода:
constexpr unsigned long long f(unsigned long long n, unsigned long long s=0) {
return n ? f(n-1,s+n) : s;
}
int main(int, char *[]) {
return f(0xffffffff);
}
, тогда оба компилятора принимают ее и генерируют код, который вычисляет результат во время выполнения.При сборке с -O0
этот код не выполняется из-за переполнения стека.При сборке с -O2
оптимизаторы компилятора преобразуют код, чтобы использовать хвостовую рекурсию и код функционирует правильно (но учтите, что эта хвостовая рекурсия не связана с constexpr
оценкой).