从捕获constexpr函数返回值的变量中删除constexpr会删除编译时评估

问题描述 投票:3回答: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

如果我们从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_strcmpchar_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的相同行为呢?
c++ c++17 constexpr char-traits
3个回答
3
投票

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

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

(强调我的)

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


4
投票

我们有三个工作案例:

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

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


2
投票

您的程序有未定义的行为,因为您总是比较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是第一个差异的索引)。

© www.soinside.com 2019 - 2024. All rights reserved.