【问题标题】:C++ constexpr function in return statementreturn 语句中的 C++ constexpr 函数
【发布时间】:2019-06-08 10:59:33
【问题描述】:

为什么 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;
} 

结果是

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 6
        mov     eax, 6
        pop     rbp
        ret

使用 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>();
} 

我明白了

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 的 sn-ps 并在运行时对其进行评估。为什么这对 clang 来说是无效的 constexpr?

所有评估均在 Godbolt 编译器资源管理器中完成。

【问题讨论】:

  • 您应该使用-O3 以获得最佳行为,没有它,这个问题毫无意义。正如发布的那样,问题实际上是关于“为什么没有优化的编译器会做这件事”,答案通常是“使调试更容易”
  • 这两个编译器在启用优化的情况下完全符合您的预期:godbolt.org/z/cCbFDX
  • constexpr 的存在是为了让您编写自然代码以在需要它们的上下文中生成常量表达式。这不是对编译器的“优化这个总是”的提示。最好不要混淆它。
  • @StoryTeller 这条评论帮助了我上千本书。我认为你应该把它作为一个答案。我一直认为constexpr 是告诉编译器“这应该在编译时评估”,而我现在明白它是“这必须在编译时评估”。突然之间,关于它是否在编译时被评估的困惑感觉不那么严重了。
  • 值得一提的是,在这个特定的示例中,不需要将x 作为模板参数传递。一个“普通”参数在这里就足够了。因此,您可以使用constexpr 函数作为更易读的替代一些旧式元编程模板代码,这些代码以前必须用于生成编译时整数。

标签: c++ gcc clang constexpr


【解决方案1】:

关于constexpr 的一个常见误解是它的意思是“这将在编译时进行评估”1

事实并非如此。引入constexpr 是为了让我们编写自然代码,可能在需要它们的上下文中生成常量表达式。这意味着“这必须在编译时可评估”,这是编译器将检查的内容。

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

constexpr 函数仍然是常规函数。 constexpr 说明符并不意味着编译器具有2 来优化它以在编译时进行常量折叠。最好不要因为这样的提示而混淆它。


1 - 感谢 user463035818 的措辞。
2 - consteval 是另一回事 :)

【讨论】:

  • 实际上,它的意思是“这必须在编译时对至少一个输入(参数和模板参数)进行评估。”,它仍然较弱。是的,这应该适用于任何有用的输入,但期望通常会令人失望。
  • 你知道为什么constexpr 是必要的吗?为什么编译器不能假设所有东西前面都有constexpr
  • @Mehrdad - 这绝不是权威,只是我的 2c。该标准禁止在 constexpr 函数中使用某些构造,现在比在 C++11 中少,但仍然如此。我想为了便于翻译,为了让程序员保持理智,最好让它成为一个可选功能。这也有点符合 C++ 的一般哲学。
  • @StoryTeller-UnslanderMonica 不是误解。 Bjarne Stroustrup 在“C++ 之旅”的第 1.7 节中说:“ constexpr: 大致意思是“在编译时进行评估。”“
  • @doubleE - “C++ 之旅”不是标准。出于对 Bjarne 的所有应有的尊重,添加 consteval 是有原因的。所以是的,一个误解
【解决方案2】:

StoryTeller 的回答很好,但我认为可能会有一点不同。

constexpr,分三种情况来区分:

  1. 编译时上下文中需要结果,例如数组大小。在这种情况下,参数也必须在编译时知道。评估可能在编译时进行,并且至少所有可诊断的错误都会在编译时发现。

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

  3. 参数可能在编译时可用,但只有在运行时才需要结果。

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

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

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

【讨论】:

  • 不应该情况2导致编译错误吗? constexpr 表示表达式必须在编译时可计算。
  • @fishinear 否。答案是指constexpr 函数的使用。例如。如果我有constexpr int factorial(int n),并且我有一个int input,我只在运行时知道,int output = factorial(input); 非常好。错误将声明constexpr int output = factorial(input);,因为input 不是constexpr,所以output 也不能是constexpr
猜你喜欢
  • 2015-12-04
  • 2017-12-04
  • 2022-11-17
  • 2018-03-05
  • 2011-10-29
  • 2017-12-24
  • 1970-01-01
  • 2012-01-11
  • 2018-03-07
相关资源
最近更新 更多