Ответ 1
По правилам в [implimits]
, реализации разрешено устанавливать ограничение глубины рекурсии на вычисления constexpr
. Два компилятора, которые имеют полные реализации constexpr
(gcc и clang), применяют такой предел, используя по умолчанию 512 рекурсивных вызовов, как это предлагается стандартом. Для обоих этих компиляторов, а также для любой другой реализации, которая следует за стандартным предложением, оптимизация хвостовой рекурсии будет по существу неопределяемой (если компилятор не завершил бы иначе до достижения предела рекурсии).
Реализация может вместо этого выбирать только подсчет вызовов, для которых он не может использовать оптимизацию хвостовой рекурсии в своем пределе глубины рекурсии или не предоставлять такой предел. Однако такая реализация, вероятно, нанесет вред своим пользователям, так как это может привести к сбою (из-за) или не завершиться при оценках constexpr
, которые повторяются глубоко или бесконечно.
Что касается того, что происходит при достижении предела глубины рекурсии, пример Pubby вызывает интересную точку. [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
).