Вызов функции consteval внутри if consteval вызывает ошибку в контексте, отличном от constexpr

Следующий код не компилируется с 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++. Мои вопросы:

  1. Почему код недействителен?

  2. Должен ли if consteval отличаться от if (std::is_constant_evaluted()), и если да, то правильно ли clang++ принимать код с if consteval?

  3. Есть ли лучший способ определить функцию типа plusone, которая не должна случайно вызываться во время выполнения, но не вызывая таких ошибок? Что-то вроде static_assert(std::is_runtime_evaluated()) — за исключением того, что это, по-видимому, бесполезно, поскольку статическое утверждение всегда будет оцениваться во время компиляции.

Что бы это ни стоило, в моем более сложном приложении у меня есть разные распределители памяти для времени компиляции и времени выполнения, поэтому я хочу сделать что-то вроде этого

if consteval {
  p = my_consteval_allocator(n);
}
else {
  p = my_real_allocator(n);
}

с аналогичной логикой для освобождения. К сожалению, компилятор пытается вызвать my_consteval_allocator в контекстах, отличных от constexpr, когда ему известен требуемый размер выделения.

С if consteval у меня он компилируется как в GCC, так и в Clang.

user17732522 13.07.2024 12:13

С gcc он компилируется только тогда, когда вы отключите оптимизацию. Я должен был уточнить. Добавьте -O и вы увидите ошибку.

user3188445 13.07.2024 19:48

Если бы if consteval ничем не отличался от if (std::is_constant_evaluated()), мы бы его не добавляли (у нас было std::is_constant_evaluated() в C++20, а if consteval было функцией C++23).

Barry 13.07.2024 22:05

В некоторых комментариях вы говорили о gcc с оптимизацией, так что это может быть ошибка gcc: gcc.gnu.org/bugzilla/show_bug.cgi?id=115583

Artyer 13.07.2024 22:47

@Artyer Да, я думаю, это именно та ошибка, с которой я столкнулся. Спасибо.

user3188445 15.07.2024 01:16

@Барри, ну, у if (std::is_constant_evaluated()) всегда была проблема, о которой писали программисты if constexpr (std::is_constant_evaluated()), чего на самом деле не может быть с if consteval.

Bolpat 16.07.2024 11:58

@Bolpat Я знаю, но компиляторы надежно предупреждают об этом случае, так что это в лучшем случае незначительная неприятность, а не настоящая проблема.

Barry 16.07.2024 18:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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 13.07.2024 19:50

@user3188445 user3188445 Да, это похоже на ошибку. Либо это постоянное выражение, либо нет. Оптимизация не может на это повлиять.

user17732522 13.07.2024 19:57

Неужели оптимизация не может дать никакого эффекта? По крайней мере, на сайте en.cppreference.com/w/cpp/types/is_constant_evaluated говорится, что компиляторы могут выполнять пробную оценку константы, но это не обязательно. Я знаю, что контекст немного другой, но результат может быть тот же: компилятор выполняет пробную оценку только с оптимизацией.

user3188445 13.07.2024 22:05

@user3188445 user3188445 В этом особом случае в определении есть цикличность: инициализация явно оценивается константой, если она является постоянным выражением. Значение std::is_constant_evaluated() равно true тогда и только тогда, когда инициализация явно оценивается константой.

user17732522 13.07.2024 22:27

Таким образом, если предположить, что инициализация в const int a = std::is_constant_evaluated() ? y : 1; является постоянным выражением, это означает, что std::is_constant_evaluated() является true, подразумевая, что это не постоянное выражение, потому что y нельзя использовать в константных выражениях.

user17732522 13.07.2024 22:27

С другой стороны, предположение, что это не постоянное выражение, означает, что контекст явно не оценивается константой и, следовательно, std::is_constant_evaluated() оценивается как false, подразумевая, что инициализация является постоянным выражением, поскольку доступа к y не происходит. Оба являются противоречиями, и я думаю, что в стандарте должно быть более четко указано, что произойдет в этом случае. Я был бы удивлен, если бы какой-нибудь компилятор решил сделать в этом случае выражение константным.

user17732522 13.07.2024 22:27

@user3188445 user3188445 Аналогично в const int b = std::is_constant_evaluated() ? 2 : y; у вас есть обратная проблема: обе интерпретации как постоянного выражения, так и как непостоянного выражения являются согласованными. Опять же, это следует уточнить, но я ожидаю, что в этом случае компиляторы всегда будут предпочитать интерпретацию как постоянное выражение. В любом случае я не ожидаю, что компиляторы сделают другой выбор в зависимости от оптимизации.

user17732522 13.07.2024 22:28

Другие вопросы по теме