Ulepszenia zgodności języka C++, zmiany zachowania i poprawki błędów w programie Visual Studio 2019

Język Microsoft C/C++ w programie Visual Studio (MSVC) wprowadza ulepszenia zgodności i poprawki błędów w każdej wersji. W tym artykule wymieniono ulepszenia według wersji głównej, a następnie według wersji. Aby przejść bezpośrednio do zmian dla określonej wersji, użyj poniższej listy w tym artykule.

Ten dokument zawiera listę zmian w programie Visual Studio 2019. Aby zapoznać się ze zmianami w programie Visual Studio 2022, zobacz Ulepszenia zgodności języka C++ w programie Visual Studio 2022. Aby uzyskać informacje o zmianach w programie Visual Studio 2017, zobacz Ulepszenia zgodności języka C++ w programie Visual Studio 2017. Aby uzyskać pełną listę poprzednich ulepszeń zgodności, zobacz Visual C++ What's New 2003–2015 (Co nowego w programie Visual C++ do 2015).

Ulepszenia zgodności w programie Visual Studio 2019 RTW (wersja 16.0)

Program Visual Studio 2019 RTW zawiera następujące ulepszenia zgodności, poprawki błędów i zmiany zachowania w kompilatorze języka Microsoft C++.

Uwaga

Funkcje języka C++20 były dostępne tylko w /std:c++latest trybie w programie Visual Studio 2019 do momentu zakończenia implementacji języka C++20. Program Visual Studio 2019 w wersji 16.11 wprowadza tryb kompilatora /std:c++20 . W tym artykule funkcje, które pierwotnie wymagały /std:c++latest trybu, działają teraz w /std:c++20 trybie lub nowszym w najnowszych wersjach programu Visual Studio. Zaktualizowaliśmy dokumentację, aby wspomnieć , /std:c++20mimo że ta opcja nie była dostępna, gdy funkcje zostały wydane po raz pierwszy.

Ulepszona obsługa modułów dla szablonów i wykrywania błędów

Moduły są teraz oficjalnie w standardzie C++20. Ulepszona obsługa została dodana w programie Visual Studio 2017 w wersji 15.9. Aby uzyskać więcej informacji, zobacz Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (Lepsza obsługa szablonów i wykrywanie błędów w modułach języka C++ z programem MSVC 2017 w wersji 15.9).

Zmodyfikowana specyfikacja typu agregacji

Specyfikacja typu agregującego została zmieniona w języku C++20 (zobacz Zakaz agregacji za pomocą konstruktorów zadeklarowanych przez użytkownika). W programie Visual Studio 2019 w obszarze /std:c++latest (lub /std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej) klasa z dowolnym konstruktorem zadeklarowanym przez użytkownika (na przykład z zadeklarowanym = default konstruktorem lub = delete) nie jest agregacja. Wcześniej tylko konstruktory dostarczane przez użytkownika zdyskwalifikują klasę z bycia agregacji. Ta zmiana nakłada więcej ograniczeń na sposób inicjowania takich typów.

Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale zgłasza błędy C2280 i C2440 w programie Visual Studio 2019 w obszarze /std:c++20 lub /std:c++latest:

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

Częściowa obsługa dla operator <=>

P0515R3 C++20 wprowadza <=> trójkierunkowy operator porównania, znany również jako "operator statku kosmicznego". Program Visual Studio 2019 w wersji 16.0 w /std:c++latest trybie wprowadza częściową obsługę operatora przez zgłaszanie błędów składni, które są teraz niedozwolone. Na przykład następujący kod kompiluje się bez błędów w programie Visual Studio 2017, ale zgłasza wiele błędów w programie Visual Studio 2019 w programie /std:c++20 Lub /std:c++latest:

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.
}

Aby uniknąć błędów, wstaw spację w linii obrażającej przed końcowym nawiasem kątowym: U<&S::operator<= > u;.

Odwołania do typów z niezgodnymi kwalifikatorami cv

Uwaga

Ta zmiana dotyczy tylko programu Visual Studio 2019 w wersji od 16.0 do 16.8. Został przywrócony, począwszy od programu Visual Studio 2019 w wersji 16.9

Wcześniej MSVC zezwolił na bezpośrednie powiązanie odwołania z typu z niezgodnymi kwalifikatorami cv poniżej najwyższego poziomu. To powiązanie może umożliwić modyfikację rzekomo const danych, do których odwołuje się odwołanie.

Kompilator dla programu Visual Studio 2019 w wersji od 16.0 do 16.8 zamiast tego tworzy tymczasowy element, zgodnie z wymaganiami standardu w tym czasie. Później standardowo zmienił się, wprowadzając poprzednie zachowanie programu Visual Studio 2017 i jego wcześniejsze wersje oraz zachowanie programu Visual Studio 2019 w wersji 16.0 do 16.8. W związku z tym ta zmiana została przywrócona, począwszy od programu Visual Studio 2019 w wersji 16.9.

Zobacz Podobne typy i powiązanie odwołania, aby uzyskać powiązaną zmianę.

Na przykład w programie Visual Studio 2017 poniższy kod kompiluje się bez ostrzeżeń. W programie Visual Studio 2019 w wersji od 16.0 do 16.8 kompilator zgłasza ostrzeżenie C4172. Począwszy od programu Visual Studio 2019 w wersji 16.9, kod po raz kolejny kompiluje się bez ostrzeżeń:

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
}

reinterpret_cast z przeciążonej funkcji

Argument to reinterpret_cast nie jest jednym z kontekstów, w których dozwolony jest adres przeciążonej funkcji. Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 zgłasza błąd C2440:

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'
}

Aby uniknąć tego błędu, użyj dozwolonego rzutowania dla tego scenariusza:

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

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

Zamknięcia lambda

W języku C++14 typy zamknięcia lambda nie są literałami. Podstawową konsekwencją tej reguły jest to, że lambda może nie być przypisana do zmiennej constexpr . Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 zgłasza błąd C2127:

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

Aby uniknąć błędu, usuń constexpr kwalifikator lub zmień tryb zgodności na /std:c++17 lub nowszy.

std::create_directory kody błędów

Zaimplementowano język P1164 z języka C++20 bezwarunkowo. Te zmiany std::create_directory umożliwiają sprawdzenie, czy obiekt docelowy był już katalogiem po awarii. Wcześniej wszystkie błędy typu ERROR_ALREADY_EXISTS zostały przekształcone w kody success-but-directory-not-created.

operator<<(std::ostream, nullptr_t)

Na LWG 2221 dodano operator<<(std::ostream, nullptr_t) zapis nullptr do strumieni.

Bardziej równoległe algorytmy

Nowe równoległe wersje is_sorted, , is_sorted_until, set_intersectionis_partitionedset_difference, , is_heapi is_heap_until.

Poprawki w inicjalizacji niepodzielnej

P0883 "Naprawianie inicjowania niepodzielnego" zmienia std::atomic się w celu zainicjowania zawartej T zamiast inicjowania domyślnego. Poprawka jest włączona w przypadku korzystania z języka Clang/LLVM z biblioteką standardową firmy Microsoft. Obecnie jest on wyłączony dla kompilatora języka Microsoft C++, ponieważ jest to obejście problemu podczas constexpr przetwarzania.

remove_cvref i remove_cvref_t

remove_cvref Zaimplementowano cechy typów i remove_cvref_t z P0550. Usuwają one odwołania i kwalifikacje cv z typu bez rozkładania funkcji i tablic do wskaźników (w przeciwieństwie std::decay do i std::decay_t).

Makra testu funkcji

P0941R2 — makra testów funkcji są kompletne z obsługą programu __has_cpp_attribute. Makra testów funkcji są obsługiwane we wszystkich trybach standardowych.

Zakaz agregacji za pomocą konstruktorów zadeklarowanych przez użytkownika

C++20 P1008R1 — kończy się zakaz agregacji z konstruktorami zadeklarowanymi przez użytkownika.

reinterpret_castconstexpr w funkcji

Element A reinterpret_cast jest niedozwolony constexpr w funkcji. Kompilator języka Microsoft C++ zostałby wcześniej odrzucony reinterpret_cast tylko wtedy, gdy był używany w constexpr kontekście. W programie Visual Studio 2019 we wszystkich trybach standardów językowych kompilator poprawnie diagnozuje reinterpret_cast element w definicji constexpr funkcji. Poniższy kod generuje teraz kod C3615:

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

Aby uniknąć błędu, usuń constexpr modyfikator z deklaracji funkcji.

Poprawna diagnostyka konstruktora zakresu basic_string

W programie Visual Studio 2019 basic_string konstruktor zakresu nie pomija już diagnostyki kompilatora za pomocą polecenia static_cast. Poniższy kod kompiluje się bez ostrzeżeń w programie Visual Studio 2017, pomimo możliwej utraty danych z wchar_t do char podczas inicjowania outprogramu :

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

Program Visual Studio 2019 poprawnie zgłasza ostrzeżenie C4244. Aby uniknąć ostrzeżenia, możesz zainicjować element std::string , jak pokazano w tym przykładzie:

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

Niepoprawne wywołania do += i -= w obszarze /clr lub /ZW są teraz prawidłowo wykrywane

Wprowadzono usterkę w programie Visual Studio 2017, która spowodowała, że kompilator dyskretnie ignorował błędy i nie wygenerował żadnego kodu dla nieprawidłowych wywołań metody += i -= w obszarze /clr lub /ZW. Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 poprawnie zgłasza błąd 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.
}

Aby uniknąć błędu w tym przykładzie, użyj += operatora z ToString() metodą : s += E::e.ToString();.

Inicjatory dla wbudowanych składowych danych statycznych

Nieprawidłowe dostępy do składowych w ramach inline inicjatorów i static constexpr są teraz poprawnie wykrywane. Poniższy przykład kompiluje bez błędu w programie Visual Studio 2017, ale w programie Visual Studio 2019 /std:c++17 w trybie lub nowszym zgłasza błąd C2248:

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'.
};

Aby uniknąć błędu, zadeklaruj element członkowski X::c jako chroniony:

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

Przywrócono C4800

