如果编译器只能执行恒定折叠,则是毫无意义的

Is constexpr pointless if the compiler can just perform constant folding?

本文关键字:毫无意义 折叠 编译器 执行 如果      更新时间:2023-10-16

谢谢,我不需要任何书来教我constexpr的含义。我正在教constexpr,我的简单示例失败了说服学生为什么他们应该通过constexpr来使用编译时间计算的优势。

也请严格避免链接到没有装配代码或分析的问题,它们对我的问题毫无意义。


我正在寻找一个示例,以说明为什么constexpr根本有用,并且不能被解散。

好吧,在许多情况下,如果constexprconst取代,那么实际上没有任何错误。因此,我设计了以下示例:

main_const.cpp

#include <iostream>
using namespace std;
const int factorial(int N)
{
    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);
}
int main()
{
    cout<<factorial(10)<<endl;
    return 0;
}

main_constexpr.cpp

#include <iostream>
using namespace std;
constexpr int factorial(int N)
{
    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);
}
int main()
{
    cout<<factorial(10)<<endl;
    return 0;
}

但问题是对于他们来说,汇编代码为

main_const.asm

12:main_last.cpp **** int main()
13:main_last.cpp **** {
132                     .loc 1 13 0
133                     .cfi_startproc
134 0000 4883EC08       subq    $8, %rsp
135                     .cfi_def_cfa_offset 16
14:main_last.cpp ****   cout<<factorial(10)<<endl;
136                     .loc 1 14 0
137 0004 BE005F37       movl    $3628800, %esi
137      00
138 0009 BF000000       movl    $_ZSt4cout, %edi
138      00
139 000e E8000000       call    _ZNSolsEi

对于后一个,它是

main_constexpr.asm

12:main_now.cpp  **** int main()
13:main_now.cpp  **** {
11                      .loc 1 13 0
12                      .cfi_startproc
13 0000 4883EC08        subq    $8, %rsp
14                      .cfi_def_cfa_offset 16
14:main_now.cpp  ****   cout<<factorial(10)<<endl;
15                      .loc 1 14 0
16 0004 BE005F37        movl    $3628800, %esi
16      00
17 0009 BF000000        movl    $_ZSt4cout, %edi
17      00
18 000e E8000000        call    _ZNSolsEi
18      00

这意味着编译器显然已经使用cosntconstexpr进行了(10!) = 3628800的恒定折叠。

汇编是通过

进行的
g++ -O3 -std=c++17 -Wa,-adhln -g main.cpp>main.asm

尽管在大多数情况下,许多人都认为该代码现在没有进行任何调查,因为编译器很聪明,我想知道constexpr背后是否有任何真实,诚实和有意义的优化好处?

对于优化的 sole目的,不可能构造 constexpr表达式/函数调用序列,该序列是不可能的编译器以优化非constexpr等效的编译器。当然,这是因为constexpr对其使用有许多要求。任何constexpr代码都必须与该翻译单元的编译器进行内衬和可见。递归通过所有导致constexpr值产生的表达式。

同样,constexpr函数不允许执行诸如分配内存(尚未分配(,进行低级功能指针操作(尚未(,调用非constexpr功能以及其他可以阻止编译器能够能够能够能够保持的事情在编译时执行它们。

因此,如果您具有constexpr构造,则等效的非constexpr版本将具有其实现的所有这些相同属性。而且,由于编译器必须能够在编译时执行constexpr代码,因此至少必须在理论上能够对非constexpr等效执行相同的操作。

在任何特定情况下它是否确实优化它是无关紧要的;每个编译器版本都会发生这种变化。它可以足够的事实。

您的问题是您相信constexpr的主要目的是性能。这是对允许您无法做的事情进行的优化。

constexpr在性能中的作用主要是,通过将函数或变量标记为constexpr,编译器可以防止您执行实现可能无法在编译时执行的事情。如果您想跨平台进行编译时执行,则必须保持在编译时执行的标准定义边界内。

语法意味着编译器会积极阻止您做非constexpr的事情。您不能意外编写无法在编译时运行的代码。

也就是说,您应该查看的问题不是如果没有它,constexpr代码是否可以以相同的方式编写。这是您是否会以constexpr的方式编写代码,而没有关键字。对于任何复杂性系统,答案越来越多地接近"否",如果没有其他原因,则很容易意外地执行编译器无法在编译时运行的事情。

您必须在constexpr表达式中使用它来强制编译时间评估:

int main()
{
    constexpr int fact_10 = factorial(10); // 3628800
    std::cout << fact_10 << std::endl;
    return 0;
}

否则您依靠编译器优化。

此外,constexpr允许其使用,而简单的const不允许:

所以假设:

constexpr int const_expr_factorial(int) {/*..*/}
int factorial(int) {/*..*/}

您有:

char buffer[const_expr_factorial(5)]; // OK
char buffer[factorial(5)]; // KO, might compile due to VLA extension
std::integral_constant<int, const_expr_factorial(10)> fact_10; // OK
std::integral_constant<int, factorial(10)> fact_10; // KO