Постоянные целые числа и постоянная оценка

Рассмотрим следующую программу:

#include <iostream>
#include <type_traits>

constexpr int f() {
  if (std::is_constant_evaluated())
    return -1;
  else return 1;
}

int main() {
  int const i = f();
  std::cout << i;
}

Он выводит -1 при запуске (wandbox).

Однако, если я сделаю функцию throw при оценке во время компиляции::

#include <iostream>
#include <type_traits>

constexpr int f() {
  if (std::is_constant_evaluated())
    throw -1; // <----------------------- Changed line
  else return 1;
}

int main() {
  int const i = f();
  std::cout << i;
}

он компилируется нормально и выводит 1 (wandbox). Почему вместо этого я не получил ошибку компиляции?


person Pilar Latiesa    schedule 06.09.2019    source источник
comment
Насколько я знаю, вам не разрешено использовать функцию constexpr, создающую вторую программу UB.   -  person NathanOliver    schedule 06.09.2019
comment
@FrançoisAndrieux О да. Я забыл упомянуть! Да, если i аннотирован constexpr, то он не скомпилируется.   -  person Pilar Latiesa    schedule 06.09.2019
comment
@NathanOliver Я знаю. Я видел эту технику, чтобы сделать так, чтобы не удалось скомпилировать. Вам разрешено бросать, но не в кодовом пути, который оценивается как константа.   -  person Pilar Latiesa    schedule 06.09.2019
comment
Ага. Просто еще немного посмотрел, и все в порядке, пока это не выбранный путь.   -  person NathanOliver    schedule 06.09.2019


Ответы (1)


Разве постоянная оценка не доставляет удовольствия?

В языке есть несколько мест, где мы пытаемся выполнять постоянную оценку, и если это не удается, мы возвращаемся к непостоянной оценке. Статическая инициализация — одно из таких мест, инициализация постоянных целых чисел — другое.

Что происходит с:

int const i = f();

заключается в том, что это может быть постоянной оценкой, но это не обязательно. Поскольку (не-constexpr) целые числа-константы по-прежнему можно использовать в качестве константных выражений, если они удовлетворяют всем остальным условиям, мы должны попробовать. Например:

const int n = 42;       // const, not constexpr
std::array<int, n> arr; // n is a constant expression, this is ok

Так что попробуем сделать - вызовем f() как постоянное выражение. В этом контексте std::is_constant_evaluated() равно true, поэтому мы попали в ветку с throw и в итоге потерпели неудачу. Невозможно throw во время постоянной оценки, поэтому наша постоянная оценка завершается ошибкой.

Но затем мы отступаем и пытаемся снова — на этот раз вызывая f() как непостоянное выражение (т. е. std::is_constant_evaluated() равно false). Этот путь завершается успешно, что дает нам 1, поэтому i инициализируется значением 1. Но примечательно, что i не является константным выражением на данный момент. Последующее static_assert(i == 1) будет иметь неверный формат, потому что инициализатор i не является константным выражением! Даже несмотря на то, что неконстантный путь инициализации (в противном случае) полностью удовлетворяет требованиям константного выражения.


Обратите внимание, что если бы мы попытались:

constexpr int i = f();

Это не удалось бы, потому что мы не можем вернуться к непостоянной инициализации.

person Barry    schedule 06.09.2019
comment
еще одна причина, почему бы не использовать const для вещей, которые должны быть постоянными во времени компиляции. - person Guillaume Racicot; 06.09.2019
comment
@GuillaumeRacicot: C++03 пытался использовать const для имитации constexpr, которого у него не было; конечно, мы должны использовать настоящую вещь сейчас. - person Davis Herring; 06.09.2019
comment
Самое интересное, что f() является основным константным выражением, если оно оценивается не как константное выражение. Вот почему у нас должен быть язык о «явно оцениваемой константе». - person Davis Herring; 06.09.2019
comment
OT, но мне действительно интересно узнать о реализации. Если это волшебство, интересно, почему это не ключевое слово. - person Red.Wave; 06.09.2019
comment
Что мне здесь интересно, так это то, что второй f() также является теоретической возможностью оптимизации, если компилятор достаточно опытен в логическом выводе. Если std::is_constant_evaluated(), то f() выбрасывает. Это недопустимо в постоянном выражении, поэтому оно не является постоянным выражением, если std::is_constant_evaluated(). Поэтому f() никогда не является константным выражением, и поэтому std::is_constant_evaluated() всегда должно быть false. Теоретически он может оптимизироваться до constexpr int f() { return 1; }, поскольку ветвь true доказуемо непригодна для использования (но технически достижима)... - person Justin Time - Reinstate Monica; 07.09.2019
comment
...Но, по иронии судьбы, это останется функцией, которую можно вычислить только во время компиляции в условиях, когда это не константное выражение. (Умный компилятор мог бы встроить его и заменить f() только на 1, но поскольку выбрасывание f() не может быть допустимым константным выражением, это можно было бы делать только в тех ситуациях, когда его встраивание не привело бы к константному выражению. ) [Все это, конечно, строго теоретически. Тем не менее интересная причуда.] - person Justin Time - Reinstate Monica; 07.09.2019