MSVC używane do ostrzeżenia o wydajności C4800 o niejawnej konwersji na bool. To było zbyt hałaśliwe i nie można go pominąć, co doprowadziło nas do usunięcia go w programie Visual Studio 2017. Jednak w całym cyklu życia programu Visual Studio 2017 uzyskaliśmy wiele opinii na temat przydatnych przypadków rozwiązywania problemów. Przywracamy program Visual Studio 2019 starannie dostosowany C4800 wraz z objaśnieniami C4165. Oba te ostrzeżenia są łatwe do pomijania: przy użyciu jawnego rzutowania lub w porównaniu do 0 odpowiedniego typu. C4800 jest ostrzeżeniem poza domyślnym poziomem 4, a C4165 jest domyślnie ostrzeżeniem poziomu 3 poza. Oba są wykrywalne przy użyciu opcji kompilatora /Wall .

Poniższy przykład zgłasza C4800 i C4165 w obszarze /Wall:

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?
}

Aby uniknąć ostrzeżeń w poprzednim przykładzie, możesz napisać kod w następujący sposób:

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
}

Funkcja składowa klasy lokalnej nie ma treści

W programie Visual Studio 2017 ostrzeżenie C4822 jest zgłaszane tylko wtedy, gdy opcja /w14822 kompilatora jest jawnie ustawiona. Nie jest on wyświetlany za pomocą polecenia /Wall. W programie Visual Studio 2019 C4822 jest domyślnym ostrzeżeniem, które sprawia, że można /Wall je odnaleźć bez konieczności jawnego ustawiania/w14822.

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

Elementy szablonu funkcji zawierające instrukcje if constexpr

W programie Visual Studio 2019 w obszarze /std:c++20 lub /std:c++latestfunkcje szablonu zawierające instrukcje mają if constexpr włączone dodatkowe kontrole związane z analizowaniem. Na przykład w programie Visual Studio 2017 poniższy kod generuje kod C7510 tylko wtedy, gdy opcja jest ustawiona /permissive- . W programie Visual Studio 2019 ten sam kod zgłasza błędy nawet wtedy, gdy opcja jest ustawiona /permissive :

// 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";
}

Aby uniknąć błędu, dodaj typename słowo kluczowe do deklaracji a: typename T::Type a;.

Wbudowany kod zestawu nie jest obsługiwany w wyrażeniu lambda

Zespół microsoft C++ został niedawno poinformowany o problemie z zabezpieczeniami, w którym użycie asemblera wbudowanego w środowisku lambda może prowadzić do uszkodzenia (rejestru adresów zwrotnych ebp ) w czasie wykonywania. Złośliwy atakujący może skorzystać z tego scenariusza. Wbudowany asembler jest obsługiwany tylko w architekturze x86, a interakcja między wbudowanym asemblerem a resztą kompilatora jest słaba. Biorąc pod uwagę te fakty i charakter problemu, najbezpieczniejszym rozwiązaniem tego problemu było uniemożliwienie asemblera wbudowanego w wyrażeniu lambda.

Jedynym użyciem wbudowanego asemblera w wyrażeniu lambda, które znaleźliśmy "na wolności", było przechwycenie adresu zwrotnego. W tym scenariuszu można przechwycić adres zwrotny na wszystkich platformach, używając wbudowanego kompilatora _ReturnAddress().

Poniższy kod generuje kod C7553 w programie Visual Studio 2017 15.9 i nowszych wersjach programu 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;
}

Aby uniknąć błędu, przenieś kod zestawu do nazwanej funkcji, jak pokazano w poniższym przykładzie:

#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());
}

Debugowanie iteratora i std::move_iterator

Funkcja debugowania iteratora została nauczona prawidłowego odpakowania std::move_iterator. Na przykład std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) można teraz zaangażować szybką ścieżkę memcpy .

Poprawki wymuszania <słów kluczowych xkeycheck.h>

Naprawiono wymuszanie biblioteki standardowej w <pliku xkeycheck.h> dla makr zastępujących słowo kluczowe. Biblioteka emituje teraz rzeczywiste słowo kluczowe problemu wykryte, a nie ogólny komunikat. Obsługuje również słowa kluczowe języka C++20 i pozwala uniknąć podstępowania funkcji IntelliSense, mówiąc, że słowa kluczowe losowe są makrami.

Typy alokatora nie są już przestarzałe

std::allocator<void>, std::allocator::size_typei std::allocator::difference_type nie są już przestarzałe.

Poprawne ostrzeżenie dotyczące konwersji ciągów zawężających

Usunięto fałszywe static_cast z std::string tego nie zostało wywołane przez standard, i że przypadkowo pominięte ostrzeżenia C4244 zawężające. Próby wywołania std::string::string(const wchar_t*, const wchar_t*) teraz prawidłowo emitują C4244 o zawężaniu wchar_t do elementu char.

Różne poprawki <poprawności systemu> plików

  • Rozwiązano problem powodujący std::filesystem::last_write_time niepowodzenie podczas próby zmiany czasu ostatniego zapisu katalogu.
  • Konstruktor std::filesystem::directory_entry przechowuje teraz wynik niepowodzenia, zamiast zgłaszać wyjątek, gdy podano nieistniejącą ścieżkę docelową.
  • Wersja std::filesystem::create_directory 2-parametru została zmieniona, aby wywołać wersję 1-parametru, ponieważ funkcja bazowa CreateDirectoryExW będzie używana copy_symlink , gdy existing_p był to sylink.
  • std::filesystem::directory_iterator nie kończy się już niepowodzeniem, gdy zostanie znaleziony uszkodzony link syymlinku.
  • std::filesystem::space teraz akceptuje ścieżki względne.
  • std::filesystem::path::lexically_relative nie jest już mylony przez końcowe ukośniki, zgłaszane jako LWG 3096.
  • CreateSymbolicLinkW Obejśliśmy odrzucanie ścieżek za pomocą ukośników w elemecie std::filesystem::create_symlink.
  • Obejście funkcji trybu delete usuwania POSIX, która istniała w systemie Windows 10 LTSB 1609, ale nie mogła w rzeczywistości usunąć plików.
  • Konstruktory std::boyer_moore_searcher i std::boyer_moore_horspool_searcher konstruktory kopii i operatory przypisania kopiowania teraz faktycznie kopiują elementy.

Algorytmy równoległe w systemie Windows 8 lub nowszym

Biblioteka algorytmów równoległych teraz prawidłowo używa prawdziwej WaitOnAddress rodziny w systemie Windows 8 i nowszych, a nie zawsze przy użyciu systemu Windows 7 i wcześniejszych fałszywych wersji.

std::system_category::message() Odstępu

std::system_category::message() teraz przycina końcowe białe znaki z zwróconego komunikatu.

std::linear_congruential_engine dzielenie przez zero

Naprawiono niektóre warunki, które spowodowałyby std::linear_congruential_engine wyzwolenie dzielenia przez 0.

Poprawki dotyczące unwrapping iteratora

Niektóre maszyny iteracyjne rozpatkujące zostały po raz pierwszy uwidocznione na potrzeby integracji programisty-użytkownik w programie Visual Studio 2017 15.8. Został on opisany w artykule C++ Team Blog (Blog zespołu języka C++) Dotyczący funkcji i poprawek biblioteki STL w programie VS 2017 15.8. Te maszyny nie rozpakowuje już iteratorów pochodzących z iteratorów bibliotek standardowych. Na przykład użytkownik, który pochodzi z std::vector<int>::iterator i próbuje dostosować zachowanie, teraz pobiera dostosowane zachowanie podczas wywoływania standardowych algorytmów biblioteki, a nie zachowania wskaźnika.

Nieurządkowana funkcja kontenera reserve faktycznie rezerwuje elementy N zgodnie z opisem w lwg 2156.

Obsługa czasu

  • Wcześniej niektóre wartości czasu przekazane do biblioteki współbieżności przepełniły się, na przykład condition_variable::wait_for(seconds::max()). Teraz naprawiono, przepełnienia zmieniły zachowanie w pozornie losowym cyklu 29-dniowym (gdy uint32_t milisekund zaakceptowane przez bazowe interfejsy API Win32 przepełnione).

  • <Nagłówek ctime> teraz poprawnie deklaruje timespec i timespec_get w przestrzeni nazwstd, a także deklaruje je w globalnej przestrzeni nazw.

Różne poprawki dla kontenerów

  • Wiele standardowych funkcji kontenera wewnętrznego biblioteki zostało wykonanych private w celu uzyskania ulepszonego środowiska funkcji IntelliSense. Więcej poprawek oznaczania elementów członkowskich zgodnie private z oczekiwaniami w kolejnych wersjach programu MSVC.

  • Rozwiązano problemy z poprawnością bezpieczeństwa wyjątków, które spowodowały uszkodzenie kontenerów opartych na węzłach, takich jak list, mapi unordered_map, . Podczas operacji ponownego propagate_on_container_copy_assignment przypisania lub propagate_on_container_move_assignment zwolnilibyśmy węzeł sentinel kontenera ze starym alokatorem, wykonaj przypisanie POCCA/POCMA za pośrednictwem starego alokatora, a następnie spróbujemy uzyskać węzeł sentinel z nowego alokatora. Jeśli ta alokacja nie powiodła się, kontener został uszkodzony. Nie można go nawet zniszczyć, ponieważ posiadanie węzła sentinel jest niezmienną strukturą danych. Ten kod został naprawiony w celu utworzenia nowego węzła sentinel przy użyciu alokatora kontenera źródłowego przed zniszczeniem istniejącego węzła sentinel.

  • Kontenery zostały naprawione tak, aby zawsze kopiować/przenosić/zamieniać alokatory zgodnie z propagate_on_container_copy_assignmentpropagate_on_container_move_assignment, i , nawet propagate_on_container_swapdla alokatorów zadeklarowanych is_always_equal.

  • Dodano przeciążenia dla funkcji scalania kontenerów i wyodrębniania elementów członkowskich, które akceptują kontenery rvalue. Aby uzyskać więcej informacji, zobacz P0083 "Splicing Mapy And Sets" (Splicing Mapy And Sets)

std::basic_istream::read\r\n`` =>przetwarzanie \n'

std::basic_istream::read został naprawiony, aby nie zapisywać części dostarczonego buforu tymczasowo w ramach przetwarzania \r\n\n . Ta zmiana daje pewne korzyści z wydajności uzyskane w programie Visual Studio 2017 15.8 w przypadku operacji odczytu większych niż 4K. Jednak ulepszenia wydajności przed unikaniem trzech wywołań wirtualnych na znak są nadal obecne.

std::bitset Konstruktor

Konstruktor std::bitset nie odczytuje już tych i zer w odwrotnej kolejności dla dużych zestawów bitów.

std::pair::operator= Regresji

