Verbesserungen der C++-Konformität, Verhaltensänderungen und Fehlerbehebungen in Visual Studio 2019

Microsoft C/C++ in Visual Studio (MSVC) nimmt bei jedem Release Verbesserungen bei der Konformität mit Standards sowie bei Fehlerbehebungen vor. Dieser Artikel listet die Verbesserungen nach Hauptrelease und dann nach Versionen auf. Um direkt zu den Änderungen für eine bestimmte Version zu gelangen, verwenden Sie die unten stehende Liste In diesem Artikel.

In diesem Dokument werden die Änderungen in Visual Studio 2019 aufgelistet. Informationen zu den Änderungen in Visual Studio 2022 finden Sie unter Verbesserungen der C++-Konformität in Visual Studio 2022. Informationen zu den Änderungen in Visual Studio 2017 finden Sie unter Verbesserungen der C++-Konformität in Visual Studio 2017. Eine vollständige Liste der vorherigen Verbesserungen der Konformität finden Sie unter Visual C++: Neuerungen von 2003 bis 2015.

Verbesserungen der Konformität in Visual Studio 2019 RTW (Version 16.0)

Visual Studio 2019 (RTW) enthält die folgenden Verbesserungen der Konformität, Fehlerbehebungen und Verhaltensänderungen im Microsoft C++-Compiler.

Hinweis

C++20-Features waren in Visual Studio 2019 nur im Modus /std:c++latest verfügbar, bis die C++20-Implementierung als abgeschlossen galt. In der Version 16.11 von Visual Studio 2019 wird der Compilermodus /std:c++20 eingeführt. In diesem Artikel funktionieren Features, für die ursprünglich der Modus /std:c++latest erforderlich war, nun in den neuesten Versionen von Visual Studio im Modus /std:c++20 (oder höher). Wir haben die Dokumentation mit /std:c++20 aktualisiert, auch wenn diese Option bei der ursprünglichen Veröffentlichung der Features noch nicht verfügbar war.

Verbesserte Unterstützung von Modulen für Vorlagen und die Fehlererkennung

Module entsprechen nun offiziell dem C++20-Standard. Die verbesserte Unterstützung wurde in Visual Studio 2017 Version 15.9 hinzugefügt. Weitere Informationen finden Sie unter Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (Verbesserte Unterstützung von Vorlagen und Fehlererkennung in C++-Modulen mit MSVC 2017 Version 15.9).

Geänderte Spezifikation des Aggregattyps

Die Spezifikation von Aggregattypen wurde in C++20 geändert (siehe Prohibit aggregates with user-declared constructors (Verbieten von Aggregaten mit benutzerdeklarierten Konstruktoren)). In Visual Studio 2019 ist unter /std:c++latest (bzw. unter /std:c++20 ab Version 16.11 von Visual Studio 2019) eine Klasse mit einem beliebigen benutzerdeklarierten Konstruktor (z. B. Einschließen von mit = default oder = delete deklarierten Konstruktoren) kein Aggregat. Zuvor wurden Klassen nur durch von Benutzern bereitgestellte Konstruktoren als Aggregat disqualifiziert. Durch diese Änderung wird die Art und Weise, in der solche Typen initialisiert werden, weiter eingeschränkt.

Der folgende Code wird in Visual Studio 2017 fehlerfrei kompiliert, löst in Visual Studio 2019 unter /std:c++20 oder /std:c++latest jedoch die Fehler C2280 und C2440 aus:

struct A
{
    A() = delete; // user-declared ctor
};

struct B
{
    B() = default; // user-declared ctor
    int i = 0;
};

A a{}; // ill-formed in C++20, previously well-formed
B b = { 1 }; // ill-formed in C++20, previously well-formed

Teilweise Unterstützung für operator <=>

P0515R3 C++20 führt den Operator für Dreiwegevergleiche <=> ein, der auch als „Raumschiffoperator“ bekannt ist. In der Version 16.0 von Visual Studio 2019 wird der Operator im Modus /std:c++latest teilweise unterstützt, indem Fehler für die inzwischen unzulässige Syntax ausgelöst werden. Der folgende Code wird beispielsweise in Visual Studio 2017 fehlerfrei kompiliert, löst in Visual Studio 2019 unter /std:c++20 oder /std:c++latest jedoch mehrere Fehler aus:

struct S
{
    bool operator<=(const S&) const { return true; }
};

template <bool (S::*)(const S&) const>
struct U { };

int main(int argc, char** argv)
{
    U<&S::operator<=> u; // In Visual Studio 2019 raises C2039, 2065, 2146.
}

Fügen Sie ein Leerzeichen in die betroffene Zeile vor der letzten spitzen Klammer ein, um die Fehler zu vermeiden: U<&S::operator<= > u;.

Verweise auf Typen mit nicht übereinstimmenden CV-Qualifizierern

Hinweis

Diese Änderung betrifft nur die Visual Studio 2019-Versionen 16.0 bis 16.8. Ab der Visual Studio 2019-Version 16.9 wurde sie rückgängig gemacht.

MSVC hat zuvor die direkte Einbindung von Verweisen auf einen Typ mit nicht übereinstimmenden CV-Qualifizierern unter der obersten Ebene zugelassen. Diese Bindung kann die Änderung von vermeintlichen const-Daten ermöglichen, auf die sich der Verweis bezieht.

Der Compiler für die Visual Studio 2019-Versionen 16.0 bis 16.8 erstellt stattdessen einen temporären Verweis, wie zu diesem Zeitpunkt vom Standard gefordert. Später wurde der Standard rückwirkend geändert, sodass das vorherige Verhalten von Visual Studio 2017 und früher korrekt und das Verhalten der Visual Studio 2019-Versionen 16.0 bis 16.8 falsch ist. Daher wurde diese Änderung ab der Visual Studio 2019-Version 16.9 rückgängig gemacht.

Informationen zu einer entsprechenden Änderung finden Sie unter Ähnliche Typen und Verweisbindung.

Beispielsweise wird der folgende Code in Visual Studio 2017 ohne Warnungen kompiliert. In den Visual Studio 2019-Versionen 16.0 bis 16.8 gibt der Compiler die Warnung C4172 aus. Ab der Visual Studio 2019-Version 16.9 lässt sich der Code wieder ohne Warnungen kompilieren:

struct X
{
    const void* const& PData() const
    {
        return _pv;
    }

    void* _pv;
};

int main()
{
    X x;
    auto p = x.PData(); // C4172 <func:#1 "?PData@X@@QBEABQBXXZ"> returning address of local variable or temporary
}

Der Operator reinterpret_cast einer überladenen Funktion

Das Argument für reinterpret_cast entspricht keinem der Kontexte, in denen die Adresse einer überladenen Funktion zulässig ist. Der folgende Code wird in Visual Studio 2017 fehlerfrei kompiliert, in Visual Studio 2019 wird jedoch der Fehler „C2440“ ausgelöst:

int f(int) { return 1; }
int f(float) { return .1f; }
using fp = int(*)(int);

int main()
{
    fp r = reinterpret_cast<fp>(&f); // C2440: cannot convert from 'overloaded-function' to 'fp'
}

Verwenden Sie für dieses Szenario eine zulässige Umwandlung, um den Fehler zu vermeiden:

int f(int);
int f(float);
using fp = int(*)(int);

int main()
{
    fp r = static_cast<fp>(&f); // or just &f;
}

Lambda-Closures

In C++14 sind Lambda-Closure-Typen nicht literal. Die primäre Folge dieser Regel ist, dass ein Lambda keinen constexpr-Variablen zugewiesen werden kann. Der folgende Code wird in Visual Studio 2017 fehlerfrei kompiliert, in Visual Studio 2019 wird jedoch der Fehler „C2127“ ausgelöst:

int main()
{
    constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}

Entfernen Sie zur Vermeidung dieses Fehlers entweder den Qualifizierer constexpr , oder ändern Sie den Konformitätsmodus mindestens in /std:c++17 .

std::create_directory-Fehlercodes

P1164 des C++20-Standards wurde bedingungslos implementiert. Dadurch wurde std::create_directory so geändert, dass überprüft wird, ob das Ziel beim Auftreten des Fehlers bereits ein Verzeichnis war. Zuvor wurden alle Fehler vom Typ ERROR_ALREADY_EXISTS in erfolgreiche Codes umgewandelt, die jedoch kein Verzeichnis erstellt haben.

operator<<(std::ostream, nullptr_t)

Gemäß LWG 2221 wurde operator<<(std::ostream, nullptr_t) zum Schreiben von nullptr in Streams hinzugefügt.

Weitere parallele Algorithmen

Neue parallele Versionen von is_sorted, is_sorted_until, is_partitioned, set_difference, set_intersection, is_heap und is_heap_until wurden hinzugefügt.

Fixe bei der atomischen Initialisierung

P0883 "Fixing atomic initialization" (Beheben der atomischen Initialisierung) ändert std::atomic, um den enthaltenen Wert T zu initialisieren, anstatt eine Standardinitialisierung durchzuführen. Die Behebung wird aktiviert, wenn Clang/LLVM mit der Microsoft-Standardbibliothek verwendet wird. Sie ist derzeit für den C++-Compiler von Microsoft als Workaround für einen Fehler in der Verarbeitung von constexpr deaktiviert.

remove_cvref und remove_cvref_t

Die Typmerkmale remove_cvref und remove_cvref_t aus P0550 wurden implementiert. Diese entfernen die reference- und CV-Qualifizierer eines Typs ohne verfallende Funktionen und Arrays in Zeigern (im Gegensatz zu std::decay und std::decay_t).

Makros für Featuretests

Die Entwicklung der Featuretestmakros (P0941R2) ist nun abgeschlossen. Dadurch wird __has_cpp_attribute unterstützt. Featuretestmakros werden in allen Standardmodi unterstützt.

Unterbinden von Aggregaten mit benutzerdeklarierten Konstruktoren

C++20 P1008R1 – Unterbinden von Aggregaten mit benutzerdeklarierten Konstruktoren ist abgeschlossen.

reinterpret_cast in einer constexpr-Funktion

reinterpret_cast ist in einer constexpr -Funktion nicht zulässig. Der Microsoft-C++-Compiler lehnte reinterpret_cast früher nur bei Verwendung in einem constexpr -Kontext ab. In Visual Studio 2019 diagnostiziert der Compiler im Modus für alle Sprachstandards reinterpret_cast ordnungsgemäß in der Definition einer constexpr -Funktion. Der folgende Code löst jetzt „C3615“ aus:

long long i = 0;
constexpr void f() {
    int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}

Entfernen Sie den Modifizierer constexpr aus der Funktionsdeklaration, um den Fehler zu vermeiden.

Richtige Diagnose für den Bereichskonstruktor „basic_string“

In Visual Studio 2019 unterdrückt der Bereichskonstruktor basic_string nicht mehr die Compilerdiagnose mit static_cast. Der folgende Code wird in Visual Studio 2017 fehlerfrei kompiliert, obwohl bei der Konvertierung von wchar_t in char ein Datenverlust auftreten kann, wenn out initialisiert wird:

std::wstring ws = /* . . . */;
std::string out(ws.begin(), ws.end()); // VS2019 C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data.

Visual Studio 2019 löst ordnungsgemäß die Warnung „C4244“ aus. Sie können std::string wie im folgenden Beispiel initialisieren, um die Warnung zu vermeiden:

std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
    out.push_back(static_cast<char>(ch));
}

Fehlerhafte Aufrufe von += und -= unter /clr und /ZW werden nun ordnungsgemäß erkannt

In Visual Studio 2017 wurde ein Fehler eingeführt, durch den der Compiler Fehler stillschweigend ignoriert und keinen Code für ungültige Aufrufe von += und -= unter /clr oder /ZW generiert hat. Der folgende Code wird in Visual Studio 2017 fehlerfrei kompiliert, löst in Visual Studio 2019 jedoch ordnungsgemäß den Fehler C2845:

public enum class E { e };

void f(System::String ^s)
{
    s += E::e; // in VS2019 C2845: 'System::String ^': pointer arithmetic not allowed on this type.
}

Um den Fehler in diesem Beispiel zu vermeiden, verwenden Sie den Operator += mit der Methode ToString(): s += E::e.ToString();.

Initialisierer für statische Inline-Datenmember

Ungültiger Memberzugriff innerhalb der Initialisierer inline und static constexpr wird nun ordnungsgemäß erkannt. Der folgende Beispielcode wird in Visual Studio 2017 fehlerfrei kompiliert. In Visual Studio 2019 wird jedoch ab dem Modus /std:c++17 der Fehler „C2248“ ausgelöst:

struct X
{
    private:
        static inline const int c = 1000;
};

struct Y : X
{
    static inline int d = c; // VS2019 C2248: cannot access private member declared in class 'X'.
};

Deklarieren Sie den Member X::c als geschützt, um diesen Fehler zu vermeiden:

struct X
{
    protected:
        static inline const int c = 1000;
};

Wiederherstellung der Warnung C4800

MSVC verwendete bisher eine Leistungswarnung C4800 bezüglich der impliziten Konvertierung in bool. Sie war zu störend und konnte nicht unterdrückt werden, sodass wir sie in Visual Studio 2017 entfernt haben. Allerdings gab es während des Lebenszyklus von Visual Studio 2017 viel Feedback zu den nützlichen Fällen, die dank dieser Warnung behoben werden konnten. Daher haben wir die Warnung C4800 sorgfältig angepasst und bieten sie in Visual Studio 2019 zusammen mit einer erläuternden C4165 wieder an. Beide Warnungen können problemlos unterdrückt werden: entweder durch eine explizite Umwandlung oder durch den Vergleich mit 0 des entsprechenden Typs. Die Warnung C4800 ist eine standardmäßig deaktivierte Warnung der Stufe 4, und die Warnung C4165 ist eine standardmäßig deaktivierte Warnung der Stufe 3. Beide können mithilfe der Compileroption /Wall gefunden werden.

Im folgenden Beispiel werden die Warnungen C4800 und C4165 in /Wall ausgelöst:

