是否允许编译器在运行时调用立即(consteval)函数

Is compiler allowed to call an immediate (consteval) function during runtime?

本文关键字:consteval 函数 调用 编译器 运行时 是否      更新时间:2023-10-16

这可能是一个愚蠢的问题,但我很困惑。我有一种感觉,immediate(consteval)函数必须在编译时执行,而我们根本无法在二进制文件中看到它的主体。

这篇文章清楚地支持了我的感受:

这意味着只有在编译时才能看到[立即]函数。符号不是为函数发出的,您不能获取这样一个函数的地址,调试器等工具也无法显示它们。在这方面,立即函数类似于宏。

Herb Sutter的出版物《》中也有类似的有力声明

请注意,C++20草案已经包含了第一轮反射相关工作的一部分:保证在编译时运行的consteval函数,这些函数来自反射工作,专门用于处理反射信息。

然而,有许多证据并不清楚这一事实。

来自cppreference:

consteval-指定函数是立即函数,也就是说,对该函数的每次调用都必须产生一个编译时常数。

这并不意味着它只能在编译时调用

来自P1073R3提案:

现在人们普遍认为,未来对反射的语言支持应该使用constexpr函数,但由于"反射函数"通常在编译时要评估,因此它们实际上很可能是即时函数。

这似乎意味着我的想法,但仍然没有明确说明。来自同一提案:

然而,有时我们想表达一个函数在被调用时(直接或间接)应该总是产生一个常数,而非常数的结果应该产生一个错误。

同样,这并不意味着只需要在编译时评估函数。

根据这个答案:

您的代码必须生成编译时常量表达式。但是编译时常量表达式在您使用它的上下文中不是一个可观察的属性,在链接或甚至运行时执行它也没有副作用!并且似乎没有什么可以阻止

最后,还有一个实时演示,其中consteval函数在运行时被明确调用。然而,我希望这是因为consteval在clang中还没有得到适当的支持,而且行为实际上是不正确的,就像在"为什么consteval函数允许未定义的行为?"?

更准确地说,我想听听引用的文章中以下哪些陈述是正确的:

  1. 立即函数只在编译时可见(不能在运行时求值)
  2. 符号不是为立即函数发出的
  3. 调试器等工具将无法显示即时函数

更准确地说,我想听听被引用文章的以下哪些陈述是正确的:

  1. 立即函数只在编译时可见(不能在运行时求值)
  2. 符号不是为立即函数发出的
  3. 调试器等工具将无法显示即时函数

这些几乎都不是C++标准所能给出的答案。该标准没有定义";符号";或者什么工具可以显示。就标准而言,几乎所有这些都是经销商的选择。

事实上,即使是";编译时";与";运行时间";是标准没有处理的问题。唯一与标准有关的问题是某个事物是否是一个常量表达式。调用constexpr函数可能会生成一个常量表达式,具体取决于其参数。以不产生常量表达式的方式调用consteval函数是错误的。

标准所做的定义的一件事是;看到";。尽管这并不是真正的";编译时";。C++20中有许多语句禁止大多数函数处理直接函数的指针/引用。例如,[expr.prim.id]/3:中的C++20状态

表示立即函数的id表达式只能出现

  • 作为立即调用或的子表达式

  • 在直接函数上下文中。

因此,如果您不在立即函数中,或者您没有使用立即函数的名称来调用另一个立即函数(传递指向该函数的指针/引用),则无法命名立即函数。如果不命名函数,就无法获得指向该函数的指针/引用。

规范中的这一语句和其他语句(比如指向立即函数的指针不是常量表达式的有效结果)本质上使指向立即功能的指针/引用不可能泄漏到常量表达式之外。

因此,在某种程度上,关于直接函数可见性的陈述是正确的。符号可以用于立即函数,但不能以阻止实现丢弃所述符号的方式使用立即函数。

consteval基本上就是这样。它没有使用标准语言来强制执行必须发生的事情。它使用标准语言使其无法以防止这些事情发生的方式使用函数。所以更合理的说法是:

  1. 不能以阻止编译器在编译时执行立即函数的方式使用立即函数。

  2. 您不能以防止编译器丢弃符号的方式使用立即函数。

  3. 不能以强制调试器能够看到即时函数的方式使用即时函数。

预计实施质量将从中受益。

还应该注意的是,调试构建用于。。。调试。高级编译器工具能够调试生成常量表达式的代码是完全合理的。因此,一个能够看到即时函数执行的调试器是一项完全理想的技术。随着编译时代码变得越来越复杂,这种情况变得越来越严重。

提案提到:

这个规范的一个结果是,即时函数永远不需要被后端看到。

因此,用常量替换调用肯定是提案的意图。换句话说,常量表达式是在翻译过程中评估的。

然而,它并没有说后端不需要看到它。事实上,在提案的另一句话中,它只是说这不太可能:

这也意味着,与普通的constexpr函数不同,consteval函数不太可能出现在符号调试器中。


更一般地说,我们可以将问题重新陈述为:

编译器是否被迫计算常量表达式(无论何时何地;而不仅仅是在他们确实需要的时候)?

例如,如果常量表达式是数组的元素数,编译器需要对其求值,因为它需要静态地确定数组的总大小。

然而,编译器可能不需要评估其他用途,尽管任何像样的优化编译器都会尝试这样做,但这并不意味着它需要这样做。

另一个需要考虑的有趣案例是解释器:虽然解释器仍然需要评估一些常量表达式,但它可能只是一直懒洋洋地执行,而不执行任何常量折叠。

所以,据我所知,它们不是必需的,但我不知道我们需要从标准中引用什么来证明这一点(或其他)。也许这本身就是一个很好的后续问题,它也会回答这个问题。

例如,在[expr.const]p1中,有一个注释说它们可以,而不是说它们是:

[注意:常量表达式可以在翻译过程中求值。--结束注释]