Usunęliśmy regresję w operatorze std::pair przypisania wprowadzonym podczas implementowania lwg 2729 "Brak SFINAE w " std::pair::operator=;". Teraz poprawnie akceptuje typy konwertowane na std::pair ponownie.

Konteksty niezwiązane z add_const_t

Usunęliśmy usterkę cech drobnego typu, w której add_const_t i powiązane funkcje powinny być kontekstem nieudukowanym. Innymi słowy, add_const_t powinien być aliasem dla typename add_const<T>::typeelementu , a nie const T.

Ulepszenia zgodności w wersji 16.1

char8_t

P0482r6. Język C++20 dodaje nowy typ znaku używany do reprezentowania jednostek kodu UTF-8. u8 literały ciągu w języku C++20 mają typ const char8_t[N] zamiast const char[N], co miało miejsce wcześniej. Podobne zmiany zostały zaproponowane dla standardu C w N2231. Sugestie dotyczące char8_t korygowania zgodności z poprzednimi wersjami podano w wersji P1423r3. Kompilator języka Microsoft C++ dodaje obsługę char8_t programu Visual Studio 2019 w wersji 16.1 podczas określania opcji kompilatora /Zc:char8_t . Można go przywrócić do zachowania języka C++17 za pomocą polecenia /Zc:char8_t-. Kompilator EDG, który obsługuje funkcję IntelliSense, nie obsługuje go jeszcze w programie Visual Studio 2019 w wersji 16.1. Mogą pojawić się fałszywe błędy tylko dla funkcji IntelliSense, które nie wpływają na rzeczywistą kompilację.

Przykład

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

std::type_identity metafunction i std::identity obiekt funkcji

P0887R1 type_identity. Przestarzałe std::identity rozszerzenie szablonu klasy zostało usunięte i zastąpione metafunction języka C++20 std::type_identity i std::identity obiektem funkcji. Oba są dostępne tylko w obszarze /std:c++latest (/std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej).

Poniższy przykład generuje ostrzeżenie o wycofaniu kodu C4996 dla std::identity (zdefiniowane w type_traits>) w <programie Visual Studio 2017:

#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);

W poniższym przykładzie pokazano, jak używać nowego std::identity (zdefiniowanego w <funkcjonalności>) wraz z nowym std::type_identityelementem :

#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);

Sprawdzanie składni dla ogólnych wyrażeń lambd

Nowy procesor lambda umożliwia sprawdzanie składniowe w trybie zgodności w ogólnych wyrażeniach lambda w obszarze /std:c++latest (/std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej) lub w innym trybie /Zc:lambda językowym w programie Visual Studio 2019 w wersji 16.9 lub nowszej (wcześniej dostępnej jako /experimental:newLambdaProcessor począwszy od programu Visual Studio 2019 w wersji 16.3).

Starszy procesor lambda kompiluje ten przykład bez ostrzeżeń, ale nowy procesor lambda generuje błąd C2760:

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

W tym przykładzie przedstawiono poprawną składnię, która jest wymuszana przez kompilator:

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

Wyszukiwanie zależne od argumentów dla wywołań funkcji

P0846R0 (C++20) Zwiększona możliwość znajdowania szablonów funkcji za pomocą wyszukiwania zależnego od argumentów dla wyrażeń wywołań funkcji z jawnymi argumentami szablonu. Wymaga /std:c++latest (lub /std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej).

Wyznaczona inicjalizacja

P0329R4 (C++20) Wyznaczone inicjowanie umożliwia wybranie określonych elementów członkowskich w zagregowanej inicjalizacji Type t { .member = expr } przy użyciu składni. Wymaga /std:c++latest (lub /std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej).

Klasyfikacja konwersji wyliczenia na stały typ bazowy

Kompilator klasyfikuje teraz konwersje wyliczenia zgodnie z N4800 11.3.3.2 Klasyfikowanie niejawnych sekwencji konwersji (4.2):

  • Konwersja, która promuje wyliczenie, którego typ bazowy jest stały dla jego typu bazowego, jest lepszy niż taki, który promuje promowany typ bazowy, jeśli te dwa są różne.

Ten ranking konwersji nie został poprawnie zaimplementowany przed programem Visual Studio 2019 w wersji 16.1. Zachowanie zgodne może spowodować zmianę zachowania rozwiązywania przeciążenia lub uwidocznienie niejednoznaczności, gdy wcześniej nie wykryto tego zachowania.

Ta zmiana zachowania kompilatora dotyczy wszystkich /std trybów i jest zarówno zmianą powodującą niezgodność źródłową, jak i binarną.

W poniższym przykładzie pokazano, jak zmienia się zachowanie kompilatora w wersji 16.1 lub nowszej:

#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{});
}

Nowe i zaktualizowane funkcje biblioteki standardowej (C++20)

  • starts_with()i dla basic_string i basic_string_viewends_with() .
  • contains() dla kontenerów asocjacyjnych.
  • remove(), remove_if() i unique() dla obiektów list i forward_list zwracają teraz obiekt size_type.
  • shift_left()i shift_right() dodane do algorytmu<>.

Ulepszenia zgodności w wersji 16.2

noexceptconstexpr, funkcje

constexpr funkcje nie są już traktowane noexcept domyślnie, gdy są używane w wyrażeniu stałym. Ta zmiana zachowania wynika z rozwiązania podstawowej grupy roboczej (CWG) CWG 1351 i jest włączona w systemie /permissive-. Poniższy przykład kompiluje się w programie Visual Studio 2019 w wersji 16.1 lub starszej, ale tworzy C2338 w programie Visual Studio 2019 w wersji 16.2:

constexpr int f() { return 0; }

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

Aby naprawić błąd, dodaj noexcept wyrażenie do deklaracji funkcji:

constexpr int f() noexcept { return 0; }

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

Wyrażenia binarne z różnymi typami wyliczeniowymi

Język C++20 wycofał zwykłe konwersje arytmetyczne na operandach, gdzie:

  • Jeden operand ma typ wyliczenia i

  • druga jest innego typu wyliczenia lub typu zmiennoprzecinkowego.

Aby uzyskać więcej informacji, zobacz P1120R0.

W programie Visual Studio 2019 w wersji 16.2 lub nowszej poniższy kod generuje ostrzeżenie poziomu 4 C5054 po włączeniu /std:c++latest opcji kompilatora (/std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej):

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

Aby uniknąć ostrzeżenia, użyj polecenia static_cast , aby przekonwertować drugi operand:

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

Użycie operacji binarnej między wyliczeniem a typem zmiennoprzecinkowa jest teraz ostrzeżeniem poziomu 1 C5055 po włączeniu /std:c++latest opcji kompilatora (/std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej):

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

Aby uniknąć ostrzeżenia, użyj polecenia static_cast , aby przekonwertować drugi operand:

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

Porównanie równości i relacyjnych tablic

Porównania równości i relacyjne między dwoma operandami typu tablicy są przestarzałe w języku C++20 (P1120R0). Innymi słowy, operacja porównania między dwiema tablicami (pomimo podobieństw rangi i zakresu) jest teraz ostrzeżeniem. W programie Visual Studio 2019 w wersji 16.2 lub nowszej poniższy kod generuje ostrzeżenie poziomu 1 C5056 po włączeniu /std:c++latest opcji kompilatora (/std:c++20 w programie Visual Studio 2019 w wersji 16.11 lub nowszej):

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

Aby uniknąć ostrzeżenia, możesz porównać adresy pierwszych elementów:

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

Aby określić, czy zawartość dwóch tablic jest równa, użyj std::equal funkcji :

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

Wpływ definiowania operatora statku kosmicznego na == i !=

Definicja operatora statku kosmicznego (<=>) nie będzie już ponownie pisać wyrażeń z udziałem == lub != chyba że operator statku kosmicznego jest oznaczony jako = default (P1185R2). Poniższy przykład kompiluje w programie Visual Studio 2019 RTW i wersji 16.1, ale tworzy C2678 w programie Visual Studio 2019 w wersji 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
}

Aby uniknąć błędu, zdefiniuj operator== go lub zadeklaruj jako domyślny:

#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;
}

Ulepszenia biblioteki standardowej

  • <znak z>to_chars() stałą/naukową precyzją. (Ogólna precyzja jest obecnie planowana dla wersji 16.4).
  • P0020R6: atomic<float>, , atomic<double>atomic<long double>
  • P0463R1: endian
  • P0482R6: obsługa biblioteki dlachar8_t
  • P0600R1: [[nodiscard]] dla biblioteki STL, część 1
  • P0653R2:to_address()
  • P0754R2: <wersja>
  • P0771R1: noexcept dla std::functionkonstruktora przenoszenia

Komparatory const dla kontenerów asocjacyjnych

Kod do wyszukiwania i wstawiania w pliku set, multisetmap, i multimap został scalony w celu zmniejszenia rozmiaru kodu. Operacje wstawiania teraz wywołają porównanie mniejsze niż w narzędziu const functor porównania w taki sam sposób, jak wcześniej wykonywane operacje wyszukiwania. Poniższy kod kompiluje się w programie Visual Studio 2019 w wersji 16.1 lub starszej, ale zgłasza język C3848 w programie Visual Studio 2019 w wersji 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);

}

Aby uniknąć błędu, wykonaj operator constporównania :

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

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.3

Operatory wyodrębniania strumieni do char* usunięcia

Operatory wyodrębniania strumieni dla znaków wskaźnika zostały usunięte i zastąpione przez operatory wyodrębniania dla tablicy znaków (na P0487R1). WG21 uważa, że usunięte przeciążenia są niebezpieczne. W /std:c++20 trybie lub /std:c++latest poniższy przykład generuje teraz kod C2679:

// 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)
}

Aby uniknąć błędu, użyj operatora wyodrębniania ze zmienną char[] :

#include <iostream>
#include <iomanip>

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

Nowe słowa kluczowe requires i concept

Nowe słowa kluczowe requires i concept zostały dodane do kompilatora języka Microsoft C++. Jeśli próbujesz użyć jednego jako identyfikatora w /std:c++20 trybie lub /std:c++latest , kompilator zgłasza błąd składniowy C2059.

Konstruktory jako niedozwolone nazwy typów

W tym przypadku kompilator nie uwzględnia już nazw konstruktorów jako wstrzykiwanych nazw klas: po pojawieniu się w kwalifikowanej nazwie po aliasie specjalizacji szablonu klasy. Wcześniej konstruktory były używane jako nazwa typu do deklarowania innych jednostek. Poniższy przykład generuje teraz kod C3646:

