Berechtigt, ein Array in einem constexpr-Konstruktor zu initialisieren?

11

Ist der folgende Code legitim?

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 akzeptiert es, aber GCC und MSVC lehnen es ab.

Der Fehler von GCC ist:

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

Wenn diese Art von Code in Ordnung wäre, könnte ich einige Verwendungen von index_sequences abschneiden .

Yongwei Wu
quelle
1
Gcc10 akzeptiert es auch.
Songyuanyao
Könnten Sie den Fehler von MSVC transkribieren?
max66
... und GCC auch.
Evg
1
@songyuanyao - g ++ 10 akzeptiert das Kompilieren von C ++ 20; lehnt es ab, C ++ 17 oder älter zu kompilieren; Der Punkt scheint, dass _vin der Initialisierungsliste bis C ++ 17 initialisiert werden sollte. Vielleicht hat sich in C ++ 20 etwas geändert.
max66
2
@Evg Das ist eigentlich interessant, weil es darauf hindeuten könnte, dass Clang sein "Bewusstsein" nutzt, dass ein Objekt mit statischer Speicherdauer auf Null gesetzt wird, um zu sagen: "Okay, dieses Objekt wurde möglicherweise standardmäßig initialisiert, aber Lesevorgänge von seinem intMitglied haben niemals ein undefiniertes Verhalten ". Ich frage mich, ob GCC dies nicht konform ist oder umgekehrt ...
Lightness Races im Orbit

Antworten:

13

Die triviale Standardinitialisierung war in einem constexprKontext bis C ++ 20 verboten .

Ich vermute, der Grund ist, dass es leicht ist, "versehentlich" aus standardmäßig initialisierten Grundelementen zu lesen, eine Handlung, die Ihrem Programm undefiniertes Verhalten verleiht, und Ausdrücke mit undefiniertem Verhalten sind direkt verboten constexpr( ref ). Die Sprache wurde jedoch erweitert, sodass ein Compiler nun prüfen muss, ob ein solcher Lesevorgang stattfindet, und falls dies nicht der Fall ist, sollte die Standardinitialisierung akzeptiert werden. Es ist ein bisschen mehr Arbeit für den Compiler, aber (wie Sie gesehen haben!) Hat erhebliche Vorteile für den Programmierer.

In diesem Dokument wird vorgeschlagen, die Standardinitialisierung für trivial standardmäßige konstruierbare Typen in constexpr-Kontexten zuzulassen, während der Aufruf von undefiniertem Verhalten weiterhin nicht zulässig ist. Kurz gesagt, solange nicht initialisierte Werte nicht gelesen werden, sollten solche Zustände in constexpr sowohl in Heap- als auch in Stack-zugewiesenen Szenarien zulässig sein.

Seit C ++ 20 ist es legal, v_"nicht initialisiert" zu lassen, wie Sie es getan haben. Dann haben Sie alle seine Elementwerte zugewiesen, was großartig ist.

Leichtigkeitsrennen im Orbit
quelle
4
@ max66 Ich auch! Ich habe nur die C ++ 20-Änderungsliste auf Wikipedia gescannt, etwas Relevantes gefunden constexprund den verknüpften Vorschlag überflogen;)
Lightness Races in Orbit
3
Das Schlimme ist, dass ich seit mehr als 20 Jahren C ++ benutze. Wenn ich jeden Tag etwas Neues lerne ... oder ein schlechter Programmierer bin oder C ++ zu kompliziert wird.
max66
5
@ max66 Es ist mit ziemlicher Sicherheit das letztere. Auch die Tatsache, dass es sich alle paar Jahre grundlegend ändert, macht es zu einem sich schnell bewegenden Ziel. Wer kann damit mithalten?! Auch die Compiler halten damit nicht mit.
Leichtigkeitsrennen im Orbit
@ max66 Dieses Papier fällt mir ein: Erinnere dich an die Vasa!
Evg
@Evg Oh, wow, das Papier war an mir vorbei gegangen (IRONY). Genau richtig!
Leichtigkeitsrennen im Orbit