请考虑以下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
从constexpr
删除bool result
:
如果我们从constexpr
中删除constexpr bool result
,则呼叫将不再被优化。
#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
检查:
如果我们首先比较char_traits::length
与{{1}中的两个参数,则 之前调用static_strcmp
,不 char_traits::compare
在constexpr
上,通话再次优化。
bool result
godbolt显示我们已经回到优化呼叫的状态:
#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; }
main:
xor eax, eax
ret
的调用中删除constexpr
会导致恒定评估失败?static_strcmp
也很明显,在编译时会评估对constexpr
的调用,所以为什么在第一版的char_traits::length
中没有相同的行为constexpr
?答案 0 :(得分:4)
我们有三个工作案例:
1)计算值是初始化constexpr
值所必需的,或者在严格要求编译时已知值的地方(非类型模板参数,C样式数组的大小, static_assert()
,...)
2)constexpr
函数使用编译时未知的值(例如:从标准输入接收的值。
3)constexpr
函数接收编译时已知的值,但是结果放在不需要编译时的位置。
如果我们忽略了常规规则,我们将拥有:
在情况(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
)但是结果
用于不需要编译时值(普通变量)的地方。因此,编译器可以选择,并且根据您的情况选择带有该函数版本的运行时计算,以及带有另一个版本的编译时计算。
答案 1 :(得分:3)
请注意,标准明确中的任何内容都不需要在编译时调用constexpr
函数,请参阅最新草案中的9.1.5.7:
调用constexpr函数产生的结果与调用constexpr函数产生的结果相同 等效的非constexpr函数,在所有方面 ,除了(7.1) 对constexpr函数的调用可以出现在常量表达式中,并且 (7.2)复制省略不是在常量表达式中执行 ([class.copy.elision])。
(强调我的)
现在,当调用以常量表达式出现时,编译器无法避免在编译时运行该函数,因此,它必须履行义务。如果没有(如第二个片段所示),这只是缺少优化的一种情况。这里周围不乏那些人。
答案 2 :(得分:2)
您的程序具有未定义的行为,因为您总是比较strlen(a)
个字符。字符串b
没有太多字符。
如果您将字符串修改为相等的长度(以便使程序定义明确),则您的程序将像您期望的那样optimised。
所以这不是错过的优化。编译器会优化您的程序,但是由于它包含未定义的行为,因此不会对其进行优化。
请注意,无论是否为未定义行为,都不是很清楚。考虑到编译器使用memcmp
,它认为两个输入字符串都必须至少strlen(a)
长。因此,根据编译器的行为,这是未定义的行为。
以下是当前标准草案中关于比较的内容:
返回:如果对于[0,n)中的每个i,X :: eq(p [i],q [i])为
true
,则返回0。否则,如果对于[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
是第一个差异的索引)。