考虑以下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
之前首先将static_strcmp
与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
的调用进行了评估,那么为什么不在constexpr
的第一个版本中没有static_strcmp
的相同行为呢?注意,标准中没有任何内容明确要求在编译时调用constexpr
函数,请参见最新草案中的9.1.5.7:
对constexpr函数的调用产生与在所有方面调用等效的非constexpr函数相同的结果,除了(7.1)对constexpr函数的调用可以出现在常量表达式中并且(7.2)不执行复制省略常量表达式([class.copy.elision])。
(强调我的)
现在,当调用出现在常量表达式中时,编译器无法避免在编译时运行该函数,因此它尽职尽责。当它没有时(如在你的第二个片段中),它只是缺少优化的情况。这里的人并不缺。
我们有三个工作案例:
1)计算值需要初始化constexpr
值或严格要求编译时已知值(非类型模板参数,C样式数组的大小,static_assert()
中的测试,......)
2)constexpr
函数使用的值不是编译时知道的(例如:从标准输入接收的值)。
3)constexpr
函数接收值编译时已知,但结果是在一个不需要编译时的地方。
如果我们忽略了as-if规则,我们就有:
用初始代码
constexpr bool result = static_strcmp(a, b);
你遇到的情况是(1):编译器必须计算编译时间,因为result
变量被声明为constexpr
。
删除constexpr
,
bool result = static_strcmp(a, b); // no more constexpr
你的代码在灰色区域(case(3))中进行转换,其中编译时计算是可能的但不是严格要求的,因为输入值是已知的编译时间(a
和b
),但结果是在值不是编译的地方 - 所需时间(普通变量)。因此编译器可以选择,在您的情况下,选择使用该函数版本的运行时计算,使用另一个版本进行编译时计算。
您的程序有未定义的行为,因为您总是比较strlen(a)
字符。字符串b
没有那么多字符。
如果你修改你的字符串是相同的长度(所以你的程序定义明确),你的程序将是你期望的optimised。
所以这不是错过优化。编译器会优化您的程序,但由于它包含未定义的行为,因此不会对其进行优化。
注意,无论是否是未定义的行为,都不是很清楚。考虑到编译器使用memcmp
,它认为两个输入字符串必须至少为strlen(a)
。所以根据编译器的行为,它是未定义的行为。
以下是目前的标准草案中有关比较的内容:
返回:如果对于[0,n)中的每个i,则返回0,X :: eq(p [i],q [i])是
true
;否则,如果对于[0,n)中的某些j,X :: lt(p [j],q [j])是true
,并且对于[0,j)X :: eq(p中的每个i),则为负值[i],q [i])是true
;否则是正值。
现在,没有指定是否允许compare
读取p[j+1..n)
或q[j+1..n)
(其中j
是第一个差异的索引)。