18

仅仅因为一个函数(或构造函数)......

  • 被声明为 constexpr 并且
  • 函数定义满足 constexpr 要求

...并不意味着编译器将在翻译期间评估 constexpr 函数。我一直在查看 C++11 FDIS(N3242,可在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/获得)试图确定两件事:

  • 编译器何时有义务在翻译期间评估 constexpr 函数?
  • 何时允许编译器在翻译期间评估 constexpr 函数?

第 5.19 节第 1 段说常量表达式可以在翻译过程中求值。据我所知,第 5.19 节的其余部分阐述了 constexpr 函数定义中的有效规则。

我知道我可以在翻译期间通过将 constexpr 函数的结果声明为 constexpr 来强制进行 constexpr 评估。像这样:

// Declaration
constexpr double eulers_num() { return 2.718281828459045235360287471; }

// Forced evaluation during translation
constexpr double twoEulers = eulers_num() * 2.0;
static_assert(twoEulers > 5.0, "Yipes!");

到目前为止,我一直无法在 FDIS 中找到以下段落:

  • 强制twoEulers在翻译过程中进行评估或
  • 指定编译器在翻译期间可能或必须评估 constexpr 函数的其他情况。

我特别感兴趣的发现是翻译期间的 constexpr 评估是否由以下因素触发:

  1. 当传递给 constexpr 函数的所有参数都是文字时,或者
  2. 重载决议期间隐含的对象参数(第 13.3.1 节第 3 段)是 constexpr 或需要文字(例如对于数组维度),或者
  3. 完全不同的东西。

如果可能,请在您的回复中引用我可以查找的 FDIS 部分或我可以在 FDIS 中搜索的关键短语。标准中的英语有些迟钝,所以我可能一直在阅读相关段落,完全错过了它们的含义或意图。

4

4 回答 4

7

constexpr只要实际上可以这样做,就“允许”在编译时评估调用。请记住,规范在“好像”规则下运行。因此,如果您无法区分,编译器可以为所欲为。

当编译器在编译时实际需要答案时,编译器需要在编译时评估调用。例如:constexpr

constexpr int foo() {return 5;}

std::array<float, foo()> arr;

编译器需要在编译时知道数组大小。因此,它必须在编译时计算常量表达式。如果constexpr函数无法在编译时执行,则会出现编译时错误。

于 2012-11-27T06:18:55.433 回答
4

Nicol Bolas 是 100% 正确的,但还有另一个有趣的方面:​​表达式是否在翻译时评估以及是否在运行时评估是完全独立的问题。由于常量表达式不能有副作用,它可以被计算任意次数,并且没有什么能阻止它在翻译时和运行时被计算。

假设常量表达式是一个大数组(不是 a std::array,只是一个数组),这是完全合法的,并且程序没有表明它具有静态存储。还假设在需要编译时计算的上下文中仅使用数组的元素 7。编译器计算整个数组,使用元素 7,丢弃它,并在运行时在使用它的范围内插入代码来计算它是非常合理的,而不是用整个计算数组来膨胀二进制文件。我相信这不是理论上的问题;我已经在各种上下文中使用各种编译器观察到它。constexpr并不意味着。_static

当然,如果编译器可以确定该数组在运行时没有被使用,它甚至可能不会插入代码来计算它,但这是另一个问题。

如果你确实在运行时使用了这样的对象,并且你想向编译器表明它值得在程序运行期间保留它,你应该将它声明为static.

于 2012-11-27T06:41:11.573 回答
4

通过梳理 FDIS,我发现了三个地方,它们指定了在翻译过程中必须对 constexpr 表达式进行评估的位置。

第 3.6.2 节非局部变量的初始化,第 2 段说,如果使用构造函数初始化具有静态或线程本地存储持续时间的对象,constexpr则在翻译期间评估构造函数:

