3

考虑以下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

如果我们删除constexprfrom 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先比较in 中的两个参数,而没有on ,则调用会再次被优化掉。static_strcmp char_traits::compare constexprbool 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没有相同的行为呢?constexprstatic_strcmp
4

3 回答 3

4

我们有三个工作案例:

1) 需要计算值来初始化一个constexpr值或严格要求编译时已知值(非类型模板参数、C 样式数组的大小、a 中的测试static_assert(),...)

2)该constexpr函数使用编译时未知的值(例如:从标准输入接收的值。

3)constexpr函数接收编译时已知的值,但结果出现在编译时不需要的地方。

如果我们忽略 as-if 规则,我们有:

  • 在情况 (1) 编译器必须计算值 compile-time 因为计算的值是必需的 compile-time

  • 如果(2)编译器必须计算值运行时,因为它不可能计算它编译时

  • 在情况(3)中,我们处于灰色区域,编译器可以在编译时计算值,但计算的值不是严格要求的编译时间;在这种情况下,编译器可以选择计算编译时还是运行时。

使用初始代码

constexpr bool result = static_strcmp(a, b);

您在情况(1)中:编译器必须计算编译时间,因为result声明了变量constexpr

删除constexpr,

bool result = static_strcmp(a, b); // no more constexpr

您的代码在灰色区域(案例(3))中进行转换,其中编译时计算是可能的,但不是严格要求的,因为输入值是已知的编译时间(ab),但结果会出现在值不是编译时间的地方必需的(普通变量)。因此,编译器可以选择,并且在您的情况下,选择使用函数版本的运行时计算,使用另一个版本的编译时计算。

于 2019-03-20T15:16:30.927 回答
3

请注意,标准中没有明确要求constexpr在编译时调用函数,请参阅最新草案中的 9.1.5.7:

对 constexpr 函数的调用在所有方面都与对等价的非 constexpr 函数的调用产生相同的结果,除了 (7.1) 对 constexpr 函数的调用可以出现在常量表达式中并且 (7.2) 不执行复制省略一个常量表达式([class.copy.elision])。

(强调我的)

现在,当调用出现在常量表达式中时,编译器无法避免在编译时运行该函数,因此它尽职尽责。如果没有(如您的第二个片段),则只是缺少优化的一种情况。这里不乏人。

于 2019-03-20T15:15:59.173 回答
2

您的程序具有未定义的行为,因为您总是比较strlen(a)字符。该字符串b没有那么多字符。

如果您将字符串修改为相等长度(因此您的程序变得定义明确),您的程序将按照您的预期进行优化。

所以这不是错过优化。编译器会优化你的程序,但因为它包含未定义的行为,所以它不会优化它。


请注意,它是否是未定义的行为并不是很清楚。考虑到编译器使用memcmp,它认为两个输入字符串都必须至少strlen(a)很长。所以根据编译器的行为,它是未定义的行为。

以下是当前标准草案中关于比较的内容:

返回:如果对于 [0, n) 中的每个 i,X::eq(p[i],q[i]) 为 0,则返回true0 否则,如果对于 [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第一个差异的索引在哪里)。

于 2019-03-20T15:25:27.333 回答