С++ функция 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 - и consteval - это consteval история :)

Ответ 2

Ответ StoryTeller хорош, но я думаю, что возможен немного другой вариант.

С constexpr можно выделить три ситуации:

  1. Результат необходим в контексте времени компиляции, таком как размеры массива. В этом случае аргументы тоже должны быть известны во время компиляции. Оценка, вероятно, выполняется во время компиляции, и, по крайней мере, все диагностируемые ошибки будут обнаружены во время компиляции.

  2. Аргументы известны только во время выполнения, а результат не требуется во время компиляции. В этом случае оценка обязательно должна происходить во время выполнения.

  3. Аргументы могут быть доступны во время компиляции, но результат необходим только во время выполнения.

Четвертая комбинация (аргументы, доступные только во время выполнения, результат, необходимый во время компиляции), является ошибкой; компилятор отклонит такой код.

Теперь в случаях 1 и 3 расчет может произойти во время компиляции, поскольку все входные данные доступны. Но чтобы упростить вариант 2, компилятор должен иметь возможность создавать версию во время выполнения, и он может решить использовать этот вариант и в других случаях - если это возможно.

Например, некоторые компиляторы внутренне поддерживают массивы переменного размера, поэтому, даже если язык требует границ массивов во время компиляции, реализация может решить не делать этого.