#include <chrono>

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

Aby uniknąć błędu, zadeklaruj TotalDuration , jak pokazano poniżej:

#include <chrono>

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

Bardziej rygorystyczne sprawdzanie extern "C" funkcji

extern "C" Jeśli funkcja została zadeklarowana w różnych przestrzeniach nazw, poprzednie wersje kompilatora Microsoft C++ nie sprawdziły, czy deklaracje są zgodne. W programie Visual Studio 2019 w wersji 16.3 lub nowszej kompilator sprawdza zgodność. W /permissive- trybie następujący kod generuje błędy C2371 i C2733:

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

Aby uniknąć błędów w poprzednim przykładzie, należy użyć bool zamiast BOOL spójnie w obu deklaracjach f.

Ulepszenia biblioteki standardowej

Niestandardowe nagłówki <stdexcpt.h> i <typeinfo.h> zostały usunięte. Kod, który je zawiera, powinien zawierać odpowiednio wyjątek nagłówków <standardowych i <informacje> o typie.>

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.4

Lepsze wymuszanie wyszukiwania nazw dwufazowych dla kwalifikowanych identyfikatorów w /permissive-

Wyszukiwanie nazw dwufazowych wymaga, aby nazwy inne niż zależne używane w treści szablonu były widoczne dla szablonu w czasie definicji. Wcześniej takie nazwy mogły zostać znalezione po utworzeniu wystąpienia szablonu. Ta zmiana ułatwia pisanie przenośnego i zgodnego kodu w MSVC pod flagą /permissive- .

W programie Visual Studio 2019 w wersji 16.4 z ustawionym /permissive- flagą poniższy przykład generuje błąd, ponieważ N::f nie jest widoczny, gdy f<T> szablon jest zdefiniowany:

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

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

Zazwyczaj ten błąd można naprawić, włączając brakujące nagłówki lub funkcje lub zmienne deklaratywne, jak pokazano w poniższym przykładzie:

namespace N {
    int f();
}

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

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

Niejawna konwersja wyrażeń stałych całkowitych na wskaźnik o wartości null

Kompilator MSVC implementuje teraz problem CWG 903 w trybie zgodności (/permissive-). Ta reguła nie zezwala na niejawną konwersję wyrażeń stałych całkowitych (z wyjątkiem literału całkowitego "0") na stałe wskaźnika null. Poniższy przykład generuje kod C2440 w trybie zgodności:

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 *'
}

Aby naprawić błąd, użyj polecenia nullptr zamiast false. Literał 0 jest nadal dozwolony:

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

Standardowe reguły dla typów literałów liczb całkowitych

W trybie zgodności (włączonym przez /permissive-program ) MSVC używa standardowych reguł dla typów literałów całkowitych. Literały dziesiętne są zbyt duże, aby zmieściły się w obiekcie signed int , wcześniej podano typ unsigned int. Teraz takie literały otrzymują następny największy signed typ liczb całkowitych: long long. Ponadto literały z sufiksem "ll", które są zbyt duże, aby zmieścić się w typie signed , mają typ unsigned long long.

Ta zmiana może prowadzić do wygenerowania innej diagnostyki ostrzegawczej oraz różnic w zachowaniu operacji arytmetycznych na literałach.

W poniższym przykładzie przedstawiono nowe zachowanie w programie Visual Studio 2019 w wersji 16.4. Zmienna i ma teraz typ unsigned int, więc jest wywoływane ostrzeżenie. Bity o wysokiej kolejności zmiennej j są ustawione na 0.

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
}

W poniższym przykładzie pokazano, jak zachować stare zachowanie i uniknąć ostrzeżeń i zmian zachowania w czasie wykonywania:

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

Parametry funkcji, które parametry szablonu w tle

Kompilator MSVC zgłasza teraz błąd, gdy parametr funkcji w tle parametru szablonu:

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);
}

Aby naprawić błąd, zmień nazwę jednego z parametrów:

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);
}

Specjalizacje typu udostępniane przez użytkownika

W przypadku zgodności z podkatalizami meta.rqmts w warstwie Standardowa kompilator MSVC zgłasza teraz błąd w przypadku znalezienia specjalizacji zdefiniowanej przez użytkownika jednego z określonych type_traits szablonów w std przestrzeni nazw. O ile nie określono inaczej, takie specjalizacje powodują niezdefiniowane zachowanie. Poniższy przykład ma niezdefiniowane zachowanie, ponieważ narusza regułę, a static_assert błąd C2338 kończy się niepowodzeniem.

#include <type_traits>
struct S;

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

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

Aby uniknąć błędu, zdefiniuj strukturę dziedziczą z preferowanego type_traitelementu i specjalizuj się, że:

#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");

Zmiany w operatorach porównania dostarczonych przez kompilator

Kompilator MSVC implementuje teraz następujące zmiany dla operatorów porównania na P1630R1 po włączeniu /std:c++20 opcji lub /std:c++latest :

Kompilator nie ponownie zapisuje już wyrażeń przy użyciuoperator==, jeśli obejmują typ zwracany, który nie jest .bool Poniższy kod generuje teraz błąd 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
}

Aby uniknąć błędu, należy jawnie zdefiniować wymagany operator:

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;
}

Kompilator nie definiuje już domyślnego operatora porównania, jeśli jest członkiem klasy podobnej do unii. Poniższy przykład generuje teraz błąd 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;
}

Aby uniknąć błędu, zdefiniuj treść operatora:

#include <compare>

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

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

Kompilator nie będzie już definiować domyślnego operatora porównania, jeśli klasa zawiera składową odwołania. Poniższy kod generuje teraz błąd C2120:

#include <compare>

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

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

Aby uniknąć błędu, zdefiniuj treść operatora:

#include <compare>

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

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

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.5

Jawna deklaracja specjalizacji bez inicjatora nie jest definicją

W obszarze /permissive-program MSVC wymusza teraz standardową regułę, że jawne deklaracje specjalizacji bez inicjatorów nie są definicjami. Wcześniej deklaracja zostałaby uznana za definicję z inicjatorem domyślnym. Efekt jest zauważalny w czasie połączenia, ponieważ program w zależności od tego zachowania może teraz mieć nierozwiązane symbole. W tym przykładzie zostanie wyświetlony błąd:

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.

Aby rozwiązać ten problem, dodaj inicjator:

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;
}

Dane wyjściowe preprocesora zachowują nowe linie

Eksperymentalny preprocesor zachowuje teraz nowe linie i białe znaki w przypadku używania polecenia /P lub /E z /experimental:preprocessorprogramem .

Biorąc pod uwagę to przykładowe źródło,

#define m()
line m(
) line

Poprzednie dane wyjściowe /E :

line line
#line 2

Nowe dane wyjściowe polecenia /E to teraz:

line
 line

import i module słowa kluczowe są zależne od kontekstu

Na P1857R1import i module dyrektywy preprocesora mają nowe ograniczenia dotyczące ich składni. W tym przykładzie nie są już kompilowane:

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

Aby rozwiązać ten problem, zachowaj import w tym samym wierszu:

import m; // OK

Usunięcie i std::weak_equalitystd::strong_equality

Scalanie P1959R0 wymaga od kompilatora usunięcia zachowania i odwołań do std::weak_equality typów i std::strong_equality .

Kod w tym przykładzie nie jest już kompilowany:

#include <compare>

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

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

Przykład prowadzi teraz do następujących błędów:

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'

Aby rozwiązać ten problem, zaktualizuj element w celu preferowania wbudowanych operatorów relacyjnych i zastąp usunięte typy:

#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<=>;
}

Zmiany funkcji TLS Guard

Wcześniej zmienne lokalne wątku w bibliotekach DLL nie zostały poprawnie zainicjowane. Poza wątkiem, który załadował bibliotekę DLL, nie zostały zainicjowane przed pierwszym użyciem w wątkach, które istniały przed załadowaniem biblioteki DLL. Ta usterka została poprawiona. Zmienne lokalne wątku w takiej biblioteki DLL są inicjowane bezpośrednio przed ich pierwszym użyciem w takich wątkach.

To nowe zachowanie testowania na potrzeby inicjowania użycia zmiennych lokalnych wątku może zostać wyłączone przy użyciu opcji kompilatora /Zc:tlsGuards- . Lub przez dodanie atrybutu [[msvc:no_tls_guard]] do określonych zmiennych lokalnych wątku.

Lepsza diagnostyka wywołań do usuniętych funkcji

Nasz kompilator był bardziej permissive o wywołaniach do usuniętych funkcji wcześniej. Jeśli na przykład wywołania wystąpiły w kontekście treści szablonu, nie zdiagnozowalibyśmy wywołania. Ponadto jeśli było wiele wystąpień wywołań do usuniętych funkcji, wydalibyśmy tylko jedną diagnostykę. Teraz wydajemy diagnostykę dla każdego z nich.

Jedną z konsekwencji nowego zachowania może być mała zmiana powodująca niezgodność: kod, który nazwał usuniętą funkcję, nie zostanie zdiagnozowany, jeśli nigdy nie był potrzebny do generowania kodu. Teraz diagnozujemy go z góry.

W tym przykładzie pokazano kod, który teraz generuje błąd:

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

Aby rozwiązać ten problem, usuń wywołania funkcji usuniętych:

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 };

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.6

Strumienie biblioteki standardowej odrzucają wstawiania nieprawidłowo zakodowanych typów znaków

Tradycyjnie wstawianie elementu wchar_tstd::ostreamdo elementu i wstawianie elementu lub char32_t do elementu std::ostream lub std::wostreampowoduje wyprowadzenie char16_t jej wartości całkowitej. Wstawianie wskaźników do tych typów znaków powoduje wyprowadzenie wartości wskaźnika. Programiści nie znajdą żadnej intuicyjnej wielkości liter. Często oczekuje się, że zamiast tego biblioteka standardowa transkoduje znak lub ciąg znaków zakończony o wartości null i wyświetli wynik.

