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

时间:2019-03-20 14:57:44

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

请考虑以下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::compareconstexpr上,通话再次优化。

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

3 个答案:

答案 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))中进行转换,因为输入值是已知的编译时(ab)但是结果 用于不需要编译时值(普通变量)的地方。因此,编译器可以选择,并且根据您的情况选择带有该函数版本的运行时计算,以及带有另一个版本的编译时计算。

答案 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是第一个差异的索引)。