bool test(IUnknown* p)
{
    bool valid = p; // warning C4800: Implicit conversion from 'IUnknown*' to bool. Possible information loss
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return hr; // warning C4165: 'HRESULT' is being converted to 'bool'; are you sure this is what you want?
}

Sie können den Code folgendermaßen schreiben, um die Warnungen im vorherigen Beispiel zu vermeiden:

bool test(IUnknown* p)
{
    bool valid = p != nullptr; // OK
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return SUCCEEDED(hr);  // OK
}

Lokale Klassenmemberfunktion enthält keinen Text

In Visual Studio 2017 wird die Warnung „C4822“ nur ausgelöst, wenn die Compileroption /w14822 explizit festgelegt ist. Bei Verwendung von /Wall wird sie nicht angezeigt. In Visual Studio 2019 ist die Warnung C4822 standardmäßig deaktiviert und in /Wall auffindbar, ohne dass /w14822 explizit festgelegt sein muss.

void example()
{
    struct A
        {
            int boo(); // warning C4822: Local class member function doesn't have a body
        };
}

Funktionsvorlagentexte, die if constexpr-Anweisungen enthalten

In Visual Studio 2019 sind unter /std:c++20 oder /std:c++latest für Vorlagenfunktionskörper mit Anweisungen vom Typ if constexpr zusätzliche Analyseprüfungsfunktionen aktiviert. Der folgende Code löst in Visual Studio 2017 z. B. nur dann C7510 aus, wenn die Option /permissive- festgelegt ist. Der gleiche Code löst in Visual Studio 2019 Fehler aus, unabhängig davon, ob die /permissive-Option festgelegt ist:

// C7510.cpp
// compile using: cl /EHsc /W4 /permissive /std:c++latest C7510.cpp
#include <iostream>

template <typename T>
int f()
{
    T::Type a; // error C7510: 'Type': use of dependent type name must be prefixed with 'typename'
    // To fix the error, add the 'typename' keyword. Use this declaration instead:
    // typename T::Type a;

    if constexpr (a.val)
    {
        return 1;
    }
    else
    {
        return 2;
    }
}

struct X
{
    using Type = X;
    constexpr static int val = 1;
};

int main()
{
    std::cout << f<X>() << "\n";
}

Fügen Sie das Schlüsselwort typename zur Deklaration von a hinzu, um den Fehler zu vermeiden: typename T::Type a;.

Inlineassemblycode wird in Lambdaausdrücken nicht unterstützt

Das Microsoft C++-Team wurde vor Kurzem auf ein Sicherheitsproblem hingewiesen, durch das die Verwendung von Inlineassemblern innerhalb eines Lambdaausdrucks bei der Ausführung zur Beschädigung von ebp (das Register für Rückgabeadressen) führen kann. Ein böswilliger Angreifer könnte sich dieses Szenario zunutze machen. Der Inlineassembler wird nur bei x86 unterstützt, und zwischen dem Inlineassembler und den übrigen Elementen des Compilers findet keine zufriedenstellende Interaktion statt. Aufgrund dieser Tatsachen und der Art des Problems bestand die sicherste Lösung darin, den Inlineassembler innerhalb eines Lambdaausdrucks nicht zuzulassen.

Die einzige Verwendung eines Inlineassemblers innerhalb eines Lambdaausdrucks, die in der Praxis gefunden wurde, war das Erfassen der Rückgabeadresse. In diesem Szenario können Sie die Rückgabeadresse auf allen Plattformen erfassen, indem Sie einfach die intrinsische Compilerfunktion _ReturnAddress() verwenden.

Der folgende Code erzeugt C7553 in Visual Studio 2017 15.9 und in späteren Versionen von Visual Studio:

#include <cstdio>

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;

    auto lambda = [&]
    {
        __asm {  // C7553: inline assembler is not supported in a lambda

            mov eax, x
            mov y, eax
        }
    };

    lambda();
    return y;
}

Verschieben Sie den Assemblycode wie im folgenden Beispiel gezeigt in eine benannte Funktion, um diesen Fehler zu vermeiden:

#include <cstdio>

void g(int& x, int& y)
{
    __asm {
        mov eax, x
        mov y, eax
    }
}

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;
    auto lambda = [&]
    {
        g(x, y);
    };
    lambda();
    return y;
}

int main()
{
    std::printf("%d\n", f());
}

Iteratordebuggen und std::move_iterator

Das Feature für das Iteratordebuggen kann std::move_iterator nun ordnungsgemäß entpacken. Beispielsweise kann std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) nun den schnellen Pfad memcpy binden.

Fehlerbehebung der Schlüsselworterzwingung <xkeycheck.h>

Die Erzwingung der Standardbibliothek in <xkeycheck.h> für Makros, die ein Schlüsselwort ersetzen, wurde korrigiert. Die Bibliothek gibt nun das tatsächlich erkannte problematische Schlüsselwort statt einer allgemeinen Meldung aus. C++20-Schlüsselwörter werden ebenfalls unterstützt, und es wird vermieden, dass IntelliSense zufällige Schlüsselwörter als Makros erkennt.

Zuweisungstypen sind nicht mehr als veraltet markiert.

std::allocator<void>, std::allocator::size_type und std::allocator::difference_type sind nicht mehr als veraltet markiert.

Richtige Warnungen für einschränkende Zeichenfolgenkonvertierungen

Ein nicht beabsichtigter static_cast von std::string, der für den Standard nicht erforderlich ist und versehentlich C4244-Warnungen unterdrückt hat, wurde entfernt. Der Versuch, std::string::string(const wchar_t*, const wchar_t*) aufzurufen, löst jetzt ordnungsgemäß die Warnung „C4244“ zur Einschränkung von wchar_t auf char aus.

Verschiedene Fixes der Richtigkeit von <filesystem>

  • Das Fehlschlagen von std::filesystem::last_write_time beim Versuch, den letzten Schreibzugriff eines Verzeichnisses zu ändern, wurde behoben.
  • Der Konstruktor std::filesystem::directory_entry speichert nun ein fehlgeschlagenes Ergebnis, anstatt eine Ausnahme auszulösen, wenn ein nicht vorhandener Zielpfad angegeben wird.
  • Die Version von std::filesystem::create_directory mit zwei Parametern wurde geändert, sodass nun die Version mit einem Parameter aufgerufen wird, da die zugrundeliegende CreateDirectoryExW-Funktion copy_symlink verwenden würde, wenn existing_p eine symbolische Verknüpfung ist.
  • std::filesystem::directory_iterator schlägt nicht mehr fehl, wenn eine fehlerhafte symbolische Verknüpfung festgestellt wird.
  • std::filesystem::space akzeptiert jetzt relative Pfade.
  • std::filesystem::path::lexically_relative wird nicht mehr durch nachstehende Schrägstriche behindert (siehe LWG 3096).
  • Das Ablehnen von Pfaden mit umgekehrten Schrägstrichen von CreateSymbolicLinkW in std::filesystem::create_symlink wurde behoben.
  • Es wurde ein Problem behoben, bei dem die delete-Funktion des POSIX-Löschmodus unter Windows 10 LTSB 1609 zwar vorhanden war, aber keine Dateien löschen konnte.
  • Die Kopierkonstruktoren und Kopierzuweisungsoperatoren von std::boyer_moore_searcher und std::boyer_moore_horspool_searcher sind jetzt in der Lage, Kopiervorgänge tatsächlich durchzuführen.

Parallele Algorithmen unter Windows 8 und höher

Die Bibliothek mit parallelen Algorithmen verwendet unter Windows 8 und höher nun ordnungsgemäß die echte WaitOnAddress-Familie, anstatt immer die gefälschten Versionen von Windows 7 und früher zu verwenden.

std::system_category::message() Leerzeichen

std::system_category::message() schneidet nachstehenden Leerraum von der zurückgegebenen Meldung nun ab.

std::linear_congruential_engine Division durch 0

Einige Bedingungen wurden behoben, die dazu führten, dass std::linear_congruential_engine eine Division durch 0 (null) durchführte.

Fehlerbehebungen für das Entpacken von Iteratoren

In Visual Studio 2017, Version 15.8 wurde erstmalig eine Funktion zum Entpacken von Iteratoren für Programmierer zur Verfügung gestellt, wie im C++-Teamblogartikel STL Features and Fixes in VS 2017 15.8 (STL-Funktionen und Fehlerbehebungen in VS 2017 15.8) beschrieben. Mit dieser Funktion werden Iteratoren, die von Standardbibliotheksiteratoren abgeleitet wurden, nicht mehr entpackt. Ein Benutzer, der beispielsweise von std::vector<int>::iterator abgeleitet wird und versucht, das Verhalten anzupassen, erhält nun das angepasste Verhalten, wenn er Algorithmen der Standardbibliothek aufruft, anstelle des Verhaltens eines Zeigers.

Die reserve-Funktion für ungeordnete Container reserviert N-Elemente nun tatsächlich (siehe LWG 2156).

Behandlung von Zeit

  • Zuvor führten einige Zeitwerte, die an die Parallelitätsbibliothek übergeben wurden, zu einem Überlauf, z. B. condition_variable::wait_for(seconds::max()). Diese mittlerweile behobenen Überläufe haben das Verhalten in einem scheinbar willkürlichen 29-tägigen Zyklus geändert (wenn von den zugrundeliegenden Win32-APIs akzeptierte uint32_t Millisekunden zu einem Überlauf führten).

  • Der <ctime>-Header deklariert timespec und timespec_get jetzt im Namespace std und auch im globalen Namespace ordnungsgemäß.

Verschiedene Fehlerbehebungen für Container

  • Viele interne Containerfunktionen der Standardbibliothek sind jetzt private, um die Funktionsweise von IntelliSense zu verbessern. In zukünftigen Releases von MSVC sind weitere Fehlerbehebungen zu erwarten, die Member als private kennzeichnen.

  • Probleme mit der Richtigkeit der Sicherheit wurden korrigiert, die zu einer Beschädigung knotenbasierter Container wie list, map und unordered_map führten. Während einem propagate_on_container_copy_assignment- oder propagate_on_container_move_assignment-Neuzuweisungsvorgang würden Sie den Sentinelknoten des Containers mit der alten Zuweisung freistellen, die alte Zuweisung mit der POCCA/POCMA-Zuweisung überschreiben und dann versuchen, den Sentinelknoten der neuen Zuweisung abzurufen. Wenn diese Zuteilung fehlschlug, wurde der Container beschädigt. Er konnte nicht einmal zerstört werden, da der Besitz eines Sentinelknotens eine feste Datenstrukturinvariante darstellt. Dieser Code wurde so korrigiert, dass der neue Sentinelknoten unter Verwendung des Zuteilers des Quellcontainers erstellt wurde, bevor der bestehende Sentinelknoten zerstört wurde.

  • Die Container wurden behoben, sodass sie Zuweisungen immer in propagate_on_container_copy_assignment, propagate_on_container_move_assignment und propagate_on_container_swap kopieren/verschieben/tauschen. Dies gilt auch für Zuweisungen mit einer Deklaration von is_always_equal.

  • Es wurden die Überladungen für Funktionen zum Zusammenführen und Extrahieren von Membern hinzugefügt, die rvalue-Container akzeptieren. Weitere Informationen finden Sie unter P0083 „Splicing Maps and Sets“.

