Почему initializer_list значений enum не считается константным выражением? - PullRequest
0 голосов
/ 17 января 2019

В следующем коде (проверено локально и на Wandbox):

#include <iostream>

enum Types
{
    A, B, C, D
};

void print(std::initializer_list<Types> types)
{
    for (auto type : types)
    {
        std::cout << type << std::endl;
    }
}

int main()
{
    constexpr auto const group1 = { A, D };
    print(group1);
    return 0;
}

MSVC 15.8.5 не может быть скомпилирован с:

error C2131: expression did not evaluate to a constant
note: failure was caused by a read of a variable outside its lifetime
note: see usage of '$S1'

(все относятся к строке, содержащей constexpr)

Clang 8 (HEAD) сообщает:

error: constexpr variable 'group1' must be initialized by a constant expression
    constexpr auto const group1 = { A, D };
                         ^        ~~~~~~~~
note: pointer to subobject of temporary is not a constant expression
note: temporary created here
    constexpr auto const group1 = { A, D };
                                  ^

gcc 9 (HEAD) сообщает:

In function 'int main()':
error: 'const std::initializer_list<const Types>{((const Types*)(&<anonymous>)), 2}' is not a constant expression
   18 |     constexpr auto const group1 = { A, D };
      |                                          ^
error: could not convert 'group1' from 'initializer_list<const Types>' to 'initializer_list<Types>'
   19 |     print(group1);
      |           ^~~~~~
      |           |
      |           initializer_list<const Types>

Почему?

Во-первых, все они, по-видимому, считают enum-идентификаторы непостоянными, несмотря на то, что они на самом деле являются общеизвестными постоянными значениями времени компиляции.

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

В-третьих, у gcc есть странная жалоба const-vs-non-const, которую я не могу понять, поскольку списки инициализаторов всегда постоянны.

Наконец, все, кроме gcc, с радостью скомпилируют и запустят этот код без проблем, если удалить constexpr. Конечно, в этом случае не обязательно , но я не вижу веских причин, чтобы он не работал.

Между тем, gcc будет компилировать и запускать код только в том случае, если тип параметра будет изменен на std::initializer_list<const Types> - и это изменение приведет к сбою компиляции как в MSVC, так и в clang.

(Интересно: gcc 8 с изменением типа параметра успешно компилирует и запускает код, включающий constexpr, где gcc 9 выдает ошибки.)


FWIW, изменив объявление на следующее:

    constexpr auto const group1 = std::array<Types, 2>{ A, D };

Компилируется и запускается на всех трех компиляторах. Так что, скорее всего, сам initializer_list ведет себя не так, как значения enum. Но синтаксис более раздражает. (Это немного менее раздражает при подходящей реализации make_array, но я до сих пор не понимаю, почему оригинал недействителен.)


    constexpr auto const group1 = std::array{ A, D };

Также работает, благодаря вводу шаблона C ++ 17. Хотя сейчас print не может взять initializer_list; он должен быть основан на общей концепции контейнера / итератора, что неудобно.

Ответы [ 2 ]

0 голосов
/ 17 января 2019

Когда вы инициализируете std::initializer_list, это происходит так:

[dcl.init.list] (выделено мной)

5 Создан объект типа std :: initializer_list из списка инициализатора , как будто реализация сгенерирована и материализовано значение типа «массив из N const E» , где N - это количество элементов в списке инициализатора. Каждый элемент этого массива инициализируется с помощью соответствующего элемента инициализатора список, и объект std :: initializer_list создан для обратитесь к этому массиву. [ Примечание : конструктор или функция преобразования выбранный для копии должен быть доступен в контексте список инициализаторов. - конечная нота ] Если требуется сужающее преобразование чтобы инициализировать любой из элементов, программа плохо сформирована. [ Пример :

struct X {
  X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

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

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

при условии, что реализация может создать initializer_list объект с парой указателей. - конец примера ]

То, как этот временный массив используется для инициализации std::initializer_list, определяет, инициализируется ли initializer_list постоянным выражением. В конечном счете, в соответствии с примером (несмотря на то, что он ненормативный), эта инициализация будет принимать адрес массива или его первого элемента, который будет производить значение типа указателя. И это недопустимое константное выражение.

[expr.const] (выделено мной)

5 A константное выражение является либо основной константой glvalue выражение, которое относится к объекту, который является разрешенным результатом константное выражение (как определено ниже) или постоянная основная константа выражение, значение которого удовлетворяет следующим ограничениям:

  • если значение является объектом типа класса, каждый нестатический член данных ссылочного типа относится к объекту, который является разрешенным результатом константное выражение,
  • если значение имеет тип указателя, оно содержит адрес объекта со статической длительностью хранения, адрес после окончания такого объект ([expr.add]), адрес функции или нулевой указатель значение и
  • если значение является объектом класса или типа массива, каждый подобъект удовлетворяет этим ограничениям для значения.

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

Если бы массив был статическим объектом, то этот инициализатор представлял бы собой допустимое константное выражение, которое можно использовать для инициализации constexpr объекта. Так как std::initializer_list имеет эффект продления времени жизни для этого временного на [dcl.init.list] / 6 , когда вы объявляете group1 как статический объект , clang и gcc Кажется, массив также выделяется как статический объект, что делает правильную инициализацию подчиненной только тому, является ли std::initializer_list литеральным типом, а используемый конструктор - constexpr.

В конечном счете, все немного мутно.

0 голосов
/ 17 января 2019

Похоже, std::initializer_list еще не (в C ++ 17) соответствует требованиям литеральный тип (что является требованием типа constexpr переменной ) удовлетворить).

Обсуждение того, делает ли это в C ++ 14, можно найти в этом посте: Почему std::initializer_list не определен как литеральный тип? который сам был продолжением поста, обсуждающего Законно ли объявлять объект constexpr initializer_list?

Я сравнил цитаты, приведенные в соответствующей статье C ++ 14 (стандарта C ++ 14), с окончательным рабочим проектом (стандарта C ++ 17), и они совпадают. Так что нет явного требования, чтобы std::initializer_list был литеральным типом.

Цитаты из окончательного рабочего проекта C ++ 17 (n4659):

[basic.types] /10.5

(10.5) возможно cv-квалифицированный тип класса (раздел 12), который имеет все следующие свойства:
(10.5.1) - имеет тривиальный деструктор,
(10.5.2) - это либо тип закрытия (8.1.5.1), либо агрегатный тип (11.6.1), или имеет хотя бы один конструктор constexpr или шаблон конструктора (возможно, унаследованный (10.3.3) от базового класса), который не является копией или переместить конструктор,
(10.5.3) - если это объединение, то по крайней мере один из его нестатических элементов данных имеет тип энергонезависимого литерала, и
(10.5.4) - если это не объединение, все его нестатические элементы данных и база классы имеют нелетучие литеральные типы .

[initializer_list.syn] / 1

  1. Объект типа initializer_list обеспечивает доступ к массиву объектов типа const E. [Примечание: Пара указателей или указатель плюс длина будут очевидными представлениями для initializer_list. initializer_list используется для реализации списков инициализаторов, как указано в 11.6.4. Копирование списка инициализатора не копирует базовые элементы. —Конечная записка]

По этой причине объявлять объект constexpr initializer_list недопустимым.

...