С++ функция constexpr в операторе возврата
Почему функция constexpr оценивается не во время компиляции, а во время выполнения в операторе возврата основной функции?
Пробовал
template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
const int x = fac<3>();
return x;
}
и результат
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 6
mov eax, 6
pop rbp
ret
с gcc 8.2. Но когда я вызываю функцию в операторе возврата
template<int x>
constexpr int fac() {
return fac<x - 1>() * x;
}
template<>
constexpr int fac<1>() {
return 1;
}
int main() {
return fac<3>();
}
я получил
int fac<1>():
push rbp
mov rbp, rsp
mov eax, 1
pop rbp
ret
main:
push rbp
mov rbp, rsp
call int fac<3>()
nop
pop rbp
ret
int fac<2>():
push rbp
mov rbp, rsp
call int fac<1>()
add eax, eax
pop rbp
ret
int fac<3>():
push rbp
mov rbp, rsp
call int fac<2>()
mov edx, eax
mov eax, edx
add eax, eax
add eax, edx
pop rbp
ret
Почему первый код оценивается во время компиляции, а второй - во время выполнения?
Также я попробовал оба фрагмента с Clang 7.0.0, и они оцениваются во время выполнения. Почему этот недействительный constexpr для clang?
Вся оценка была сделана в проводнике компилятора Godbolt.
Ответы
Ответ 1
Распространенное заблуждение в отношении constexpr
заключается в том, что оно означает "это будет оценено во время компиляции" 1.
Это не. constexpr
был введен, чтобы позволить нам писать естественный код, который может генерировать константные выражения в контекстах, которые в них нуждаются Это означает "это должно быть оценено во время компиляции", что проверяет компилятор.
Таким образом, если вы написали функцию constexpr
возвращающую int, вы можете использовать ее для вычисления аргумента шаблона, инициализатора для переменной constexpr
(также const
если это целочисленный тип) или размера массива. Вы можете использовать функцию для получения естественного, декларативного, читаемого кода вместо старых трюков метапрограммирования, к которым нужно было прибегать в прошлом.
Но функция constexpr
- все еще регулярная функция. Спецификатор constexpr
не означает, что у компилятора есть 2, чтобы оптимизировать его для хека и делать постоянное свертывание во время компиляции. Лучше не путать это с таким намеком.
1 - Спасибо user463035818 за формулировку.
2 - c++20 и consteval
- это consteval
история :)
Ответ 2
Ответ StoryTeller хорош, но я думаю, что возможен немного другой вариант.
С constexpr
можно выделить три ситуации:
-
Результат необходим в контексте времени компиляции, таком как размеры массива. В этом случае аргументы тоже должны быть известны во время компиляции. Оценка, вероятно, выполняется во время компиляции, и, по крайней мере, все диагностируемые ошибки будут обнаружены во время компиляции.
-
Аргументы известны только во время выполнения, а результат не требуется во время компиляции. В этом случае оценка обязательно должна происходить во время выполнения.
-
Аргументы могут быть доступны во время компиляции, но результат необходим только во время выполнения.
Четвертая комбинация (аргументы, доступные только во время выполнения, результат, необходимый во время компиляции), является ошибкой; компилятор отклонит такой код.
Теперь в случаях 1 и 3 расчет может произойти во время компиляции, поскольку все входные данные доступны. Но чтобы упростить вариант 2, компилятор должен иметь возможность создавать версию во время выполнения, и он может решить использовать этот вариант и в других случаях - если это возможно.
Например, некоторые компиляторы внутренне поддерживают массивы переменного размера, поэтому, даже если язык требует границ массивов во время компиляции, реализация может решить не делать этого.