Как этот код может быть constexpr? (std :: chrono)

В стандарте P0092R1 Говард Хиннант написал:

template <class To, class Rep, class Period,
          class = enable_if_t<detail::is_duration<To>{}>>
constexpr
To floor(const duration<Rep, Period>& d)
{
    To t = duration_cast<To>(d);
    if (t > d)
        --t;
    return t;
}

Как может работать этот код? Проблема в том, что operator-- на std::chrono::duration не является операцией constexpr. Это определяется как:

duration& operator--();

И все же этот код компилируется и дает правильный ответ во время компиляции:

static_assert(floor<hours>(minutes{3}).count() == 0, "”);

Что с этим?


person Marshall Clow    schedule 15.11.2015    source источник
comment
FWIW, исходная версия (P0) статьи Говарда доступна здесь: open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0092r0.html   -  person Marshall Clow    schedule 15.11.2015


Ответы (2)


Ответ заключается в том, что не все операции в подпрограмме времени компиляции должны быть constexpr; только те, которые выполняются во время компиляции.

В приведенном выше примере это следующие операции:

hours t = duration_cast<hours>(d);
if (t > d) {} // which is false, so execution skips the block
return t;

все это можно сделать во время компиляции.

Если же, с другой стороны, вы должны были попробовать:

static_assert(floor<hours>(minutes{-3}).count() == -1, "”);

это дало бы ошибку времени компиляции, говорящую (используя clang):

error: static_assert expression is not an integral constant expression
        static_assert(floor<hours>(minutes{-3}).count() == -1, "");
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: non-constexpr function 'operator--' cannot be used in a constant expression
                        --t;
                        ^
note: in call to 'floor(minutes{-3})'
        static_assert(floor<hours>(minutes{-3}).count() == -1, "");

При написании кода constexpr вы должны учитывать все пути через код.

P.S. Вы можете исправить предложенную подпрограмму floor следующим образом:

template <class To, class Rep, class Period,
          class = enable_if_t<detail::is_duration<To>{}>>
constexpr
To floor(const duration<Rep, Period>& d)
{
    To t = duration_cast<To>(d);
    if (t > d)
        t = t - To{1};
    return t;
}
person Marshall Clow    schedule 15.11.2015

Согласно правилам n3597 и n3652, выражения внутри функции constexpr выполняют сами по себе не обязательно должны быть постоянными выражениями, если они не изменяют глобально видимое состояние.

Есть пример

constexpr int f(int a) {
  int n = a;
  ++n;                  // '++n' is not a constant expression
  return n * a;
}
int k = f(4);           // OK, this is a constant expression.
                        // 'n' in 'f' can be modified because its lifetime
                        // began during the evaluation of the expression.

Скорее всего, это те правила, которым следовал Ховард Хиннант при написании упомянутой вами статьи.

Чтобы работать с duration<T> кодом в вопросе, operator-- нужно сделать constexpr функцией. Поскольку constexpr изменения в библиотеке не были окончательными, легко понять, как Ховард мог полагаться на такое изменение.

person Ben Voigt    schedule 15.11.2015
comment
Извини, Бен, Маршалл совершенно прав. Упомянутые вами документы написаны на C ++ 14, и я сделал эту ошибку совсем недавно, используя C ++ 14. - person Howard Hinnant; 15.11.2015