Проверка времени выполнения constexpr std:: initializer_list

Я пытаюсь выполнить проверку времени компиляции некоторых жестко заданных значений. У меня есть следующая упрощенная попытка:

using Type = std::initializer_list<int>;

constexpr bool all_positive(const Type& list)
{
    bool all_positive = true;
    for (const auto& elem : list)
    {
        all_positive &= (elem > 0);
    }
    return all_positive;
}

int main()
{
    static constexpr Type num_list{ 1000, 10000, 100 };

    static_assert(all_positive(num_list), "all values should be positive");

    return 0;
}

gcc компилирует это и работает точно так, как я ожидал, но clang не выполняет компиляцию с ошибкой:

static_assert_test.cc:79:16: error: static_assert expression is not an integral constant expression
    static_assert(all_positive(num_list), "all values should be positive");
                  ^~~~~~~~~~~~~~~~~~~~~~
static_assert_test.cc:54:20: note: read of temporary is not allowed in a constant expression outside the expression that created the temporary
            all_positive &= (elem > 0);
                             ^
static_assert_test.cc:79:16: note: in call to 'all_positive(num_list)'
    static_assert(all_positive(num_list), "all values should be positive");
                  ^
static_assert_test.cc:77:32: note: temporary created here
    static constexpr Type num_list{ 1000, 10000, 100 };

Какое ожидаемое поведение здесь? Должна ли эта компиляция или нет? А если нет, есть ли альтернативный способ проверки жестко закодированных значений?

Ответ 1

Как говорит Yola ответ, создается временный массив, а выражение elem > 0 пытается применить преобразование lvalue-to-rval в elem. Теперь мы ссылаемся на стандартный [expr.const]/2:

Выражение e является выражением основной константы, если оценка e, следуя правилам абстрактной машины, не будет оценивать одно из следующих выражений:

  • ...

  • преобразование lvalue-rvalue, если оно не применяется к

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

    • нестабильное значение glvalue, которое ссылается на подобъект строкового литерала или

    • нестабильное значение glvalue, которое относится к энергонезависимому объекту, определенному с помощью constexpr, или относится к не изменяемому подобъекту такого объекта, или

    • нестабильный glvalue типа literal, который относится к энергонезависимому объекту, чье время жизни начиналось с оценки e;

  • ...

Обратите внимание, что первая пуля здесь не применяется, потому что elem не относится к объекту complete (это подобъект массива). Третья пуля тоже не применяется, поскольку временный массив не определен с помощью constexpr, хотя это объект const. В результате all_positive(num_list) не может стать постоянным выражением.

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

const int ci[1] = {0};
const int &ri = ci[0];
constexpr int i = ri; // error

Ответ 2

Проблема заключается в том, что вы пытаетесь использовать временный массив для инициализации constexpr.

Объект типа std:: initializer_list создается из списка инициализаторов, как если бы реализация (7.4) - значение типа "массив из N const E", где N - количество элементов в списке инициализаторов.

Но этот временный массив не является константой per se. Он может работать следующим образом:

static constexpr array<int,4> arr = { 1000, 10000, 100 };
static constexpr Type num_list(&arr[0], &arr[3]);
static_assert(all_positive(num_list), "all values should be positive");