Зачем нам нужны два определения: интегральное постоянное выражение и преобразованное постоянное выражение?

§5.19/3 в С++ 14 определяет интегральное постоянное выражение и преобразованное константное выражение:

Интегральное постоянное выражение представляет собой выражение интегрального или неперечисленный тип перечисления, неявно преобразованный в prvalue, где преобразованное выражение является выражением постоянной константы. [Примечание: такие выражения могут использоваться как границы массива (8.3.4, 5.3.4), так как бит-поле длины (9.6), в качестве инициализаторов перечислителя, если базовый тип не фиксированы (7.2) и как выравнивания (7.6.2). -end note] Преобразованная постоянное выражение типа T является выражением, неявно преобразуется в prvalue типа T, где преобразованное выражение основное константное выражение и неявная последовательность преобразования содержит только пользовательские преобразования, преобразования lvalue-to-rvalue (4.1), интегральные продвижения (4.5) и интегральные преобразования (4.7) чем сужение конверсий (8.5.4). [Примечание: такие выражения могут быть используется в выражениях new (5.3.4), как выражения case (6.4.2), так как инициализаторы перечислителя, если базовый тип фиксирован (7.2), так как массива (8.3.4) и в качестве интегрального или перечисляемого шаблона непигового типа аргументы (14.3). -end note]

Возможно, мне что-то не хватает, но первое впечатление, что каждое интегральное постоянное выражение является преобразованным постоянным выражением.

Изменить

И я также считаю, что в этом абзаце есть ошибка:

Вместо:

A converted constant expression of type T is an expression, implicitly converted to a prvalue of type T, ...

это должно быть:

A converted constant expression of type T is an expression, implicitly converted to a prvalue of an integral type, ...

И это изменение позволяет компилировать следующий код:

#include <iostream>
struct A { operator int() { return 5; } } a;

int main() {
    int b[a]{ 0, 1, 2, 3, 4 };
    std::cout << b[4] << '\n';
}

где a в объявлении int b[a]{ 0, 1, 2, 3, 4}; представляет собой преобразованное константное выражение типа a, неявно преобразованное в prvalue интегрального типа (int), где преобразованное выражение 5 является выражением постоянной константы, и неявная последовательность преобразования содержит только пользовательское преобразование.

Ответы

Ответ 1

Оба определения необходимы, потому что есть вещи, которые вы можете сделать с одним, а не с другим. И нет, не каждое интегральное постоянное выражение действительно является преобразованным постоянным выражением. Для очевидного примера преобразованное константное выражение запрещает сужение конверсий, но интегральное постоянное выражение не имеет.

Поэтому я не могу этого сделать:

enum x : char { a = 1024 };

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

Как диаграмма Венна, я бы сделал ситуацию примерно так:

введите описание изображения здесь

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

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

введите описание изображения здесь

Ответ 2

Примечание: этот ответ основан на последнем стандарте проекта на данный момент, известном как N4567. Указываются некоторые различия между ним и стандартом С++ 11/14.

интегральное постоянное выражение и преобразованное константное выражение различаются при использовании типов классов. В С++ 98/03, когда типы классов не могли быть использованы здесь (поскольку в то время не было функций преобразования constexpr), на самом деле не было такого термина, как преобразованное константное выражение типа T.

Для целочисленного постоянного выражения тип назначения неизвестен. Но для преобразованного константного выражения типа T тип назначения известен как T, а T не обязательно является целым или неперечисленным типом перечисления 1.

Итак, чтобы скомпилировать интегральное постоянное выражение, компилятору необходимо сначала решить, какой тип назначения. Если выражение имеет интегральный или неперечисленный тип перечисления, то, очевидно, тип назначения является только типом выражения. В противном случае, если выражение имеет тип литерала класса (пусть этот тип E), то используется следующий процесс 2:

Компилятор проверяет все неявные функции преобразования в E 3. Пусть говорят, что результирующие типы этих функций образуют множество S. Если S содержит ровно один интегральный или неперечисленный тип перечисления (ссылочный модификатор разделяется, а критерии const и volatile игнорируются: const volatile int& рассматривается как int в этом процессе), тогда тип назначения таков, что тип. В противном случае определение не выполняется.

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

Как следствие, например, если тип класса имеет две функции преобразования, один - constexpr operator int, а другой - constexpr operator long, то этот тип не может использоваться в интегральном постоянном выражении (тип назначения неразрешима). Однако такой тип может использоваться в преобразованном константном выражении типа int или в преобразованном константном выражении типа long.

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

В следующем примере Var{} является допустимым интегральным постоянным выражением, но является недопустимым преобразованным константным выражением типа std::size_t (пример вдохновлен на этот вопрос).

class Var
{
public:

    constexpr operator int ()
    { return 42; }

    template <typename T>
    constexpr operator T () = delete;
};

enum {
    x = Var{} // the initializer of `x` is expected to be an 
              // integral constant expression
              // x has value 42
};

int t[ Var{} ]; // the array bound is expected to be a
                // converted constant expression of type std::size_t
                // this declaration is ill-formed

Ссылки

N4567 5.20 [expr.const] p7

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

N4567 4 [conv] p5

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

Примечания

  • В С++ 11/14 преобразованное константное выражение может быть только целочисленным или перечисляемым. N4268 изменяет это.
  • В С++ 11 такого процесса не было, вместо этого требуется, чтобы "тип класса должен иметь одну неявную функцию преобразования для интегрального или перечисляемого типа и что функция преобразования должна быть constexpr." N3323 изменил это на текущую формулировку.
  • Слово "неявный" не существовало в стандарте С++ 14. Он был добавлен CWG 1981.

Ответ 3

После обсуждения ответов, представленных Джерри Коффином и cpplearner, позвольте мне предложить переписать эти проклятые правила, например:

[expr.const] 5.20\3 (изменено)

Интегральное постоянное выражение представляет собой выражение целочисленного или неперечисленного типа перечисления, которое неявно преобразуется в выражение константы ядра prvalue того же типа, что неявная последовательность преобразования содержит только преобразование lvalue-to-rvalue.

[expr.const] 5.20\4 (изменено)

Преобразованное константное выражение типа T является выражением любого тип, который неявно преобразуется в постоянное выражение типа T так что неявная последовательность преобразования содержит только:

  • пользовательские преобразования,
  • преобразования lvalue-to-rvalue,
  • преобразование массива в указатель,
  • преобразования функции-к-указателю,
  • квалификационные преобразования,
  • интегральные акции,
  • интегральные преобразования, кроме сужения конверсий,
  • конверсии нулевого указателя из std::nullptr_t,
  • конверсии указателей нулевого элемента из std::nullptr_t и
  • преобразования указателей функций,

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

Теперь разница очевидна, мм? Также следует напомнить, что согласно 5.20\7; 4\5 в некоторых случаях вместо интегрального постоянного выражения может использоваться выражение типа литерала.