Czy możesz zainicjować tablicę w konstruktorze constexpr?

11

Czy następujący kod jest prawidłowy?

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 to akceptuje, ale GCC i MSVC to odrzucają.

Błąd GCC to:

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];
      |         ^~

Gdyby tego rodzaju kod był OK, mógłbym wyciąć całkiem sporo zastosowań index_sequences.

Yongwei Wu
źródło
1
Gcc10 też to akceptuje.
songyuanyao
czy mógłbyś przepisać błąd z MSVC?
max66
... i GCC też.
Evg
1
@ songyuanyao - g ++ 10 zaakceptuj kompilację C ++ 20; odmawia kompilacji C ++ 17 lub starszej; wydaje się, że _vnależy zainicjować na liście inicjalizacji, aż do C ++ 17. Może coś się zmieniło w C ++ 20.
max66
2
@Evg To naprawdę interesujące, ponieważ może sugerować, że Clang używa swojej „świadomości”, że obiekt o czasie przechowywania statycznego zostaje wyzerowany, mówiąc „OK, ten obiekt mógł zostać zainicjowany domyślnie, ale odczyty jego intczłonka nigdy nie będą miały niezdefiniowanego zachowania „. Zastanawiam się, czy GCC tego nie robi, czy odwrotnie ...
Lekkość ściga się na orbicie

Odpowiedzi:

13

Trywialna domyślna inicjalizacja była zabroniona w constexprkontekście do C ++ 20 .

Wydaje mi się, że powodem jest to, że łatwo „przypadkowo” odczytać z prymitywów zainicjowanych domyślnie, czynność, która nadaje programowi niezdefiniowane zachowanie, a wyrażeniom o nieokreślonym zachowaniu zabrania się constexpr( ref ). Język został jednak rozszerzony, więc teraz kompilator musi sprawdzić, czy taki odczyt ma miejsce, a jeśli nie, należy zaakceptować domyślną inicjalizację. Jest to trochę więcej pracy dla kompilatora, ale (jak widzieliście!) Ma znaczące zalety dla programisty.

W tym artykule zaproponowano zezwolenie na domyślną inicjalizację dla trywialnie domyślnych typów konstrukcyjnych w kontekście constexpr, jednocześnie nie pozwalając na wywoływanie niezdefiniowanego zachowania. Krótko mówiąc, dopóki nie zostaną odczytane niezainicjowane wartości, takie stany powinny być dozwolone w constexpr zarówno w scenariuszach alokowanych na stosie, jak i na stosie.

Od C ++ 20 pozostawienie v_„niezainicjowanej” tak jak ty jest legalne . Następnie przypisałeś wszystkie wartości jego elementów, co jest świetne.

Lekkość Wyścigi na orbicie
źródło
4
@ max66 Ja też! Wszystko, co zrobiłem, to zeskanowanie listy zmian C ++ 20 na Wikipedii, znalezienie czegoś odpowiedniego constexpri przejrzenie powiązanej propozycji;)
Lekkość wyścigów na orbicie
3
Złe jest to, że od ponad 20 lat używam C ++. Jeśli każdego dnia uczę się czegoś nowego ... lub jestem złym programistą lub C ++ staje się zbyt skomplikowane.
max66
5
@ max66 To prawie na pewno to drugie. Również fakt, że zmienia się zasadniczo co kilka lat, czyni go szybko zmieniającym się celem. Kto może nadążyć za tym ?! Nawet kompilatory nie nadążają za tym.
Wyścigi lekkości na orbicie
@ max66 Ten artykuł przychodzi mi na myśl: Pamiętaj o
Evg
@ Evg Och, wow, ten papier minął mnie (IRONY). Spot on!
Wyścigi lekkości na orbicie