Propozycja języka C++20 P1423R3 dodaje przeciążenia operatora wstawiania usuniętego strumienia dla tych kombinacji typów strumienia i znaków lub wskaźników znaków. W obszarze /std:c++20 lub /std:c++latestprzeciążenia sprawiają, że te wstawki są źle sformułowane, zamiast zachowywać się w sposób prawdopodobny niezamierzony. Kompilator zgłasza błąd C2280 po znalezieniu. Aby przywrócić stare zachowanie, można zdefiniować makro _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX201 "ucieczka kreskowania". (Propozycja usuwa również operatory wstawiania strumieni dla elementu char8_t. Nasza biblioteka standardowa zaimplementowała podobne przeciążenia, gdy dodaliśmy char8_t obsługę, więc "nieprawidłowe" zachowanie nigdy nie było dostępne dla elementu char8_t.

W tym przykładzie pokazano zachowanie z tą zmianą:

#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';
}

Kod generuje teraz następujące komunikaty diagnostyczne:

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

Można osiągnąć efekt starego zachowania we wszystkich trybach języka, konwertując typy znaków na unsigned inttypy znaków na , lub wskaźniki do :const void*

#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"
}

Zmieniono typ zwracany std::pow() dla std::complex

Wcześniej implementacja MSVC reguł podwyższania poziomu dla zwracanego typu szablonu std::pow() funkcji była niepoprawna. Na przykład wcześniej pow(complex<float>, int) zwrócono complex<float>wartość . Teraz poprawnie zwraca wartość complex<double>. Poprawka została zaimplementowana bezwarunkowo dla wszystkich trybów standardów w programie Visual Studio 2019 w wersji 16.6.

Ta zmiana może spowodować błędy kompilatora. Na przykład wcześniej można było pomnożyć pow(complex<float>, int) przez element float. Ponieważ complex<T> operator* oczekuje argumentów tego samego typu, poniższy przykład emituje teraz błąd kompilatora C2676:

// 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

Istnieje wiele możliwych poprawek:

  • Zmień typ mnożenia na floatdouble. Ten argument można przekonwertować bezpośrednio na element , aby był zgodny z typem complex<double> zwracanym przez powelement .

  • Zawęź wynik do complex<float> , pow mówiąc complex<float>{pow(ARG, ARG)}. Następnie możesz kontynuować mnożenie przez float wartość.

  • Przekaż float zamiast int do pow. Ta operacja może być wolniejsza.

  • W niektórych przypadkach można go całkowicie uniknąć pow . Na przykład pow(cf, -1) można zastąpić podziałem.

switch ostrzeżenia dla języka C

W programie Visual Studio 2019 w wersji 16.6 lub nowszej kompilator implementuje niektóre istniejące ostrzeżenia języka C++ dla kodu skompilowanego jako C. Następujące ostrzeżenia są teraz włączone na różnych poziomach: C4060, C4061, C4062, C4063, C4064, C4065, C4808 i C4809. Ostrzeżenia C4065 i C4060 są domyślnie wyłączone w języku C.

Ostrzeżenia wyzwalają brakujące case instrukcje, niezdefiniowane enumi nieprawidłowe bool instrukcje przełącznika (czyli te, które zawierają zbyt wiele przypadków). Na przykład:

#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
    }
}

Aby naprawić ten kod, usuń nadmiarowy default przypadek:

#include <stdbool.h>

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

Nienazwane klasy w typedef deklaracjach

W programie Visual Studio 2019 w wersji 16.6 lub nowszej typedef zachowanie deklaracji zostało ograniczone do zgodności z P1766R1. W przypadku tej aktualizacji klasy nienazwane w typedef deklaracji nie mogą mieć żadnych elementów członkowskich innych niż:

  • niestatyczne składowe danych bez domyślnych inicjatorów składowych,
  • klasy składowe lub
  • wyliczenia składowych.

Te same ograniczenia są stosowane rekursywnie do każdej zagnieżdżonej klasy. Ograniczenie to ma zapewnić prostotę struktur, które mają typedef nazwy do celów powiązania. Muszą być na tyle proste, że nie są konieczne żadne obliczenia łączenia, zanim kompilator przejdzie do typedef nazwy połączenia.

Ta zmiana ma wpływ na wszystkie tryby standardów kompilatora. W domyślnych (/std:c++14) i /std:c++17 trybach kompilator emituje ostrzeżenie C5208 dla niezgodnego kodu. Jeśli /permissive- zostanie określony, kompilator emituje ostrzeżenie C5208 jako błąd w obszarze /std:c++14 i emituje błąd C7626 w obszarze /std:c++17. Kompilator emituje błąd C7626 dla niezgodnego kodu, gdy /std:c++20 jest określony lub /std:c++latest .

W poniższym przykładzie przedstawiono konstrukcje, które nie są już dozwolone w strukturach bez nazw. W zależności od określonego trybu standardów emitowane są błędy C5208 lub C7626 lub ostrzeżenia:

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;

Powyższy kod można naprawić, nadając nienazwanej klasie nazwę:

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

Domyślny import argumentów w języku C++/interfejsie wiersza polecenia

Rosnąca liczba interfejsów API ma domyślne argumenty na platformie .NET Core. Dlatego teraz obsługujemy domyślny import argumentów w języku C++/CLI. Ta zmiana może przerwać istniejący kod, w którym zadeklarowano wiele przeciążeń, jak w tym przykładzie:

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

Po zaimportowaniu tej klasy do języka C++/interfejsu wiersza polecenia wywołanie do jednego z przeciążeń powoduje błąd:

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

Kompilator emituje błąd C2668, ponieważ oba przeciążenia pasują do tej listy argumentów. W drugim przeciążeniu drugi argument jest wypełniany przez domyślny argument. Aby obejść ten problem, możesz usunąć nadmiarowe przeciążenie (1). Możesz też użyć pełnej listy argumentów i jawnie podać argumenty domyślne.

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.7

jest trywialnie kopiowalną definicją

Język C++20 zmienił definicję jest trywialnie kopiowalny. Jeśli klasa ma niestatyczny element członkowski danych z volatile kwalifikowanym typem, nie oznacza już, że każdy konstruktor kopii lub przenoszenia generowany przez kompilator albo operator przypisania kopiowania lub przenoszenia nie jest trywialny. Komitet standardowy języka C++ zastosował tę zmianę wstecznie jako raport o wadach. W środowisku MSVC zachowanie kompilatora nie zmienia się w różnych trybach języka, takich jak /std:c++14 lub /std:c++latest.

Oto przykład nowego zachowania:

#include <type_traits>

struct S
{
    volatile int m;
};

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

