返回语句中的C++ constexpr函数

Tho*_*lik 20 c++ gcc clang constexpr

为什么constexpr函数在编译时没有被评估,而是在main函数的return语句中运行时?

它试过了

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;
} 
Run Code Online (Sandbox Code Playgroud)

结果是

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 6
        mov     eax, 6
        pop     rbp
        ret
Run Code Online (Sandbox Code Playgroud)

与gcc 8.2.但是当我在return语句中调用该函数时

template<int x>
constexpr int fac() {
    return fac<x - 1>() * x; 
} 

template<>
constexpr int fac<1>() {
    return 1; 
} 

int main() {
    return fac<3>();
} 
Run Code Online (Sandbox Code Playgroud)

我明白了

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
Run Code Online (Sandbox Code Playgroud)

为什么第一个代码在编译时评估,第二个代码在运行时评估?

此外,我尝试了两个与clang 7.0.0的片段,并在运行时进行评估.为什么这对于clang来说不是有效的constexpr?

所有评估都在godbolt编译器资源管理器中完成.

Sto*_*ica 26

一个常见的误解constexpr是它意味着"这将在编译时进行评估" 1.

它不是.constexpr介绍是为了让我们编写可能在需要它们的上下文中生成常量表达式的自然代码.这意味着"这必须在编译时可以评估",这是编译器将检查的内容.

因此,如果您编写了一个constexpr返回int 的函数,您可以使用它来计算模板参数,constexpr变量的初始值设定项(const如果它是整数类型)或数组大小.您可以使用该函数来获取自然的,声明性的,可读的代码,而不是过去需要采用的旧元编程技巧.

constexpr功能仍然是常规功能.的constexpr说明符并不意味着一个编译器具有2到它优化到赫克和在编译时做常量合并.最好不要将它混淆为这样的提示.


1 - 感谢用户463035818的短语.
2 - ,consteval然而却是一个不同的故事:)


MSa*_*ers 12

StoryTeller的回答很好,但我认为可能会有一些不同的看法.

constexpr,有三种情况可以区分:

  1. 结果在编译时上下文中是必需的,例如数组大小.在这种情况下,参数也必须在编译时知道.评估可能在编译时,至少所有可诊断的错误都将在编译时找到.

  2. 参数仅在运行时已知,并且在编译时不需要结果.在这种情况下,评估必须在运行时进行.

  3. 参数可以在编译时获得,但结果仅在运行时才需要.

第四个组合(仅在运行时可用的参数,编译时需要的结果)是一个错误; 编译器会拒绝这样的代码.

现在在情况1和3中,计算可能在编译时发生,因为所有输入都可用.但是为了方便案例2,编译器必须能够创建运行时版本,并且它可能决定在其他情况下也使用此变体 - 如果可以的话.

例如,一些编译器内部支持可变大小的数组,因此即使语言需要编译时数组边界,实现也可能决定不这样做.