为什么 constexpr context 使编译器失败,而 w/o 它优化完美?
Why does constexpr context make the compiler fail, while w/o it optimizes perfectly?
我尝试了 constexpr
并发现了一些有趣的行为:
- 在某些情况下,在函数前面添加
constexpr
可以使 GCC 尝试更努力地优化,从而完全优化函数并仅提供计算值。
- 但是,从
constexpr
上下文调用这样一个完全优化的函数会导致错误,因为它在内部使用(编译器内置的)functions/intrinsics 未标记为 constexpr
(特别是 memcpy
).
- (Clang 在将
constexpr
应用于此类函数时直接失败,即使没有 constexpr
上下文。)
为什么会这样?
- 编译器 (GCC) 不应该仍然能够优化,即使在
constexpr
上下文中吗?
- C++ 提案 P0202 (which made it into C++20) wanted to make functions like
memcpy
constexpr
(see section III.B in original revision), but that was rejected and changed because compiler-built-in versions of such functions would achieve the same (see section III.A in latest revision).
- 所以,GCC 和 Clang 不允许
memcpy
在 constexpr
[=96= 中是错误的吗? ]? (注意:memcpy
和 __builtin_memcpy
是等价的。)
例子比较容易理解,这里举这样一个例子。
(您甚至可以在 Compiler Explorer here 中更轻松地查看它的结果。)
注意: 我无法想出一个简单的例子,在函数中简单地添加 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;
}
GCC 能够将其优化为:
main: # @main
mov eax, -1
ret
Clang也可以优化,如果去掉函数定义前面的constexpr
。
但是,如果在函数调用前的 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
叮当声:
<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
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.
由于 memcpy
不是 constexpr
,您的程序格式错误 NDR。
使用 contsexpr
上下文中的函数将允许进行诊断。
In some situations adding constexpr
in front of a function enables GCC to try optimizing harder which results in fully optimizing the function away and just providing the calculated value.
这是一个很好的提示(如之前的inline
)。
constexpr
函数可以是"misused":
constexpr std::size_t factorial(std::size_t n) {/*..*/}
int main()
{
std::cout << factorial(5); // computed at runtime (but probably optimized)
}
正确的方法是
int main()
{
constexpr auto fact5 = factorial(5); // computed at compile time
std::cout << fact5;
}
我尝试了 constexpr
并发现了一些有趣的行为:
- 在某些情况下,在函数前面添加
constexpr
可以使 GCC 尝试更努力地优化,从而完全优化函数并仅提供计算值。 - 但是,从
constexpr
上下文调用这样一个完全优化的函数会导致错误,因为它在内部使用(编译器内置的)functions/intrinsics 未标记为constexpr
(特别是memcpy
). - (Clang 在将
constexpr
应用于此类函数时直接失败,即使没有constexpr
上下文。)
为什么会这样?
- 编译器 (GCC) 不应该仍然能够优化,即使在
constexpr
上下文中吗? - C++ 提案 P0202 (which made it into C++20) wanted to make functions like
memcpy
constexpr
(see section III.B in original revision), but that was rejected and changed because compiler-built-in versions of such functions would achieve the same (see section III.A in latest revision). - 所以,GCC 和 Clang 不允许
memcpy
在constexpr
[=96= 中是错误的吗? ]? (注意:memcpy
和__builtin_memcpy
是等价的。)
例子比较容易理解,这里举这样一个例子。
(您甚至可以在 Compiler Explorer here 中更轻松地查看它的结果。)
注意: 我无法想出一个简单的例子,在函数中简单地添加 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;
}
GCC 能够将其优化为:
main: # @main
mov eax, -1
ret
Clang也可以优化,如果去掉函数定义前面的constexpr
。
但是,如果在函数调用前的 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
叮当声:
<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
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.
由于 memcpy
不是 constexpr
,您的程序格式错误 NDR。
使用 contsexpr
上下文中的函数将允许进行诊断。
In some situations adding
constexpr
in front of a function enables GCC to try optimizing harder which results in fully optimizing the function away and just providing the calculated value.
这是一个很好的提示(如之前的inline
)。
constexpr
函数可以是"misused":
constexpr std::size_t factorial(std::size_t n) {/*..*/}
int main()
{
std::cout << factorial(5); // computed at runtime (but probably optimized)
}
正确的方法是
int main()
{
constexpr auto fact5 = factorial(5); // computed at compile time
std::cout << fact5;
}