Следующий код не компилируется с g++ 14.1 или clang++ 18.1:
#include <type_traits>
consteval int
plusone(int n)
{
return n+1;
}
constexpr int
maybeplusone(int n)
{
if (std::is_constant_evaluated()) {
n = plusone(n);
}
return n;
}
int
not_consteval()
{
return maybeplusone(1);
}
Компилятор жалуется, что n не является постоянным выражением. Если я изменю if (std::is_constant_evaluated())
на if consteval
, то код компилируется с помощью clang++, но не g++. Мои вопросы:
Почему код недействителен?
Должен ли if consteval
отличаться от if (std::is_constant_evaluted())
, и если да, то правильно ли clang++ принимать код с if consteval
?
Есть ли лучший способ определить функцию типа plusone
, которая не должна случайно вызываться во время выполнения, но не вызывая таких ошибок? Что-то вроде static_assert(std::is_runtime_evaluated())
— за исключением того, что это, по-видимому, бесполезно, поскольку статическое утверждение всегда будет оцениваться во время компиляции.
Что бы это ни стоило, в моем более сложном приложении у меня есть разные распределители памяти для времени компиляции и времени выполнения, поэтому я хочу сделать что-то вроде этого
if consteval {
p = my_consteval_allocator(n);
}
else {
p = my_real_allocator(n);
}
с аналогичной логикой для освобождения. К сожалению, компилятор пытается вызвать my_consteval_allocator в контекстах, отличных от constexpr, когда ему известен требуемый размер выделения.
С gcc он компилируется только тогда, когда вы отключите оптимизацию. Я должен был уточнить. Добавьте -O и вы увидите ошибку.
Если бы if consteval
ничем не отличался от if (std::is_constant_evaluated())
, мы бы его не добавляли (у нас было std::is_constant_evaluated()
в C++20, а if consteval
было функцией C++23).
В некоторых комментариях вы говорили о gcc с оптимизацией, так что это может быть ошибка gcc: gcc.gnu.org/bugzilla/show_bug.cgi?id=115583
@Artyer Да, я думаю, это именно та ошибка, с которой я столкнулся. Спасибо.
@Барри, ну, у if (std::is_constant_evaluated())
всегда была проблема, о которой писали программисты if constexpr (std::is_constant_evaluated())
, чего на самом деле не может быть с if consteval
.
@Bolpat Я знаю, но компиляторы надежно предупреждают об этом случае, так что это в лучшем случае незначительная неприятность, а не настоящая проблема.
if (std::is_constant_evaluted())
— это совершенно нормальный if
поток управления, только он std::is_constant_evaluted()
оценивается как true
или false
в зависимости от того, происходит ли оценка в контексте, который явно оценивается с константой.
В частности, если вызов функции consteval
появляется в ветке if (std::is_constant_evaluted())
, то обычно вызов этой функции сам по себе должен быть константным выражением там, где он появляется.
Поскольку n
— это параметр функции, значение которого неизвестно во время компиляции plusone(n)
, когда функция определена, это не постоянное выражение.
if consteval
имеет более сильную семантику. Известно, что первая ветвь такого if
может быть выполнена только во время компиляции, и поэтому вызовы consteval
функций, появляющиеся внутри него, не обязательно должны быть константными выражениями. Они должны быть постоянными подвыражениями только при вычислении всего константного выражения, которое приводит к вызову функции, содержащей оператор if consteval
.
Поэтому всегда используйте if consteval
и забудьте о существовании std::is_constant_evaluted()
, если только вам не нужна поддержка C++20. Но если вам это нужно, то вы также не сможете использовать функции consteval
.
В not_consteval
вызов maybeplusone
не является контекстом, который явно оценивается с константой, и поэтому будет выбрана ветвь времени выполнения if consteval
. Код действителен с if consteval
вместо if (std::is_constant_evaluated())
и у меня он компилируется как в GCC, так и в Clang.
Что касается вашего последнего примера: с if consteval
все должно быть в порядке, и компилятор не должен вызывать my_consteval_allocator
, за исключением контекста оценки, которая явно оценивается с константой. Проблемы, которые, скорее всего, могут вызвать у вас проблемы, — это особые случаи инициализации const
переменных целочисленного или перечислимого типа, а также статических переменных или переменных длительности хранения потока. В этих случаях может произойти вычисление константного выражения, хотя это и не очевидно из синтаксиса.
Однако проблема заключается в том, что ветви времени выполнения и времени компиляции имеют разное значение. Вы можете легко вызвать выделение в контексте, явно оцениваемом константой, а затем передать указатель в контекст времени выполнения и случайно вызвать для него освобождение времени выполнения. Вам всегда придется вручную проверять контекст, в котором вызываются функции, чтобы они совпадали.
Конечно, такой утечки быть не должно. Выделение в константном выражении также всегда должно быть освобождено в том же выражении. Таким образом, если my_consteval_allocator
использует new
, то компилятор в любом случае будет жаловаться на эту ситуацию уже при выделении.
Это имеет смысл... Так что это ошибка, заключающаяся в том, что gcc не может скомпилировать код if consteval с оптимизацией.
@user3188445 user3188445 Да, это похоже на ошибку. Либо это постоянное выражение, либо нет. Оптимизация не может на это повлиять.
Неужели оптимизация не может дать никакого эффекта? По крайней мере, на сайте en.cppreference.com/w/cpp/types/is_constant_evaluated говорится, что компиляторы могут выполнять пробную оценку константы, но это не обязательно. Я знаю, что контекст немного другой, но результат может быть тот же: компилятор выполняет пробную оценку только с оптимизацией.
@user3188445 user3188445 В этом особом случае в определении есть цикличность: инициализация явно оценивается константой, если она является постоянным выражением. Значение std::is_constant_evaluated()
равно true
тогда и только тогда, когда инициализация явно оценивается константой.
Таким образом, если предположить, что инициализация в const int a = std::is_constant_evaluated() ? y : 1;
является постоянным выражением, это означает, что std::is_constant_evaluated()
является true
, подразумевая, что это не постоянное выражение, потому что y
нельзя использовать в константных выражениях.
С другой стороны, предположение, что это не постоянное выражение, означает, что контекст явно не оценивается константой и, следовательно, std::is_constant_evaluated()
оценивается как false
, подразумевая, что инициализация является постоянным выражением, поскольку доступа к y
не происходит. Оба являются противоречиями, и я думаю, что в стандарте должно быть более четко указано, что произойдет в этом случае. Я был бы удивлен, если бы какой-нибудь компилятор решил сделать в этом случае выражение константным.
@user3188445 user3188445 Аналогично в const int b = std::is_constant_evaluated() ? 2 : y;
у вас есть обратная проблема: обе интерпретации как постоянного выражения, так и как непостоянного выражения являются согласованными. Опять же, это следует уточнить, но я ожидаю, что в этом случае компиляторы всегда будут предпочитать интерпретацию как постоянное выражение. В любом случае я не ожидаю, что компиляторы сделают другой выбор в зависимости от оптимизации.
С
if consteval
у меня он компилируется как в GCC, так и в Clang.