为什么 constexpr 上下文会使编译器失败,而它却无法完美优化?

Den*_*dir 1 c++ g++ compiler-optimization clang++ c++20

我尝试了一下constexpr并发现了一些有趣的行为:

  • 在某些情况下,constexpr在函数前面添加使GCC能够尝试更努力地优化,从而导致完全优化函数并仅提供计算值。
  • 然而,从上下文中调用这样一个完全优化的函数constexpr会导致错误,因为它内部使用了未标记的constexpr(特别是)函数/内部函数(编译器内置memcpy)。
  • (当应用到这样的函数时,即使没有上下文, Clang也会直接失败。)constexprconstexpr

为什么会这样?

  • 即使在上下文中,编译器(GCC)不应该仍然能够优化吗constexpr
  • C++ 提案P0202(将其纳入 C++20)想要创建类似的函数memcpy constexpr(请参阅原始修订版中的第III.B节),但这被拒绝并更改,因为此类函数的编译器内置版本将实现相同的功能(请参阅最新修订版中的第 III.A节)。
  • 那么,GCCClang不允许memcpyconstexpr函数/上下文中使用是错误的吗?(注:memcpy__builtin_memcpy是等效的。)

为了更容易理解,这里举个例子。(您甚至可以在此处的Compiler Explorer
中更轻松地查看其结果。)

注意:我无法想出一个简单的例子,其中简单地添加constexpr到函数中就可以帮助GCC优化器完全优化,否则它不会。但相信我,我有这样的例子,它们更复杂(遗憾的是闭源)。

#include <array>
#include <cstdint>
#include <cstring>

constexpr std::uint32_t extract(const std::uint8_t* data) noexcept
{
    std::uint32_t num;
    memcpy(&num, data, sizeof(std::uint32_t));
    return num;
}

int main()
{
    constexpr std::array<std::uint8_t, 4> a1 {{ 0xff, 0xff, 0xff, 0xff }};
    /*constexpr*/ auto val = extract(a1.data());  // <--- Using constexpr here makes compiler fail.
    return val;
}
Run Code Online (Sandbox Code Playgroud)

GCC能够将其优化为:

main:     # @main
    mov   eax, -1
    ret
Run Code Online (Sandbox Code Playgroud)

如果删除constexpr函数定义前面的, Clang也可以对其进行优化。

但是,如果在constexpr函数调用前面进行注释(从而从constexpr上下文中调用该函数),编译器会失败,并显示如下内容:

海湾合作委员会:

<source>: In function 'int main()':
<source>:15:33:   in 'constexpr' expansion of 'extract(a1.std::array<unsigned char, 4>::data())'
<source>:8:11: error: 'memcpy(((void*)(& num)), ((const void*)(& a1.std::array<unsigned char, 4>::_M_elems)), 4)' is not a constant expression
    8 |     memcpy(&num, data, sizeof(std::uint32_t));
      |     ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

铛:

<source>:5:25: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
constexpr std::uint32_t extract(const std::uint8_t* data) noexcept
                        ^
<source>:8:5: note: non-constexpr function 'memcpy' cannot be used in a constant expression
    memcpy(&num, data, sizeof(std::uint32_t));
    ^
<source>:15:20: error: constexpr variable 'val' must be initialized by a constant expression
    constexpr auto val = extract(a1.data());  // <--- Error!
                   ^     ~~~~~~~~~~~~~~~~~~
<source>:8:5: note: non-constexpr function 'memcpy' cannot be used in a constant expression
    memcpy(&num, data, sizeof(std::uint32_t));
    ^
<source>:15:26: note: in call to 'extract(&a1._M_elems[0])'
    constexpr auto val = extract(a1.data());  // <--- Error!

                         ^
2 errors generated.
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)

Jar*_*d42 5

根据dcl.constexpr

对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,可以是某些常量初始化对象([basic.start.static])的初始化完整表达式,程序格式错误,无需诊断。

否则,您的程序的 NDR 格式不正确memcpyconstexpr

在上下文中使用该函数contsexpr可以进行诊断。

在某些情况下,constexpr在函数前面添加使 GCC 能够尝试更努力地优化,从而导致完全优化函数并仅提供计算值。

这是一个很好的提示(和inline以前一样)。

constexpr函数可能被“滥用”:

constexpr std::size_t factorial(std::size_t n) {/*..*/}

int main()
{
    std::cout << factorial(5); // computed at runtime (but probably optimized)
}
Run Code Online (Sandbox Code Playgroud)

正确的方法是

int main()
{
    constexpr auto fact5 = factorial(5); // computed at compile time
    std::cout << fact5; 
}
Run Code Online (Sandbox Code Playgroud)