Это какое-то продолжение Эта тема и имеет дело с небольшой частью этого. Как и в предыдущем разделе, давайте рассмотрим, что наш компилятор 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;
}
Я понял, что здесь происходит:
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
— конец примера]
Ваши примеры все плохо сформированы.
ТЛ / дг: Инициализатор не является константой, потому что он ссылается на разные временные значения при каждой оценке функции.
Декларация:
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 все еще отклоняет это.