Где в стандарте С++ 11 указывается, когда функция constexpr может быть оценена во время перевода?

Просто потому, что функция (или конструктор)...

  • объявлен constexpr и
  • определение функции соответствует требованиям constexpr

... не означает, что компилятор будет оценивать функцию constexpr во время перевода. Я просматривал С++ 11 FDIS (N3242, доступный в http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/), чтобы попытаться определить две вещи:

  • Когда компилятор обязан оценивать функцию constexpr во время перевода?
  • Когда компилятору разрешено оценивать функцию constexpr во время перевода?

Раздел 5.19 В пункте 1 говорится, что константные выражения могут быть оценены во время перевода. Насколько я могу понять, в оставшейся части раздела 5.19 излагаются правила того, что справедливо в определении функции constexpr.

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

// Declaration
constexpr double eulers_num() { return 2.718281828459045235360287471; }

// Forced evaluation during translation
constexpr double twoEulers = eulers_num() * 2.0;
static_assert(twoEulers > 5.0, "Yipes!");

До сих пор мне не удалось найти абзацы в FDIS, что:

  • Force twoEulers для оценки во время перевода или
  • Укажите другие ситуации, когда компилятор может или должен оценивать функцию constexpr во время перевода.

То, что меня особенно интересует, заключается в том, запускается ли constexpr при переводе:

  • Когда все параметры, переданные функции constexpr, являются литералами или
  • Подразумеваемый аргумент объекта при разрешении перегрузки (раздел 13.3.1, параграф 3) является либо constexpr, либо требует литерала (например, для размеров массива) или
  • Что-то еще полностью.

По возможности, в ваших ответах укажите разделы FDIS, которые я могу найти или ключевые фразы, которые я могу найти в FDIS. Английский в стандарте несколько тупой, поэтому я, возможно, читал соответствующие параграфы и полностью упустил их смысл или намерение.

Ответ 1

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

Раздел 3.6.2 Инициализация нелокальных переменных, параграф 2 говорит, что если объект со статикой или временем локального хранения потока инициализируется конструктором constexpr, тогда конструктор оценивается во время перевода:

Выполняется постоянная инициализация:

  •   
  • если объект со статикой или длительностью хранилища потоков инициализируется вызовом конструктора, если конструктор является конструктором constexpr, если все аргументы конструктора являются постоянными выражениями (включая преобразования) и если после замены вызова функции (7.1).5), каждый вызов конструктора и полное выражение в mem-инициализаторах является постоянным выражением;  

. Раздел 7.1.5. Спецификатор constexpr, пункт 9 говорит, что если объявление объекта включает спецификатор constexpr, этот объект оценивается во время перевода (т.е. является литералом):

A constexpr спецификатор, используемый в объявлении объекта, объявляет объект как const. Такой объект должен иметь буквальный тип и должен быть инициализирован. Если он инициализируется вызовом конструктора, этот вызов должен быть постоянным выражением (5.19). В противном случае каждое полное выражение, которое появляется в его инициализаторе, должно быть постоянным выражением. Каждое неявное преобразование, используемое при преобразовании выражений инициализатора и каждого вызова конструктора, используемого для инициализации, должно быть одним из разрешенных в постоянном выражении (5.19).

Ive слышал, как люди утверждали, что этот абзац оставляет место для реализации, чтобы отложить инициализацию до времени выполнения, если эффект не может быть обнаружен во время трансляции из-за, скажем, static_assert. Вероятно, это не точный взгляд, потому что, независимо от того, инициализируется ли значение во время перевода, в некоторых случаях наблюдаемо. Это представление подкрепляется Разделом 5.19 Постоянные выражения, пункт 4:

[Примечание. Хотя в некоторых контекстах постоянные выражения должны оцениваться во время перевода программы, другие могут оцениваться во время выполнения программы. Поскольку этот международный стандарт не налагает никаких ограничений на точность операций с плавающей запятой, неясно, дает ли оценка выражения с плавающей запятой во время перевода результат того же результата, что и оценка одного и того же выражения (или тех же операций с одинаковыми значениями ) во время выполнения программы... - примечание к концу]

Раздел 9.4.2 Элементы статических данных, параграф 3 говорит, что если константный статический член данных типа literal инициализируется функцией constexpr или конструктором, тогда эту функцию или конструктор необходимо оценить во время перевода:

Если статический член данных имеет тип const literal, его декларация в определении класса может указывать логический или ортогональный инициализатор, в котором каждое предложение-инициализатор, являющееся выражением присваивания, является постоянным выражением. Статический член данных типа literal может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его декларации указывается логический или равный-инициализатор, в котором каждое предложение-инициализатор, являющееся выражением-присваиванием, является постоянным выражением. [Примечание. В обоих случаях член может отображаться в постоянных выражениях. - конечная нота]

Интересно, что я ничего не нашел в FDIS, который требовал выражения constexpr для оценки, если его результат используется как измерение массива. Я вполне уверен, что стандартный комитет ожидает, что так будет. Но я также мог пропустить это в моих поисках.

Вне этих условий стандарт С++ 11 позволяет выполнять вычисления в конструкторских функциях и конструкторах во время перевода. Но это не требует этого. Вычисления могут возникать во время выполнения. Какие вычисления, которые выполняет компилятор во время перевода, в определенной степени являются вопросом качества реализации.

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

Итак, чтобы добраться до настоящей точки, кажется, что оценка constexpr во время трансляции запускается с помощью:

  • Подразумеваемый аргумент объекта при разрешении перегрузки (раздел 13.3.1, абзац 3) либо constexpr, либо требует литерала.

Я надеюсь, что это поможет кому-то, кроме меня. Спасибо всем, кто внес вклад.

Ответ 2

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

Компилятор должен оценивать вызовы constexpr во время компиляции, когда он действительно нуждается в ответе во время компиляции. Например:

constexpr int foo() {return 5;}

std::array<float, foo()> arr;

Компилятор должен знать размер массива во время компиляции. Поэтому он должен оценивать постоянное выражение во время компиляции. Если функция constexpr не может быть выполнена во время компиляции, вы получите ошибку времени компиляции.

Ответ 3

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

Предположим, что константное выражение представляет собой большой массив (а не std::array, просто массив), который является полностью законным, и программа не указывает, что у него есть статическое хранилище. Предположим также, что в контексте, в котором требуется вычисление времени компиляции, используется только элемент 7 массива. Для компилятора вполне разумно вычислить весь массив, использовать элемент 7, отбросить его и вставить код, чтобы вычислить его во время выполнения в области, в которой он используется, вместо того, чтобы раздувать бинарный файл с помощью всего вычислимого массива. Я считаю, что это не теоретический вопрос; Я наблюдал это с различными компиляторами в различных контекстах. constexpr не означает static.

Конечно, если компилятор может определить, что массив не используется во время выполнения, он может даже не вставлять код для его вычисления, но это еще одна проблема.

Если вы используете такой объект во время выполнения, и хотите указать компилятору, что его стоит поддерживать на протяжении всей программы, вы должны объявить его как static.

Ответ 4

Я не думаю, что это было где угодно. У меня тоже был взгляд, это сложно, потому что в этом списке нет одной статьи об constexpr; все они, кажется, добавляют/удаляют из предыдущего сборника статей.

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

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

из википедии

который, как представляется, получает информацию от this pdf:

функции constexpr: функция constexpr - это функция, которая является "suf- достаточно простой", чтобы он обеспечивал постоянное выражение, когда вызываемые с аргументами, которые являются постоянными значениями (см. п. 2.1).