Я поигрался с constexpr
и понял интересное поведение:
- В некоторых ситуациях добавление
constexpr
перед функцией позволяет G CC попробовать более сложная оптимизация, которая приводит к полной оптимизации функции и предоставлению только вычисленного значения. - Однако вызов такой полностью оптимизированной функции из контекста
constexpr
приводит к ошибкам, поскольку она внутренне использует (встроенный компилятором ) функции / встроенные функции, которые не помечены constexpr
(в частности memcpy
). - ( Clang не работает напрямую при применении
constexpr
к такой функции, даже без constexpr
context.)
Почему это так?
- Не должен ли компилятор ( G CC) быть в состоянии все еще оптимизировать, даже в контексте
constexpr
? - Предложение C ++ P0202 (которое перешло в C ++ 20) хотело создавать функции типа
memcpy
constexpr
( см. раздел III.B в оригинальной редакции ), но это было отклонено и изменилось, потому что встроенные в компилятор версии таких функций могли бы достичь того же (см. раздел III.A в последняя редакция ). - Итак, G CC и Clang неправильно, что не разрешено
memcpy
в constexpr
функции / контекст? (Примечание: memcpy
и __builtin_memcpy
эквивалентны.)
Поскольку примеры легче понять, вот такой пример.
(Вы можно даже увидеть его более комфортно благодаря его результатам в Compiler Explorer здесь .)
Примечание: Я не смог придумать простой пример, в котором простое добавление constexpr
к функции помогло оптимизатору G CC полностью оптимизировать, чего в противном случае не произошло бы. Но поверьте мне, что у меня есть такие примеры, которые являются более сложными (и, к сожалению, с закрытым исходным кодом).
#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;
}
G CC может оптимизировать это просто:
main: # @main
mov eax, -1
ret
Clang также может оптимизировать его, если удалить constexpr
перед определением функции.
Однако, если комментировать в constexpr
перед вызова функции (и, следовательно, вызова функции из constexpr
контекста), компилятор завершается ошибкой с чем-то вроде этого:
G CC:
<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
Clang:
<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