Подтвердить что ты не робот

Путаница относительно постоянных выражений

Это какое-то продолжение для этой темы и касается небольшой части. Как и в предыдущем разделе, рассмотрим, что наш компилятор имеет функции 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, которые планируются для С++ 14. Я использовал GCC 4.7.

EDIT: Хорошо, вот большой пример, чтобы показать, что отклонено, а что нет:

#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;
}
4b9b3361

Ответ 1

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

 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’

То же поведение наблюдается в clang.

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

Преобразование 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

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

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

Объявление:

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

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

Теперь, на С++ 11 [expr.const] p4,

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

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

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

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

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

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

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

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

Использование Clang и libС++ (с constexpr добавлено в libС++ <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 все еще отвергает это.