Законно ли инициализировать массив в конструкторе constexpr?

11

Является ли следующий код законным?

template <int N>
class foo {
public:
    constexpr foo()
    {
        for (int i = 0; i < N; ++i) {
            v_[i] = i;
        }
    }

private:
    int v_[N];
};

constexpr foo<5> bar;

Clang принимает это, но GCC и MSVC отклоняют это.

Ошибка GCC:

main.cpp:15:18: error: 'constexpr foo<N>::foo() [with int N = 5]' called in a constant expression
   15 | constexpr foo<5> bar;
      |                  ^~~
main.cpp:4:15: note: 'constexpr foo<N>::foo() [with int N = 5]' is not usable as a 'constexpr' function because:
    4 |     constexpr foo()
      |               ^~~
main.cpp:4:15: error: member 'foo<5>::v_' must be initialized by mem-initializer in 'constexpr' constructor
main.cpp:12:9: note: declared here
   12 |     int v_[N];
      |         ^~

Если бы этот вид кода был в порядке, я мог бы сократить несколько вариантов использования index_sequences.

Юнвэй Ву
источник
1
Gcc10 тоже это принимает.
Songyuanyao
не могли бы вы расшифровать ошибку из MSVC?
макс66
... и GCC тоже.
Евг
1
@songyuanyao - g ++ 10 принимает компиляцию C ++ 20; отказывается от компиляции C ++ 17 или старше; Похоже, что точка _vдолжна быть инициализирована в списке инициализации до C ++ 17. Может быть, что-то изменилось в C ++ 20.
max66
2
@Evg Это на самом деле интересно, потому что он может предположить, что Clang использует свою «осведомленность» о том, что объект static-storage-duration обнуляется, чтобы сказать «хорошо, этот объект мог быть инициализирован по умолчанию, но чтение из его intчлена никогда не будет иметь неопределенное поведение» ». Интересно, GCC не выполняет этого, или наоборот ...
Гонки легкости на орбите

Ответы:

14

Тривиальная инициализация по умолчанию была запрещена в constexprконтексте до C ++ 20 .

Я предполагаю, что причина в том, что легко «случайно» читать из инициализированных по умолчанию примитивов, действие, которое придает вашей программе неопределенное поведение, а выражения с неопределенным поведением запрещено constexpr( ref ). Язык был расширен, так что теперь компилятор должен проверить, происходит ли такое чтение, и, если это не так, должна быть принята инициализация по умолчанию. Это немного больше работы для компилятора, но (как вы видели!) Имеет существенные преимущества для программиста.

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

Начиная с C ++ 20, законно оставлять v_«неинициализированным», как у вас. Затем вы перешли к присвоению значений всех его элементов, и это здорово.

Гонки легкости на орбите
источник
4
@ max66 Я тоже! Все, что я сделал, это просканировал список изменений C ++ 20 в Википедии, нашел что-то подходящее constexprи просканировал связанное предложение;)
Гонки Легкости на Орбите
3
Плохая часть в том, что более 20 лет я использую C ++. Если каждый день я узнаю что-то новое ... или я плохой программист, или C ++ становится слишком сложным.
max66
5
@ max66 Это почти наверняка последнее. Кроме того, тот факт, что он постоянно меняется каждые несколько лет, делает его быстро меняющейся целью. Кто может идти в ногу с этим ?! Даже компиляторы не поспевают за этим.
Гонки легкости на орбите
@ max66 Эта статья приходит на ум: Вспомните Васа!
Evg
@Evg О, вау, эта бумага прошла мимо меня (IRONY). Пятно на!
Гонки легкости на орбите