执行常量初始化

  • 如果具有静态或线程存储持续时间的对象由构造函数调用初始化,如果构造函数是constexpr构造函数,如果所有构造函数参数都是常量表达式(包括转换),并且如果在函数调用替换 (7.1.5) 之后,每个构造函数mem-initializers中的 call 和 full-expression是一个常量表达式;

第 7.1.5 节 constexpr 说明符,第 9 段说如果对象声明包含说明constexpr符,则在翻译期间评估该对象(即,是文字):

对象声明中使用的constexpr说明符将对象声明为 const。这样的对象应具有文字类型并应被初始化。如果它由构造函数调用初始化,则该调用应为常量表达式(5.19)。否则,出现在其初始化程序中的每个完整表达式都应是一个常量表达式。用于转换初始化表达式的每个隐式转换和用于初始化的每个构造函数调用都应是常量表达式(5.19)中允许的那些之一。

我听说有人争辩说,这一段为实现将初始化推迟到运行时留出了空间,除非在翻译过程中可以检测到效果,例如,static_assert. 这可能不是一个准确的观点,因为在某些情况下,值是否在翻译过程中被初始化是可以观察到的。第 5.19 节常量表达式第 4 段强化了这一观点:

[注意:虽然在某些情况下必须在程序翻译期间计算常量表达式,但可以在程序执行期间计算其他表达式。由于本国际标准对浮点运算的准确性没有限制,因此未指定在翻译期间对浮点表达式的求值是否与对相同表达式的求值产生相同的结果(或对相同值的相同操作) ) 在程序执行期间... —结束注]

第 9.4.2 节静态数据成员,第 3 段说如果文字类型的 const 静态数据成员由 constexpr 函数或构造函数初始化,则必须在翻译期间评估该函数或构造函数:

如果静态数据成员是 const 字面量类型,则它在类定义中的声明可以指定一个大括号或相等初始化器,其中作为赋值表达式的每个初始化器子句都是一个常量表达式。文字类型的静态数据成员可以在类定义中用说明符声明;如果是这样,它的声明应指定一个大括号或等式初始化器,其中作为赋值表达式的每个初始化器子句都是一个常量表达式。[注意:在这两种情况下,成员都可能出现在常量表达式中。——尾注]constexpr

有趣的是,我在 FDIS 中没有发现任何需要constexpr计算表达式的结果(如果其结果用作数组维度)。我很确定标准委员会希望如此。但我也可能在搜索中错过了这一点。

在这些情况之外,C++11 标准允许在翻译期间执行 constexpr 函数和构造函数中的计算。但它不需要它。计算可能发生在运行时。编译器在翻译过程中执行哪些计算,在一定程度上是实现的质量问题。

在我定位的所有三种情况下,翻译时评估的触发器都是基于使用调用结果的目标的constexpr要求。从不考虑函数的参数是否constexpr是文字(尽管它是有效评估的先决条件)。

因此,要真正了解这一点,constexpr翻译期间的评估似乎是由以下因素触发的:

  • 重载决议期间隐含的对象参数(第 13.3.1 节第 3 节)是 constexpr 或需要文字。

我希望这对我以外的人有帮助。感谢所有做出贡献的人。

于 2012-12-09T02:31:03.890 回答
1

我不认为它在任何地方都是被迫的。我也看了一下,这很棘手,因为该列表中没有一篇关于 constexpr 的论文;他们似乎都从以前的论文集中添加/删除。

我认为一般的想法是当 constexpr 函数的输入本身是 constexpr 时,这一切都将在编译时完成;并且通过扩展非函数 constexpr 语句,如果您使用的是半智能编译器,那么无论如何都是文字将在编译时运行。

如果使用非常量表达式的参数调用 constexpr 函数或构造函数,则调用的行为就好像该函数不是 constexpr,并且结果值不是常量表达式。

来自维基百科

这似乎从这个 pdf中获取信息:

constexpr 函数:constexpr 函数是一个“足够简单”的函数,因此当使用常量值的参数调用时,它会传递一个常量表达式(参见第 2.1 节)。

于 2012-11-27T05:58:55.797 回答