仅仅因为一个函数(或构造函数)...
将
...并不意味着编译器将在翻译过程中计算constexpr函数。我一直在浏览C++11 FDIS (N3242,在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/上可以找到),试图确定两件事:
在translation?
期间计算常量表达式函数
第5.19节第1段说,常量表达式可以在翻译过程中进行计算。据我所知,第5.19节的其余部分阐述了在常量表达式函数的定义中什么是有效的规则。
我知道,我可以在转换过程中通过将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函数的所有参数都是文字时为
如果可能,请在您的回复中引用FDIS中我可以查找的部分或我可以在FDIS中搜索的关键短语。标准中的英文有些生涩,所以我可能一直在阅读相关段落,完全忽略了它们的意思或意图。
发布于 2012-12-09 10:31:04
通过梳理FDIS,我发现了三个地方,它们指定了在转换过程中必须在何处计算常量表达式。
第3.6.2节非局部变量的初始化,第2段说,如果使用constexpr
构造函数初始化具有静态或线程本地存储持续时间的对象,则在转换期间计算构造函数:
执行
常量初始化:
constexpr
构造函数,如果所有构造函数参数都是常量表达式(包括转换),并且在函数调用替换(7.1.5)之后,内存初始化器中的每个构造函数调用和完整表达式都是常量表达式;第7.1.5节constexpr说明符,第9段指出,如果对象声明包含constexpr说明符,则在转换期间对该对象进行求值(即,是文字):
对象声明中使用的
constexpr
说明符,将对象声明为const。这样的对象应该是文本类型,并且应该被初始化。如果它是由构造函数调用初始化的,则该调用应为常量表达式(5.19)。否则,出现在其初始值设定项中的每个完整表达式都应该是常量表达式。在转换初始化器表达式时使用的每个隐式转换和用于初始化的每个构造函数调用都应是常量表达式(5.19)中允许的转换之一。
我听到有人争论说,这段话为实现留出了空间,可以将初始化推迟到运行时,除非在翻译过程中由于static_assert
而检测到效果。这可能不是一个准确的视图,因为在某些情况下,在转换过程中是否初始化一个值是可以观察到的。第5.19节常量表达式第4段强化了这一观点:
注意:尽管在某些上下文中,常量表达式必须在程序转换期间求值,但其他常量表达式可能会在程序执行期间求值。由于本国际标准对浮点运算的精度没有限制,因此在翻译期间对浮点表达式的求值是否与在程序执行期间对相同表达式的求值(或对相同值的相同运算)产生相同的结果未作规定。-结束注释
constexpr第9.4.2节静态数据成员,第3段指出,如果文本类型的常量静态数据成员是由函数或构造函数初始化的,则该函数或构造函数必须在转换过程中求值:
如果静态数据成员是常量文本类型,则其在类定义中的声明可以指定一个花括号初始化器,在该初始化器中,作为赋值表达式的每个初始化器子句都是常量表达式。可以在类定义中使用
constexpr
说明符声明文本类型的静态数据成员;如果是这样,则其声明应指定一个花括号或相等初始化器,在该初始化器中,每个作为赋值表达式的初始化器子句都是常量表达式。注意:在这两种情况下,成员都可能出现在常量表达式中。-结束注释
有趣的是,如果结果用作数组维数,我在FDIS中没有发现任何需要计算constexpr
表达式的东西。我很确定标准委员会也希望如此。但我也可能在搜索中遗漏了这一点。
除了这些情况之外,C++11标准还允许在转换期间执行常量表达式函数和构造函数中的计算。但这并不是必需的。计算可以在运行时进行。在某种程度上,编译器在翻译过程中执行哪些计算是一个实现质量问题。
在我找到的所有三种情况下,翻译时间评估的触发器都是基于使用constexpr
调用的结果的目标的需求。constexpr
函数的参数是否为文字永远不会被考虑(尽管这是有效计算的先决条件)。
因此,为了真正了解这一点,翻译过程中的constexpr
计算似乎是由以下因素触发的:
我希望这对我以外的人有帮助。感谢每一位做出贡献的人。
发布于 2012-11-27 14:18:55
只要有可能,“允许”在编译时计算constexpr
调用。请记住,规范是在“如同”规则下运行的。因此,如果你不能区分,编译器可以做它想做的任何事情。
编译器需要在编译时评估constexpr
调用,而实际上它需要在编译时得到答案。例如:
constexpr int foo() {return 5;}
std::array<float, foo()> arr;
编译器需要知道编译时的数组大小。因此,它必须在编译时计算常量表达式。如果constexpr
函数不能在编译时执行,则会出现编译时错误。
发布于 2012-11-27 14:41:12
Nicol Bolas是100%正确的,但还有另一个有趣的方面:表达式是否在翻译时求值,以及它是否在运行时求值是完全独立的问题。因为常量表达式不能有副作用,所以它可以被计算任意次,并且没有什么可以阻止它在翻译时和运行时都被计算。
假设常量表达式是一个很大的数组(不是std::array
,只是一个数组),这是完全合法的,并且程序没有指明它有静态存储。还假设在需要编译时计算的上下文中仅使用数组的元素7。编译器计算整个数组,使用元素7,丢弃它,并在运行时在使用它的作用域中插入代码来计算它,而不是用整个计算的数组来膨胀二进制文件,这是非常合理的。我认为这不是一个理论问题;我在不同的上下文中观察到了不同的编译器。constexpr
并不意味着static
。
当然,如果编译器可以确定在运行时没有使用该数组,它甚至可能不会插入代码来计算它,但这是另一个问题。
如果您确实在运行时使用了这样的对象,并且希望向编译器表明值得在程序期间保留该对象,则应该将其声明为static
。
https://stackoverflow.com/questions/13571749
复制相似问题