std::basic_istream::read-Verarbeitung von \r\n`` =>\n`

std::basic_istream::read wurde korrigiert, sodass bei der Verarbeitung von \r\n zu \n nicht temporär in den angegebenen Puffer geschrieben wird. Durch diese Änderung entfällt ein Teil des Leistungsvorteils, der in Visual Studio 2017 15.8 für Lesezugriffe größer als 4K erzielt wurde. Es gibt jedoch weiterhin Effizienzsteigerungen durch die Vermeidung von drei virtuellen Aufrufen pro Zeichen.

std::bitset-Konstruktor

Der Konstruktor von std::bitset liest die Einsen und Nullen von großen Bitsets nicht mehr in umgekehrter Reihenfolge.

std::pair::operator=-Regression

Eine Regression im std::pair-Zuweisungsoperator wurde behoben, die bei der Implementierung von LWG 2729 "Missing SFINAE on std::pair::operator=" (SFINAE für std::pair::operator= fehlt) eingeführt wurde. Typen, die in std::pair konvertiert werden können, werden nun wieder ordnungsgemäß akzeptiert.

Nicht abgeleitete Kontexte für add_const_t

Ein kleiner Fehler bei Typmerkmalen wurde behoben, bei dem add_const_t und zugehörige Funktionen einem nicht abgeleiteten Kontext entsprechen sollten. Das heißt, add_const_t sollte ein Alias für typename add_const<T>::type sein, nicht für const T.

Verbesserungen der Konformität in 16.1

char8_t

P0482r6. C++20 führt einen neuen Zeichentyp ein, der für die Darstellung von UTF-8-Codeeinheiten verwendet wird. u8-Zeichenfolgenliterale in C++20 verfügen über den Typ const char8_t[N] anstelle von const char[N] (der vorherige Typ). Ähnliche Änderungen wurden in N2231 für den C-Standard vorgeschlagen. Vorschläge für die Wiederherstellung der Abwärtskompatibilität von char8_t wurden in P1423r3 angegeben. In Visual Studio 2019 Version 16.1 fügt der C++-Compiler von Microsoft Unterstützung für char8_t hinzu, wenn die Compileroption /Zc:char8_t angegeben wird. Über /Zc:char8_t- kann das Verhalten von C++17 wiederhergestellt werden. Der EDG-Compiler, mit dem IntelliSense arbeitet, unterstützt dies in Version 16.1 von Visual Studio 2019 noch nicht. Möglicherweise sehen Sie falsche nur auf IntelliSense bezogene Fehler, die sich nicht auf die tatsächliche Kompilierung auswirken.

Beispiel

const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20

std::type_identity-Metafunktion und std::identity-Funktionsobjekt

P0887R1 type_identity. Die veraltete std::identity-Klassenvorlagenerweiterung wurde entfernt und durch die std::type_identity-Metafunktion und das std::identity-Funktionsobjekt von C++20 ersetzt. Beide stehen nur unter /std:c++latest (bzw. unter /std:c++20 ab Version 16.11 von Visual Studio 2019) zur Verfügung.

Im folgenden Beispiel wird die Veraltungswarnung C4996 für std::identity (in <type_traits> definiert) in Visual Studio 2017 erzeugt:

#include <type_traits>

using T = std::identity<int>::type;
T x, y = std::identity<T>{}(x);
int i = 42;
long j = std::identity<long>{}(i);

Im folgenden Beispiel wird die Verwendung des neuen std::identity-Funktionsobjekts (in <functional> definiert) gemeinsam mit der neuen std::type_identity-Metafunktion veranschaulicht:

#include <type_traits>
#include <functional>

using T = std::type_identity<int>::type;
T x, y = std::identity{}(x);
int i = 42;
long j = static_cast<long>(i);

Syntaxprüfung für generische Lambdas

Der neue Lambdafunktionsprozessor ermöglicht im Konformitätsmodus unter /std:c++latest (bzw. unter /std:c++20 ab Version 16.11 von Visual Studio 2019) oder in einem beliebigen Sprachmodus mit /Zc:lambda ab Version 16.9 von Visual Studio 2019 (ehemals verfügbar als /experimental:newLambdaProcessor ab Version 16.3 von Visual Studio 2019) einige syntaktische Überprüfungen in generischen Lambdafunktionen.

Der Legacy-Lambdafunktionsprozessor kompiliert dieses Beispiel ohne Warnungen. Der neue Lambdafunktionsprozessor erzeugt jedoch den Fehler C2760:

void f() {
    auto a = [](auto arg) {
        decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
    };
}

Im folgenden Beispiel wird die richtige Syntax veranschaulicht, die nun durch den Compiler erzwungen wird:

void f() {
    auto a = [](auto arg) {
        typename decltype(arg)::Type t;
    };
}

Argumentbezogene Suchen nach Funktionsaufrufen

P0846R0 (C++20) Die Möglichkeit, Funktionsvorlagen über argumentbezogene Suchen nach Funktionsaufrufausdrücken zu suchen, wurde mit expliziten Vorlagenargumenten erweitert. Erfordert /std:c++latest (bzw. /std:c++20 ab Version 16.11 von Visual Studio 2019)

Designierte Initialisierung

P0329R4 (C++20) Die designierte Initialisierung ermöglicht, dass bestimmte Member in der Aggregatinitialisierung mithilfe der Type t { .member = expr }-Syntax ausgewählt werden können. Erfordert /std:c++latest (bzw. /std:c++20 ab Version 16.11 von Visual Studio 2019)

Rangfolge der Konvertierung der Enumeration in den festen zugrunde liegenden Typ

Der Compiler bewertet Enumerationskonvertierungen gemäß N4800 „11.3.3.2 Ranking implicit conversion sequences (4.2)“ (Rangfolge von impliziten Konvertierungssequenzen):

  • Eine Konvertierung, die eine Enumeration, deren zugrunde liegender Typ fest ist, in ihren zugrunde liegenden Typ höherstuft, ist besser als eine Konvertierung, die in den höhergestuften zugrunde liegenden Typ höherstuft, wenn die beiden Typen unterschiedlich sind.

Diese Konvertierungsrangfolge wurde vor der Version 16.1 von Visual Studio 2019 nicht ordnungsgemäß implementiert. Das übereinstimmende Verhalten kann das Verhalten bei der Überladungsauflösung ändern oder eine Mehrdeutigkeit verfügbar machen, die zuvor nicht erkannt wurde.

Diese Änderung des Compilerverhaltens gilt für alle /std -Modi und ist sowohl ein quellbezogener als auch ein binärer Breaking Change.

Im folgenden Beispiel wird veranschaulicht, wie sich das Compilerverhalten in Version 16.1 und höher ändert:

#include <type_traits>

enum E : unsigned char { e };

int f(unsigned int)
{
    return 1;
}

int f(unsigned char)
{
    return 2;
}

struct A {};
struct B : public A {};

int f(unsigned int, const B&)
{
    return 3;
}

int f(unsigned char, const A&)
{
    return 4;
}

int main()
{
    // Calls f(unsigned char) in 16.1 and later. Called f(unsigned int) in earlier versions.
    // The conversion from 'E' to the fixed underlying type 'unsigned char' is better than the
    // conversion from 'E' to the promoted type 'unsigned int'.
    f(e);
  
    // Error C2666. This call is ambiguous, but previously called f(unsigned int, const B&). 
    f(e, B{});
}

Neue und aktualisierte Standardbibliotheksfunktionen (C++20)

  • starts_with() und ends_with() für basic_string und basic_string_view.
  • contains() für assoziative Container.
  • remove(), remove_if() und unique() für list und forward_list geben nun size_type zurück.
  • shift_left() und shift_right() wurden zu <algorithm> hinzugefügt.

Verbesserungen der Konformität in 16.2

noexceptconstexpr-Funktionen

constexpr -Funktionen werden bei Verwendung in einem konstanten Ausdruck nicht mehr standardmäßig als noexcept angesehen. Dieser Behavior Change stammt aus der Auflösung von Core Working Group (CWG) CWG 1351 und ist in /permissive- aktiviert. Das folgende Beispiel wird in Visual Studio 2019 Version 16.1 und früher kompiliert, erzeugt jedoch C2338 in Visual Studio 2019 Version 16.2:

constexpr int f() { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}

Fügen Sie den Ausdruck noexcept zur Funktionsdeklaration hinzu, um den Fehler zu beheben:

constexpr int f() noexcept { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept");
}

Binäre Ausdrücke mit unterschiedlichen Enumerationstypen

C++20 hat die üblichen arithmetischen Konvertierungen für Operanden als veraltet markiert, wobei:

  • ein Operand ein Enumerationstyp und

  • der andere ein anderer Enumerations- oder Gleitkommatyp ist.

Weitere Informationen finden Sie unter P1120R0.

Ab der Version 16.2 von Visual Studio 2019 wird durch den folgenden Code eine Warnung vom Typ C5054 der Stufe 4 ausgelöst, wenn die Compileroption /std:c++latest aktiviert ist (/std:c++20 ab Version 16.11 von Visual Studio 2019):

enum E1 { a };
enum E2 { b };
int main() {
    int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}

Konvertieren Sie den zweiten Operanden mit static_cast, um zu vermeiden, dass die Warnung ausgelöst wird:

enum E1 { a };
enum E2 { b };
int main() {
  int i = a | static_cast<int>(b);
}

Das heißt, die Verwendung einer binären Operation zwischen einem Enumerations- und einem Gleitkommatyp löst jetzt eine Warnung der Stufe 1 vom Typ C5055 aus, wenn die Compileroption /std:c++latest aktiviert ist (/std:c++20 ab Visual Studio 2019 Version 16.11):

enum E1 { a };
int main() {
  double i = a * 1.1;
}

Konvertieren Sie den zweiten Operanden mit static_cast, um zu vermeiden, dass die Warnung ausgelöst wird:

enum E1 { a };
int main() {
   double i = static_cast<int>(a) * 1.1;
}

Gleichheitsvergleiche und relationale Vergleiche von Arrays

Gleichheitsvergleiche und relationale Vergleiche zwischen zwei Operanden des Arraytyps werden in C++20 (P1120R0) als veraltet eingestuft. Das heißt, dass ein Vergleichsvorgang zwischen zwei Arrays (trotz des Rangs und der Ähnlichkeiten der Erweiterung) jetzt eine Warnung auslöst. Ab Version 16.2 von Visual Studio 2019 wird durch den folgenden Code eine Warnung vom Typ C5056 der Stufe 1 ausgelöst, wenn die Compileroption /std:c++latest aktiviert ist (/std:c++20 ab Version 16.11 von Visual Studio 2019):

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}

Sie können die Adressen der ersten Elemente vergleichen, um zu vermeiden, dass eine Warnung ausgelöst wird:

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (&a[0] == &b[0]) { return 1; }
}

Um zu ermitteln, ob der Inhalt von zwei Arrays gleich ist, verwenden Sie die Funktion std::equal:

std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));

Auswirkung der Definition des Spaceship-Operators auf == und !=

Eine Definition des Spaceship-Operators (<=>) allein schreibt keine Ausdrücke mit == oder != mehr neu, es sei denn, der Spaceship-Operator ist als = default (P1185R2) markiert. Das folgende Beispiel wird in Visual Studio 2019 RTW und Version 16.1 kompiliert, erzeugt jedoch C2678 in Visual Studio 2019 Version 16.2:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs; // error C2676
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs; // error C2676
}

Sie können diesen Fehler vermeiden, indem Sie operator== definieren oder als Standard deklarieren:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
  bool operator==(const S&) const = default;
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs;
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

Verbesserungen der Standardbibliothek

  • <charcharv>to_chars() mit fester/wissenschaftlicher Genauigkeit (Die allgemeine Genauigkeit ist derzeit für Version 16.4 geplant.)
  • P0020R6: atomic<float>, atomic<double>, atomic<long double>
  • P0463R1: endian
  • P0482R6: Bibliotheksunterstützung für char8_t
  • P0600R1: [[nodiscard]] für STL, Teil 1
  • P0653R2: to_address()
  • P0754R2: <version>
  • P0771R1: noexcept für den Verschiebekonstruktor von std::function

Const-Vergleichsoperatoren für assoziative Container

Der Code für die Suche und Einfügung in set, map, multiset und multimap wurde zusammengeführt, um den Codeumfang zu reduzieren. Auf die gleiche Weise wie bei den Suchvorgängen zuvor bezeichnen Einfügevorgänge nun den „Kleiner als“-Vergleich auf einem const-Vergleichsfunktor. Der folgende Code wird in Visual Studio 2019 Version 16.1 und früher kompiliert, erzeugt jedoch C3848 in Visual Studio 2019 Version 16.2:

#include <iostream>
#include <map>

using namespace std;

struct K
{
   int a;
   string b = "label";
};

struct Comparer  {
   bool operator() (K a, K b) {
      return a.a < b.a;
   }
};

map<K, double, Comparer> m;

K const s1{1};
K const s2{2};
K const s3{3};

int main() {

   m.emplace(s1, 1.08);
   m.emplace(s2, 3.14);
   m.emplace(s3, 5.21);

}

Erstellen Sie den Vergleichsoperator const, um den Fehler zu vermeiden:

struct Comparer  {
   bool operator() (K a, K b) const {
      return a.a < b.a;
   }
};

Verbesserungen der Konformität in Visual Studio 2019, Version 16.3

Streamextraktionsoperatoren für char* entfernt

Die Streamextraktionsoperatoren für Zeiger auf Zeichen wurden entfernt und durch Extraktionsoperatoren für Arrayzeichen ersetzt (vgl. P0487R1). WG21 betrachtet die entfernten Überladungen als unsicher. Im Modus /std:c++20 oder /std:c++latest wird durch das folgende Beispiel jetzt C2679 ausgelöst:

// stream_extraction.cpp
// compile by using: cl /std:c++latest stream_extraction.cpp

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    char* p = x;
    std::cin >> std::setw(42);
    std::cin >> p;  // C2679: binary '>>': no operator found which takes a right-hand operand of type 'char *' (or there is no acceptable conversion)
}

Sie können den Fehler vermeiden, indem Sie den Extraktionsoperator mit einer char[]-Variablen verwenden:

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    std::cin >> std::setw(42);
    std::cin >> x;  // OK
}

Neue Schlüsselwörter requires und concept

Die neuen Schlüsselwörter requires und concept wurden zum Microsoft C++-Compiler hinzugefügt. Wenn Sie versuchen, eines dieser Schlüsselwörter im Modus /std:c++20 oder /std:c++latest als Bezeichner zu verwenden, löst der Compiler C2059 aus, um auf einen Syntaxfehler hinzuweisen.

Konstruktoren als Typnamen nicht zulässig

Konstruktornamen werden vom Compiler in diesem Fall nicht mehr als injizierte Klassennamen betrachtet, wenn sie in einem qualifizierten Namen nach einem Alias für eine Spezialisierung einer Klassenvorlage enthalten sind. Zuvor waren Konstruktoren als Typname zur Deklaration anderer Entitäten einsetzbar. Mit dem folgenden Beispiel wird nun dieser Fehler ausgelöst: C3646:

#include <chrono>

class Foo {
   std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};

Sie können den Fehler vermeiden, indem wie im Folgenden gezeigt TotalDuration deklarieren:

#include <chrono>

class Foo {
  std::chrono::milliseconds TotalDuration {};
};

Strengere Überprüfung von extern "C"-Funktionen

Wenn eine extern "C" -Funktion in verschiedenen Namespaces deklariert wurde, haben frühere Versionen des Microsoft C++-Compilers nicht überprüft, ob die Deklarationen kompatibel waren. Ab Visual Studio 2019, Version 16.3 führt der Compiler Prüfungen auf Kompatibilität durch. Im Modus /permissive- löst der folgende Code die Fehler „C2371“ und „C2733“ aus:

using BOOL = int;

namespace N
{
   extern "C" void f(int, int, int, bool);
}

void g()
{
   N::f(0, 1, 2, false);
}

extern "C" void f(int, int, int, BOOL){}
    // C2116: 'N::f': function parameter lists do not match between declarations
    // C2733: 'f': you cannot overload a function with 'extern "C"' linkage

Sie können die Fehler im vorherigen Beispiel vermeiden, indem Sie konsistent bool anstelle von BOOL in beiden Deklarationen von f verwenden.

Verbesserungen der Standardbibliothek

Die nicht standardmäßigen Header <stdexcpt.h> und <typeinfo.h> wurden entfernt. Code, der diese enthält, sollte stattdessen die standardmäßigen Header <exception> und <typeinfo> enthalten.

Verbesserungen der Konformität in Visual Studio 2019, Version 16.4

Bessere Durchsetzung der zweiphasigen Namenssuche für qualified-ids in /permissive-

Die Zweiphasennamenssuche erfordert, dass nicht abhängige Namen in Vorlagentexten zum Zeitpunkt der Definition für die Vorlage sichtbar sind. Zuvor wurden solche Namen gefunden, wenn die Vorlage instanziiert wurde. Diese Änderung vereinfacht das Schreiben von portierbarem, konformem Code in MSVC bei Verwendung des Flags /permissive-.

Wenn in Visual Studio 2019, Version 16.4, das Flag /permissive- festgelegt ist, erzeugt das folgende Beispiel einen Fehler, weil N::f beim Definieren der Vorlage f<T> nicht sichtbar ist:

template <class T>
int f() {
    return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}

namespace N {
    int f() { return 42; }
}

In der Regel lässt sich dieser Fehler durch Einschließen fehlender Header oder Vorausdeklarieren von Funktionen oder Variablen beheben, wie im folgenden Beispiel gezeigt:

namespace N {
    int f();
}

template <class T>
int f() {
    return N::f() + T{};
}

namespace N {
    int f() { return 42; }
}

Implizite Konvertierung integraler konstanter Ausdrücke in NULL-Zeiger

Der MSVC-Compiler implementiert jetzt CWG Issue 903 im Konformitätsmodus (/permissive-). Diese Regel lässt keine implizite Konvertierung von integralen konstanten Ausdrücken (mit Ausnahme des Integerliterals „0“) in NULL-Zeiger-Konstanten zu. Das folgende Beispiel erzeugt C2440 im Konformitätsmodus:

int* f(bool* p) {
    p = false; // error C2440: '=': cannot convert from 'bool' to 'bool *'
    p = 0; // OK
    return false; // error C2440: 'return': cannot convert from 'bool' to 'int *'
}

Verwenden Sie zur Behebung des Fehlers nullptr anstelle von false . Das Literal „0“ ist weiterhin erlaubt:

int* f(bool* p) {
    p = nullptr; // OK
    p = 0; // OK
    return nullptr; // OK
}

Standardregeln für Typen von Integerliteralen

Im Konformitätsmodus (aktiviert durch /permissive-) verwendet MSVC die Standardregeln für Typen von Integerliteralen. Dezimalliteralen, die zu groß für einen signed int -Typ sind, wurde früher der Typ unsigned int zugewiesen. Jetzt erhalten solche Literale den nächstgrößeren signed -Integertyp: long long . Darüber hinaus wird Literalen mit dem Suffix „ll“, die zu groß für einen signed -Typ sind, der Typ unsigned long long zugewiesen.

Dies kann dazu führen, dass verschiedene Warnungsdiagnosen generiert werden und Unterschiede im Verhalten bei arithmetischen Operationen in Literalen auftreten.

Das folgende Beispiel zeigt das neue Verhalten in Visual Studio 2019, Version 16.4. Die i-Variable ist jetzt vom Typ unsigned int, sodass die Warnung ausgelöst wird. Die höchstwertigen Bits der Variable j werden auf 0 festgelegt.

void f(int r) {
    int i = 2964557531; // warning C4309: truncation of constant value
    long long j = 0x8000000000000000ll >> r; // literal is now unsigned, shift will fill high-order bits with 0
}

Das folgende Beispiel zeigt, wie Sie das alte Verhalten beibehalten und Warnungen und die Verhaltensänderung zur Laufzeit vermeiden:

void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}

Funktionsparameter, die ein Shadowing für Vorlagenparameter durchführen

Der MSVC-Compiler löst jetzt einen Fehler aus, wenn ein Funktionsparameter ein Shadowing für einen Vorlagenparameter durchführt:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T(&buffer)[Size], int& Size) // error C7576: declaration of 'Size' shadows a template parameter
{
    return f(buffer, Size, Size);
}

Um den Fehler zu beheben, ändern Sie Namen eines der Parameter:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T (&buffer)[Size], int& size_read)
{
    return f(buffer, Size, size_read);
}

Vom Benutzer bereitgestellte Spezialisierungen von Typmerkmalen

In Übereinstimmung mit der Unterklausel meta.rqmts der Standardversion löst der MSVC-Compiler jetzt einen Fehler aus, wenn eine benutzerdefinierte Spezialisierung einer der angegebenen type_traits-Vorlagen im Namespace std gefunden wird. Sofern nicht anders angegeben, führen solche Spezialisierungen zu nicht definiertem Verhalten. Das folgende Beispiel zeigt ein nicht definiertes Verhalten, weil es die Regel verletzt, und bei static_assert tritt der Fehler C2338 auf.

#include <type_traits>
struct S;

template<>
struct std::is_fundamental<S> : std::true_type {};

static_assert(std::is_fundamental<S>::value, "fail");

Definieren Sie eine Struktur, die vom bevorzugten type_trait-Element erbt, und legen Sie Folgendes fest, um den Fehler zu vermeiden:

#include <type_traits>

struct S;

template<typename T>
struct my_is_fundamental : std::is_fundamental<T> {};

template<>
struct my_is_fundamental<S> : std::true_type { };

static_assert(my_is_fundamental<S>::value, "fail");

Änderungen an vom Compiler bereitgestellten Vergleichsoperatoren

Der MSVC-Compiler implementiert jetzt die folgenden Änderungen an Vergleichsoperatoren gemäß P1630R1, wenn die Option /std:c++20 oder /std:c++latest aktiviert ist:

Der Compiler schreibt Ausdrücke nicht mehr mithilfe von operator== neu, wenn diese einen Rückgabetyp beinhalten, der kein bool -Wert ist. Der folgende Code erzeugt jetzt den Fehler „C2088“:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;  // C2088: '!=': illegal for struct
}

Um diesen Fehler zu vermeiden, müssen Sie den erforderlichen Operator explizit definieren:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
    U operator!=(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

Der Compiler definiert keinen Standardvergleichsoperator mehr, wenn es sich um einen Member einer union-ähnlichen Klasse handelt. Das folgende Codebeispiel erzeugt jetzt den Fehler „C2120“:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const = default;
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

Um diesen Fehler zu vermeiden, definieren Sie einen Text für den Operator:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const { ... }
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

Der Compiler definiert keinen standardmäßigen Vergleichsoperator mehr, wenn die Klasse einen Verweismember enthält. Der folgende Code erzeugt jetzt den Fehler „C2120“:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const = default;
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Um diesen Fehler zu vermeiden, definieren Sie einen Text für den Operator:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const { ... };
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Verbesserungen der Konformität in Visual Studio 2019, Version 16.5

Eine explizite Spezialisierungsdeklaration ohne Initialisierer ist keine Definition

Unter /permissive- erzwingt MSVC jetzt eine Standardregel, die besagt, dass explizite Spezialisierungsdeklarationen ohne Initialisierer keine Definitionen sind. Zuvor galt die Deklaration als Definition mit einem Standardinitialisierer. Der Effekt kann zum Zeitpunkt der Verknüpfung beobachtet werden, da ein Programm, das von diesem Verhalten abhängig ist, nun nicht aufgelöste Symbole aufweisen kann. Dieses Beispiel führt jetzt zu einem Fehler:

template <typename> struct S {
    static int a;
};

// In permissive-, this declaration isn't a definition, and the program won't link.
template <> int S<char>::a;

int main() {
    return S<char>::a;
}
error LNK2019: unresolved external symbol "public: static int S<char>::a" (?a@?$S@D@@2HA) referenced in function _main at link time.

Fügen Sie einen Initialisierer hinzu, um das Problem zu beheben:

template <typename> struct S {
    static int a;
};

// Add an initializer for the declaration to be a definition.
template <> int S<char>::a{};

int main() {
    return S<char>::a;
}

Präprozessorausgabe behält Zeilenumbrüche bei

Der experimentelle Präprozessor behält bei der Verwendung von /P oder /E mit /experimental:preprocessor nun Zeilenumbrüche und Leerzeichen bei.

Gehen wir von folgender Beispielquelle aus:

#define m()
line m(
) line

Die vorherige Ausgabe von /E lautete:

line line
#line 2

Die neue Ausgabe von /E lautet nun:

line
 line

Die Schlüsselwörter import und module sind kontextabhängig

Gemäß P1857R1 gelten für die Präprozessoranweisungen import und module neue Einschränkungen in Bezug auf die Syntax. Das folgende Beispiel wird nicht mehr kompiliert:

import // Invalid
m;     // error C2146: syntax error: missing ';' before identifier 'm'

Behalten Sie den Import auf derselben Zeile:

import m; // OK

Entfernen von std::weak_equality und std::strong_equality

Für das Zusammenführen von P1959R0 muss der Compiler Verhaltensweisen sowie Verweise auf die Typen std::weak_equality und std::strong_equality entfernen.

Das folgende Beispiel wird nicht mehr kompiliert:

#include <compare>

struct S {
    std::strong_equality operator<=>(const S&) const = default;
};

void f() {
    nullptr<=>nullptr;
    &f <=> &f;
    &S::operator<=> <=> &S::operator<=>;
}

Das Beispiel führt nun zu folgenden Fehlern:

error C2039: 'strong_equality': is not a member of 'std'
error C2143: syntax error: missing ';' before '<=>'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C7546: binary operator '<=>': unsupported operand types 'nullptr' and 'nullptr'
error C7546: binary operator '<=>': unsupported operand types 'void (__cdecl *)(void)' and 'void (__cdecl *)(void)'
error C7546: binary operator '<=>': unsupported operand types 'int (__thiscall S::* )(const S &) const' and 'int (__thiscall S::* )(const S &) const'

Aktualisieren Sie den Code, sodass die integrierten relationalen Operatoren bevorzugt und die entfernten Typen ersetzt werden, um das Problem zu beheben:

#include <compare>

struct S {
    std::strong_ordering operator<=>(const S&) const = default; // prefer 'std::strong_ordering'
};

void f() {
    nullptr != nullptr; // use pre-existing builtin operator != or ==.
    &f != &f;
    &S::operator<=> != &S::operator<=>;
}

Änderung des TLS-Wächters

Zuvor wurden lokale Threadvariablen in DLLs nicht ordnungsgemäß initialisiert. Anders als beim Thread, der die DLL geladen hat, wurden sie bei Threads, die bereits vor dem Laden der DLL vorhanden waren, vor der ersten Verwendung nicht initialisiert. Dieser Fehler wurde nun behoben. Threadlokale Variablen in einer solchen DLL werden sofort initialisiert, bevor sie zum ersten Mal in solchen Threads verwendet werden.

Dieses neue Verhalten des Tests auf Initialisierung bei Verwendung von threadlokalen Variablen kann mithilfe der Compileroption /Zc:tlsGuards- deaktiviert werden. Eine weitere Möglichkeit ist das Hinzufügen des [[msvc:no_tls_guard]]-Attributs zu bestimmten threadlokalen Variablen.

Bessere Diagnose des Aufrufs gelöschter Funktionen

Unser Compiler war bisher weniger einschränkend in Bezug auf Aufrufe gelöschter Funktionen. Wenn die Aufrufe beispielsweise im Kontext eines Vorlagentexts stattfanden, wurden sie nicht diagnostiziert. Zudem wurde bei mehreren Instanzen von Aufrufen gelöschter Funktionen nur eine Diagnose ausgegeben. Nun geben wir für jede dieser Instanzen eine Diagnose aus.

Eine Konsequenz des neuen Verhaltens kann zu einem kleinen Breaking Change führen: Code, der eine gelöschte Funktion aufgerufen hat, wurde nicht diagnostiziert, wenn er nie für die Codegenerierung benötigt wurde. Nun wird er vorab diagnostiziert.

In diesem Beispiel wird Code angezeigt, der nun zu einem Fehler führt:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s{};
};

U u{ 0 };
error C2280: 'S::S(void)': attempting to reference a deleted function
note: see declaration of 'S::S'
note: 'S::S(void)': function was explicitly deleted

Entfernen Sie Aufrufe gelöschter Funktionen, um das Problem zu beheben:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s;  // Do not call the deleted ctor of 'S'.
};

U u{ 0 };

Verbesserungen der Konformität in Visual Studio 2019, Version 16.6

Bei Datenströmen der Standardbibliothek werden Einfügungen von nicht korrekt codierten Zeichentypen abgelehnt

Beim Einfügen von wchar_t in std::ostream sowie von char16_t oder char32_t in std::ostream oder std::wostream wird üblicherweise ein ganzzahliger Wert ausgegeben. Beim Einfügen von Zeigern in diese Zeichentypen wird der Zeigerwert ausgegeben. Für Programmierer sind beide Verhalten nicht intuitiv. Vielmehr erwarten sie, dass die Standardbibliothek die auf Zeichen oder NULL endende Zeichenfolge transcodiert und das Ergebnis ausgibt.

Mit dem C++20-Vorschlag P1423R3 werden gelöschte insertion-Operatorüberladungen im Datenstrom für diese Kombinationen aus Datenstrom und Zeichen- oder Zeichenzeigertypen hinzugefügt. Unter /std:c++20 oder /std:c++latest haben die Überladungen zur Folge, dass diese Einfügungen nicht korrekt angegeben werden, anstatt ein wahrscheinlich unbeabsichtigtes Verhalten zur Folge zu haben. Wird ein solches Element ermittelt, löst der Compiler den Fehler C2280 aus. Um das alte Verhalten wiederherzustellen, können Sie das escape hatch-Makro _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20 als 1 definieren. (Der Vorschlag löscht auch Streameinfügungsoperatoren für char8_t. Unsere Standardbibliothek implementierte ähnliche Überladungen, als wir Unterstützung für char8_t hinzugefügt haben, daher war das „falsche“ Verhalten für char8_t nie verfügbar.)

Dieses Beispiel zeigt das Verhalten bei dieser Änderung:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << cw << ' ' << pw << '\n';
    std::cout << c16 << ' ' << p16 << '\n';
    std::cout << c32 << ' ' << p32 << '\n';
    std::wcout << c16 << ' ' << p16 << '\n';
    std::wcout << c32 << ' ' << p32 << '\n';
}

Der Code erzeugt nun folgende Diagnosemeldungen:

error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,wchar_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char32_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char32_t)': attempting to reference a deleted function

Sie können die Auswirkungen des alten Verhaltens in allen Sprachmodi erzielen, indem Sie Zeichentypen in unsigned int bzw. Zeiger-zu-Zeichen-Typen in const void* konvertieren:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << (unsigned)cw << ' ' << (const void*)pw << '\n'; // Outputs "120 0052B1C0"
    std::cout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::cout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
    std::wcout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::wcout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
}

Änderung des Rückgabetyps von std::pow() für std::complex

In der Vergangenheit war die MSVC-Implementierung der Aktionsregeln für den Rückgabetyp der Funktionsvorlage std::pow() nicht korrekt. Beispielsweise wurde von pow(complex<float>, int) früher complex<float> zurückgegeben. Nun wird der richtige Typ complex<double> zurückgegeben. Die Korrektur wurde ohne Bedingungen für alle Standardmodi in Visual Studio 2019 Version 16.6 implementiert.

Diese Änderung kann zu Compilerfehlern führen. So konnte pow(complex<float>, int) beispielsweise früher mit float multipliziert werden. Da complex<T> operator* Argumente desselben Typs erwartet, gibt das folgende Beispiel jetzt den Compilerfehler „C2676“ aus:

// pow_error.cpp
// compile by using: cl /EHsc /nologo /W4 pow_error.cpp
#include <complex>

int main() {
    std::complex<float> cf(2.0f, 0.0f);
    (void) (std::pow(cf, -1) * 3.0f);
}
pow_error.cpp(7): error C2676: binary '*': 'std::complex<double>' does not define this operator or a conversion to a type acceptable to the predefined operator

Für dieses Problem sind eine Vielzahl von Korrekturen möglich:

  • Ändern Sie den Typ des Multiplikands float in double . Dieses Argument kann direkt in ein complex<double>-Element konvertiert werden, um dem von pow zurückgegebenen Typ zu entsprechen.

  • Schränken Sie das Ergebnis von pow auf complex<float> ein, indem Sie complex<float>{pow(ARG, ARG)} angeben. Anschließend ist eine Multiplikation mit einem float -Wert möglich.

  • Übergeben Sie float anstelle von int an pow. Dieser Vorgang ist möglicherweise langsamer.

  • In einigen Fällen können Sie pow vollständig vermeiden. Beispielsweise kann pow(cf, -1) durch eine Division ersetzt werden.

switch-Warnungen für C

Ab Visual Studio 2019 Version 16.6 implementiert der Compiler einige bereits vorhandene C++-Warnungen für Code, der als C kompiliert wird. Die folgenden Warnungen sind nun auf unterschiedlichen Ebenen aktiviert: C4060, C4061, C4062, C4063, C4064, C4065, C4808 und C4809. Die Warnungen C4065 und C4060 sind in C standardmäßig deaktiviert.

Sie werden bei fehlenden case -Anweisungen, nicht definierten enum -Elementen und nicht korrekten bool -switch-Anweisungen (Anweisungen mit zu vielen case-Angaben) ausgelöst. Zum Beispiel:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
        default: break; // C4809: switch statement has redundant 'default' label;
                        // all possible 'case' labels are given
    }
}

Entfernen Sie den redundanten default -Fall, um dieses Problem zu beheben:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
    }
}

Unbenannte Klassen in typedef-Deklarationen

Ab Visual Studio 2019, Version 16.6, wurde das Verhalten von typedef-Deklarationen so beschränkt, dass es P1766R1 entspricht. Durch diese Änderung können unbenannte Klassen innerhalb einer typedef-Deklaration keine anderen Member enthalten als die folgenden:

  • Nicht statische Datenmitglieder ohne Standard-Memberinitialisierer,
  • Memberklassen oder
  • Memberenumerationen.

Dieselben Einschränkungen gelten rekursiv für alle geschachtelten Klassen. Durch diese Einschränkung soll die Einfachheit von Strukturen mit typedef-Namen zu Bindungszwecken sichergestellt werden. Sie müssen so einfach sein, dass keine Bindungsberechnungen erforderlich sind, bevor der Compiler den typedef-Namen für die Bindung erreicht.

Diese Änderung wirkt sich auf alle Standardmodi des Compilers aus. Bei der Standardeinstellung (/std:c++14) und den /std:c++17-Modi gibt der Compiler bei nicht konformem Code die Warnung C5208 aus. Wenn /permissive- angegeben wird, gibt der Compiler die Warnung C5208 als Fehler bei Verwendung von /std:c++14 und Fehler C7626 bei Verwendung von /std:c++17 aus. Der Compiler gibt den Fehler C7626 für nicht konformen Code aus, wenn /std:c++20 oder /std:c++latest angegeben wird.

Das folgende Beispiel zeigt die Konstrukte, die in unbenannten Strukturen nicht länger zulässig sind. Abhängig vom angegebenen Standardmodus werden die Fehler oder Warnungen C5208 bzw. C7626 ausgegeben:

struct B { };
typedef struct : B { // inheriting from 'B'; ill-formed
    void f(); // ill-formed
    static int i; // ill-formed
    struct U {
        void f(); // nested class has non-data member; ill-formed
    };
    int j = 10; // default member initializer; ill-formed
} S;

Der obige Code kann korrigiert werden, indem Sie der unbenannten Klasse einen Namen zuweisen:

struct B { };
typedef struct S_ : B {
    void f();
    static int i;
    struct U {
        void f();
    };
    int j = 10;
} S;

Importieren von Standardargumenten in C++/CLI

Eine zunehmende Anzahl von APIs hat in .NET Core Standardargumente. Aus diesem Grund unterstützen wir nun den Import von Standardargumenten in C++/CLI. Durch diese Änderung kann es bei vorhandenem Code, in dem mehrere Überladungen deklariert sind, zu Fehlern kommen, wie im folgenden Beispiel gezeigt:

public class R {
    public void Func(string s) {}   // overload 1
    public void Func(string s, string s2 = "") {} // overload 2;
}

Wenn diese Klasse in C++/CLI importiert wird, tritt beim Aufruf einer der Überladungen ein Fehler auf:

    (gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function

Der Compiler gibt den Fehler C2668 aus, weil beide Überladungen dieser Argumentliste entsprechen. In der zweiten Überladung wird das zweite Argument vom Standardargument gefüllt. Um dieses Problem zu umgehen, können Sie die redundante Überladung (1) löschen. Alternativ verwenden Sie die vollständige Argumentliste und geben die Standardargumente explizit an.

Verbesserungen der Konformität in Visual Studio 2019, Version 16.7

Definition von ist trivial kopierbar

In C++20 wurde die Definition von ist trivial kopierbar geändert. Wenn eine Klasse einen nicht statischen Datenmember mit als mit volatile qualifiziertem Typ hat, bedeutet dies nicht mehr, dass ein vom Compiler generierter Kopier- oder Verschiebekonstruktor oder ein Kopier- oder Verschiebezuweisungsoperator nicht trivial ist. Der Ausschuss für den C++-Standard hat diese Änderung rückwirkend als Fehlerbericht angewendet. In MSVC ändert sich das Compilerverhalten in verschiedenen Sprachmodi, wie z. B. /std:c++14 oder /std:c++latest , nicht.

Es folgt ein Beispiel des neuen Verhaltens:

#include <type_traits>

struct S
{
    volatile int m;
};

static_assert(std::is_trivially_copyable_v<S>, "Meow!");

Dieser Code lässt sich nicht in Versionen von MSVC vor Visual Studio 2019, Version 16.7 kompilieren. Es gibt eine standardmäßig deaktivierte Compilerwarnung, die Sie verwenden können, um diese Änderung zu erkennen. Wenn Sie den obigen Code mithilfe von cl /W4 /w45220 kompilieren, wird die folgende Warnung angezeigt:

warning C5220: `'S::m': a non-static data member with a volatile qualified type no longer implies that compiler generated copy/move constructors and copy/move assignment operators are non trivial`

Konvertierungen von Pointer-to-Member-Funktionen und Zeichenfolgenliteralen in bool sind einschränkend.

Der Ausschuss für den C++-Standard hat vor Kurzem den Fehlerbericht P1957R2 akzeptiert, der die Konvertierung von T* in bool als einschränkende Konvertierung einstuft. In MSVC wurde ein Fehler in der Implementierung behoben, der die Konvertierung von T* in bool als einschränkend diagnostizierte, aber die Konvertierung eines Zeichenfolgenliterals in bool oder einer Pointer-to-Member-Funktion in bool nicht erkannte.

Das folgende Programm ist in Visual Studio 2019, Version 16.7 fehlerhaft formatiert:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" }); // error: conversion from 'const char [8]' to 'bool' requires a narrowing conversion

    int (X::* p) = nullptr;
    f(X { p }); // error: conversion from 'int X::*' to 'bool' requires a narrowing conversion
}

Um diesen Code zu korrigieren, fügen Sie entweder nullptr explizite Vergleiche hinzu, oder vermeiden Sie Kontexte, in denen einschränkende Konvertierungen fehlerhaft formatiert sind:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" != nullptr }); // Absurd, but OK

    int (X::* p) = nullptr;
    f(X { p != nullptr }); // OK
}

nullptr_t kann nur als direkte Initialisierung in bool konvertiert werden

In C++11 ist nullptr nur als eine direkte Konvertierung in bool konvertierbar, z. B. wenn Sie ein bool mithilfe einer Initialisierungsliste in geschweiften Klammern initialisieren. Diese Einschränkung wurde von MSVC nie erzwungen. MSVC implementiert die Regel nun gemäß /permissive-. Implizite Konvertierungen werden nun als fehlerhaft formatiert diagnostiziert. Eine kontextabhängige Konvertierung in bool ist weiterhin zulässig, da die direkte Initialisierung bool b(nullptr) gültig ist.

In den meisten Fällen können Sie den Fehler beheben, indem Sie, wie im folgenden Beispiel gezeigt, nullptr durch false ersetzen:

struct S { bool b; };
void g(bool);
bool h() { return nullptr; } // error, should be 'return false;'

int main() {
    bool b1 = nullptr; // error: cannot convert from 'nullptr' to 'bool'
    S s { nullptr }; // error: cannot convert from 'nullptr' to 'bool'
    g(nullptr); // error: cannot convert argument 1 from 'nullptr' to 'bool'

    bool b2 { nullptr }; // OK: Direct-initialization
    if (!nullptr) {} // OK: Contextual conversion to bool
}

Konformes Initialisierungsverhalten für Arrayinitialisierungen mit fehlenden Initialisierern

Bislang zeigte MSVC ein nicht konformes Verhalten für Arrayinitialisierungen mit fehlenden Initialisierern. MSVC rief stets den Standardkonstruktor für jedes Arrayelement ohne Initialisierer auf. Das Standardverhalten besteht darin, jedes Element mit einer leeren Initialisierungsliste in geschweiften Klammern zu initialisieren ( {} ). Der Initialisierungskontext für eine leere Initialisierungsliste in geschweiften Klammern ist die Kopierinitialisierung, die keine Aufrufe an explizite Konstruktoren erlaubt. Es kann auch Laufzeitunterschiede geben, weil die Verwendung von {} zur Initialisierung einen Konstruktor aufrufen kann, der anstelle des Standardkonstruktors std::initializer_list verwendet. Das konforme Verhalten wird gemäß /permissive- aktiviert.

Es folgt ein Beispiel des geänderten Verhaltens:

struct B {
    explicit B() {}
};

void f() {
    B b1[1]{}; // Error in /permissive-, because aggregate init calls explicit ctor
    B b2[1]; // OK: calls default ctor for each array element
}

Initialisierung von Klassenmembern mit überladenen Namen wird richtig sequenziert

Wir haben einen Fehler in der internen Darstellung von Klassendatenmembern festgestellt, wenn ein Typname auch als Name eines Datenmembers überladen wird. Dieser Fehler führte zu Inkonsistenzen bei der Aggregatinitialisierung und der Initialisierungsreihenfolge der Member. Der generierte Initialisierungscode ist nun korrekt. Diese Änderung kann jedoch zu Fehlern oder Warnungen in der Quelle führen, die wie in diesem Beispiel versehentlich auf die falsch geordneten Member zurückgriffen:

// Compiling with /w15038 now gives:
// warning C5038: data member 'Outer::Inner' will be initialized after data member 'Outer::v'
struct Outer {
    Outer(int i, int j) : Inner{ i }, v{ j } {}

    struct Inner { int x; };
    int v;
    Inner Inner; // 'Inner' is both a type name and data member name in the same scope
};

In früheren Versionen hat der Konstruktor den Datenmember Inner fälschlicherweise vor dem Datenmember v initialisiert. (Der C++-Standard erfordert eine Initialisierungsreihenfolge, die mit der Deklarationsreihenfolge der Member übereinstimmt.) Nun, da der generierte Code dem Standard folgt, ist die Memberinitialisierungsliste nicht in der richtigen Reihenfolge. Der Compiler generiert eine Warnung für dieses Beispiel. Um dies zu korrigieren, ordnen Sie die Memberinitialisierungsliste so an, dass sie die Deklarationsreihenfolge widerspiegelt.

Überladungsauflösung mit integralen Überladungen und long-Argumenten

Der C++-Standard erfordert als Standardkonvertierung eine Konvertierung von long in int . Frühere MSVC-Compiler stufen sie fälschlicherweise als integrale Höherstufung ein, die für die Überladungsauflösung einen höheren Rang einnimmt. Diese Rangfolge kann bewirken, dass die Überladungsauflösung erfolgreich erfolgt, wenn Sie als nicht eindeutig eingestuft wird.

Der Compiler berücksichtigt nun im Modus /permissive- den Rang ordnungsgemäß. Ungültiger Code wird wie in diesem Beispiel ordnungsgemäß diagnostiziert:

void f(long long);
void f(int);

int main() {
    long x {};
    f(x); // error: 'f': ambiguous call to overloaded function
    f(static_cast<int>(x)); // OK
}

Sie können dieses Problem auf verschiedene Arten beheben:

  • Ändern Sie in der Aufrufsite den Typ des übergebenen Arguments in int . Sie können entweder den Variablentyp ändern oder ihn umwandeln.

  • Wenn es viele Aufrufsites gibt, können Sie eine weitere Überladung hinzufügen, die ein long -Argument verwendet. In dieser Funktion wandeln Sie das Argument um und leiten es an die int -Überladung weiter.

Verwendung einer nicht definierten Variablen mit interner Bindung

Versionen von MSVC vor Visual Studio 2019, Version 16.7 akzeptierten die Verwendung der deklarierten Variablen extern , die eine interne Bindung hatte und nicht definiert war. Solche Variablen können in keiner anderen Übersetzungseinheit definiert werden und kein gültiges Programm bilden. Der Compiler diagnostiziert diesen Fall nun zur Kompilierzeit. Der Fehler ähnelt dem Fehler für nicht definierte statische Funktionen.

namespace {
    extern int x; // Not a definition, but has internal linkage because of the anonymous namespace
}

int main()
{
    return x; // Use of 'x' that no other translation unit can possibly define.
}

Dieses Programm wurde zuvor falsch kompiliert und verknüpft, gibt jetzt aber den Fehler „C7631“ aus.

error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined

Solche Variablen müssen in derselben Übersetzungseinheit definiert werden, in der sie verwendet werden. Beispielsweise können Sie einen expliziten Initialisierer oder eine separate Definition bereitstellen.

Typvollständigkeit und von Basisklasse abgeleitete Zeigerkonvertierungen

In C++-Standards vor C++20 erforderte eine Konvertierung von einer abgeleiteten Klasse in eine Basisklasse nicht, dass die abgeleitete Klasse ein vollständiger Klassentyp sein musste. Der Ausschuss für den C++-Standard hat eine rückwirkende Änderung des Fehlerberichts genehmigt, die für alle Versionen der Sprache C++ gilt. Durch diese Änderung wird der Konvertierungsprozess an Typmerkmale, wie z. B. std::is_base_of, angeglichen, die erfordern, dass die abgeleitete Klasse ein vollständiger Klassentyp ist.

Hier sehen Sie ein Beispiel:

template<typename A, typename B>
struct check_derived_from
{
    static A a;
    static constexpr B* p = &a;
};

struct W { };
struct X { };
struct Y { };

// With this change this code will fail as Z1 is not a complete class type
struct Z1 : X, check_derived_from<Z1, X>
{
};

// This code failed before and it will still fail after this change
struct Z2 : check_derived_from<Z2, Y>, Y
{
};

// With this change this code will fail as Z3 is not a complete class type
struct Z3 : W
{
    check_derived_from<Z3, W> cdf;
};

Diese Behavior Change gilt für alle C++-Sprachmodi von MSVC, nicht nur für /std:c++20 oder /std:c++latest .

Einschränkende Konvertierungen werden einheitlicher diagnostiziert

MSVC gibt eine Warnung für einschränkende Konvertierungen in einem Listeninitialisierer in geschweiften Klammern aus. Bisher diagnostizierte der Compiler keine einschränkenden Konvertierungen von größeren zugrunde liegenden enum -Typen in einschränkendere integrale Typen. (Der Compiler betrachtete sie fälschlicherweise als integrale Höherstufung statt als Konvertierung.) Wenn die einschränkende Konvertierung beabsichtigt ist, können Sie die Warnung vermeiden, indem Sie für das Initialisiererargument static_cast verwenden. Oder wählen Sie einen größeren integralen Zieltyp aus.

Hier ist ein Beispiel für die Verwendung einer expliziten static_cast , um auf die Warnung zu reagieren:

enum E : long long { e1 };
struct S { int i; };

void f(E e) {
    S s = { e }; // warning: conversion from 'E' to 'int' requires a narrowing conversion
    S s1 = { static_cast<int>(e) }; // Suppress warning with explicit conversion
}

Verbesserungen der Konformität in Visual Studio 2019, Version 16.8

Erweiterung zum Verwenden eines rvalue-Ausdrucks einer Klasse als lvalue-Ausdruck

MSVC verfügt über eine Erweiterung, mit deren Hilfe ein rvalue-Ausdruck einer Klasse als lvalue-Ausdruck verwendet werden kann. Die Erweiterung verlängert die Lebensdauer des rvalue-Ausdrucks der Klasse nicht und kann zu undefiniertem Verhalten zur Laufzeit führen. Wir erzwingen jetzt die Standardregel und lassen diese Erweiterung unter /permissive- nicht mehr zu. Wenn Sie /permissive- noch nicht verwenden können, können Sie /we4238 nutzen, um die Erweiterung explizit zu unterbinden. Hier sehen Sie ein Beispiel:

// Compiling with /permissive- now gives:
// error C2102: '&' requires l-value
struct S {};

S f();

void g()
{
    auto p1 = &(f()); // The temporary returned by 'f' is destructed after this statement. So 'p1' points to an invalid object.

    const auto &r = f(); // This extends the lifetime of the temporary returned by 'f'
    auto p2 = &r; // 'p2' points to a valid object
}

Erweiterung zur expliziten Spezialisierung im Nicht-Namespacebereich

MSVC umfasste eine Erweiterung, die eine explizite Spezialisierung im Nicht-Namespacebereich zuließ. Diese ist nach der Auflösung von CWG 727 jetzt Teil des Standards. Es gibt jedoch Unterschiede im Verhalten. Wir haben das Verhalten unseres Compilers so angepasst, dass es dem Standard entspricht.

// Compiling with 'cl a.cpp b.cpp /permissive-' now gives:
//   error LNK2005: "public: void __thiscall S::f<int>(int)" (??$f@H@S@@QAEXH@Z) already defined in a.obj
// To fix the linker error,
// 1. Mark the explicit specialization with 'inline' explicitly. Or,
// 2. Move its definition to a source file.

// common.h
struct S {
    template<typename T> void f(T);
    template<> void f(int);
};

// This explicit specialization is implicitly inline in the default mode.
template<> void S::f(int) {}

// a.cpp
#include "common.h"

int main() {}

// b.cpp
#include "common.h"

Überprüfen auf abstrakte Klassentypen

Der C++20-Standard hat den Prozess geändert, den Compiler verwenden, um die Verwendung eines abstrakten Klassentyps als Funktionsparameter zu erkennen. Dies ist kein SFINAE-Fehler mehr. Vorher galt: Wenn der Compiler erkannte, dass eine Spezialisierung einer Funktionsvorlage die Instanz eines abstrakten Klassentyps als Funktionsparameter enthielt, wurde diese Spezialisierung als falsch formatiert betrachtet. Sie wurde dem Satz geeigneter Kandidatfunktionen nicht hinzugefügt. In C++20 erfolgt die Überprüfung auf einen Parameter eines abstrakten Klassentyps erst, wenn die Funktion aufgerufen wird. Dies bewirkt, dass der zum Kompilieren verwendete Code keinen Fehler verursacht. Hier sehen Sie ein Beispiel:

class Node {
public:
    int index() const;
};

class String : public Node {
public:
    virtual int size() const = 0;
};

class Identifier : public Node {
public:
    const String& string() const;
};

template<typename T>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

int compare(const Node& x, const Node& y)
{
    return compare(x.index(), y.index());
}

int f(const Identifier& x, const String& y)
{
    return compare(x.string(), y);
}

Zuvor hätte der Aufruf von compare versucht, die Funktionsvorlage compare zu spezialisieren, wobei ein T-Vorlagenargument für String verwendet worden wäre. Damit hätte keine gültige Spezialisierung generiert werden können, weil String eine abstrakte Klasse ist. Der einzige geeignete Kandidat wäre compare(const Node&, const Node&) gewesen. In C++20 erfolgt die Überprüfung auf den abstrakten Klassentyps jedoch erst, wenn die Funktion aufgerufen wird. Daher wird die Spezialisierung compare(String, String) zum Satz geeigneter Kandidaten hinzugefügt und als bester Kandidat ausgewählt, weil die Konvertierung von const String& in String eine bessere Sequenz darstellt als die Konvertierung von const String& in const Node&.

In C++20 ist eine mögliche Problemlösung für dieses Beispiel die Verwendung von Konzepten, also die Änderung der Definition von compare in:

template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

Falls keine C++-Konzepte verfügbar sind, können Sie auf SFINAE zurückgreifen:

template<typename T, std::enable_if_t<!std::is_abstract_v<T>, int> = 0>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

Unterstützung für P0960R3: Zulassen der Initialisierung von Aggregaten über eine in Klammern gesetzte Liste mit Werten

C++20 P0960R3 fügt Unterstützung für die Initialisierung eines Aggregats mithilfe einer in Klammern gesetzten Initialisiererliste hinzu. Der folgende Code beispielsweise ist in C++20 gültig:

struct S {
    int i;
    int j;
};

S s(1, 2);

Der größte Teil dieses Features ist additiv, d. h., es kann jetzt Code kompiliert werden, bei dem dies vorher nicht möglich war. Allerdings ändert das Feature das Verhalten von std::is_constructible. Im C++17-Modus kommt es für static_assert zu einem Fehler, im C++20-Modus ist der Vorgang erfolgreich:

static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");

Wenn Sie dieses Typmerkmal zum Steuern der Überladungsauflösung verwenden, kann dies zu einem Behavior Change zwischen C++17 und C++20 führen.

Überladungsauflösung mit Funktionsvorlagen

Zuvor ließ der Compiler die Kompilierung von Code unter /permissive- zu, der nicht kompiliert werden sollte. Dadurch rief der Compiler die falsche Funktion auf, was zu einer Änderung des Laufzeitverhaltens führte:

int f(int);

namespace N
{
    using ::f;
    template<typename T>
    T f(T);
}

template<typename T>
void g(T&& t)
{
}

void h()
{
    using namespace N;
    g(f);
}

Der Aufruf von g verwendet einen Überladungssatz, der die beiden Funktionen ::f und N::f enthält. Da N::f eine Funktionsvorlage ist, sollte der Compiler das Funktionsargument als nicht abgeleiteten Kontext behandeln. Das bedeutet, dass in diesem Fall der Aufruf von g nicht erfolgreich sein darf, weil der Compiler keinen Typ für den Vorlagenparameter T ableiten kann. Leider hat der Compiler die vorherige Entscheidung außer Acht gelassen, dass ::f ein guter Kandidat für einen Funktionsaufruf ist. Anstatt einen Fehler auszugeben, generiert der Compiler Code zum Aufrufen von g mit ::f als Argument.

Da die Verwendung von ::f als Funktionsargument in vielen Fällen vom Benutzer erwartet wird, wird nur dann ein Fehler ausgegeben, wenn der Code mit /permissive- kompiliert wird.

Migrieren von /await zu C++20-Coroutinen

C++20-Standardcoroutinen sind jetzt unter /std:c++20 und /std:c++latest standardmäßig aktiviert. Sie unterscheiden sich von Coroutines TS und der Unterstützung in der Option /await . Bei einer Migration von /await zu Standardcoroutinen sind möglicherweise einige Quelländerungen erforderlich.

Nicht standardmäßige Schlüsselwörter

Die alten Schlüsselwörter await und yield werden im C++20-Modus nicht unterstützt. Der Code muss stattdessen co_await und co_yield verwenden. Im Standardmodus ist die Verwendung von return in einer Coroutine nicht zulässig. Jedes return -Element in einer Coroutine muss co_return verwenden.

// /await
task f_legacy() {
    ...
    await g();
    return n;
}
// /std:c++latest
task f() {
    ...
    co_await g();
    co_return n;
}

Typen von „initial_suspend“ und „final_suspend“

Unter /await können die Zusagefunktionen „initial“ und „suspend“ als Rückgabe von bool deklariert werden. Dies ist kein Standardverhalten. In C++20 müssen diese Funktionen einen awaitable-Klassentyp zurückgeben. Dies ist häufig einer der trivialen awaitable-Typen: std::suspend_always, wenn die Funktion zuvor true zurückgegeben hat, oder std::suspend_never, wenn die Rückgabe false lautete.

// /await
struct promise_type_legacy {
    bool initial_suspend() noexcept { return false; }
    bool final_suspend() noexcept { return true; }
    ...
};

// /std:c++latest
struct promise_type {
    auto initial_suspend() noexcept { return std::suspend_never{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
    ...
};

Typ von yield_value

In C++20 muss die Zusagefunktion yield_value einen awaitable-Typ zurückgeben. Im /await -Modus konnte die yield_value-Funktion void zurückgeben und wurde immer angehalten. Solche Funktionen können durch eine Funktion ersetzt werden, die std::suspend_always zurückgibt.

// /await
struct promise_type_legacy {
    ...
    void yield_value(int x) { next = x; };
};

// /std:c++latest
struct promise_type {
    ...
    auto yield_value(int x) { next = x; return std::suspend_always{}; }
};

Funktion zur Ausnahmebehandlung

/await unterstützt einen Zusagetyp ohne Ausnahmebehandlungsfunktion oder mit einer Ausnahmebehandlungsfunktion namens set_exception, die std::exception_ptr akzeptiert. In C++20 muss der Zusagetyp eine Funktion namens unhandled_exception aufweisen, die keine Argument akzeptiert. Das Ausnahmeobjekt kann bei Bedarf von std::current_exception abgerufen werden.

// /await
struct promise_type_legacy {
    void set_exception(std::exception_ptr e) { saved_exception = e; }
    ...
};
// /std:c++latest
struct promise_type {
    void unhandled_exception() { saved_exception = std::current_exception(); }
    ...
};

Keine Unterstützung für abgeleitete Rückgabetypen von Coroutinen

C++20 unterstützt keine Coroutinen mit einem Rückgabetyp, der einen Platzhaltertyp wie z. B. auto enthält. Rückgabetypen von Coroutinen müssen explizit deklariert werden. Unter /await beinhalten diese abgeleiteten Typen immer einen experimentellen Typ und erfordern die Einbindung eines Headers, der den erforderlichen Typ definiert: entweder std::experimental::task<T>,std::experimental::generator<T> oder std::experimental::async_stream<T>.

// /await
auto my_generator() {
    ...
    co_yield next;
};

// /std:c++latest
#include <experimental/generator>
std::experimental::generator<int> my_generator() {
    ...
    co_yield next;
};

Rückgabetyp von return_value

Der Rückgabetyp der Zusagefunktion return_value muss void sein. Im /await -Modus kann der Rückgabetyp beliebig sein und wird ignoriert. Mithilfe der Diagnose lassen sich nahezu unmerkliche Fehler finden, bei denen der Autor fälschlicherweise angenommen hat, dass der Rückgabewert von return_value an einen Aufrufer zurückgegeben wird.

// /await
struct promise_type_legacy {
    ...
    int return_value(int x) { return x; } // incorrect, the return value of this function is unused and the value is lost.
};

// /std:c++latest
struct promise_type {
    ...
    void return_value(int x) { value = x; }; // save return value
};

Konvertierungsverhalten für Rückgabeobjekte

Wenn der deklarierte Rückgabetyp einer Coroutine nicht mit dem Rückgabetyp der Zusagefunktion get_return_object übereinstimmt, wird das von get_return_object zurückgegebene Objekt in den Rückgabetyp der Coroutine konvertiert. Unter /await erfolgt diese Konvertierung frühzeitig, bevor der Coroutinentext ausgeführt werden kann. In /std:c++20 oder /std:c++latest erfolgt diese Konvertierung, wenn der Wert an den Aufrufer zurückgegeben wird. So können Coroutinen, die nicht beim ersten Anhaltepunkt angehalten werden, das von get_return_object im Coroutinentext zurückgegebene Objekt verwenden.

Zusageparameter für Coroutinen

In C++20 versucht der Compiler, die Coroutinenparameter (sofern vorhanden) an einen Konstruktor des Zusagetyps zu übergeben. Wenn hierbei ein Fehler auftritt, versucht der Compiler den Vorgang erneut mit einem Standardkonstruktor. Im /await -Modus wurde nur der Standardkonstruktor verwendet. Diese Änderung kann zu einem Unterschied im Verhalten führen, wenn die Zusage mehrere Konstruktoren enthält. Dies gilt auch bei einer Konvertierung von einem Coroutineparameter in den Zusagetyp.

struct coro {
    struct promise_type {
        promise_type() { ... }
        promise_type(int x) { ... }
        ...
    };
};

coro f1(int x);

// Under /await the promise gets constructed using the default constructor.
// Under /std:c++latest the promise gets constructed using the 1-argument constructor.
f1(0);

struct Object {
template <typename T> operator T() { ... } // Converts to anything!
};

coro f2(Object o);

// Under /await the promise gets constructed using the default constructor
// Under /std:c++latest the promise gets copy- or move-constructed from the result of
// Object::operator coro::promise_type().
f2(Object{});

/permissive- und C++20-Module unter /std:c++20 standardmäßig aktiviert

Die Unterstützung von C++20-Modulen ist unter /std:c++20 und /std:c++latest standardmäßig aktiviert. Weitere Informationen zu dieser Änderung sowie zu den Szenarien, in denen module und import bedingt als Schlüsselwörter behandelt werden, finden Sie unter Standard C++20 Modules support with MSVC in Visual Studio 2019 version 16.8 (Standardmäßige Unterstützung für C++20-Module mit MSVC in Visual Studio 2019, Version 16.8).

Als Voraussetzung für die Modulunterstützung ist permissive- jetzt aktiviert, wenn /std:c++20 oder /std:c++latest angegeben wird. Weitere Informationen finden Sie unter /permissive-.

Bei Code, der zuvor unter /std:c++latest kompiliert wurde und nicht konformes Compilerverhalten erfordert, kann /permissive angegeben werden, um den strengen Konformitätsmodus im Compiler zu deaktivieren. Die Compileroption muss in der Liste der Befehlszeilenargumente nach /std:c++latest angezeigt werden. /permissive führt jedoch zu einem Fehler, wenn die Nutzung von Modulen festgestellt wird:

Fehler C1214: Module stehen in Konflikt mit dem Verhalten, das über option angefordert wird und nicht dem Standard entspricht.

Die häufigsten Werte für option lauten:

Option BESCHREIBUNG
/Zc:twoPhase- Für C++20-Module ist eine zweiphasige Namenssuche erforderlich und wird durch /permissive- impliziert.
/Zc:hiddenFriend- Hiermit wird angegeben, dass die Standardregeln für die Suche nach verborgenen Anzeigenamen für C++20-Module erforderlich sind und von /permissive- impliziert werden.
/Zc:lambda- Die standardmäßige Lambdafunktionsverarbeitung ist für C++20-Module erforderlich und wird durch den Modus /std:c++20 oder höhere Modi impliziert.
/Zc:preprocessor- Der konforme Präprozessor ist nur für die Nutzung und Erstellung von C++20-Headereinheiten erforderlich. Benannte Module erfordern diese Option nicht.

Die Option /experimental:module ist weiterhin erforderlich, um die std.* -Module zu nutzen, die im Lieferumfang von Visual Studio vorhanden sind, da sie noch nicht standardisiert sind.

Die Option /experimental:module impliziert auch /Zc:twoPhase, /Zc:lambda und /Zc:hiddenFriend. Zuvor konnte mit Modulen kompilierter Code mit /Zc:twoPhase- kompiliert werden, wenn das Modul nur genutzt wurde. Dieses Verhalten wird nicht mehr unterstützt.

Verbesserungen der Konformität in Visual Studio 2019, Version 16.9

Kopierinitialisierung temporärer Variablen in Verweisdirektinitialisierung

Das Core Working Group-Issue CWG 2267 behandelt eine Inkonsistenz zwischen einer in runden Klammern gesetzten Initialisiererliste und einer in geschweiften Klammern gesetzten Initialisiererliste. Die Auflösung harmonisiert die beiden Formen.

Visual Studio 2019, Version 16.9 implementiert das geänderte Verhalten in allen /std -Compilermodi. Da es sich jedoch potenziell um einen Breaking Change der Quelle handelt, wird dies nur unterstützt, wenn der Code mit /permissive- kompiliert wird.

Das folgende Beispiel veranschaulicht die Änderung des Verhaltens:

struct A { };

struct B {
    explicit B(const A&);
};

void f()
{
    A a;
    const B& b1(a);     // Always an error
    const B& b2{ a };   // Allowed before resolution to CWG 2267 was adopted: now an error
}

Destruktormerkmale und potenziell konstruierte Teilobjekte

Das Core Working Group-Issue CWG 2336 deckt eine Auslassung bei impliziten Ausnahmespezifikationen von Destruktoren in Klassen ab, die über virtuelle Basisklassen verfügen. Durch die Auslassung konnte ein Destruktor in einer abgeleiteten Klasse eine schwächere Ausnahmespezifikation haben als eine Basisklasse, wenn die Basis abstrakt war und über eine virtual-Basis verfügte.

Visual Studio 2019, Version 16.9 implementiert das geänderte Verhalten in allen /std -Compilermodi.

Das folgende Beispiel zeigt, wie sich die Interpretation geändert hat:

class V {
public:
    virtual ~V() noexcept(false);
};

class B : virtual V {
    virtual void foo () = 0;
    // BEFORE: implicitly defined virtual ~B() noexcept(true);
    // AFTER: implicitly defined virtual ~B() noexcept(false);
};

class D : B {
    virtual void foo ();
    // implicitly defined virtual ~D () noexcept(false);
};

Vor der Änderung war noexcept der implizit definierte Konstruktor für B, weil nur potenziell konstruierte Teilobjekte berücksichtigt werden. Die Basisklasse V ist kein potenziell konstruiertes Teilobjekt, weil es sich um eine virtual-Basis handelt und B abstrakt ist. Die Basisklasse V ist jedoch ein potenziell konstruiertes Teilobjekt der Klasse D, daher wird D::~D als noexcept(false) festgelegt, was zu einer abgeleiteten Klasse mit einer schwächeren Spezifikation als die zugehörige Basis führt. Diese Interpretation ist unsicher. Sie kann zu falschem Laufzeitverhalten führen, wenn ein Destruktor einer aus B abgeleiteten Klasse eine Ausnahme auslöst.

Mit dieser Änderung ist ein Destruktor ebenfalls potenziell auslösend, wenn er über einen virtuellen Destruktor verfügt und eine beliebige Basisklasse einen potenziell auslösenden Destruktor enthält.

Ähnliche Typen und Verweisbindung

Das Core Working Group-Issue CWG 2352 behandelt eine Inkonsistenz zwischen Verweisbindungsregeln und Änderungen an der Typähnlichkeit. Die Inkonsistenz wurde in früheren Fehlerberichten eingeführt (z. B. CWG 330). Dies betraf die Visual Studio 2019-Versionen 16.0 bis 16.8.

Mit dieser Änderung kann ab der Visual Studio 2019-Version 16.9 Code, der zuvor einen Verweis an einen temporären Verweis in den Visual Studio 2019-Versionen 16.0 bis 16.8 gebunden hat, jetzt direkt binden, wenn sich die Typen nur durch cv-Qualifizierer unterscheiden.

Visual Studio 2019, Version 16.9 implementiert das geänderte Verhalten in allen /std -Compilermodi. Dies kann ein Breaking Change der Quelle sein.

Informationen zu einer entsprechenden Änderung finden Sie unter Verweise auf Typen mit nicht übereinstimmenden cv-Qualifizierern.

Das folgende Beispiel zeigt das geänderte Verhalten:

int *ptr;
const int *const &f() {
    return ptr; // Now returns a reference to 'ptr' directly.
    // Previously returned a reference to a temporary and emitted C4172
}

Das Update ändert möglicherweise das Programmverhalten in Bezug auf ein eingeführtes temporäres Element:

int func() {
    int i1 = 13;
    int i2 = 23;
    
    int* iptr = &i1;
    int const * const&  iptrcref = iptr;

    // iptrcref is a reference to a pointer to i1 with value 13.
    if (*iptrcref != 13)
    {
        return 1;
    }
    
    // Now change what iptr points to.

    // Prior to CWG 2352 iptrcref should be bound to a temporary and still points to the value 13.
    // After CWG 2352 it is bound directly to iptr and now points to the value 23.
    iptr = &i2;
    if (*iptrcref != 23)
    {
        return 1;
    }

    return 0;
}

Änderung des Verhaltens der Optionen /Zc:twoPhase und /Zc:twoPhase-

Normalerweise funktionieren die MSVC-Compileroptionen nach dem Prinzip: Der Letzte gewinnt. Leider war dies bei den Optionen /Zc:twoPhase und /Zc:twoPhase- nicht der Fall. Diese Optionen waren „fixiert“ und konnten daher von späteren Optionen nicht überschrieben werden. Zum Beispiel:

cl /Zc:twoPhase /permissive a.cpp

In diesem Fall aktiviert die erste /Zc:twoPhase -Option die strikte zweiphasige Namenssuche. Die zweite Option sollte den strikten Konformitätsmodus deaktivieren (sie ist das Gegenteil von /permissive- ), deaktivierte aber /Zc:twoPhase nicht.

Visual Studio 2019, Version 16.9, ändert dieses Verhalten in allen /std -Compilermodi. /Zc:twoPhase und /Zc:twoPhase- sind nicht mehr „fixiert“, und spätere Optionen können sie überschreiben.

Explizite noexcept-Spezifizierer in Dekonstruktorvorlagen

Der Compiler akzeptierte zuvor eine Dekonstruktorvorlage, die mit einer nicht auslösenden Ausnahmespezifikation deklariert wurde, aber ohne expliziten noexcept-Spezifizierer definiert war. Die implizite Ausnahmespezifikation eines Destruktors hängt von Klasseneigenschaften ab – Eigenschaften, die zum Zeitpunkt der Definition einer Vorlage möglicherweise nicht bekannt sind. Der C++-Standard erfordert auch dieses Verhalten: Wenn ein Destruktor ohne noexcept-Spezifizierer deklariert wird, verfügt er über eine implizite Ausnahmespezifikation, und keine andere Deklaration der Funktion darf einen noexcept-Spezifizierer besitzen.

Visual Studio 2019, Version 16.9, ändert dies in ein konformes Verhalten in allen /std -Compilermodi.

Das folgende Beispiel zeigt die Änderung am Compilerverhalten:

template <typename T>
class B {
    virtual ~B() noexcept; // or throw()
};

template <typename T>
B<T>::~B() { /* ... */ } // Before: no diagnostic.
// Now diagnoses a definition mismatch. To fix, define the implementation by 
// using the same noexcept-specifier. For example,
// B<T>::~B() noexcept { /* ... */ }

Neu geschriebene Ausdrücke in C++20

Seit Visual Studio 2019, Version 16.2, akzeptierte der Compiler unter /std:c++latest Code wie den folgenden:

#include <compare>

struct S {
    auto operator<=>(const S&) const = default;
    operator bool() const;
};

bool f(S a, S b) {
    return a < b;
}

Der Compiler rief jedoch nicht unbedingt die Vergleichsfunktion auf, die der Autor erwartete. Im oben stehenden Code hätte a < b als (a <=> b) < 0 neu geschrieben werden müssen. Stattdessen verwendete der Compiler die benutzerdefinierte Konvertierungsfunktion operator bool() und verglich bool(a) < bool(b). Ab Visual Studio 2019, Version 16.9 schreibt der Compiler den Ausdruck mithilfe des Spaceship-Operatorausdrucks neu.

Breaking Changes in der Quelle

Die ordnungsgemäße Anwendung von Konvertierungen auf neu geschriebene Ausdrücke hat noch einen weiteren Effekt: Der Compiler diagnostiziert Mehrdeutigkeiten aus Versuchen zum Neuschreiben des Ausdrucks korrekt. Betrachten Sie das folgende Beispiel:

struct Base {
    bool operator==(const Base&) const;
};

struct Derived : Base {
    Derived();
    Derived(const Base&);
    bool operator==(const Derived& rhs) const;
};

bool b = Base{} == Derived{};

In C++17 würde dieser Code aufgrund der aus der Basis abgeleiteten Konvertierung von Derived auf der rechten Seite des Ausdrucks akzeptiert. In C++20 wird auch der synthetisierte Ausdruckskandidat hinzugefügt: Derived{} == Base{}. Aufgrund der Regeln im Standard in Bezug darauf, welche Funktion basierend auf Konvertierungen gewinnt, stellt sich heraus, dass eine Entscheidung zwischen Base::operator== und Derived::operator== nicht möglich ist. Das liegt daran, dass die Konvertierungssequenzen in den beiden Ausdrücken weder besser noch schlechter sind als die jeweils andere und der Beispielcode daher zu einer Mehrdeutigkeit führt.

Um diese Mehrdeutigkeit aufzulösen, fügen Sie einen neuen Kandidaten hinzu, der den beiden Konvertierungssequenzen nicht unterliegt:

bool operator==(const Derived&, const Base&);

Breaking Change in der Runtime

Aufgrund der Regeln zum Neuschreiben von Operatoren in C++20 ist es möglich, dass eine Überladungsauflösung einen neuen Kandidaten findet, der in einem anderen Sprachmodus nicht gefunden würde. Der neue Kandidat ist möglicherweise sogar besser geeignet als der ältere Kandidat. Betrachten Sie das folgende Beispiel:

struct iterator;
struct const_iterator {
  const_iterator(const iterator&);
  bool operator==(const const_iterator &ci) const;
};

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == *this; }
};

In C++17 ist const_iterator::operator== der einzige Kandidat für ci == *this. Es ist eine Übereinstimmung, da *this eine aus der Basis abgeleitete Konvertierung in const_iterator durchläuft. In C++20 wird ein weiterer neu geschriebener Kandidat hinzugefügt: *this == ci, wodurch iterator::operator== aufgerufen wird. Dieser Kandidat erfordert keine Konvertierungen, ist also besser geeignet als const_iterator::operator==. Das Problem mit dem neuen Kandidaten ist, dass es sich um die Funktion handelt, die gerade definiert wird. Daher verursacht die neue Semantik der Funktion eine unendlich rekursive Definition von iterator::operator==.

Zur Unterstützung von Code wie in diesem Beispiel implementiert der Compiler eine neue Warnung:

$ cl /std:c++latest /c t.cpp
t.cpp
t.cpp(8): warning C5232: in C++20 this comparison calls 'bool iterator::operator ==(const const_iterator &) const' recursively

Wenn Sie den Code korrigieren, geben Sie explizit an, welche Konvertierung verwendet werden soll:

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};

Verbesserungen der Konformität in Visual Studio 2019, Version 16.10

Auswahl einer falschen Überladung für die Kopierinitialisierung einer Klasse

Betrachten Sie folgenden Beispielcode:

struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});

In früheren Versionen des Compilers würde das Argument von f mit dem vorlagenbasierten Konvertierungskonstruktor von A fälschlicherweise vom Typ C in A konvertiert. Im normalen C++ muss stattdessen der Konvertierungsoperator B::operator A verwendet werden. Ab Visual Studio 2019, Version 16.10 wurde das Auflösungsverhalten für Überladungen so geändert, dass die richtige Überladung verwendet wird.

Diese Änderung kann auch die ausgewählte Überladung in einigen anderen Situationen korrigieren:

struct Base 
{
    operator char *();
};

struct Derived : public Base
{
    operator bool() const;
};

void f(Derived &d)
{
    // Implicit conversion to bool previously used Derived::operator bool(), now uses Base::operator char*.
    // The Base function is preferred because operator bool() is declared 'const' and requires a qualification
    // adjustment for the implicit object parameter, while the Base function does not.
    if (d)
    {
        // ...
    }
}

Falsches Parsing von Gleitkommaliteralen

Ab Visual Studio 2019, Version 16.10 werden Gleitkommaliterale basierend auf ihrem tatsächlichen Typ geparst. In früheren Versionen des Compilers wurde ein Gleitkommaliteral immer so geparst, als ob es den Typ double hätte, und das Ergebnis dann in den tatsächlichen Typ konvertiert. Dieses Verhalten kann zu einer fehlerhaften Rundung und Zurückweisung gültiger Werte führen:

// The binary representation is '0x15AE43FE' in VS2019 16.9
// The binary representation is '0x15AE43FD' in VS2019 16.10
// You can use 'static_cast<float>(7.038531E-26)' if you want the old behavior.
float f = 7.038531E-26f;

Falscher Deklarationspunkt

In früheren Versionen des Compilers konnte selbstreferenzieller Code wie im folgenden Beispiel nicht kompiliert werden:

struct S {
    S(int, const S*);

    int value() const;
};

S s(4, &s);

Der Compiler würde die Variable s erst deklarieren, wenn die gesamte Deklaration einschließlich der Konstruktorargumente analysiert wurde. Die Suche von s in der Liste der Konstruktorargumente würde fehlschlagen. Ab Visual Studio 2019, Version 16.10 wird dieses Beispiel nun ordnungsgemäß kompiliert.

Diese Änderung kann allerdings vorhandenen Code funktionsunfähig machen, wie im folgenden Beispiel:

S s(1, nullptr); // outer s
// ...
{
   S s(s.value(), nullptr); // inner s
}

In früheren Versionen des Compilers wird bei der Suche von s in den Konstruktorargumenten für die „innere“ Deklaration von s die vorherige Deklaration („äußeres“ s) gefunden, und der Code wird kompiliert. Ab Version 16.10 gibt der Compiler stattdessen die Warnung C4700 aus. Der Grund dafür ist, dass der Compiler jetzt das „innere“ s deklariert, bevor die Konstruktorargumente analysiert werden. Daher findet die Suche von s das „innere“ s, das noch nicht initialisiert wurde.

Explizit spezialisierter Member einer Klassenvorlage

In früheren Versionen des Compilers wurde eine explizite Spezialisierung eines Klassenvorlagenmembers fälschlicherweise als inline markiert, wenn sie auch in der primären Vorlage definiert wurde. Dieses Verhalten bedeutete, dass vom Compiler manchmal konformer Code abgelehnt wurde. Ab Visual Studio 2019, Version 16.10 wird eine explizite Spezialisierung im /permissive--Modus nicht mehr implizit als inline markiert. Betrachten Sie das folgende Beispiel:

Quelldatei s.h:

// s.h
template<typename T>
struct S {
    int f() { return 1; }
};
template<> int S<int>::f() { return 2; }

Quelldatei s.cpp:

// s.cpp
#include "s.h"

Quelldatei main.cpp:

// main.cpp
#include "s.h"

int main()
{
}

Um den Linkerfehler im obigen Beispiel zu beheben, wird inline explizit zu S<int>::f hinzugefügt:

template<> inline int S<int>::f() { return 2; }

Mangling von Namen für abgeleitete Rückgabetypen

Ab Visual Studio 2019, Version 16.10 wurde das Compilerverhalten bei der Generierung von Mangling-Funktionsnamen mit abgeleiteten Rückgabetypen geändert. Betrachten Sie beispielsweise die folgenden Funktionen:

auto f() { return 0; }
auto g() { []{}; return 0; }

In früheren Versionen des Compilers wurden diese Namen für den Linker generiert:

f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)

Überraschenderweise würde der Rückgabetyp von g aufgrund des anderen semantischen Verhaltens, das durch den lokalen Lambda-Ausdruck im Funktionstext verursacht wird, ausgelassen. Diese Inkonsistenz erschwerte die Implementierung exportierter Funktionen mit einem abgeleiteten Rückgabetyp: Die Modulschnittstelle benötigt Informationen darüber, wie der Rumpf einer Funktion kompiliert wurde. Sie benötigt die Informationen, um eine Funktion auf der Importseite zu erstellen, die ordnungsgemäß mit der Definition verknüpft werden kann.

Der Compiler lässt nun den Rückgabetyp einer Funktion mit einem abgeleiteten Rückgabetyp aus. Dieses Verhalten ist mit anderen wichtigen Implementierungen konsistent. Es gibt eine Ausnahme für Funktionsvorlagen: Diese Version des Compilers führt ein neues Verhalten von Mangling-Namen für Funktionsvorlagen ein, die einen abgeleiteten Rückgabetyp aufweisen:

template <typename T>
auto f(T) { return 1; }

template <typename T>
decltype(auto) g(T) { return 1.; }

int (*fp1)(int) = &f;
double (*fp2)(int) = &g;

Die Mangling-Namen für auto und decltype(auto) werden jetzt in der Binärdatei angezeigt, nicht der abgeleitete Rückgabetyp:

f: ??$f@H@@YA?A_PH@Z -> auto __cdecl f<int>(int)
g: ??$g@H@@YA?A_TH@Z -> decltype(auto) __cdecl g<int>(int)

In früheren Versionen des Compilers war der abgeleitete Rückgabetyp als Teil der Signatur enthalten. Wenn der Rückgabetyp im Mangling-Namen enthalten war, konnte dies zu Linkerproblemen führen. Einige ansonsten wohlgeformte Szenarien würden für den Linker mehrdeutig werden.

Das neue Compilerverhalten kann zu einem binären Breaking Change führen. Betrachten Sie das folgende Beispiel:

Quelldatei a.cpp:

// a.cpp
auto f() { return 1; }

Quelldatei main.cpp:

// main.cpp
int f();
int main() { f(); }

In Versionen vor Version 16.10 hat der Compiler einen Namen für auto f() erstellt, der wie int f() aussieht, obwohl es sich um semantisch unterschiedliche Funktionen handelt. Das Beispiel würde also kompiliert werden. Um das Problem zu beheben, sollte auto nicht in der ursprünglichen Definition von f verwendet werden. Verwenden Sie stattdessen int f(). Da Funktionen mit abgeleiteten Rückgabetypen immer kompiliert werden, werden die Auswirkungen auf ABI minimiert.

Warnung für ignoriertes nodiscard-Attribut

In früheren Versionen des Compilers wurden bestimmte Verwendungen eines nodiscard-Attributs stillschweigend ignoriert. Das Attribut wurde ignoriert, wenn es an einer syntaktischen Position vorhanden war, die nicht für die deklarierte Funktion oder Klasse gilt. Zum Beispiel:

static [[nodiscard]] int f() { return 1; }

Ab Visual Studio 2019, Version 16.10 gibt der Compiler stattdessen die Warnung C5240 der Stufe 4 aus:

a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position

Um dieses Problem zu beheben, verschieben Sie das Attribut an die richtige syntaktische Position:

[[nodiscard]] static int f() { return 1; }

Warnung für include-Direktiven mit Systemheadernamen im Geltungsbereich eines Moduls

Ab Visual Studio 2019, Version 16.10 gibt der Compiler eine Warnung aus, um einen allgemeinen Fehler bei der Erstellung von Modulschnittstellen zu verhindern. Wenn Sie einen Standardbibliotheksheader nach einer export module-Anweisung einfügen, gibt der Compiler die Warnung C5244 aus. Hier sehen Sie ein Beispiel:

export module m;
#include <vector>

export
void f(std::vector<int>);

Vom Entwickler war wahrscheinlich nicht beabsichtigt, dass das Modul m den Inhalt von <vector> umfasst. Der Compiler gibt nun eine Warnung aus, um das Problem zu finden und zu beheben:

m.ixx(2): warning C5244: '#include <vector>' in the purview of module 'm' appears erroneous. Consider moving that directive before the module declaration, or replace the textual inclusion with an "import <vector>;".
m.ixx(1): note: see module 'm' declaration

Um dieses Problem zu beheben, verschieben Sie #include <vector> vor export module m;:

#include <vector>
export module m;

export
void f(std::vector<int>);

Warnung für nicht verwendete interne Verknüpfungsfunktionen

Ab Visual Studio 2019, Version 16.10 gibt der Compiler in mehr Situationen eine Warnung aus, in denen eine nichtreferenzierte Funktion mit interner Verknüpfung entfernt wurde. In früheren Versionen des Compilers wurde die Warnung C4505 für den folgenden Code ausgegeben:

static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}

Der Compiler gibt jetzt auch eine Warnung für nichtreferenzierte auto-Funktionen und nichtreferenzierte Funktionen in anonymen Namespaces aus. Für beide folgende Funktionen wird die standardmäßig deaktivierte Warnung C5245 ausgegeben:

namespace
{
    void f1() // warning C5245: '`anonymous-namespace'::f1': unreferenced function with internal linkage has been removed
    {
    }
}

