Удаление constexpr из переменной, получающей возвращаемое значение функции constexpr, удаляет оценку во время компиляции
Рассмотрим следующее 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
для двух аргументов в static_strcmp
перед звонком char_traits::compare
без constexpr
на bool result
, вызов снова оптимизируется.
#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;
}
Godbolt показывает, что мы вернулись к оптимизации вызова:
main: xor eax, eax ret
- Почему удаление
constexpr
от первоначального звонка доstatic_strcmp
вызвать постоянную оценку провал? - Ясно даже без
constexpr
Призыв кchar_traits::length
оценивается во время компиляции, так почему бы не использовать то же поведение безconstexpr
в первой версииstatic_strcmp
?
3 ответа
Обратите внимание, что ничего в стандарте явно не требуется constexpr
функция, вызываемая во время компиляции, см. 9.1.5.7 в последней версии:
Вызов функции constexpr дает тот же результат, что и вызов эквивалентной функции non-constexpr во всех отношениях, за исключением того, что (7.1) вызов функции constexpr может появляться в константном выражении, а (7.2) исключение копирования не выполняется в константное выражение ([class.copy.elision]).
(подчеркивает мой)
Теперь, когда вызов появляется в константном выражении, компилятор не может избежать запуска функции во время компиляции, так что это должным образом обязывает. Когда это не так (как в вашем втором фрагменте), это просто случай отсутствия оптимизации. Там нет недостатка тех, кто здесь.
У нас есть три рабочих случая:
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
ваш код переводится в серую область (case (3)), где вычисления во время компиляции возможны, но не являются строго обязательными, поскольку входные значения известны как время компиляции (a
а также b
) но результат идет там, где значение не требуется во время компиляции (обычная переменная). Таким образом, компилятор может выбрать и, в вашем случае, выбрать вычисление во время выполнения с версией функции, вычисление во время компиляции с другой версией.
Ваша программа имеет неопределенное поведение, потому что вы всегда сравниваете strlen(a)
персонажи. Строка b
не так много персонажей.
Если вы измените свои строки на равную длину (чтобы ваша программа стала четкой), ваша программа будет оптимизирована, как вы ожидаете.
Так что это не пропущенная оптимизация. Компилятор оптимизирует вашу программу, но поскольку он содержит неопределенное поведение, он не оптимизирует его.
Обратите внимание, что независимо от того, является ли это поведение неопределенным или нет, не совсем понятно. Учитывая, что компилятор использует memcmp
, он думает, что обе входные строки должны быть по крайней мере strlen(a)
долго. Таким образом, согласно поведению компилятора, это неопределенное поведение.
Вот что говорит текущий проект стандарта о сравнении:
Возвращает: 0, если для каждого i в [0, n) X::eq(p[i],q[i])
true
; иначе, отрицательное значение, если для некоторого j в [0, n) X::lt(p[j],q[j])true
и для каждого i в [0, j) X::eq(p[i],q[i])true
; еще положительное значение.
Теперь не указано, compare
разрешено читать p[j+1..n)
или же q[j+1..n)
(где j
это индекс первой разницы).