Условно constexpr выражения с if constexpr - PullRequest
2 голосов
/ 06 августа 2020

Предположим, у вас есть шаблон функции, который вызывает другую функцию, которая может быть или не быть функцией constexpr в зависимости от параметра шаблона. C ++ позволяет объявить вызываемого constexpr в любом случае, если хотя бы одно возможное создание экземпляра дает constexpr.

Например:

template <class T>
constexpr bool bar() { return true; }

template <>
bool bar<int>() { return false; }

template <class T>
constexpr bool foo() { return bar<T>(); }

foo<short>();
foo<int>(); // not a constexpr, but compiles anyway

Это позволяет получить хорошую степень гибкость, так что мы можем производить вызовы функций constexpr, когда это возможно, но в противном случае возвращаться к не-constexpr.

Однако я заметил, что такая же гибкость не распространяется на C ++ 17 if constexpr.

Например:

if constexpr(foo<short>()) { /* do something */ }; // works
if constexpr(foo<int>()) { /* do something */ }; // won't compile, bar<int>() is not constexpr!

Я сталкивался с ситуациями, когда я хотел бы использовать if constexpr, чтобы избежать накладных расходов времени компиляции на создание экземпляров определенных шаблонов, но вычисляемое выражение может не всегда быть constexpr в зависимости от параметров шаблона. Есть ли какая-то причина, по которой if constexpr не просто «деградирует» до оператора, отличного от constexpr if, если условное выражение зависит от параметра шаблона, а шаблон создает экземпляр без constexpr? Так же, как с поведением constexpr функций?

Это просто произвольное упущение в стандарте (т.е. никто не думал, что это будет полезно), или есть более фундаментальная причина, по которой if constexpr не может "деградировать" до не-constexpr if?

Ответы [ 3 ]

4 голосов
/ 06 августа 2020

Он не «ухудшается» по той же причине, что и не ухудшается:

constexpr auto value = expression;

Если вы объявляете переменную как constexpr, тогда вы имеете в виду . Вы имеете в виду, что его значение является константой времени компиляции, и компилятор будет выполнять оценку константы, чтобы сгенерировать ее значение.

То же самое касается if constexpr; условие - это постоянное выражение. if constexpr существует для выбора между различными частями кода в зависимости от того, дает ли конкретное постоянное выражение определенные значения. У него есть особая механика отбрасывания, которая позволяет плохо сформированному коду существовать в условиях, не принятых при определенных обстоятельствах.

Здесь нет «деградации», потому что не существует предполагаемого . На самом деле вопрос не в том, почему if constexpr не может «деградировать»; поэтому функция constexpr делает"деградирующим". Это функция constexpr, которая является странной, и эта странность заключается в том, что нам пришлось изобрести совершенно новое ключевое слово в C ++ 20 , чтобы означать «да, я действительно определенно имею в виду, что эта функция постоянное выражение ".

1 голос
/ 06 августа 2020

Ключевое слово constexpr означает очень разные вещи в этих двух контекстах:

constexpr void f();

и

if constexpr(expr)

В случае f constexpr означает, что f может быть оценено во время компиляции. Но вполне нормально вызывать f и во время выполнения.

В случае if constexpr выражение expr должно быть выражением, которое можно вычислить во время компиляции.

Итак, в случае функции f имеет смысл «ухудшить» constexpr-ness, чтобы функция могла быть вызвана во время выполнения, но это не делает Смысл в контексте constexpr if.

Я предлагаю думать об этих двух формах как о не связанных друг с другом (подумайте о ключевом слове constexpr, встречающемся в обоих случаях как о совпадении). Если вам известно ключевое слово consteval, то вы можете думать о if constexpr как о if consteval, поскольку выражение должно быть вычислено во время компиляции, аналогично функции consteval.

0 голосов
/ 07 августа 2020

Если обе ветви if компилируются, вы можете использовать только обычный if. Если компилятор может доказать, что условие всегда (или никогда) выполняется, он может исключить одну ветвь (хотя он по-прежнему должен создать все необходимое для другой). Это может включать непостоянные выражения, такие как i>1 || i<2, но может включать или не включать какое-либо заданное постоянное выражение, потому что компилятору не требуется полностью раскрывать все, чтобы проверить (и оптимизация в любом случае необязательна).

В том маловероятном случае, когда вы каким-то образом знаете, что будет скомпилирована неправильная ветвь, за исключением, возможно, подмножества обстоятельств, при которых условие будет постоянным выражением (с правильным значением!), Вы можете использовать SFINAE для обнаружения то:

template<class T,class=int[void(T::maybe()),1]>
constexpr bool use_maybe(int) {return true;}
template<class> constexpr bool use_maybe(long) {return false;}

template<class T> void client() {
  if(T::maybe()<4) {  // non-constant values are bigger
    if constexpr(use_maybe<T>(1)) {
      int buf[T::maybe()];
      T::use(buf);
    } else std::abort();
  } else {
    std::vector<int> buf(T::maybe());
    T::use(buf.data());
  }
}
...