auto f2() // warning C5245: 'f2': unreferenced function with internal linkage has been removed
{
    return []{ return 13; };
}

Warnung bei Auslassung von geschweiften Klammern

Ab Visual Studio 2019, Version 16.10 gibt der Compiler bei Initialisierungslisten, die keine geschweiften Klammern für Unterobjekte verwenden, eine Warnung aus. Der Compiler gibt die standardmäßig deaktivierte Warnung C5246 aus.

Hier sehen Sie ein Beispiel:

struct S1 {
  int i, j;
};

struct S2 {
   S1 s1;
   int k;
};

S2 s2{ 1, 2, 3 }; // warning C5246: 'S2::s1': the initialization of a subobject should be wrapped in braces

Um dieses Problem zu beheben, setzen Sie die Initialisierung des Unterobjekts in geschweifte Klammern:

S2 s2{ { 1, 2 }, 3 };

Richtige Erkennung, ob ein const-Objekt nicht initialisiert wurde

Ab Visual Studio 2019, Version 16.10 gibt der Compiler nun den Fehler C2737 aus, wenn Sie versuchen, ein const-Objekt zu definieren, das nicht vollständig initialisiert wurde:

struct S {
   int i;
   int j = 2;
};

const S s; // error C2737: 's': const object must be initialized

In früheren Versionen des Compilers konnte dieser Code kompiliert werden, obwohl S::i nicht initialisiert war.

Um dieses Problem zu beheben, initialisieren Sie alle Member, bevor Sie eine const-Instanz eines Objekts erstellen:

struct S {
   int i = 1;
   int j = 2;
};

Verbesserungen der Konformität in Visual Studio 2019, Version 16.11

/std:c++20-Compilermodus

Ab Visual Studio 2019, Version 16.11, unterstützt der Compiler den /std:c++20-Compilermodus. Zuvor waren C++20-Features nur im /std:c++latest-Modus in Visual Studio 2019 verfügbar. C++20-Features, für die ursprünglich der /std:c++latest-Modus erforderlich war, funktionieren nun im /std:c++20-Modus oder in den neuesten Versionen von Visual Studio.

Siehe auch

Microsoft C/C++-Sprachkonformität