Почему контекст constexpr приводит к сбою компилятора, а без оптимизации? - PullRequest
0 голосов
/ 07 марта 2020

Я поигрался с 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

1 Ответ

3 голосов
/ 07 марта 2020

В соответствии с dcl.constexpr

Для функции constexpr или конструктора constexpr, который не является ни значением по умолчанию, ни шаблоном, если не существует значений аргумента, таких как вызов функции или конструктор может быть оцененным подвыражением основного константного выражения, или, для конструктора, оцененным подвыражением полного выражения инициализации некоторого объекта с постоянной инициализацией ([basi c .start.static]), программа неправильно сформирован, диагностика не требуется c.

Поскольку memcpy не constexpr, ваша программа некорректно сформирована NDR.

Использование функция в контексте contsexpr позволит диагностировать c.

В некоторых ситуациях добавление constexpr перед функцией позволяет G CC попытаться оптимизировать сложнее, что приводит к полной оптимизация функции и просто предоставление рассчитанного значения.

Это хороший совет (как и inline ранее).

constexpr функция может быть "неправильно использована":

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; 
}
...