Путаница с константными выражениями

Это какое-то продолжение Эта тема и имеет дело с небольшой частью этого. Как и в предыдущем разделе, давайте рассмотрим, что наш компилятор constexpr функции для std::initializer_list а также std::array, Теперь давайте перейдем прямо к делу.

Это работает:

#include <array>
#include <initializer_list>

int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr int a0 = a[0];
constexpr int a1 = a[1];
constexpr int a2 = a[2];
constexpr std::initializer_list<int> b = { a0, a1, a2 };

return 0;
}

Это не:

#include <array>
#include <initializer_list>

int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

return 0;
}

Вылетает с этой ошибкой:

error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression

Хотя я читал некоторые статьи о constexpr и постоянные выражения между тем, это поведение все еще не имеет никакого смысла для меня. Почему первый пример считается допустимым константным выражением, а не вторым? Я приветствовал бы любое объяснение, чтобы я мог покоиться с миром впоследствии.

НОТА: Я уточню это сразу, Clang не сможет скомпилировать первый фрагмент, поскольку он не реализует constexpr дополнения библиотеки, которые запланированы для C ++ 14. Я использовал GCC 4.7.

РЕДАКТИРОВАТЬ: Хорошо, вот большой пример, показывающий, что отклонено, а что нет:

#include <array>
#include <initializer_list>

constexpr int foo = 42;
constexpr int bar() { return foo; }
struct eggs { int a, b; };

int main()
{
constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
constexpr int a0 = a[0];
constexpr int a1 = a[1];
constexpr int a2 = a[2];

// From Xeo and Andy tests
constexpr std::array<int, 1> a = { bar() }; // OK
constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK
std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK
constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK
constexpr std::initializer_list<int> b = { foo }; // OK
constexpr std::initializer_list<int> c = { bar() }; // ERROR
constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR

// From Matheus Izvekov and Daniel Krügler
constexpr eggs good = { 1, 2 }; // OK
constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR
constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR

return 0;
}

14

Решение

Я понял, что здесь происходит:

 constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

a[0] типа const int& неявно преобразуется во временный тип const int,
Затем g ++ преобразует его в const int* перейти в initializer_list Частный конструктор.
На последнем шаге он принимает временный адрес, поэтому он не является константным выражением.

Проблема в неявном преобразовании в const int. Пример:

constexpr int v = 1;
const int& r = v; // ok
constexpr int& r1 = v; // error: invalid initialization of reference of
// type ‘int&’ from expression of type ‘const int’

Такое же поведение в лязг.

Я думаю, что это обращение законно, ничего не говорит об обратном.

Около const int& в const int конвертация, [expr] пункт 5:

Если выражение изначально имеет тип «ссылка на T», тип
скорректирована до Т до любого дальнейшего анализа. Выражение обозначает
объект или функция, обозначенная ссылкой, и выражение
lvalue или xvalue, в зависимости от выражения.

Результат a[0] выражение — это временное значение типа x const int в таком случае.

О неявных преобразованиях в инициализаторе constexpr, [dcl.constexpr] параграф 9:

… Каждое неявное преобразование используется при преобразовании инициализатора
выражения и каждый вызов конструктора, используемый для инициализации
должен быть одним из тех, которые разрешены в постоянном выражении.

О получении адреса временного, [expr.const] пункта 2:

…вызов функции constexpr с аргументами, которые, когда
заменяется вызовом функции, не
произвести постоянное выражение; [ Пример:

constexpr const int* addr(const int& ir) { return &ir; } // OK
static const int x = 5;
constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an
// address contant expression
constexpr const int* tp = addr(5); // error, initializer for constexpr variable
// not a constant expression;
// (const int*)&(const int&)5 is not a
// constant expression because it takes
// the address of a temporary

— конец примера]

2

Другие решения

Ваши примеры все плохо сформированы.

ТЛ / дг: Инициализатор не является константой, потому что он ссылается на разные временные значения при каждой оценке функции.

Декларация:

constexpr std::initializer_list<int> b = { a0, a1, a2 };

создает временный массив типа const int [3] (C ++ 11 [Dcl.init.list] р5), затем связывает std::initializer_list<int> возражать против этого временного как будто путем привязки ссылки на него (C ++ 11 [Dcl.init.list] р6).

Теперь по C ++ 11 [Expr.const] р4,

Для литерального константного выражения массива или типа класса каждый подобъект […] должен быть инициализирован константным выражением. […] Ан адресное выражение […] оценивается по адресу объекта с статическая продолжительность хранения.

поскольку b имеет автоматическую продолжительность хранения, когда std::initializer_list<int> объект связывается с const int [3] временное, временное также дается автоматическая продолжительность хранения, поэтому инициализация b является не константное выражение, потому что оно относится к адресу объекта, который не имеет статической длительности хранения. Итак, декларация b плохо сформирован.

Почему GCC принимает некоторые из constexpr std::initializer_list объекты

В тех случаях, когда инициализатор является достаточно тривиальным, GCC (и Clang) продвигают массив в глобальное хранилище, а не каждый раз создают новое временное хранилище. Однако в GCC этот метод реализации просачивается в семантику языка — GCC рассматривает массив как имеющий статическую продолжительность хранения и принимает инициализацию (как случайное или преднамеренное расширение правил C ++ 11).

Обходной путь (только Clang)

Вы можете сделать ваши примеры действительными, дав std::initializer_list<int> Длительность статического хранения объектов:

static constexpr std::initializer_list<int> b = { a0, a1, a2 };

Это, в свою очередь, дает статическую длительность хранения массива временному, что делает инициализацию постоянным выражением.

Использование Clang и libc ++ (с constexpr добавлено в libc ++ <array> а также <initializer_list> в соответствующих местах), этот твик добавления static достаточно, чтобы ваши примеры были приняты.

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

<stdin>:21:61: error: ‘const std::initializer_list<int>{((const int*)(& _ZGRZ4mainE1c0)), 1u}’ is not a constant expression

Вот, _ZGRZ4mainE1c0 является искаженным именем временного расширенного массива (со статической продолжительностью хранения), и мы можем видеть, что GCC неявно вызывает (private) initializer_list<int>(const int*, size_t) конструктор. Я не уверен, почему GCC все еще отклоняет это.

2

По вопросам рекламы [email protected]