从捕获 constexpr 函数返回值的变量中删除 constexpr 会删除编译时计算
removing constexpr from a variable capturing a constexpr function return value removes compile-time evaluation
考虑以下constexpr
函数static_strcmp
,它使用 C++17 的constexpr
char_traits::compare
函数:
#include <string>
constexpr bool static_strcmp(char const *a, char const *b)
{
return std::char_traits<char>::compare(a, b,
std::char_traits<char>::length(a)) == 0;
}
int main()
{
constexpr const char *a = "abcdefghijklmnopqrstuvwxyz";
constexpr const char *b = "abc";
constexpr bool result = static_strcmp(a, b);
return result;
}
Godbolt 表明,这在编译时被评估,并优化为:
main: xor eax, eax ret
从bool result
中删除constexpr
:
如果我们从constexpr bool result
中删除constexpr
,现在不再优化调用。
#include <string>
constexpr bool static_strcmp(char const *a, char const *b)
{
return std::char_traits<char>::compare(a, b,
std::char_traits<char>::length(a)) == 0;
}
int main()
{
constexpr const char *a = "abcdefghijklmnopqrstuvwxyz";
constexpr const char *b = "abc";
bool result = static_strcmp(a, b); // <-- note no constexpr
return result;
}
Godbolt 表明我们现在调用memcmp
:
.LC0: .string "abc" .LC1: .string "abcdefghijklmnopqrstuvwxyz" main: sub rsp, 8 mov edx, 26 mov esi, OFFSET FLAT:.LC0 mov edi, OFFSET FLAT:.LC1 call memcmp test eax, eax sete al add rsp, 8 movzx eax, al ret
添加短路length
检查:
如果我们先比较static_strcmp
中两个参数的char_traits::length
,然后再调用char_traits::compare
,而不constexpr
bool result
,调用再次被优化。
#include <string>
constexpr bool static_strcmp(char const *a, char const *b)
{
return
std::char_traits<char>::length(a) == std::char_traits<char>::length(b)
&& std::char_traits<char>::compare(a, b,
std::char_traits<char>::length(a)) == 0;
}
int main()
{
constexpr const char *a = "abcdefghijklmnopqrstuvwxyz";
constexpr const char *b = "abc";
bool result = static_strcmp(a, b); // <-- note still no constexpr!
return result;
}
Godbolt 表明我们又回到了正在优化的呼叫中:
main: xor eax, eax ret
- 为什么从初始调用中删除
constexpr
static_strcmp
会导致持续评估失败? - 显然,即使没有
constexpr
,对char_traits::length
的调用也在编译时被计算,那么为什么不在static_strcmp
的第一个版本中没有constexpr
相同的行为呢?
我们有三个工作案例:
1)初始化constexpr
值需要计算值,或者严格要求编译时已知值(非类型模板参数,C样式数组的大小,static_assert()
中的测试,...
2)constexpr
函数使用编译时未知的值(例如:从标准输入接收的值。
3)constexpr
函数接收编译时已知的值,但结果位于不需要编译时的位置。
如果我们忽略 as-if 规则,我们就会得到:
-
在情况 (1) 中,编译器必须计算值编译时,因为计算值是编译时必需的
-
在情况 (2) 中,编译器必须计算值运行时,因为它不可能计算编译时
-
在情况(3)中,我们处于灰色区域,编译器可以计算值编译时,但计算值不是严格要求的编译时;在这种情况下,编译器可以选择是计算编译时还是运行时。
使用初始代码
constexpr bool result = static_strcmp(a, b);
情况 (1):编译器必须计算编译时,因为result
变量被声明为constexpr
。
删除constexpr
,
bool result = static_strcmp(a, b); // no more constexpr
您的代码在灰色区域(情况 (3))中转换,其中编译时计算是可能的,但不是严格要求的,因为输入值是已知的编译时(a
和b
),但结果 转到不需要编译时的值(普通变量)的地方。因此,编译器可以选择,并且在您的情况下,选择具有函数版本的运行时计算,以及具有另一个版本的编译时计算。
请注意,标准中没有任何内容明确要求在编译时调用constexpr
函数,请参阅最新草案中的 9.1.5.7:
对 constexpr 函数的调用产生与调用相同的结果在所有方面等效的非 constexpr 函数,除了 (7.1) 对 constexpr 函数的调用可以出现在常量表达式中,并且 (7.2) 复制省略不在常量表达式中执行 ([class.copy.elision])。
(强调我的)
现在,当调用出现在常量表达式中时,编译器无法避免在编译时运行该函数,因此它尽职尽责地履行义务。当它没有时(如在您的第二个代码段中),这只是缺少优化的情况。这里不乏这样的人。
程序具有未定义的行为,因为您总是比较strlen(a)
字符。字符串b
没有那么多字符。
如果将字符串修改为长度相等(以便程序定义明确),则程序将按预期进行优化。
所以这不是错过优化。编译器将优化程序,但由于它包含未定义的行为,因此不会对其进行优化。
请注意,它是否是未定义的行为,并不是很清楚。考虑到编译器使用memcmp
,它认为两个输入字符串必须至少strlen(a)
长。所以根据编译器的行为,它是未定义的行为。
以下是当前标准草案对比较的看法:
返回:如果对于 [0, n] 中的每个 i,X::eq(p[i],q[i])true
;否则,如果对于 [0, n] 中的某些 j,X::lt(p[j],q[j])true
,对于 [0, j) 中的每个 i,X::eq(p[i],q[i])
true
,则为负值; 否则为正值。
现在,没有指定是否允许compare
读取p[j+1..n)
或q[j+1..n)
(其中j
是第一个差异的索引)。
- 将数组的地址分配给变量并删除
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- C/C++编译器通常会删除重复的库吗
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 从链接列表c++中删除一个项目
- C++如何通过用户输入删除列表元素
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 多成员Constexpr结构初始化
- 是否需要删除包含对象的"pair"?
- 如何在自删除后将对象设置为nullptr
- 迭代时从向量和内存中删除对象
- 条件constexpr函数
- 是否可以使用if constexpr删除控制流语句
- 有没有办法删除 constexpr
- 为什么删除默认参数会破坏此 constexpr 计数器?
- 从捕获 constexpr 函数返回值的变量中删除 constexpr 会删除编译时计算
- 从C++14模板专用化中删除constexpr
- 删除constexpr会改变链接吗?
- constexpr 和函数体 = 删除:目的是什么?
- 删除constexpr会改变gcc上数组的值