【问题标题】:removing constexpr from a variable capturing a constexpr function return value removes compile-time evaluation从捕获 constexpr 函数返回值的变量中删除 constexpr 会删除编译时评估
【发布时间】:2019-03-20 14:57:44
【问题描述】:

考虑下面的constexpr函数static_strcmp,它使用了C++17的constexprchar_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检查:

如果我们首先比较char_traits::lengthstatic_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
  • 为什么从对 static_strcmp 的初始调用中删除 constexpr 会导致持续评估失败?
  • 显然,即使没有 constexpr,对char_traits::length 的调用也是在编译时评估的,那么为什么在static_strcmp 的第一个版本中没有constexpr 的行为不一样呢?李>

【问题讨论】:

  • 第二个片段是否访问了b 指针越界,所以它是未定义的行为?它是,looks likelooks like 这是一个普通的 memcmp。所以这是未定义的行为。
  • 第一种情况和做任何其他函数调用的问题一样,编译器不必优化它。
  • 但是char_traits&lt;char&gt;::compare 中的__constant_char_array_p(__s2, __n) 将评估为假,因为b[4] 不是一个常量表达式。所以它可能是也可能不是 constexpr,因为它是未定义的。 compare 函数不是 strcmp 它是 memcmp,它不会在空终止字符处停止。
  • 因为 constexpr 函数不能保证在编译时被调用,除非它们的结果在需要常量表达式的上下文中使用。不仅仅是缺少优化的情况,还有数以百万计的情况。
  • 我觉得这是相关的,如果不是重复的stackoverflow.com/questions/54181797/…

标签: c++ c++17 constexpr char-traits


【解决方案1】:

我们有三个工作案例:

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

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

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

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

  • 如果 (1) 编译器必须计算值 compile-time 因为计算的值需要 compile-time

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

  • 1234563在这种情况下,编译器可以选择是计算编译时还是运行时。

带有初始代码

constexpr bool result = static_strcmp(a, b);

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

删除constexpr

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

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

【讨论】:

    【解决方案2】:

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

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

    (强调我的)

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

    【讨论】:

      【解决方案3】:

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

      如果您将字符串修改为相等长度(因此您的程序变得明确),您的程序将是 optimised,正如您所期望的那样。

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


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

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

      返回:如果对于 [0, n) 中的每个 i,X::eq(p[i],q[i]) 为 true,则为 0;否则,如果对于 [0, n) 中的某个 j,X::lt(p[j],q[j]) 为 true 并且对于 [0, j) X::eq 中的每个 i,则为负值(p[i],q[i]) 是true;否则为正值。

      现在,没有指定compare是否允许读取p[j+1..n)q[j+1..n)(其中j是第一个差异的索引)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-02-13
        • 2012-12-24
        • 1970-01-01
        • 2014-04-06
        • 1970-01-01
        • 2020-10-01
        • 1970-01-01
        相关资源
        最近更新 更多