Ten kod nie jest kompilowany w wersjach MSVC przed programem Visual Studio 2019 w wersji 16.7. Istnieje domyślne ostrzeżenie kompilatora, za pomocą którego można wykryć tę zmianę. Jeśli skompilujesz powyższy kod przy użyciu polecenia cl /W4 /w45220, zobaczysz następujące ostrzeżenie:

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`

Konwersje literału wskaźnika do składowej i ciągu w celu bool zawężenia

Komisja Standardowa języka C++ niedawno przyjęła P1957R2 Raport wad, który jest uważany za T*bool konwersję zawężającą. Program MSVC usunęł usterkę w implementacji, która wcześniej zdiagnozowała T*bool jako zawężenie, ale nie zdiagnozowała konwersji literału ciągu na bool lub wskaźnika do elementu członkowskiego .bool

Następujący program jest nieprawidłowo sformułowany w programie Visual Studio 2019 w wersji 16.7:

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
}

Aby poprawić ten kod, dodaj jawne porównania do nullptrelementu lub unikaj kontekstów, w których konwersje zawężające są źle sformułowane:

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 jest konwertowany tylko na bool jako bezpośrednie inicjowanie

W języku C++11 nullptr jest konwertowany tylko na bool konwersję bezpośrednią, na przykład podczas inicjowania bool elementu przy użyciu listy inicjatora nawiasu klamrowego. To ograniczenie nigdy nie zostało wymuszone przez MSVC. Program MSVC implementuje teraz regułę w obszarze /permissive-. Niejawne konwersje są teraz diagnozowane jako źle sformułowane. Konwersja kontekstowa na bool jest nadal dozwolona, ponieważ inicjowanie bool b(nullptr) bezpośrednie jest prawidłowe.

W większości przypadków błąd można naprawić, zastępując element nullptr , falsejak pokazano w tym przykładzie:

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
}

Zgodne zachowanie inicjowania dla inicjowania tablicy z brakującymi inicjatorami

Wcześniej program MSVC miał niezgodne zachowanie w przypadku inicjowania tablicy, które nie zawierały inicjatorów. Program MSVC zawsze nazywał się konstruktorem domyślnym dla każdego elementu tablicy, który nie miał inicjatora. Standardowe zachowanie polega na zainicjowaniu każdego elementu z pustą listą inicjowania nawiasów klamrowych ({}). Kontekst inicjowania pustej listy inicjatora-nawiasu klamrowego to inicjowanie kopiowania, które nie zezwala na wywołania jawnych konstruktorów. Mogą również występować różnice w czasie wykonywania, ponieważ użycie polecenia {} do inicjowania może wywołać konstruktor, który przyjmuje std::initializer_listelement , zamiast konstruktora domyślnego. Zachowanie zgodne jest włączone w obszarze /permissive-.

Oto przykład zmienionego zachowania:

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
}

Inicjowanie składowych klas z przeciążonymi nazwami jest poprawnie sekwencjonowane

Zidentyfikowaliśmy usterkę w wewnętrznej reprezentacji składowych danych klasy, gdy nazwa typu jest również przeciążona jako nazwa elementu członkowskiego danych. Ta usterka spowodowała niespójności w zagregowanej kolejności inicjowania i inicjowania składowych. Wygenerowany kod inicjowania jest teraz poprawny. Jednak ta zmiana może prowadzić do błędów lub ostrzeżeń w źródle, które przypadkowo polegały na błędnie uporządkowanych elementach członkowskich, jak w tym przykładzie:

// 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
};

W poprzednich wersjach konstruktor niepoprawnie zainicjował element członkowski Inner danych przed elementem członkowskim vdanych . (Standard C++ wymaga kolejności inicjowania, która jest taka sama jak kolejność deklaracji elementów członkowskich). Teraz, gdy wygenerowany kod jest zgodny ze standardem, lista elementów członkowskich nie jest w porządku. Kompilator generuje ostrzeżenie dla tego przykładu. Aby rozwiązać ten problem, zmień kolejność listy member-initializer-list, aby odzwierciedlić kolejność deklaracji.

Rozpoznawanie przeciążeń obejmujące przeciążenia całkowite i long argumenty

Standard C++ wymaga klasyfikacji long klasy do int konwersji jako standardowej konwersji. Poprzednie kompilatory MSVC niepoprawnie klasyfikują ją jako integralną promocję, która plasuje się wyżej w przypadku rozpoznawania przeciążeń. Ta klasyfikacja może spowodować pomyślne rozwiązanie przeciążenia, gdy należy je uznać za niejednoznaczne.

Kompilator uwzględnia teraz klasyfikację prawidłowo w /permissive- trybie. Nieprawidłowy kod jest prawidłowo diagnozowany, jak w tym przykładzie:

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
}

Ten problem można rozwiązać na kilka sposobów:

  • W lokacji wywołania zmień typ przekazanego argumentu na int. Możesz zmienić typ zmiennej lub rzutować go.

  • Jeśli istnieje wiele witryn wywołań, możesz dodać kolejne przeciążenie, które przyjmuje long argument. W tej funkcji rzutuj i przekaż argument do przeciążenia int .

Używanie niezdefiniowanej zmiennej z wewnętrznym połączeniem

Wersje MSVC przed programem Visual Studio 2019 w wersji 16.7 zaakceptowały użycie zmiennej zadeklarowanej extern , która miała połączenie wewnętrzne i nie została zdefiniowana. Takich zmiennych nie można zdefiniować w żadnej innej jednostce tłumaczenia i nie można utworzyć prawidłowego programu. Kompilator diagnozuje teraz ten przypadek w czasie kompilacji. Błąd jest podobny do błędu dla niezdefiniowanych funkcji statycznych.

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.
}

Ten program wcześniej niepoprawnie skompilowany i połączony, ale teraz emituje błąd C7631.

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

Takie zmienne muszą być zdefiniowane w tej samej jednostce tłumaczenia, w której są używane. Można na przykład podać jawny inicjator lub oddzielną definicję.

Kompletność typu i konwersje wskaźnika pochodnego na podstawowy

W standardach języka C++ przed C++20 konwersja z klasy pochodnej na klasę bazową nie wymagała, aby klasa pochodna byłaby kompletnym typem klasy. Standardowy komitet języka C++ zatwierdził zmianę raportu wad wstecznych, która ma zastosowanie do wszystkich wersji języka C++. Ta zmiana jest zgodna z procesem konwersji z cechami typów, takimi jak std::is_base_of, które wymagają, aby klasa pochodna była kompletnym typem klasy.

Oto przykład:

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;
};

Ta zmiana zachowania dotyczy wszystkich trybów języka C++ MSVC, a nie tylko /std:c++20 lub /std:c++latest.

Konwersje zawężające są bardziej spójnie diagnozowane

Program MSVC emituje ostrzeżenie dotyczące zawężania konwersji w inicjatorze listy nawiasów klamrowych. Wcześniej kompilator nie diagnozował zawężających konwersji z większych enum typów bazowych do węższych typów całkowitych. (Kompilator nieprawidłowo uznał je za integralną podwyższanie poziomu zamiast konwersji). Jeśli konwersja zawężania jest celowa, można uniknąć ostrzeżenia przy użyciu static_cast argumentu inicjatora. Możesz też wybrać większy typ całkowity miejsca docelowego.

Oto przykład użycia jawnego static_cast do rozwiązania ostrzeżenia:

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
}

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.8

Rozszerzenie "Class rvalue used as lvalue" (Klasa rvalue używana jako lvalue)

MSVC ma rozszerzenie, które umożliwia używanie wartości rvalue klasy jako lvalue. Rozszerzenie nie wydłuża okresu istnienia wartości rvalue klasy i może prowadzić do niezdefiniowanego zachowania w czasie wykonywania. Teraz wymuszamy regułę standardową i nie zezwalamy na to rozszerzenie w obszarze /permissive-. Jeśli nie możesz jeszcze używać /permissive- , możesz użyć /we4238 polecenia , aby jawnie uniemożliwić rozszerzenie. Oto przykład:

// 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
}

Rozszerzenie "Jawna specjalizacja w zakresie przestrzeni nazw"

MSVC miał rozszerzenie, które zezwalało na jawną specjalizację w zakresie przestrzeni nazw. Jest to teraz część standardu, po rozwiązaniu CWG 727. Istnieją jednak różnice w zachowaniu. Dostosowaliśmy zachowanie naszego kompilatora, aby dopasować je do standardu.

// 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"

Sprawdzanie pod kątem typów klas abstrakcyjnych

Język C++20 Standard zmienił kompilatory procesu używane do wykrywania użycia typu klasy abstrakcyjnej jako parametru funkcji. W szczególności nie jest to już błąd SFINAE. Wcześniej, jeśli kompilator wykrył, że specjalizacja szablonu funkcji będzie miała wystąpienie typu klasy abstrakcyjnej jako parametr funkcji, specjalizacja ta zostanie uznana za źle sformułowaną. Nie zostanie on dodany do zestawu realnych funkcji kandydatów. W języku C++20 sprawdzanie parametru typu klasy abstrakcyjnej nie następuje, dopóki funkcja nie zostanie wywołana. Efekt polega na tym, że kod używany do kompilowania nie spowoduje błędu. Oto przykład:

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);
}

Wcześniej wywołanie compare metody próbowałoby specjalizować szablon compare funkcji przy użyciu String argumentu szablonu szablonu dla elementu T. Nie udałoby się wygenerować prawidłowej specjalizacji, ponieważ String jest to klasa abstrakcyjna. Jedynym realnym kandydatem byłby compare(const Node&, const Node&). Jednak w języku C++20 sprawdzanie typu klasy abstrakcyjnej nie nastąpi, dopóki funkcja nie zostanie wywołana. Dlatego specjalizacja compare(String, String) jest dodawana do zestawu realnych kandydatów i jest wybierana jako najlepszy kandydat, ponieważ konwersja z const String& na String jest lepszą sekwencją konwersji niż konwersja z const String& do const Node&.

W języku C++20 jedną z możliwych poprawek dla tego przykładu jest użycie pojęć; oznacza to, że zmień definicję na compare :

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

Lub jeśli pojęcia języka C++ nie są dostępne, możesz wrócić do sfINAE:

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);
}

Obsługa P0960R3 — zezwalaj na inicjowanie agregacji z nawiasowej listy wartości

Język C++20 P0960R3 dodaje obsługę inicjowania agregacji przy użyciu listy inicjatora nawiasu. Na przykład następujący kod jest prawidłowy w języku C++20:

struct S {
    int i;
    int j;
};

S s(1, 2);

Większość tej funkcji jest addytywna, czyli kod kompiluje kod, który nie został skompilowany wcześniej. Jednak zmienia zachowanie elementu std::is_constructible. W trybie C++17 kończy się to static_assert niepowodzeniem, ale w trybie C++20 kończy się powodzeniem:

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

Jeśli używasz tej cechy typu do kontrolowania rozpoznawania przeciążeń, może to prowadzić do zmiany zachowania między C++17 i C++20.

Rozpoznawanie przeciążenia obejmujące szablony funkcji

Wcześniej kompilator zezwolił na skompilowanie kodu, który /permissive- nie powinien być kompilowany. Efekt był, kompilator nazwał niewłaściwą funkcją prowadzącą do zmiany zachowania środowiska uruchomieniowego:

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);
}

Wywołanie do g używa zestawu przeciążenia zawierającego dwie funkcje ::f i N::f. Ponieważ N::f jest szablonem funkcji, kompilator powinien traktować argument funkcji jako kontekst niezwiązany. Oznacza to, że w tym przypadku wywołanie metody powinno zakończyć się g niepowodzeniem, ponieważ kompilator nie może wywieść typu parametru Tszablonu . Niestety, kompilator nie odrzucił faktu, że już zdecydował, że ::f jest to dobry mecz dla wywołania funkcji. Zamiast emitować błąd, kompilator wygeneruje kod do wywołania g przy użyciu ::f jako argumentu.

Biorąc pod uwagę, że w wielu przypadkach użycie ::f jako argumentu funkcji jest oczekiwane przez użytkownika, emitujemy błąd tylko wtedy, gdy kod jest kompilowany za pomocą /permissive-polecenia .

Migrowanie z /await do C++20 coroutines

Coroutines języka C++20 w warstwie Standardowa jest teraz domyślnie włączona w obszarze /std:c++20 i /std:c++latest. Różnią się one od TS coroutines i obsługi w /await ramach opcji . Migracja z /await do standardowych kohroutyn może wymagać pewnych zmian w źródle.

Niestandardowe słowa kluczowe

Stare await i yield słowa kluczowe nie są obsługiwane w trybie C++20. Kod musi być używany co_await i co_yield zamiast tego. Tryb standardowy nie zezwala również na korzystanie z return metody w kohroutynie. Każdy return w kohroutynie musi używać .co_return

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

Typy initial_suspend/final_suspend

W obszarze /awaitobietnicy funkcje początkowe i wstrzymane mogą być zadeklarowane jako zwracane bool. To zachowanie nie jest standardowe. W języku C++20 te funkcje muszą zwracać oczekiwany typ klasy, często jeden z trywialnych typów oczekujących: std::suspend_always jeśli funkcja wcześniej zwróciła truewartość , lub std::suspend_never jeśli zwróciła falsewartość .

// /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 yield_value

W języku C++20 funkcja promise yield_value musi zwrócić oczekiwany typ. W /await trybie yield_value funkcja mogła zwrócić voidfunkcję i zawsze zawieszała się. Takie funkcje można zastąpić funkcją zwracającą wartość std::suspend_always.

// /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{}; }
};

Funkcja obsługi wyjątków

/await obsługuje typ obietnicy bez funkcji obsługi wyjątków lub funkcji obsługi wyjątków o nazwie set_exception , która przyjmuje std::exception_ptrwartość . W języku C++20 typ obietnicy musi mieć funkcję o nazwie unhandled_exception , która nie przyjmuje żadnych argumentów. Obiekt wyjątku można uzyskać w std::current_exception razie potrzeby.

// /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(); }
    ...
};

Dedukowane typy zwracane coroutines nie są obsługiwane

Język C++20 nie obsługuje kohroutyn z typem zwrotnym, który zawiera typ symbolu zastępczego, taki jak auto. Typy zwracane coroutines muszą być jawnie zadeklarowane. W obszarze /awaittypy te zawsze wiążą się z typem eksperymentalnym i wymagają dołączenia nagłówka definiującego wymagany typ: jeden z std::experimental::task<T>, std::experimental::generator<T>lub 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;
};

Zwracany typ return_value

Zwracany typ funkcji obietnicy return_value musi mieć wartość void. W /await trybie zwracany typ może być dowolny i jest ignorowany. Ta diagnostyka może pomóc wykrywać subtelne błędy, takie jak wtedy, gdy autor niepoprawnie zakłada, że zwracana jest wartość return_value zwracana do obiektu wywołującego.

// /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
};

Zwracanie zachowania konwersji obiektu

Jeśli zadeklarowany typ zwracany coroutine nie jest zgodny z zwracanym typem funkcji obietnicy get_return_object , obiekt zwrócony z get_return_object zostanie przekonwertowany na zwracany typ coroutine. W obszarze /awaitta konwersja jest wykonywana wcześnie, zanim treść koprutynowa ma szansę wykonać. W /std:c++20 programie lub /std:c++latestta konwersja jest wykonywana, gdy wartość jest zwracana do elementu wywołującego. Umożliwia ona współistnienie, które nie są zawieszone w początkowym punkcie wstrzymania, aby korzystać z obiektu zwróconego przez get_return_object wewnątrz ciała koprutynowego.

Parametry obietnicy coroutine

W języku C++20 kompilator próbuje przekazać parametry coroutine (jeśli istnieją) do konstruktora typu obietnicy. Jeśli zakończy się niepowodzeniem, ponawia próbę z domyślnym konstruktorem. W /await trybie użyto tylko domyślnego konstruktora. Ta zmiana może prowadzić do różnicy w zachowaniu, jeśli obietnica ma wiele konstruktorów. Lub, jeśli istnieje konwersja z parametru coroutine na typ obietnicy.

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- Moduły i C++20 są domyślnie włączone w obszarze /std:c++20

Obsługa modułów języka C++20 jest domyślnie włączona w obszarze /std:c++20 i /std:c++latest. Aby uzyskać więcej informacji na temat tej zmiany oraz scenariuszy, w których module i import są warunkowo traktowane jako słowa kluczowe, zobacz Standard C++20 Modules support with MSVC in Visual Studio 2019 version 16.8 (Obsługa modułów Standard C++20 w programie Visual Studio 2019 w wersji 16.8).

W ramach wymagań wstępnych dotyczących obsługi modułów funkcja jest teraz włączona, permissive- gdy /std:c++20 określono funkcję lub /std:c++latest . Aby uzyskać więcej informacji, zobacz /permissive-.

W przypadku kodu, który został wcześniej skompilowany w ramach /std:c++latest programu i wymaga niezgodnych zachowań kompilatora, można określić, /permissive aby wyłączyć tryb ścisłej zgodności w kompilatorze. Opcja kompilatora musi pojawić się po /std:c++latest na liście argumentów wiersza polecenia. Jednak /permissive powoduje wystąpienie błędu, jeśli zostanie wykryte użycie modułów:

błąd C1214: Moduły powodują konflikt z nietypowym zachowaniem żądanym za pośrednictwem opcji

Najbardziej typowe wartości opcji to:

Opcja Opis
/Zc:twoPhase- Wyszukiwanie dwufazowej nazwy jest wymagane dla modułów języka C++20 i implikowane przez program /permissive-.
/Zc:hiddenFriend- Standardowe reguły wyszukiwania ukrytych nazw znajomych są wymagane dla modułów języka C++20 i implikowane przez /permissive-program .
/Zc:lambda- Standardowe przetwarzanie lambda jest wymagane dla modułów języka C++20 i jest implikowane w /std:c++20 trybie lub nowszym.
/Zc:preprocessor- Zgodny preprocesor jest wymagany tylko do użycia jednostek nagłówka języka C++20 i tworzenia. Nazwane moduły nie wymagają tej opcji.

Opcja jest nadal wymagana /experimental:module do używania std.* modułów dostarczonych z programem Visual Studio, ponieważ nie są one jeszcze ustandaryzowane.

Opcja /experimental:module oznacza /Zc:twoPhaserównież , /Zc:lambdai /Zc:hiddenFriend. Wcześniej kod skompilowany za pomocą modułów może być czasami kompilowany, /Zc:twoPhase- jeśli moduł był używany tylko. To zachowanie nie jest już obsługiwane.

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.9

Inicjalizacja kopiowania tymczasowego w odwołaniu do inicjowania bezpośredniego

Problem z podstawową grupą roboczą CWG 2267 dotyczył niespójności między listą inicjatora nawiasów a listą inicjatorów nawiasów klamrowych. Rozdzielczość zharmonizuje te dwie formy.

Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std trybach kompilatora. Jednak ponieważ jest to potencjalnie zmiana powodująca niezgodność źródła, jest obsługiwana tylko wtedy, gdy kod jest kompilowany przy użyciu polecenia /permissive-.

W tym przykładzie pokazano zmianę zachowania:

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
}

Cechy destruktora i potencjalnie skonstruowane podobiekty

Problem z podstawową grupą roboczą CWG 2336 obejmuje pominięcie niejawnych specyfikacji wyjątków destruktorów w klasach, które mają wirtualne klasy bazowe. Pominięcie oznaczało, że destruktor w klasie pochodnej może mieć słabszą specyfikację wyjątku niż klasa bazowa, jeśli ta baza była abstrakcyjna i miała virtual bazę.

Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std trybach kompilatora.

W tym przykładzie pokazano, jak zmieniła się interpretacja:

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);
};

Przed tą zmianą niejawnie zdefiniowany destruktor for B to noexcept, ponieważ rozważane są tylko potencjalnie konstruowane podobiekty. Klasa bazowa V nie jest potencjalnie skonstruowanym podobiektem, ponieważ jest bazą virtual i B jest abstrakcyjna. Jednak klasa V bazowa jest potencjalnie skonstruowanym podobiektem klasy D, a więc D::~D jest określana jako noexcept(false), co prowadzi do klasy pochodnej ze słabszą specyfikacją wyjątku niż jej podstawowa. Ta interpretacja jest niebezpieczna. Może to prowadzić do nieprawidłowego zachowania środowiska uruchomieniowego, jeśli wyjątek zostanie zgłoszony z destruktora klasy pochodzącej z klasy B.

Dzięki tej zmianie destruktor jest również potencjalnie zgłaszany, jeśli ma wirtualny destruktor, a każda wirtualna klasa bazowa ma potencjalnie zgłaszający destruktor.

Podobne typy i powiązanie odwołania

Podstawowy problem z grupą roboczą CWG 2352 dotyczy niespójności między regułami powiązania odwołania a zmianami podobieństwa typu. Niespójność została wprowadzona we wcześniejszych raportach o wadach (takich jak CWG 330). Dotyczy to programu Visual Studio 2019 w wersji od 16.0 do 16.8.

Po tej zmianie, począwszy od programu Visual Studio 2019 w wersji 16.9, kod, który wcześniej powiązał odwołanie do tymczasowego odwołania w programie Visual Studio 2019 w wersji 16.0 do 16.8, może teraz wiązać się bezpośrednio, gdy powiązane typy różnią się tylko przez kwalifikatory cv.

Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std trybach kompilatora. Jest to potencjalnie zmiana powodująca niezgodność źródła.

Zobacz Odwołania do typów z niezgodnymi kwalifikatorami cv, aby uzyskać powiązaną zmianę.

W tym przykładzie pokazano zmienione zachowanie:

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
}

Aktualizacja może zmienić zachowanie programu, które polegało na wprowadzonym tymczasowym:

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;
}

/Zc:twoPhase i /Zc:twoPhase- zmiana zachowania opcji

Zwykle opcje kompilatora MSVC działają na zasadzie, że ostatni widział wygrywa. Niestety, tak nie było w przypadku opcji /Zc:twoPhase i /Zc:twoPhase- . Te opcje były "lepkie", więc późniejsze opcje nie mogły je zastąpić. Na przykład:

cl /Zc:twoPhase /permissive a.cpp

W tym przypadku pierwsza /Zc:twoPhase opcja umożliwia ścisłe wyszukiwanie nazw dwufazowych. Druga opcja jest przeznaczona do wyłączenia trybu ścisłej zgodności (jest to przeciwieństwo /permissive-), ale nie wyłączył /Zc:twoPhase.

Program Visual Studio 2019 w wersji 16.9 zmienia to zachowanie we wszystkich /std trybach kompilatora. /Zc:twoPhase i /Zc:twoPhase- nie są już "lepkie", a późniejsze opcje mogą je zastąpić.

Jawne specyfikatory noexcept w szablonach destruktorów

Kompilator wcześniej zaakceptował szablon destruktora zadeklarowany przy użyciu specyfikacji wyjątku niezrzucania, ale zdefiniowany bez jawnego specyfikatora noexcept. Niejawna specyfikacja wyjątku destruktora zależy od właściwości klasy — właściwości, które mogą nie być znane w punkcie definicji szablonu. Standard C++ wymaga również tego zachowania: jeśli destruktor jest zadeklarowany bez specyfikatora noexcept, ma niejawną specyfikację wyjątku, a żadna inna deklaracja funkcji może mieć specyfikator noexcept-.

Program Visual Studio 2019 w wersji 16.9 zmienia zachowanie zgodne ze wszystkimi /std trybami kompilatora.

W tym przykładzie przedstawiono zmianę zachowania kompilatora:

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 { /* ... */ }

Ponowne napisanie wyrażeń w języku C++20

Od wersji 16.2 programu Visual Studio 2019 w obszarze /std:c++latestkompilator zaakceptował kod podobny do następującego przykładu:

#include <compare>

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

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

Jednak kompilator nie wywoła funkcji porównania, która może oczekiwać autor. Powyższy kod powinien zostać przepisany a < b jako (a <=> b) < 0. Zamiast tego kompilator użył funkcji konwersji zdefiniowanej operator bool() przez użytkownika i porównał bool(a) < bool(b)element . W programie Visual Studio 2019 w wersji 16.9 lub nowszej kompilator ponownie zapisuje wyrażenie przy użyciu oczekiwanego wyrażenia operatora statków kosmicznych.

Zmiana powodująca niezgodność źródła

Prawidłowe stosowanie konwersji do wyrażeń przepisanych ma inny efekt: kompilator poprawnie diagnozuje niejednoznaczności z prób ponownego zapisania wyrażenia. Rozważ taki przykład:

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

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

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

W języku C++17 ten kod zostanie zaakceptowany z powodu pochodnej konwersji Derived na bazę po prawej stronie wyrażenia. W języku C++20 dodawany jest również syntetyzowany kandydat wyrażenia: Derived{} == Base{}. Ze względu na reguły w standardzie o tym, która funkcja wygrywa na podstawie konwersji, okazuje się, że wybór między Base::operator== i Derived::operator== jest niezdecydowalny. Ponieważ sekwencje konwersji w dwóch wyrażeniach nie są lepsze lub gorsze od siebie, przykładowy kod powoduje niejednoznaczność.

Aby rozwiązać niejednoznaczność, dodaj nowego kandydata, który nie będzie podlegać dwóm sekwencjom konwersji:

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

Zmiana powodująca niezgodność w czasie wykonywania

Ze względu na reguły ponownego zapisywania operatorów w języku C++20 istnieje możliwość znalezienia nowego kandydata, który w przeciwnym razie nie znajdzie się w trybie niższego języka. A nowy kandydat może być lepszym dopasowaniem niż starszy kandydat. Rozważ taki przykład:

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; }
};

W języku C++17 jedynym kandydatem jest ci == *thisconst_iterator::operator==. Jest to dopasowanie, ponieważ *this przechodzi przez konwersję pochodną na bazę na const_iteratorwartość . W języku C++20 dodawany jest inny ponownie napisany kandydat: *this == ci, który wywołuje element iterator::operator==. Ten kandydat nie wymaga konwersji, więc jest to lepsze dopasowanie niż const_iterator::operator==. Problem z nowym kandydatem polega na tym, że funkcja jest obecnie zdefiniowana, więc nowa semantyka funkcji powoduje nieskończenie rekursywną definicję iterator::operator==.

Aby ułatwić kod podobny do przykładu, kompilator implementuje nowe ostrzeżenie:

$ 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

Aby naprawić kod, należy jawnie ustalić, której konwersji użyć:

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

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.10

Nieprawidłowe przeciążenie wybrane do inicjowania kopiowania klasy

Biorąc pod uwagę ten przykładowy kod:

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

Wcześniejsze wersje kompilatora niepoprawnie przekonwertowałyby argument typu f na CA element przy użyciu szablonowego konstruktora konwertującego Awartość . Zamiast tego język C++ w warstwie Standardowa wymaga użycia operatora B::operator A konwersji. W programie Visual Studio 2019 w wersji 16.10 lub nowszej zachowanie rozpoznawania przeciążenia zostanie zmienione w celu użycia poprawnego przeciążenia.

Ta zmiana może również poprawić wybrane przeciążenie w niektórych innych sytuacjach:

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)
    {
        // ...
    }
}

Niepoprawne analizowanie literałów zmiennoprzecinkowych

W programie Visual Studio 2019 w wersji 16.10 lub nowszej literały zmiennoprzecinkowe są analizowane na podstawie ich rzeczywistego typu. Wcześniejsze wersje kompilatora zawsze analizowały literał zmiennoprzecinkowa tak, jakby miał typ double , a następnie przekonwertował wynik na rzeczywisty typ. To zachowanie może prowadzić do nieprawidłowego zaokrąglenia i odrzucenia prawidłowych wartości:

// 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;

Niepoprawny punkt deklaracji

Wcześniejsze wersje kompilatora nie mogły skompilować kodu odwołującego się do siebie w następujący przykład:

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

    int value() const;
};

S s(4, &s);

Kompilator nie zadeklarowałby zmiennej s , dopóki nie przeanalizuje całej deklaracji, w tym argumentów konstruktora. Wyszukiwanie elementu s na liście argumentów konstruktora zakończy się niepowodzeniem. W programie Visual Studio 2019 w wersji 16.10 lub nowszej ten przykład jest teraz poprawnie kompilowany.

Niestety ta zmiana może uszkodzić istniejący kod, jak w tym przykładzie:

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

We wcześniejszych wersjach kompilatora, gdy wyszukuje s on argumenty konstruktora dla deklaracji "wewnętrznej" , znajduje poprzednią deklarację s("outer" s) i kompiluje kod. Począwszy od wersji 16.10, kompilator emituje ostrzeżenie C4700 zamiast tego. Jest to spowodowane tym, że kompilator deklaruje teraz "wewnętrzny" s przed przeanalizowanie argumentów konstruktora. s Dlatego wyszukiwanie znajduje element "wewnętrzny"s, który nie został jeszcze zainicjowany.

Jawnie wyspecjalizowany element członkowski szablonu klasy

Wcześniejsze wersje kompilatora niepoprawnie oznaczyły jawną specjalizację składowej szablonu klasy, tak jakby inline została również zdefiniowana w szablonie podstawowym. To zachowanie oznaczało, że kompilator czasami odrzuca kod zgodny. W programie Visual Studio 2019 w wersji 16.10 lub nowszej jawna specjalizacja nie jest już niejawnie oznaczona jako inline w /permissive- trybie. Rozważ taki przykład:

Plik s.hźródłowy:

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

Plik s.cppźródłowy:

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

Plik main.cppźródłowy:

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

int main()
{
}

Aby rozwiązać problem z błędem konsolidatora w powyższym przykładzie, dodaj inline jawnie do S<int>::felementu :

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

Dedukowana zwracana nazwa typu mangling

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator zmienił sposób generowania nazw mangled dla funkcji, które spowodowały zwracanie typów. Rozważmy na przykład następujące funkcje:

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

Wcześniejsze wersje kompilatora wygenerowałyby te nazwy konsolidatora:

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

Co zaskakujące, typ zwracany zostanie pominięty z g powodu innego zachowania semantycznego spowodowanego przez lokalną lambdę w treści funkcji. Ta niespójność utrudniała zaimplementowanie wyeksportowanych funkcji, które mają typ zwracany: interfejs modułu wymaga informacji na temat sposobu kompilowania treści funkcji. Potrzebuje informacji, aby utworzyć funkcję po stronie importu, która może prawidłowo połączyć się z definicją.

Kompilator pomija teraz zwracany typ funkcji zwracanego typu. To zachowanie jest zgodne z innymi głównymi implementacjami. Istnieje wyjątek dla szablonów funkcji: ta wersja kompilatora wprowadza nowe zachowanie mangled-name dla szablonów funkcji, które mają typ zwracany:

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;

Nazwy mangled dla auto i decltype(auto) teraz pojawiają się w pliku binarnym, a nie wywoływanego typu zwracanego:

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)

Wcześniejsze wersje kompilatora obejmowałyby wywołany typ zwracany w ramach podpisu. Gdy kompilator uwzględnił typ zwracany w nazwie mangled, może to spowodować problemy z konsolidatorem. Niektóre w przeciwnym razie dobrze sformułowane scenariusze stałyby się niejednoznaczne dla konsolidatora.

Nowe zachowanie kompilatora może spowodować zmianę powodującą niezgodność binarną. Rozważ taki przykład:

Plik a.cppźródłowy:

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

Plik main.cppźródłowy:

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

W wersjach wcześniejszych niż 16.10 kompilator opracował nazwę, auto f() która wyglądała jak int f(), mimo że są one semantycznie odrębnymi funkcjami. Oznacza to, że przykład zostanie skompilowany. Aby rozwiązać ten problem, nie należy polegać na auto oryginalnej definicji elementu f. Zamiast tego zapisz go jako int f(). Ponieważ funkcje, które wywoływały typy zwracane, są zawsze kompilowane, implikacje ABI są zminimalizowane.

Ostrzeżenie dotyczące ignorowanych nodiscard atrybutów

Poprzednie wersje kompilatora dyskretnie ignorują pewne zastosowania atrybutu nodiscard . Zignorowali atrybut, jeśli znajdował się on w pozycji składniowej, która nie miała zastosowania do zadeklarowanej funkcji ani klasy. Na przykład:

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

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje ostrzeżenie poziomu 4 C5240 zamiast:

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

Aby rozwiązać ten problem, przenieś atrybut do poprawnej pozycji składni:

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

Ostrzeżenie dotyczące include dyrektyw z nazwami nagłówków systemu w module purview

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje ostrzeżenie, aby zapobiec błędowi tworzenia wspólnego interfejsu modułu. Jeśli po instrukcji export module dołączysz nagłówek biblioteki standardowej, kompilator emituje ostrzeżenie C5244. Oto przykład:

export module m;
#include <vector>

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

Deweloper prawdopodobnie nie zamierzał, aby moduł m był właścicielem zawartości .<vector> Kompilator emituje teraz ostrzeżenie ułatwiające znalezienie i rozwiązanie problemu:

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

Aby rozwiązać ten problem, przejdź #include <vector> przed export module m;:

#include <vector>
export module m;

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

Ostrzeżenie dotyczące nieużywanych funkcji połączenia wewnętrznego

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator ostrzega w kolejnych sytuacjach, w których usunięto funkcję bez wnioskowania z wewnętrznym połączeniem. Wcześniejsze wersje kompilatora emitują ostrzeżenie C4505 dla następującego kodu:

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

Kompilator ostrzega teraz również o funkcjach nieużywanych auto i nieużywanych funkcjach w anonimowych przestrzeniach nazw. Emituje ostrzeżenie off-by-default C5245 dla obu następujących funkcji:

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; };
}

Ostrzeżenie dotyczące elizji nawiasu klamrowego

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator ostrzega przed listami inicjowania, które nie używają nawiasów klamrowych dla podobiektów. Kompilator emituje domyślnie ostrzeżenie off-by-default C5246.

Oto przykład:

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

Aby rozwiązać ten problem, opakuj inicjowanie podobiektu w nawiasach klamrowych:

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

Poprawnie wykryj, czy const obiekt nie został zainicjowany

W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje teraz błąd C2737 podczas próby zdefiniowania const obiektu, który nie jest w pełni zainicjowany:

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

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

Wcześniejsze wersje kompilatora umożliwiły kompilowanie tego kodu, mimo że S::i nie zostało zainicjowane.

Aby rozwiązać ten problem, zainicjuj wszystkie elementy członkowskie przed utworzeniem const wystąpienia obiektu:

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

Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.11

/std:c++20 tryb kompilatora

W programie Visual Studio 2019 w wersji 16.11 lub nowszej kompilator obsługuje teraz tryb kompilatora /std:c++20 . Wcześniej funkcje języka C++20 były dostępne tylko w /std:c++latest trybie w programie Visual Studio 2019. Funkcje języka C++20, które pierwotnie wymagały /std:c++latest trybu, działają teraz w /std:c++20 trybie lub nowszym w najnowszych wersjach programu Visual Studio.

Zobacz też

Zgodność języka Microsoft C/C++