Интегральная константа, переданная по значению, рассматривается как constexpr?

Несмотря на то, что раньше я использовал такой код, и ясно, что у компилятора достаточно информации для работы, я действительно не понимаю, почему это компилируется:

template <class T, class I>
auto foo(const T& t, I i) {
    return std::get<i>(t);
}

int main()
{
    std::cerr << foo(std::make_tuple(3,4), std::integral_constant<std::size_t, 0>{});
    return 0;
}

Пример в реальном времени: http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5.

Кажется, работает как с gcc, так и с clang. Дело в том, что в то время как integral_constant имеет преобразование constexpr в сохраненное целое число, функции-члены constexpr неявно принимают объект как аргумент, и поэтому такая функция не может использоваться в контексте constexpr, если только объект мы вызываем функцию-член, которую можно рассматривать как constexpr.

Здесь i - аргумент, передаваемый foo, и поэтому i, безусловно, нельзя рассматривать как constexpr. Тем не менее, это так. Еще более простой пример:

template <class I>
void foo(I i) {
    constexpr std::size_t j = i;
}

Это тоже компилируется, пока std::integral_constant<std::size_t, 0>{} передается foo.

Я чувствую, что мне не хватает чего-то очевидного в правилах constexpr. Есть ли исключение для типов без гражданства или что-то еще? (или, может быть, ошибка компилятора в двух основных компиляторах? Этот код работает на clang 5 и gcc 7.2).

Изменить: ответ был опубликован, но я не думаю, что этого достаточно. В частности, учитывая последнее определение foo, почему:

foo(std::integral_constant<std::size_t, 0>{});

Скомпилировать, но не:

foo(0);

Оба значения 0 и std::integral_constant<std::size_t, 0>{} являются постоянными выражениями.

Edit 2: Кажется, что это сводится к тому, что вызов функции члена constexpr даже на объекте, который не является константным выражением, сам по себе может рассматриваться как постоянное выражение, если this неиспользованными. Это воспринимается как очевидное. Я не считаю это очевидным:

constexpr int foo(int x, int y) { return x; }

constexpr void bar(int y) { constexpr auto x = foo(0, y); }

Это не компилируется, потому что y, переданный в foo, не является константным выражением. Это неиспользованное не имеет значения. Таким образом, полный ответ должен показать какой-то язык из стандарта, чтобы оправдать тот факт, что функция-член constexpr может использоваться как константное выражение даже на объекте с не константным выражением, если this не используется.

Ответы

Ответ 1

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

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

Условное выражение e является выражением основной константы, если оценка e, следуя правилам абстрактной машины, не будет оценивать одно из следующих выражений:

...

  • преобразование lvalue-to-rvalue, если оно не применяется к
    • нелетучий glvalue интегрального или перечисляемого типа, который ссылается на полный энергонезависимый объект const с предшествующей инициализацией, инициализированный константным выражением, или
    • нестабильное значение glvalue, которое ссылается на подобъект строкового литерала или
    • нестабильное значение glvalue, которое относится к энергонезависимому объекту, определенному с помощью constexpr, или относится к не изменяемому подобъекту такого объекта, или
    • нелетучий glvalue типа literal, который относится к энергонезависимому объекту, чье время жизни начиналось с оценки e;

Вы правы, что третий подзаголовок не применяется. Но первый делает.

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


Вот краткий пример, почему не имеет значения, что this является неявным аргументом. В качестве аргумента не означает, что объект оценивается:

constexpr int blorg(bool const flag, int const& input)
{
    return flag? 42: input;
}

int i = 5; // 5 is an integral constant expression, but `i` is not
constexpr int x = blorg(true, i); // ok, `i` was an argument but never evaluated
constexpr int y = blorg(false, i); // no way

Для функций-членов std::integral_constant вы можете рассмотреть *this как i в функции blorg - если выполнение не вызывает его разыгрывание, это нормально, чтобы он проходил без использования времени компиляции константа.

Ответ 2

Причина, по которой это работает:

template <class T, class I>
auto foo(const T& t, I i) {
    return std::get<i>(t);
}

заключается в том, что ни одна из причин, по которым он не будет применяться, будет применяться. Когда i является std::integral_constant<size_t, S>, i может использоваться как преобразованное константное выражение типа size_t, потому что это выражение проходит через constexpr operator size_t(), который просто возвращает параметр шаблона (который является prvalue) в качестве значения. Это абсолютно достоверное постоянное выражение. Обратите внимание, что this не ссылается - только потому, что функция-член не сама по себе нарушает ограничение константного выражения.

В принципе, здесь нет "runtimey" около i.

С другой стороны, если i были int (через foo(0)), то вызов std::get<i> включал бы преобразование lvalue-to-rval для i, но этот случай не был бы встретите любой из критериев, поскольку i не имеет предшествующей инициализации с константным выражением, это не строковый литерал, он не был определяемый с помощью constexpr, и он не начинал свое время жизни в этом выражении.