Miglioramenti della conformità C++, modifiche del comportamento e correzioni di bug in Visual Studio 2019

Microsoft C/C++ in Visual Studio (MSVC) apporta miglioramenti alla conformità e correzioni di bug in ogni versione. Questo articolo elenca i miglioramenti introdotti a livello di versione principale e secondaria. Per passare direttamente alle modifiche per una versione specifica, usare l'elenco seguente In questo articolo.

Questo documento elenca le modifiche in Visual Studio 2019. Per una guida alle modifiche in Visual Studio 2022, vedere Miglioramenti della conformità di C++ in Visual Studio 2022. Per le modifiche apportate a Visual Studio 2017, vedere Miglioramenti della conformità di C++ in Visual Studio 2017. Per un elenco completo dei miglioramenti della conformità precedenti, vedere Novità di Visual C++ da 2003 a 2015.

Miglioramenti della conformità in Visual Studio 2019 RTW (versione 16.0)

Visual Studio 2019 RTW contiene i miglioramenti di conformità, le correzioni di bug e le modifiche del comportamento seguenti nel compilatore Microsoft C++.

Nota

Le funzionalità di C++20 erano disponibili solo in /std:c++latest modalità in Visual Studio 2019 fino al completamento dell'implementazione di C++20. Visual Studio 2019 versione 16.11 introduce la /std:c++20 modalità del compilatore. In questo articolo le funzionalità che in origine richiedevano /std:c++latest la modalità funzionano in /std:c++20 modalità o versioni successive nelle versioni più recenti di Visual Studio. La documentazione è stata aggiornata per menzionare /std:c++20, anche se questa opzione non era disponibile quando le funzionalità sono state rilasciate per la prima volta.

Supporto di moduli migliorato per i modelli e il rilevamento degli errori

I moduli ora supportano ufficialmente lo standard C++20. Il supporto aggiornato è stato aggiunto in Visual Studio 2017 versione 15.9. Per altre informazioni, vedere Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (Supporto di modelli e rilevamento errori migliorati nei moduli C++ con MSVC 2017 versione 15.9).

Specificazione del tipo di aggregazione modificata

La specifica di un tipo di aggregazione è stata modificata in C++20. Vedere Prohibit aggregates with user-declared constructors (Proibire aggregazioni con costruttori dichiarati dall'utente). In Visual Studio 2019, in /std:c++latest (o /std:c++20 in Visual Studio 2019 versione 16.11 e successive), una classe con qualsiasi costruttore dichiarato dall'utente (ad esempio, incluso un costruttore dichiarato = default o = delete) non è un'aggregazione. In precedenza solo i costruttori specificati dall'utente non consentivano di qualificare una classe come funzione di aggregazione. Questa modifica comporta ulteriori restrizioni sul modo in cui tali tipi possono essere inizializzati.

Il codice seguente viene compilato senza errori in Visual Studio 2017, ma genera errori C2280 e C2440 in Visual Studio 2019 in /std:c++20 o /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

Supporto parziale per operator <=>

P0515R3 C++20 introduce il supporto dell'operatore di confronto a tre vie <=>, detto anche "operatore navicella spaziale". Visual Studio 2019 versione 16.0 in /std:c++latest modalità introduce il supporto parziale per l'operatore generando errori per la sintassi non consentita. Ad esempio, il codice seguente viene compilato senza errori in Visual Studio 2017, ma genera più errori in Visual Studio 2019 /std:c++20 in o /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.
}

Per evitare gli errori, inserire uno spazio nella riga che causa l'errore prima della parentesi uncinata chiusa: U<&S::operator<= > u;.

Riferimenti a tipi con elementi cv-qualifier non corrispondenti

Nota

Questa modifica interessa solo Visual Studio 2019 versioni da 16.0 a 16.8. È stato ripristinato a partire da Visual Studio 2019 versione 16.9

In precedenza, MSVC consentiva il binding diretto di un riferimento da un tipo con elementi cv-qualifier non corrispondenti sotto il livello principale. Questo può consentire la modifica di dati presumibilmente const a cui viene fatto riferimento.

Il compilatore per Visual Studio 2019 versioni da 16.0 a 16.8 crea invece un temporaneo, come richiesto dallo standard in quel momento. Successivamente, lo standard ha modificato retroattivamente il comportamento precedente di Visual Studio 2017 e versioni precedenti e il comportamento di Visual Studio 2019 versione 16.0 a 16.8 errato. Di conseguenza, questa modifica è stata ripristinata a partire da Visual Studio 2019 versione 16.9.

Per una modifica correlata, vedere Tipi simili e binding di riferimento.

Ad esempio, in Visual Studio 2017 il codice seguente viene compilato senza avvisi. In Visual Studio 2019 versioni da 16.0 a 16.8 il compilatore genera l'avviso C4172. A partire da Visual Studio 2019 versione 16.9, il codice viene compilato di nuovo senza avvisi:

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 da una funzione in overload

L'argomento di reinterpret_cast non è uno dei contesti in cui è consentito l'indirizzo di una funzione in overload. Il codice seguente viene compilato senza errori in Visual Studio 2017, ma in Visual Studio 2019 genera l'errore 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'
}

Per evitare l'errore, usare un cast consentito per questo scenario:

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

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

Chiusure lambda

In C++14, i tipi di chiusura lambda non sono valori letterali. La conseguenza principale di questa regola è la possibilità che un'espressione lambda non sia assegnato a una variabile constexpr. Il codice seguente viene compilato senza errori in Visual Studio 2017, ma in Visual Studio 2019 genera l'errore C2127:

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

Per evitare l'errore, rimuovere il constexpr qualificatore oppure modificare la modalità di conformità in /std:c++17 o versione successiva.

Codici di errore std::create_directory

P1164 implementata da C++20 senza condizioni. Questa funzionalità modifica std::create_directory per verificare se la destinazione era già una directory in caso di errore. In precedenza tutti gli errori di tipo ERROR_ALREADY_EXISTS venivano trasformati in codici di esito positivo ma senza creazione di directory.

operator<<(std::ostream, nullptr_t)

Per LWG 2221, aggiunto operator<<(std::ostream, nullptr_t) per la scrittura nullptr in flussi.

Algoritmi più paralleli

Nuove versioni parallele di is_sorted, is_sorted_until, is_partitioned, set_difference, set_intersection, is_heap e is_heap_until.

Correzioni nell'inizializzazione atomica

P0883 "Correzione dell'inizializzazione atomica" modifiche std::atomic apportate all'inizializzazione del valore contenuto T anziché all'inizializzazione predefinita. La correzione è abilitata quando si usa Clang/LLVM con la libreria standard Microsoft. Attualmente è disabilitata per il compilatore Microsoft C++ come soluzione alternativa per un bug nell'elaborazione di constexpr.

remove_cvref e remove_cvref_t

Sono state implementate le caratteristiche dei tipi remove_cvref e remove_cvref_t da P0550. Tali caratteristiche rimuovono l'associazione a riferimenti e la qualifica cv- da un tipo senza degradare le funzioni e le matrici a puntatori (a differenza di std::decay e std::decay_t).

Macro dei test di funzionalità

Il documento P0941R2 sulle macro dei test delle funzionalità è stato completato, con il supporto per __has_cpp_attribute. Le macro dei test delle funzionalità sono supportate in tutte le modalità standard.

Vietare gli aggregati con i costruttori dichiarati dall'utente

C++20 P1008R1: non è possibile completare aggregazioni con costruttori dichiarati dall'utente.

reinterpret_cast in una constexpr funzione

Un reinterpret_cast oggetto non è valido in una constexpr funzione. Il compilatore Microsoft C++ rifiutava reinterpret_cast in precedenza solo se fosse stato usato in un constexpr contesto. In Visual Studio 2019, in tutte le modalità standard del linguaggio, il compilatore diagnostica correttamente una reinterpret_cast nella definizione di una constexpr funzione. Il codice seguente produce ora C3615:

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

Per evitare l'errore, rimuovere il constexpr modificatore dalla dichiarazione di funzione.

Diagnostica corretta per il costruttore di intervalli basic_string

In Visual Studio 2019 il costruttore di intervalli basic_string non elimina più la diagnostica del compilatore con static_cast. Il codice seguente viene compilato senza avvisi in Visual Studio 2017, nonostante la possibile perdita di dati da wchar_t a char durante l'inizializzazione di out:

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

Visual Studio 2019 genera correttamente l'avviso C4244. Per evitare l'avviso, è possibile inizializzare come std::string illustrato in questo esempio:

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

Chiamate errate a += e in /clr o /ZW-= sono ora rilevate correttamente

È stato introdotto un bug in Visual Studio 2017 che ha causato l'annullamento automatico degli errori da parte del compilatore e non genera alcun codice per le chiamate non valide a += e -= in /clr o /ZW. Il codice seguente viene compilato senza errori in Visual Studio 2017 ma in Visual Studio 2019 genera correttamente l'errore 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.
}

Per evitare l'errore in questo esempio, usare l'operatore += con il ToString() metodo : s += E::e.ToString();.

Inizializzatori per i membri dati statici inline

Gli accessi a membri non validi all'interno di inizializzatori inline e static constexpr ora vengono rilevati correttamente. L'esempio seguente viene compilato senza errori in Visual Studio 2017, ma in Visual Studio 2019 in /std:c++17 modalità o versioni successive viene generato l'errore 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'.
};

Per evitare l'errore, dichiarare il membro X::c come protetto:

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

C4800 reintegrato

In MSVC era disponibile un avviso di prestazioni C4800 sulla conversione implicita a bool. Era troppo rumoroso e non poteva essere eliminato, portandoci a rimuoverlo in Visual Studio 2017. Tuttavia, durante il ciclo di vita di Visual Studio 2017, Microsoft ha ricevuto numerosi commenti e suggerimenti relativi ai casi che tale avviso ha contribuito a risolvere. In Visual Studio 2019 torna a essere disponibile un avviso C4800 mirato, con un avviso C4165 associato. Entrambi questi avvisi sono facili da eliminare: usando un cast esplicito o confrontando con 0 del tipo appropriato. C4800 è un avviso di livello 4 disattivato e C4165 è un avviso di livello 3 disattivato per impostazione predefinita. Entrambi sono rilevabili tramite l'opzione del compilatore /Wall.

L'esempio seguente genera C4800 e C4165 sotto /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?
}

Per evitare gli avvisi nell'esempio precedente, è possibile scrivere codice simile al seguente:

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
}

Una funzione membro della classe locale non ha corpo

In Visual Studio 2017, l'avviso C4822 viene generato solo quando l'opzione /w14822 del compilatore è impostata in modo esplicito. Non viene visualizzato con /Wall. In Visual Studio 2019, C4822 è un avviso disattivato per impostazione predefinita , che lo rende individuabile /Wall in senza dover impostare /w14822 in modo esplicito.

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

Corpi del modello di funzione contenenti if constexpr istruzioni

In Visual Studio 2019 in /std:c++20 o /std:c++latest, i corpi delle funzioni modello con istruzioni hanno if constexpr controlli aggiuntivi correlati all'analisi abilitati. Ad esempio, in Visual Studio 2017 il codice seguente produce C7510 solo se l'opzione /permissive- è impostata. In Visual Studio 2019 lo stesso codice genera errori anche quando l'opzione /permissive è impostata:

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

Per evitare l'errore, aggiungere la parola chiave typename alla dichiarazione di a: typename T::Type a;.

Il codice assembly inline non è supportato in un'espressione lambda

Il team di Microsoft C++ è stato recentemente informato di un problema di sicurezza in cui l'uso di assembler inline all'interno di un'espressione lambda potrebbe causare il danneggiamento di ebp (registro degli indirizzi restituiti) in fase di esecuzione. Un utente malintenzionato potrebbe trarre vantaggio da questo scenario. L'assembler inline è supportato solo su x86 e l'interazione tra l'assembler inline e il resto del compilatore è scarsa. Dato questi fatti e la natura del problema, la soluzione più sicura a questo problema era quella di impedire l'assembler inline all'interno di un'espressione lambda.

L'unico caso d'uso dell'assembler inline all'interno di un'espressione lambda rilevato è stato un uso per l'acquisizione dell'indirizzo di restituzione. In questo scenario è possibile acquisire l'indirizzo del mittente in tutte le piattaforme, usando un elemento _ReturnAddress() intrinseco del compilatore.

Il codice seguente produce C7553 in Visual Studio 2017 15.9 e versioni successive di 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;
}

Per evitare l'errore spostare il codice dell'assembly in una funzione con nome, come illustrato nell'esempio seguente:

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

Debug degli iteratori e std::move_iterator

La funzionalità di debug dell'iteratore è stata configurata per annullare correttamente il wrapping di std::move_iterator. Ad esempio std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) ora può supportare il percorso rapido memcpy.

Correzioni per <l'imposizione delle parole chiave xkeycheck.h>

L'imposizione della libreria standard in <xkeycheck.h> per le macro che sostituiscono una parola chiave è stata corretta. La libreria genera ora la parola chiave del problema effettiva rilevata anziché un messaggio generico. Supporta anche le parole chiave C++20 e non induce IntelliSense a interpretare parole chiave casuali come macro.

Tipi di allocatore non più deprecati

std::allocator<void>, std::allocator::size_type e std::allocator::difference_type non sono più deprecati.

È stato corretto un avviso per la conversione di stringhe a tipi di dati più piccoli

Rimosso uno spurio static_cast da std::string che non è stato chiamato per dallo standard e che ha eliminato accidentalmente gli avvisi C4244 che restringevano. I tentativi di chiamata std::string::string(const wchar_t*, const wchar_t*) ora generano correttamente C4244 per restringere un oggetto wchar_t in un oggetto char.

Varie correzioni per <la correttezza del file system>

  • Correzione dell'errore std::filesystem::last_write_time durante il tentativo di modificare l'ora dell'ultima scrittura di una directory.
  • Il costruttore std::filesystem::directory_entry ora archivia un risultato "non riuscito", anziché generare un'eccezione, quando viene specificato un percorso di destinazione inesistente.
  • La versione di std::filesystem::create_directory a 2 parametri è stata modificata in modo da chiamare la versione a 1 parametro, poiché la funzione copy_symlink sottostante utilizzava CreateDirectoryExW quando existing_p era un collegamento simbolico.
  • std::filesystem::directory_iterator non restituisce più un errore quando rileva un collegamento simbolico interrotto.
  • std::filesystem::space ora accetta i percorsi relativi.
  • std::filesystem::path::lexically_relative non viene più confuso dalle barre finali, segnalate come LWG 3096.
  • Il rifiuto dei percorsi da parte di CreateSymbolicLinkW è stato risolto con barre in std::filesystem::create_symlink.
  • È stata eseguita la funzione di eliminazione delete POSIX esistente in Windows 10 LTSB 1609, ma non è stato possibile eliminare i file.
  • I std::boyer_moore_searcher costruttori e std::boyer_moore_horspool_searcher di copia e gli operatori di assegnazione di copia ora copiano gli elementi.

Algoritmi paralleli in Windows 8 e versioni successive

La libreria di algoritmi paralleli ora usa correttamente la famiglia WaitOnAddress reale in Windows 8 e versioni successive, invece di usare sempre la versione Windows 7 e le versioni non autentiche precedenti.

Spazio vuoto in std::system_category::message()

std::system_category::message() ora rimuove correttamente gli spazi finali dal messaggio restituito.

Divisione per zero in std::linear_congruential_engine

Alcune condizioni che causano l'attivazione di una divisione per 0 in std::linear_congruential_engine sono state corrette.

Correzioni per la rimozione del wrapping dell'iteratore

Alcuni macchinari iterator-unwrapping sono stati esposti per la prima volta per l'integrazione dell'utente programmatore in Visual Studio 2017 15.8. È stato descritto nell'articolo del blog del team C++ Funzionalità e correzioni STL in VS 2017 15.8. Questo macchinario non annulla più il wrapping degli iteratori derivati da iteratori di libreria standard. Ad esempio un utente che deriva da std::vector<int>::iterator e prova a personalizzare il comportamento ora ottiene il comportamento personalizzato quando chiama algoritmi della libreria standard, anziché il comportamento di un puntatore.

La funzione reserve del contenitore non ordinato ora esegue correttamente la riserva per N elementi, come descritto in LWG 2156.

Gestione del tempo

  • In precedenza alcuni valori di ora passati alla libreria di concorrenza registravano overflow, ad esempio condition_variable::wait_for(seconds::max()). Il comportamento di questi overflow, ora corretti, cambiava in base a un ciclo apparentemente casuale di 29 giorni (quando si verificava l'overflow di uint32_t millisecondi accettato dalle API Win32 sottostanti).

  • L'intestazione <ctime> ora dichiara timespec correttamente e timespec_get nello spazio dei nomi e li dichiara anche nello spazio stddei nomi globale.

Varie correzioni per i contenitori

  • Molte funzioni contenitore interne della libreria standard sono state create private per un'esperienza intelliSense migliorata. Altre correzioni per contrassegnare i membri come private previsto nelle versioni successive di MSVC.

  • Sono stati risolti problemi di correzione della sicurezza delle eccezioni che causavano il danneggiamento dei contenitori basati su nodi, ad esempio list, mape unordered_map. Durante un'operazione propagate_on_container_copy_assignment di riassegnazione o propagate_on_container_move_assignment , si libera il nodo sentinel del contenitore con l'allocatore precedente, si esegue l'assegnazione POCCA/POCMA sull'allocatore precedente e quindi si tenta di acquisire il nodo sentinel dal nuovo allocatore. Se questa allocazione non è riuscita, il contenitore è danneggiato. Non è stato nemmeno possibile distruggerlo, perché il proprietario di un nodo sentinel è una struttura di dati hard invariante. Questo codice è stato risolto per creare il nuovo nodo sentinel usando l'allocatore del contenitore di origine prima di eliminare il nodo sentinel esistente.

  • I contenitori sono stati risolti in modo da copiare/spostare/scambiare gli allocatori sempre in base a propagate_on_container_copy_assignment, propagate_on_container_move_assignment, e propagate_on_container_swap, anche per gli allocatori dichiarati is_always_equal.

  • Aggiunta degli overload per l'unione dei contenitori e l'estrazione di funzioni membro che accettano contenitori rvalue. Per altre informazioni, vedere P0083 "Splicing Mappe and sets"

std::basic_istream::read elaborazione di \r\n`` =>\n'

std::basic_istream::read è stato corretto per non scrivere in parti del buffer fornito temporaneamente come parte dell'elaborazione \r\n\n . In questo modo si rinuncia a una parte del vantaggio acquisito in termini di prestazioni in Visual Studio 2017 15.8 per le letture di dimensioni superiori a 4 KB, ma si registrano miglioramenti dell'efficienza evitando tre chiamate virtuali per ogni carattere.

Costruttore std::bitset

Il costruttore std::bitset non legge più le cifre uno e zero in ordine inverso per i set di bit di grandi dimensioni.

Regressione in std::pair::operator=

È stata corretta una regressione nell'operatore std::pair di assegnazione introdotta quando si implementa LWG 2729 "Missing SFINAE on std::pair::operator=";. Ora accetta di nuovo correttamente i tipi convertibili in std::pair.

Contesti non dedotti per add_const_t

È stato risolto un bug di tratti di tipo secondario, in cui add_const_t e le funzioni correlate dovrebbero essere un contesto non dedotto. In altre parole, add_const_t deve essere un alias per typename add_const<T>::type e non per const T.

Miglioramenti della conformità nella versione 16.1

char8_t

P0482r6. C++20 aggiunge un nuovo tipo di carattere usato per rappresentare unità di codice UTF-8. I valori letterali stringa u8 in C++20 hanno il tipo const char8_t[N] invece di const char[N] come in precedenza. Sono state proposte modifiche simili per lo standard C in N2231. I suggerimenti per la correzione della char8_t compatibilità con le versioni precedenti vengono forniti in P1423r3. Il compilatore Microsoft C++ aggiunge il supporto per char8_t in Visual Studio 2019 versione 16.1 quando si specifica l'opzione del /Zc:char8_t compilatore. Può essere ripristinato il comportamento di C++17 tramite /Zc:char8_t-. Il compilatore EDG che supporta IntelliSense non lo supporta ancora in Visual Studio 2019 versione 16.1. Potrebbero essere visualizzati errori di solo IntelliSense spuri che non influiscono sulla compilazione effettiva.

Esempio

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

std::type_identity metafunzione e std::identity oggetto funzione

P0887R1 type_identity. L'estensione del modello di classe std::identity deprecata è stata rimossa e sostituita dalla metafunzione std::type_identity e dall'oggetto funzione std::identity di C++20. Entrambi sono disponibili solo in /std:c++latest (/std:c++20 in Visual Studio 2019 versione 16.11 e successive).

L'esempio seguente genera un avviso di deprecazione C4996 per std::identity (definito in <type_traits>) in 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);

Nell'esempio seguente viene illustrato come usare il nuovo std::identity (definito in <funzionale>) insieme al nuovo std::type_identity:

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

Controllo della sintassi per espressioni lambda generiche

Il nuovo processore lambda abilita alcuni controlli sintattici in modalità conformità nelle espressioni lambda generiche, in /std:c++latest (/std:c++20 in Visual Studio 2019 versione 16.11 e successive) o in qualsiasi altra modalità del linguaggio con /Zc:lambda in Visual Studio 2019 versione 16.9 o successiva (precedentemente disponibile a /experimental:newLambdaProcessor partire da Visual Studio 2019 versione 16.3).

Il processore lambda legacy compila questo esempio senza avvisi, ma il nuovo processore lambda genera l'errore C2760:

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

Questo esempio mostra la sintassi corretta, ora applicata dal compilatore:

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

Ricerca dipendente dall'argomento per le chiamate di funzione

P0846R0 (C++20) È stata aumentata la possibilità di trovare modelli di funzione tramite la ricerca dipendente dagli argomenti per le espressioni di chiamata di funzione con argomenti di modello espliciti. Richiede /std:c++latest (o /std:c++20 in Visual Studio 2019 versione 16.11 e successive).

Inizializzazione designata

P0329R4 (C++20) Inizializzazione designata consente di selezionare membri specifici nell'inizializzazione aggregata usando la Type t { .member = expr } sintassi . Richiede /std:c++latest (o /std:c++20 in Visual Studio 2019 versione 16.11 e successive).

Classificazione della conversione enumerazione nel tipo sottostante fisso

Il compilatore classifica ora le conversioni di enumerazione in base a N4800 11.3.3.2 Classifica le sequenze di conversione implicita (4.2):

  • Una conversione che promuove un'enumerazione il cui tipo sottostante è fisso al tipo sottostante è migliore di uno che promuove al tipo sottostante alzato di livello, se i due sono diversi.

Questa classificazione di conversione non è stata implementata correttamente prima di Visual Studio 2019 versione 16.1. Il comportamento conforme può modificare il comportamento di risoluzione dell'overload o esporre un'ambiguità in cui uno in precedenza non è stato rilevato.

Questa modifica del comportamento del compilatore si applica a tutte le /std modalità ed è sia una modifica di origine che di interruzione binaria.

Nell'esempio seguente viene illustrato il modo in cui il comportamento del compilatore cambia nella versione 16.1 e successive:

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

Funzioni nuove e aggiornate della libreria standard (C++20)

  • starts_with() e ends_with() per basic_string e basic_string_view.
  • contains() per i contenitori associativi.
  • remove(), remove_if() e unique() per list e forward_list ora restituiscono size_type.
  • shift_left()e shift_right() aggiunto all'algoritmo<>.

Miglioramenti della conformità nella versione 16.2

noexceptconstexpr funzioni

constexpr Le funzioni non vengono più considerate noexcept per impostazione predefinita quando vengono usate in un'espressione costante. Questa modifica del comportamento deriva dalla risoluzione di Core Working Group (CWG) CWG 1351 ed è abilitata in /permissive-. L'esempio seguente viene compilato in Visual Studio 2019 versione 16.1 e precedenti, ma produce C2338 in Visual Studio 2019 versione 16.2:

constexpr int f() { return 0; }

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

Per correggere l'errore, aggiungere l'espressione noexcept alla dichiarazione di funzione:

constexpr int f() noexcept { return 0; }

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

Espressioni binarie con tipi di enumerazione diversi

C++20 ha deprecato le normali conversioni aritmetiche sugli operandi, dove:

  • Un operando è di tipo enumerazione e

  • l'altro è di un tipo di enumerazione diverso o di un tipo a virgola mobile.

Per altre informazioni, vedere P1120R0.

In Visual Studio 2019 versione 16.2 e successive, il codice seguente genera un avviso di livello 4 C5054 quando l'opzione del /std:c++latest compilatore è abilitata (/std:c++20 in Visual Studio 2019 versione 16.11 e successive):

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

Per evitare l'avviso, usare static_cast per convertire il secondo operando:

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

L'uso di un'operazione binaria tra un'enumerazione e un tipo a virgola mobile è ora un avviso di livello 1 C5055 quando l'opzione del /std:c++latest compilatore è abilitata (/std:c++20 in Visual Studio 2019 versione 16.11 e successive):

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

Per evitare l'avviso, usare static_cast per convertire il secondo operando:

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

Confronto tra uguaglianza e relazionali di matrici

I confronti di uguaglianza e relazionale tra due operandi di tipo matrice sono deprecati in C++20 (P1120R0). In altre parole, un'operazione di confronto tra due matrici (nonostante le analogie di rango e extent) è ora un avviso. In Visual Studio 2019 versione 16.2 e successive, il codice seguente genera l'avviso C5056 di livello 1 quando l'opzione del /std:c++latest compilatore è abilitata (/std:c++20 in Visual Studio 2019 versione 16.11 e successive):

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

Per evitare l'avviso, è possibile confrontare gli indirizzi dei primi elementi:

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

Per determinare se il contenuto di due matrici è uguale, usare la std::equal funzione :

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

Effetto della definizione dell'operatore di astronave su == e !=

Una definizione dell'operatore di astronave (<=>) da sola non riscriverà più le espressioni che coinvolgono == o != , a meno che l'operatore di astronave non sia contrassegnato come = default (P1185R2). L'esempio seguente viene compilato in Visual Studio 2019 RTW e versione 16.1, ma produce C2678 in Visual Studio 2019 versione 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
}

Per evitare l'errore, definirlo operator== o dichiararlo come predefinito:

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

Miglioramenti della libreria standard

  • <carbonnv>to_chars() con precisione fissa/scientifica. La precisione generale è attualmente pianificata per la versione 16.4.
  • P0020R6: atomic<float>, atomic<double>,atomic<long double>
  • P0463R1: endian
  • P0482R6: Supporto della libreria perchar8_t
  • P0600R1: [[nodiscard]] per sTL, parte 1
  • P0653R2:to_address()
  • P0754R2: <versione>
  • P0771R1: noexcept per std::functionil costruttore di spostamento

Confronto const per contenitori associativi

Il codice per la ricerca e l'inserimento in set, mapmultiset, e multimap è stato unito per ridurre le dimensioni del codice. Le operazioni di inserimento chiamano ora il confronto con un const funtore di confronto, allo stesso modo in cui le operazioni di ricerca sono state eseguite in precedenza. Il codice seguente viene compilato in Visual Studio 2019 versione 16.1 e precedenti, ma genera C3848 in Visual Studio 2019 versione 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);

}

Per evitare l'errore, impostare l'operatore constdi confronto :

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

Miglioramenti della conformità in Visual Studio 2019 versione 16.3

Operatori di estrazione dei flussi per char* rimossi

Gli operatori di estrazione dei flussi per i puntatori ai caratteri sono stati rimossi e sostituiti da operatori di estrazione per matrici di caratteri (per P0487R1). WG21 considera gli overload rimossi come non sicuri. In /std:c++20 modalità o /std:c++latest , l'esempio seguente produce ora 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)
}

Per evitare l'errore, usare l'operatore di estrazione con una char[] variabile:

#include <iostream>
#include <iomanip>

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

Nuove parole chiave requires e concept

Nuove parole chiave requires e concept aggiunte al compilatore Microsoft C++. Se si tenta di usare uno come identificatore in /std:c++20 modalità o /std:c++latest , il compilatore genera L2059 per indicare un errore di sintassi.

Costruttori come nomi di tipi non consentiti

Il compilatore non considera più i nomi dei costruttori come nomi di classe inseriti in questo caso: quando vengono visualizzati in un nome completo dopo un alias a una specializzazione di modello di classe. In precedenza, i costruttori erano utilizzabili come nome di tipo per dichiarare altre entità. L'esempio seguente produce ora C3646:

#include <chrono>

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

Per evitare l'errore, dichiarare TotalDuration come illustrato di seguito:

#include <chrono>

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

Controllo più rigoroso delle extern "C" funzioni

Se una extern "C" funzione è stata dichiarata in spazi dei nomi diversi, le versioni precedenti del compilatore Microsoft C++ non hanno verificato se le dichiarazioni erano compatibili. In Visual Studio 2019 versione 16.3 e successive il compilatore verifica la compatibilità. In /permissive- modalità, il codice seguente genera errori C2371 e 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

Per evitare gli errori nell'esempio precedente, usare bool anziché BOOL in modo coerente in entrambe le dichiarazioni di f.

Miglioramenti della libreria standard

Le intestazioni <non standard stdexcpt.h> e <typeinfo.h> sono state rimosse. Il codice che li include deve invece includere rispettivamente l'eccezione> di intestazioni <standard e <typeinfo>.

Miglioramenti della conformità in Visual Studio 2019 versione 16.4

Migliore applicazione della ricerca dei nomi in due fasi per gli ID qualificati in /permissive-

La ricerca del nome in due fasi richiede che i nomi non dipendenti usati nel corpo di modelli siano visibili per il modello in fase di definizione. In precedenza, tali nomi potrebbero essere stati trovati quando viene creata un'istanza del modello. Questa modifica semplifica la scrittura di codice portabile e conforme in MSVC sotto il /permissive- flag .

In Visual Studio 2019 versione 16.4 con il /permissive- flag impostato, l'esempio seguente genera un errore, perché N::f non è visibile quando viene definito il f<T> modello:

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

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

In genere, questo errore può essere risolto includendo intestazioni mancanti o funzioni o variabili forward-declaring, come illustrato nell'esempio seguente:

namespace N {
    int f();
}

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

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

Conversione implicita di espressioni costanti integrali in puntatore Null

Il compilatore MSVC implementa ora il problema CWG 903 in modalità di conformità (/permissive-). Questa regola non consente la conversione implicita di espressioni costanti integrali (ad eccezione del valore letterale intero '0') in costanti puntatore Null. L'esempio seguente produce C2440 in modalità di conformità:

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

Per correggere l'errore, usare nullptr invece di false. È ancora consentito un valore letterale 0:

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

Regole standard per i tipi di valori letterali integer

In modalità di conformità (abilitata da /permissive-), MSVC usa le regole standard per i tipi di valori letterali integer. I valori letterali decimali troppo grandi per adattarsi a un signed int oggetto sono stati assegnati in precedenza al tipo unsigned int. A questo punto, tali valori letterali vengono assegnati al tipo integer più grande signed successivo, long long. Inoltre, ai valori letterali con il suffisso 'll' troppo grande per adattarsi a un tipo viene assegnato il signed tipo unsigned long long.

Questa modifica può causare la generazione di diversi avvisi di diagnostica e differenze di comportamento per le operazioni aritmetiche sui valori letterali.

L'esempio seguente illustra il nuovo comportamento in Visual Studio 2019 versione 16.4. La i variabile è ora di tipo unsigned int, quindi viene generato l'avviso. I bit di ordine elevato della variabile j sono impostati su 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
}

L'esempio seguente illustra come mantenere il comportamento precedente ed evitare gli avvisi e la modifica del comportamento di runtime:

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

Parametri della funzione che ombreggiatura parametri del modello

Il compilatore MSVC genera ora un errore quando un parametro di funzione ombreggiato un parametro di modello:

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

Per correggere l'errore, modificare il nome di uno dei parametri:

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

Specializzazioni fornite dall'utente di tratti di tipo

In conformità con la sottoclausa meta.rqmts dello standard, il compilatore MSVC genera ora un errore quando trova una specializzazione definita dall'utente di uno dei modelli specificati type_traits nello spazio dei std nomi. Se non diversamente specificato, tali specializzazioni comportano un comportamento non definito. L'esempio seguente presenta un comportamento non definito perché viola la regola e l'errore static_assert C2338 ha esito negativo.

#include <type_traits>
struct S;

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

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

Per evitare l'errore, definire uno struct che eredita dall'oggetto preferito type_traited è specializzato in quanto:

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

Modifiche agli operatori di confronto forniti dal compilatore

Il compilatore MSVC implementa ora le modifiche seguenti agli operatori di confronto per P1630R1 quando l'opzione /std:c++20 o /std:c++latest è abilitata:

Il compilatore non riscrive più le espressioni usando operator== se implicano un tipo restituito che non è un oggetto bool. Il codice seguente genera ora l'errore 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
}

Per evitare l'errore, è necessario definire in modo esplicito l'operatore necessario:

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

Il compilatore non definisce più un operatore di confronto predefinito se è un membro di una classe simile a un'unione. L'esempio seguente genera ora l'errore 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;
}

Per evitare l'errore, definire un corpo per l'operatore :

#include <compare>

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

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

Il compilatore non definirà più un operatore di confronto predefinito se la classe contiene un membro di riferimento. Il codice seguente genera ora l'errore C2120:

#include <compare>

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

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

Per evitare l'errore, definire un corpo per l'operatore :

#include <compare>

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

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

Miglioramenti della conformità in Visual Studio 2019 versione 16.5

La dichiarazione di specializzazione esplicita senza un inizializzatore non è una definizione

In /permissive-, MSVC applica ora una regola standard che le dichiarazioni di specializzazione esplicite senza inizializzatori non sono definizioni. In precedenza, la dichiarazione verrebbe considerata una definizione con un inizializzatore predefinito. L'effetto è osservabile in fase di collegamento, poiché un programma a seconda di questo comportamento potrebbe ora avere simboli non risolti. Questo esempio genera ora un errore:

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.

Per risolvere il problema, aggiungere un inizializzatore:

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

L'output del preprocessore mantiene le nuove righe

Il preprocessore sperimentale mantiene ora nuove righe e spazi vuoti quando si usa /P o /E con /experimental:preprocessor.

Dato questo esempio di origine,

#define m()
line m(
) line

L'output precedente di /E è stato:

line line
#line 2

Il nuovo output di /E è ora:

line
 line

import le parole chiave e module dipendono dal contesto

Per P1857R1import e module le direttive del preprocessore hanno nuove restrizioni sulla sintassi. Questo esempio non viene più compilato:

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

Per risolvere il problema, mantenere l'importazione nella stessa riga:

import m; // OK

Rimozione di std::weak_equality e std::strong_equality

L'unione di P1959R0 richiede al compilatore di rimuovere il comportamento e i riferimenti ai std::weak_equality tipi e std::strong_equality .

Il codice in questo esempio non viene più compilato:

#include <compare>

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

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

L'esempio porta ora a questi errori:

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'

Per risolvere il problema, aggiornare per preferire gli operatori relazionali predefiniti e sostituire i tipi rimossi:

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

Modifiche di TLS Guard

In precedenza, le variabili thread-local nelle DLL non venivano inizializzate correttamente. Oltre al thread che ha caricato la DLL, non sono stati inizializzati prima dell'uso nei thread esistenti prima del caricamento della DLL. Questo difetto è stato corretto. Le variabili locali del thread in una DLL di questo tipo vengono inizializzate immediatamente prima del loro primo uso su tali thread.

Questo nuovo comportamento di test per l'inizializzazione in caso di uso di variabili locali del thread può essere disabilitato usando l'opzione del /Zc:tlsGuards- compilatore. In alternativa, aggiungendo l'attributo [[msvc:no_tls_guard]] a specifiche variabili locali del thread.

Diagnosi migliore della chiamata alle funzioni eliminate

Il compilatore era più permissivo sulle chiamate alle funzioni eliminate in precedenza. Ad esempio, se le chiamate si sono verificate nel contesto di un corpo del modello, non viene diagnosticata la chiamata. Inoltre, se sono presenti più istanze di chiamate alle funzioni eliminate, si emette una sola diagnostica. A questo punto viene eseguita una diagnostica per ognuna di esse.

Una conseguenza del nuovo comportamento può produrre una piccola modifica che causa un'interruzione: il codice che ha chiamato una funzione eliminata non viene diagnosticato se non è mai stato necessario per la generazione del codice. Ora lo diagnosticaremo in anticipo.

Questo esempio mostra il codice che ora genera un errore:

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

Per risolvere il problema, rimuovere le chiamate alle funzioni eliminate:

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

Miglioramenti della conformità in Visual Studio 2019 versione 16.6

I flussi di libreria standard rifiutano gli inserimenti di tipi di caratteri con codifica errata

Tradizionalmente, l'inserimento di un oggetto wchar_t in un std::ostreamoggetto e l'inserimento char16_t di o char32_t in un std::ostream oggetto o std::wostreamrestituisce il relativo valore integrale. L'inserimento di puntatori a tali tipi di caratteri restituisce il valore del puntatore. I programmatori non trovano nessuno dei due casi intuitivi. Spesso si prevede che la libreria standard transcodifica la stringa di caratteri con terminazione Null o del carattere e restituisca il risultato.

La proposta C++20 P1423R3 aggiunge overload degli operatori di inserimento del flusso eliminati per queste combinazioni di tipi di flusso e carattere o puntatore a caratteri. In /std:c++20 o /std:c++latest, gli overload rendono questi inserimenti in formato non corretto, invece di comportarsi in modo improbabile. Il compilatore genera l'errore C2280 quando viene trovato. È possibile definire la macro _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20 "tratteggio di escape" per 1 ripristinare il comportamento precedente. La proposta elimina anche gli operatori di inserimento del flusso per char8_t. La libreria standard ha implementato overload simili quando è stato aggiunto char8_t il supporto, quindi il comportamento "errato" non è mai stato disponibile per char8_t.

Questo esempio mostra il comportamento con questa modifica:

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

Il codice genera ora questi messaggi di diagnostica:

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

È possibile ottenere l'effetto del comportamento precedente in tutte le modalità del linguaggio convertendo i tipi di carattere in unsigned into tipi puntatore a carattere in 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"
}

Tipo restituito modificato di std::pow() per std::complex

In precedenza, l'implementazione MSVC delle regole di promozione per il tipo restituito di modello std::pow() di funzione non è corretta. Ad esempio, in precedenza pow(complex<float>, int) restituito complex<float>. Ora restituisce complex<double>correttamente . La correzione è stata implementata in modo incondizionato per tutte le modalità standard in Visual Studio 2019 versione 16.6.

Questa modifica può causare errori del compilatore. Ad esempio, in precedenza è possibile moltiplicare pow(complex<float>, int) per .float Poiché complex<T> operator* prevede argomenti dello stesso tipo, l'esempio seguente genera ora l'errore del compilatore 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

Esistono molte possibili correzioni:

  • Modificare il tipo della float moltiplicazione doublein . Questo argomento può essere convertito direttamente in un oggetto complex<double> in modo che corrisponda al tipo restituito da pow.

  • Restringere il risultato di pow a complex<float> dicendo complex<float>{pow(ARG, ARG)}. È quindi possibile continuare a moltiplicare per un float valore.

  • Passare float anziché int a pow. Questa operazione potrebbe risultare più lenta.

  • In alcuni casi, è possibile evitare pow completamente. Ad esempio, pow(cf, -1) può essere sostituito dalla divisione.

switch avvisi per C

In Visual Studio 2019 versione 16.6 e successive il compilatore implementa alcuni avvisi C++ preesistenti per il codice compilato come C. Gli avvisi seguenti sono ora abilitati a livelli diversi: C4060, C4061, C4062, C4063, C4064, C4065, C4808 e C4809. Gli avvisi C4065 e C4060 sono disabilitati per impostazione predefinita in C.

Gli avvisi vengono attivati in istruzioni mancanti case , istruzioni switch non predefinite enume bool non dannose, ovvero quelle che contengono troppi casi. Ad esempio:

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

Per correggere questo codice, rimuovere il caso ridondante default :

#include <stdbool.h>

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

Classi senza nome nelle typedef dichiarazioni

In Visual Studio 2019 versione 16.6 e successive il comportamento delle typedef dichiarazioni è stato limitato alla conformità alle P1766R1. Con questo aggiornamento, le classi senza nome all'interno di una typedef dichiarazione non possono avere membri diversi da:

  • membri dati non statici senza inizializzatori di membri predefiniti,
  • classi membro o
  • enumerazioni membro.

Le stesse restrizioni vengono applicate in modo ricorsivo a ogni classe annidata. La restrizione è destinata a garantire la semplicità degli struct con typedef nomi per scopi di collegamento. Devono essere sufficientemente semplici da non essere necessari calcoli di collegamento prima che il compilatore ottenga il nome per il typedef collegamento.

Questa modifica influisce su tutte le modalità standard del compilatore. Nelle modalità predefinite (/std:c++14) e /std:c++17 il compilatore genera l'avviso C5208 per il codice non conforme. Se /permissive- viene specificato, il compilatore genera l'avviso C5208 come errore in /std:c++14 e genera l'errore C7626 in /std:c++17. Il compilatore genera l'errore C7626 per il codice non conforme quando /std:c++20 viene specificato o /std:c++latest .

L'esempio seguente illustra i costrutti che non sono più consentiti in struct senza nome. A seconda della modalità standard specificata, vengono generati errori o avvisi C5208 o C7626:

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;

Il codice precedente può essere corretto assegnando alla classe senza nome un nome:

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

Importazione di argomenti predefiniti in C++/CLI

Un numero crescente di API ha argomenti predefiniti in .NET Core. Ora è supportata l'importazione dell'argomento predefinito in C++/CLI. Questa modifica può interrompere il codice esistente in cui vengono dichiarati più overload, come in questo esempio:

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

Quando questa classe viene importata in C++/CLI, una chiamata a uno degli overload causa un errore:

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

Il compilatore genera l'errore C2668 perché entrambi gli overload corrispondono a questo elenco di argomenti. Nel secondo overload il secondo argomento viene compilato dall'argomento predefinito. Per risolvere questo problema, è possibile eliminare l'overload ridondante (1). In alternativa, usare l'elenco di argomenti completo e specificare in modo esplicito gli argomenti predefiniti.

Miglioramenti della conformità in Visual Studio 2019 versione 16.7

è una definizione facilmente copiabile

C++20 ha modificato la definizione di è facilmente copiabile. Quando una classe dispone di un membro dati non statico con volatile tipo qualificato, non implica più che qualsiasi costruttore di copia o spostamento generato dal compilatore, o un operatore di assegnazione di copia o spostamento, non sia semplice. Il comitato standard C++ ha applicato questa modifica retroattivamente come segnalazione dei difetti. In MSVC il comportamento del compilatore non cambia in diverse modalità del linguaggio, ad esempio /std:c++14 o /std:c++latest.

Ecco un esempio del nuovo comportamento:

#include <type_traits>

struct S
{
    volatile int m;
};

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

Questo codice non viene compilato nelle versioni di MSVC precedenti a Visual Studio 2019 versione 16.7. È presente un avviso del compilatore disattivato per impostazione predefinita che è possibile usare per rilevare questa modifica. Se si compila il codice precedente usando cl /W4 /w45220, verrà visualizzato l'avviso seguente:

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`

Le conversioni da puntatore a membro e valori letterali stringa in bool sono di tipo narrowing

Il comitato standard C++ ha recentemente adottato la relazione sui difetti P1957R2, che considera T*bool come una conversione ridotta. MSVC ha risolto un bug nell'implementazione, che in precedenza diagnosticava T* come bool narrowing, ma non diagnosticava la conversione di un valore letterale stringa in bool o un puntatore a membro in bool.

Il programma seguente non è formato correttamente in Visual Studio 2019 versione 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
}

Per correggere questo codice, aggiungere confronti espliciti a nullptro evitare contesti in cui le conversioni di tipo narrowing non sono formate:

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 è convertibile solo in bool come inizializzazione diretta

In C++11 nullptr è convertibile bool solo in come conversione diretta, ad esempio quando si inizializza un bool oggetto usando un elenco di inizializzatori con parentesi graffa. Questa restrizione non è mai stata applicata da MSVC. MSVC implementa ora la regola in /permissive-. Le conversioni implicite vengono ora diagnosticate come in formato non corretto. Una conversione contestuale a bool è ancora consentita, perché l'inizializzazione bool b(nullptr) diretta è valida.

Nella maggior parte dei casi, l'errore può essere risolto sostituendo nullptr con false, come illustrato in questo esempio:

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
}

Comportamento di inizializzazione conforme per le inizializzazioni di matrice con inizializzatori mancanti

In precedenza, MSVC aveva un comportamento non conforme per le inizializzazioni di matrice che avevano inizializzatori mancanti. MSVC ha sempre chiamato il costruttore predefinito per ogni elemento di matrice che non ha un inizializzatore. Il comportamento standard consiste nell'inizializzare ogni elemento con un elenco di inizializzatori tra parentesi graffe{} (). Il contesto di inizializzazione per un elenco di inizializzatori tra parentesi graffe vuoto è l'inizializzazione della copia, che non consente chiamate a costruttori espliciti. Esistono anche differenze di runtime, perché l'uso di {} per inizializzare può chiamare un costruttore che accetta un std::initializer_listoggetto anziché il costruttore predefinito. Il comportamento conforme è abilitato in /permissive-.

Ecco un esempio del comportamento modificato:

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
}

L'inizializzazione dei membri della classe con nomi di overload è sequenziata correttamente

È stato identificato un bug nella rappresentazione interna dei membri dati della classe quando un nome di tipo viene anche sottoposto a overload come nome di un membro dati. Questo bug ha causato incoerenze nell'inizializzazione aggregata e nell'ordine di inizializzazione dei membri. Il codice di inizializzazione generato è ora corretto. Tuttavia, questa modifica può causare errori o avvisi nell'origine che inavvertitamente si basavano sui membri ordinati in modo non corretto, come in questo esempio:

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

Nelle versioni precedenti, il costruttore inizializza erroneamente il membro Inner dati prima del membro vdati . Lo standard C++ richiede un ordine di inizializzazione uguale all'ordine di dichiarazione dei membri. Ora che il codice generato segue lo standard, member-init-list non è in ordine. Il compilatore genera un avviso per questo esempio. Per correggerlo, riordinare l'elenco dei membri-inizializzatori in modo da riflettere l'ordine di dichiarazione.

Risoluzione dell'overload che include overload integrali e long argomenti

Lo standard C++ richiede la classificazione di una long conversione int come conversione standard. I compilatori MSVC precedenti lo classificano erroneamente come promozione integrale, che classifica più in alto per la risoluzione dell'overload. Questa classificazione può causare la risoluzione dell'overload correttamente quando deve essere considerata ambigua.

Il compilatore ora considera correttamente la classificazione in /permissive- modalità . Il codice non valido viene diagnosticato correttamente, come in questo esempio:

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
}

È possibile risolvere questo problema in diversi modi:

  • Nel sito di chiamata modificare il tipo dell'argomento passato in int. È possibile modificare il tipo di variabile o eseguirne il cast.

  • Se sono presenti molti siti di chiamata, è possibile aggiungere un altro overload che accetta un long argomento. In questa funzione eseguire il cast e inoltrare l'argomento all'overload int .

Uso di una variabile non definita con collegamento interno

Le versioni di MSVC precedenti a Visual Studio 2019 versione 16.7 accettarono l'uso di una variabile dichiarata extern che aveva un collegamento interno e non era definita. Tali variabili non possono essere definite in nessun'altra unità di conversione e non possono formare un programma valido. Il compilatore ora diagnostica questo caso in fase di compilazione. L'errore è simile all'errore per le funzioni statiche non predefinite.

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

Questo programma precedentemente compilato e collegato in modo non corretto, ma ora genererà l'errore C7631.

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

Tali variabili devono essere definite nella stessa unità di conversione in cui vengono usate. Ad esempio, è possibile fornire un inizializzatore esplicito o una definizione separata.

Completezza dei tipi e conversioni di puntatori da derivazione a base

Negli standard C++ precedenti a C++20, una conversione da una classe derivata a una classe base non richiede che la classe derivata sia un tipo di classe completo. Il comitato standard C++ ha approvato una modifica retroattiva del report sui difetti che si applica a tutte le versioni del linguaggio C++. Questa modifica allinea il processo di conversione con tratti di tipo, ad esempio std::is_base_of, che richiedono che la classe derivata sia un tipo di classe completo.

Ecco un esempio:

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

Questa modifica del comportamento si applica a tutte le modalità del linguaggio C++ di MSVC, non solo /std:c++20 o /std:c++latest.

Le conversioni di tipo narrowing vengono diagnosticate in modo più coerente

MSVC genera un avviso per le conversioni di tipo narrowing in un inizializzatore di elenco con parentesi graffe. In precedenza, il compilatore non diagnosticare le conversioni di tipo narrowing da tipi sottostanti più grandi enum a tipi integrali più stretti. Il compilatore li considera erroneamente una promozione integrale anziché una conversione. Se la conversione di tipo narrowing è intenzionale, è possibile evitare l'avviso usando un static_cast oggetto nell'argomento dell'inizializzatore. In alternativa, scegliere un tipo integrale di destinazione più grande.

Di seguito è riportato un esempio di uso static_cast esplicito per risolvere l'avviso:

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
}

Miglioramenti della conformità in Visual Studio 2019 versione 16.8

'Class rvalue used as lvalue' extension

MSVC ha un'estensione che consente l'uso di una classe rvalue come lvalue. L'estensione non estende la durata della classe rvalue e può causare un comportamento non definito in fase di esecuzione. A questo punto si applica la regola standard e si impedisce questa estensione in /permissive-. Se non è ancora possibile usare /permissive- , è possibile usare /we4238 per impedire esplicitamente l'estensione. Ecco un esempio:

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

'Specializzazione esplicita nell'ambito non dello spazio dei nomi' di estensione

MSVC aveva un'estensione che consentiva la specializzazione esplicita nell'ambito non dello spazio dei nomi. Ora fa parte dello standard, dopo la risoluzione di CWG 727. Esistono tuttavia differenze di comportamento. Il comportamento del compilatore è stato modificato in modo da allinearlo allo standard.

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

Verifica della presenza di tipi di classi astratte

Lo standard C++20 ha modificato l'uso dei compilatori di processi per rilevare l'uso di un tipo di classe astratta come parametro di funzione. In particolare, non è più un errore SFINAE. In precedenza, se il compilatore ha rilevato che una specializzazione di un modello di funzione avrebbe un'istanza del tipo di classe astratta come parametro di funzione, tale specializzazione verrebbe considerata non corretta. Non verrà aggiunto al set di funzioni candidate validi. In C++20 il controllo di un parametro del tipo di classe astratta non viene eseguito fino a quando non viene chiamata la funzione. L'effetto è che il codice usato per la compilazione non causerà un errore. Ecco un esempio:

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

In precedenza, la chiamata a compare avrebbe tentato di specializzare il modello compare di funzione usando un String argomento modello per T. Non è possibile generare una specializzazione valida, perché String è una classe astratta. L'unico candidato valido sarebbe stato compare(const Node&, const Node&). Tuttavia, in C++20 il controllo del tipo di classe astratta non viene eseguito fino a quando non viene chiamata la funzione. La specializzazione compare(String, String) viene quindi aggiunta al set di candidati validi e viene scelta come candidato migliore perché la conversione da const String& a String è una sequenza di conversione migliore rispetto alla conversione da const String& a const Node&.

In C++20, una possibile correzione per questo esempio consiste nell'usare i concetti; ovvero modificare la definizione di compare in:

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

In alternativa, se i concetti di C++ non sono disponibili, è possibile eseguire il fallback a 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);
}

Supporto per P0960R3: consente l'inizializzazione di aggregazioni da un elenco racchiuso tra parentesi di valori

C++20 P0960R3 aggiunge il supporto per l'inizializzazione di un'aggregazione usando un elenco di inizializzatori tra parentesi. Ad esempio, il codice seguente è valido in C++20:

struct S {
    int i;
    int j;
};

S s(1, 2);

La maggior parte di questa funzionalità è additivi, ovvero il codice ora compila che non è stato compilato prima. Tuttavia, modifica il comportamento di std::is_constructible. In modalità C++17 questo static_assert errore ha esito negativo, ma in modalità C++20 ha esito positivo:

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

Se si usa questo tratto di tipo per il controllo della risoluzione dell'overload, può causare una modifica del comportamento tra C++17 e C++20.

Risoluzione dell'overload che coinvolge i modelli di funzione

In precedenza, il compilatore ha consentito la compilazione di codice in /permissive- che non deve essere compilato. L'effetto è stato, il compilatore ha chiamato la funzione errata che ha portato a una modifica del comportamento di runtime:

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

La chiamata a g usa un set di overload che contiene due funzioni, ::f e N::f. Poiché N::f è un modello di funzione, il compilatore deve considerare l'argomento della funzione come contesto non dedotto. Ciò significa che, in questo caso, la chiamata a g deve avere esito negativo, perché il compilatore non può dedurre un tipo per il parametro Tdi modello . Sfortunatamente, il compilatore non ha eliminato il fatto che aveva già deciso che ::f era una buona corrispondenza per la chiamata di funzione. Anziché generare un errore, il compilatore genera codice da chiamare g usando ::f come argomento.

Dato che in molti casi l'uso ::f dell'argomento della funzione è quello previsto dall'utente, viene generato un errore solo se il codice viene compilato con /permissive-.

Migrazione da /await a coroutine C++20

Le coroutine standard C++20 sono ora attivate per impostazione predefinita in /std:c++20 e /std:c++latest. Differiscono dalle coroutine TS e dal supporto sotto l'opzione /await . La migrazione da /await a coroutine standard potrebbe richiedere alcune modifiche all'origine.

Parole chiave non standard

Le parole chiave precedenti await e yield non sono supportate in modalità C++20. Il codice deve invece usare co_await e co_yield . La modalità Standard non consente anche l'uso di return in una coroutine. Ogni return in una coroutine deve usare co_return.

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

Tipi di initial_suspend/final_suspend

In /awaitle funzioni di promessa iniziale e di sospensione possono essere dichiarate come restituite bool. Questo comportamento non è standard. In C++20, queste funzioni devono restituire un tipo di classe awaitable, spesso uno dei tipi awaitable semplici: std::suspend_always se la funzione ha restituito in precedenza o std::suspend_never se ha restituito truefalse.

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

Tipo di yield_value

In C++20 la funzione promise yield_value deve restituire un tipo awaitable. In /await modalità, la yield_value funzione è stata autorizzata a restituire voide verrebbe sempre sospesa. Tali funzioni possono essere sostituite con una funzione che restituisce 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{}; }
};

Funzione di gestione delle eccezioni

/await supporta un tipo promise senza alcuna funzione di gestione delle eccezioni o una funzione di gestione delle eccezioni denominata set_exception che accetta un oggetto std::exception_ptr. In C++20 il tipo promise deve avere una funzione denominata unhandled_exception che non accetta argomenti. L'oggetto eccezione può essere ottenuto da std::current_exception , se necessario.

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

Tipi restituiti dedotti di coroutine non supportate

C++20 non supporta le coroutine con un tipo restituito che include un tipo segnaposto, autoad esempio . I tipi restituiti di coroutine devono essere dichiarati in modo esplicito. In /awaitquesti tipi dedotti implicano sempre un tipo sperimentale e richiedono l'inclusione di un'intestazione che definisce il tipo richiesto: uno di std::experimental::task<T>, std::experimental::generator<T>o 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;
};

Tipo restituito di return_value

Il tipo restituito della funzione promise return_value deve essere void. In /await modalità il tipo restituito può essere qualsiasi elemento e viene ignorato. Questa diagnostica consente di rilevare errori sottili, ad esempio quando l'autore presuppone erroneamente che il valore restituito di return_value venga restituito a un chiamante.

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

Comportamento di conversione dell'oggetto restituito

Se il tipo restituito dichiarato di una coroutine non corrisponde al tipo restituito della funzione promise get_return_object , l'oggetto restituito da get_return_object viene convertito nel tipo restituito della coroutine. In /awaitquesta conversione viene eseguita in anticipo, prima che il corpo della coroutine abbia la possibilità di eseguire. In /std:c++20 o /std:c++latest, questa conversione viene eseguita quando il valore viene restituito al chiamante. Consente alle coroutine che non sospendono al punto di sospensione iniziale di utilizzare l'oggetto restituito dall'interno get_return_object del corpo della coroutine.

Parametri di promessa coroutine

In C++20 il compilatore tenta di passare i parametri coroutine (se presenti) a un costruttore del tipo promise. In caso di errore, viene eseguito un nuovo tentativo con un costruttore predefinito. In /await modalità è stato usato solo il costruttore predefinito. Questa modifica può causare una differenza di comportamento se la promessa ha più costruttori. In alternativa, se è presente una conversione da un parametro di coroutine al tipo promise.

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- e I moduli C++20 sono attivati per impostazione predefinita in /std:c++20

Il supporto dei moduli C++20 è attivato per impostazione predefinita in /std:c++20 e /std:c++latest. Per altre informazioni su questa modifica e sugli scenari in cui module e import vengono considerati in modo condizionale come parole chiave, vedere Supporto dei moduli C++20 standard con MSVC in Visual Studio 2019 versione 16.8.

Come prerequisito per il supporto dei moduli, permissive- è ora abilitato quando /std:c++20 viene specificato o /std:c++latest . Per ulteriori informazioni, vedere /permissive-.

Per il codice compilato /std:c++latest in precedenza in e richiede comportamenti del compilatore non conformi, /permissive può essere specificato per disattivare la modalità di conformità rigorosa nel compilatore. L'opzione del compilatore deve essere visualizzata dopo /std:c++latest nell'elenco di argomenti della riga di comando. Tuttavia, /permissive genera un errore se viene rilevato l'utilizzo dei moduli:

errore C1214: I moduli sono in conflitto con il comportamento non standard richiesto tramite 'option'

I valori più comuni per l'opzione sono:

Opzione Descrizione
/Zc:twoPhase- La ricerca dei nomi in due fasi è necessaria per i moduli C++20 e implicita da /permissive-.
/Zc:hiddenFriend- Le regole di ricerca dei nomi friend nascoste standard sono necessarie per i moduli C++20 e implicite in /permissive-.
/Zc:lambda- L'elaborazione lambda standard è necessaria per i moduli C++20 ed è implicita in /std:c++20 modalità o versioni successive.
/Zc:preprocessor- Il preprocessore conforme è necessario solo per l'utilizzo e la creazione dell'unità di intestazione C++20. I moduli denominati non richiedono questa opzione.

L'opzione /experimental:module è comunque necessaria per usare i std.* moduli forniti con Visual Studio, perché non sono ancora standardizzati.

L'opzione /experimental:module implica /Zc:twoPhaseanche , /Zc:lambdae /Zc:hiddenFriend. In precedenza, il codice compilato con i moduli può talvolta essere compilato con /Zc:twoPhase- se il modulo è stato utilizzato solo. Questo comportamento non è più supportato.

Miglioramenti della conformità in Visual Studio 2019 versione 16.9

Inizializzazione della copia temporanea in riferimento all'inizializzazione diretta

Il problema del gruppo di lavoro principale CWG 2267 ha affrontato un'incoerenza tra un elenco di inizializzatori racchiusi tra parentesi e un elenco di inizializzatori con parentesi graffe. La risoluzione armonizza le due forme.

Visual Studio 2019 versione 16.9 implementa il comportamento modificato in tutte le /std modalità del compilatore. Tuttavia, poiché si tratta di una modifica che causa un'interruzione di origine, è supportata solo se il codice viene compilato usando /permissive-.

Questo esempio illustra la modifica del comportamento:

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
}

Caratteristiche del distruttore e oggetti secondari potenzialmente costruiti

Il problema del gruppo di lavoro principale CWG 2336 riguarda un'omissione delle specifiche di eccezione implicite dei distruttori nelle classi con classi di base virtuali. L'omissione significava un distruttore in una classe derivata potrebbe avere una specifica di eccezione più debole rispetto a una classe base, se tale base era astratta e aveva una virtual base.

Visual Studio 2019 versione 16.9 implementa il comportamento modificato in tutte le /std modalità del compilatore.

Questo esempio mostra come è cambiata l'interpretazione:

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

Prima di questa modifica, il distruttore definito in modo implicito per B era noexcept, perché vengono considerati solo oggetti secondari potenzialmente costruiti. E la classe V base non è un sottooggetto potenzialmente costruito, perché è una virtual base ed B è astratta. Tuttavia, la classe V base è un sottooggetto potenzialmente costruito della classe De quindi D::~D viene determinato come noexcept(false), portando a una classe derivata con una specifica di eccezione più debole rispetto alla relativa base. Questa interpretazione non è sicura. Può causare un comportamento di runtime errato se un'eccezione viene generata da un distruttore di una classe derivata da B.

Con questa modifica, un distruttore genera potenzialmente anche un'eccezione se dispone di un distruttore virtuale e qualsiasi classe di base virtuale ha un distruttore potenzialmente generante.

Tipi simili e associazione di riferimento

Il problema del gruppo di lavoro principale CWG 2352 riguarda un'incoerenza tra le regole di associazione di riferimento e le modifiche alla somiglianza del tipo. L'incoerenza è stata introdotta nelle precedenti segnalazioni difetti (ad esempio CWG 330). Questa operazione ha interessato Visual Studio 2019 versioni da 16.0 a 16.8.

Con questa modifica, a partire da Visual Studio 2019 versione 16.9, il codice che in precedenza associava un riferimento a un oggetto temporaneo in Visual Studio 2019 versione 16.0 alla versione 16.8 ora può essere associato direttamente quando i tipi coinvolti differiscono solo per i qualificatori cv.

Visual Studio 2019 versione 16.9 implementa il comportamento modificato in tutte le /std modalità del compilatore. Si tratta potenzialmente di una modifica che causa un'interruzione.

Per una modifica correlata, vedere Riferimenti ai tipi con qualificatori cv non corrispondenti.

Questo esempio mostra il comportamento modificato:

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
}

L'aggiornamento può modificare il comportamento del programma basato su un temporaneo introdotto:

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 e /Zc:twoPhase- modifica del comportamento delle opzioni

In genere, le opzioni del compilatore MSVC funzionano sul principio che l'ultimo visto vince. Sfortunatamente, non è stato il caso con le /Zc:twoPhase opzioni e /Zc:twoPhase- . Queste opzioni erano "permanenti", quindi le opzioni successive non potevano eseguirne l'override. Ad esempio:

cl /Zc:twoPhase /permissive a.cpp

In questo caso, la prima /Zc:twoPhase opzione abilita la ricerca rigorosa dei nomi in due fasi. La seconda opzione è destinata a disabilitare la modalità di conformità rigorosa (è l'opposto di /permissive-), ma non ha disabilitato /Zc:twoPhase.

Visual Studio 2019 versione 16.9 modifica questo comportamento in tutte le /std modalità del compilatore. /Zc:twoPhase e /Zc:twoPhase- non sono più "permanenti", e le opzioni successive possono eseguirne l'override.

Identificatori noexcept espliciti nei modelli di distruttore

Il compilatore ha accettato in precedenza un modello di distruttore dichiarato con una specifica di eccezione non generata, ma definita senza un identificatore noexcept-specifier esplicito. La specifica di eccezione implicita di un distruttore dipende dalle proprietà della classe , ovvero le proprietà che potrebbero non essere note al punto di definizione di un modello. Lo standard C++ richiede anche questo comportamento: se un distruttore viene dichiarato senza un identificatore noexcept-, ha una specifica di eccezione implicita e nessun'altra dichiarazione della funzione può avere un identificatore noexcept.

Visual Studio 2019 versione 16.9 modifica il comportamento conforme in tutte le /std modalità del compilatore.

Questo esempio mostra la modifica del comportamento del compilatore:

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

Riscrivere le espressioni in C++20

A partire da Visual Studio 2019 versione 16.2, in /std:c++latest, il compilatore ha accettato codice simile all'esempio seguente:

#include <compare>

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

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

Tuttavia, il compilatore non richiama la funzione di confronto che l'autore potrebbe aspettarsi. Il codice precedente deve essere riscritto a < b come (a <=> b) < 0. Al contrario, il compilatore ha usato la operator bool() funzione di conversione definita dall'utente e ha confrontato bool(a) < bool(b). In Visual Studio 2019 versione 16.9 e successive il compilatore riscrive l'espressione usando l'espressione dell'operatore spaceship previsto.

Modifica dell'origine

L'applicazione corretta delle conversioni alle espressioni riscritte ha un altro effetto: il compilatore diagnosticare correttamente le ambiguità dai tentativi di riscrivere l'espressione. Si consideri questo esempio:

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

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

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

In C++17 questo codice verrà accettato a causa della conversione derivata da base di Derived sul lato destro dell'espressione. In C++20 viene aggiunto anche il candidato dell'espressione sintetizzata: Derived{} == Base{}. A causa delle regole nello standard su cui la funzione vince in base alle conversioni, si scopre che la scelta tra Base::operator== e Derived::operator== è indecidibile. Poiché le sequenze di conversione nelle due espressioni non sono migliori o peggiori l'una dell'altra, il codice di esempio genera un'ambiguità.

Per risolvere l'ambiguità, aggiungere un nuovo candidato che non sarà soggetto alle due sequenze di conversione:

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

Modifica che causa un'interruzione del runtime

A causa delle regole di riscrittura degli operatori in C++20, è possibile che la risoluzione dell'overload trovi un nuovo candidato che non troverebbe altrimenti in una modalità di linguaggio inferiore. E, il nuovo candidato può essere una corrispondenza migliore rispetto al candidato più vecchio. Si consideri questo esempio:

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

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

In C++17 l'unico candidato per ci == *this è const_iterator::operator==. Si tratta di una corrispondenza perché *this passa attraverso una conversione da derivata a base in const_iterator. In C++20 viene aggiunto un altro candidato riscritto: *this == ci, che richiama iterator::operator==. Questo candidato non richiede conversioni, quindi è una corrispondenza migliore di const_iterator::operator==. Il problema con il nuovo candidato è che è la funzione attualmente in fase di definizione, quindi la nuova semantica della funzione causa una definizione ricorsiva infinita di iterator::operator==.

Per semplificare il codice come l'esempio, il compilatore implementa un nuovo avviso:

$ 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

Per correggere il codice, usare in modo esplicito la conversione da usare:

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

Miglioramenti della conformità in Visual Studio 2019 versione 16.10

Overload errato scelto per l'inizializzazione della copia della classe

Dato questo codice di esempio:

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

Le versioni precedenti del compilatore convertivano erroneamente l'argomento di f da un tipo C a un A utilizzando il costruttore di conversione basato su modelli di A. Standard C++ richiede invece l'uso dell'operatore B::operator A di conversione. In Visual Studio 2019 versione 16.10 e successive il comportamento di risoluzione dell'overload viene modificato per usare l'overload corretto.

Questa modifica può anche correggere l'overload scelto in altre situazioni:

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

Analisi errata dei valori letterali a virgola mobile

In Visual Studio 2019 versione 16.10 e successive, i valori letterali a virgola mobile vengono analizzati in base al tipo effettivo. Le versioni precedenti del compilatore analizzavano sempre un valore letterale a virgola mobile come se fosse di tipo double e quindi convertiva il risultato nel tipo effettivo. Questo comportamento potrebbe causare un arrotondamento non corretto e il rifiuto di valori validi:

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

Punto di dichiarazione non corretto

Le versioni precedenti del compilatore non sono riuscite a compilare codice auto-referenziale come nell'esempio seguente:

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

    int value() const;
};

S s(4, &s);

Il compilatore non dichiara la variabile s finché non analizza l'intera dichiarazione, inclusi gli argomenti del costruttore. La ricerca di nell'elenco di s argomenti del costruttore avrà esito negativo. In Visual Studio 2019 versione 16.10 e successive questo esempio viene compilato correttamente.

Sfortunatamente, questa modifica può interrompere il codice esistente, come in questo esempio:

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

Nelle versioni precedenti del compilatore, quando cerca s negli argomenti del costruttore la dichiarazione "inner" di s, trova la dichiarazione precedente ("outer" s) e il codice viene compilato. A partire dalla versione 16.10, il compilatore genera invece l'avviso C4700 . Il compilatore dichiara ora "inner" s prima di analizzare gli argomenti del costruttore. Quindi, la s ricerca trova l'oggetto "interno", sche non è ancora stato inizializzato.

Membro esplicitamente specializzato di un modello di classe

Le versioni precedenti del compilatore contrassegnavano erroneamente una specializzazione esplicita di un membro modello di classe come inline se fosse stata definita anche nel modello primario. Questo comportamento significa che il compilatore a volte rifiuta il codice conforme. In Visual Studio 2019 versione 16.10 e successive, una specializzazione esplicita non viene più contrassegnata in modo implicito come inline in /permissive- modalità. Si consideri questo esempio:

s.hFile di origine :

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

s.cppFile di origine :

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

main.cppFile di origine :

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

int main()
{
}

Per risolvere l'errore del linker nell'esempio precedente, aggiungere inline in modo esplicito a S<int>::f:

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

Deduzione del nome del tipo restituito mangling

In Visual Studio 2019 versione 16.10 e successive il compilatore ha modificato il modo in cui genera nomi mangled per le funzioni che hanno dedotto tipi restituiti. Si considerino ad esempio queste funzioni:

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

Le versioni precedenti del compilatore generano questi nomi per il linker:

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

Sorprendentemente, il tipo restituito verrebbe omesso a g causa di un altro comportamento semantico causato dall'espressione lambda locale nel corpo della funzione. Questa incoerenza ha reso difficile implementare funzioni esportate con un tipo restituito dedotto: l'interfaccia del modulo richiede informazioni sulla compilazione del corpo di una funzione. È necessario che le informazioni producano una funzione sul lato importazione che possa collegarsi correttamente alla definizione.

Il compilatore ora omette il tipo restituito di una funzione di tipo restituito dedotto. Questo comportamento è coerente con altre implementazioni principali. Esiste un'eccezione per i modelli di funzione: questa versione del compilatore introduce un nuovo comportamento dei nomi mangled per i modelli di funzione con un tipo restituito dedotto:

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;

I nomi mangled per auto e decltype(auto) ora vengono visualizzati nel file binario, non nel tipo restituito dedotto:

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)

Le versioni precedenti del compilatore includono il tipo restituito dedotto come parte della firma. Quando il compilatore include il tipo restituito nel nome mangled, potrebbe causare problemi del linker. Alcuni scenari altrimenti ben formati diventano ambigui per il linker.

Il nuovo comportamento del compilatore può produrre una modifica di rilievo binaria. Si consideri questo esempio:

a.cppFile di origine :

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

main.cppFile di origine :

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

Nelle versioni precedenti alla versione 16.10, il compilatore ha prodotto un nome simile a auto f()int f(), anche se sono funzioni semanticamente distinte. Ciò significa che l'esempio verrà compilato. Per risolvere il problema, non basarsi sulla auto definizione originale di f. Scriverlo invece come int f(). Poiché le funzioni che hanno dedotto i tipi restituiti vengono sempre compilate, le implicazioni ABI vengono ridotte a icona.

Avviso per l'attributo ignorato nodiscard

Le versioni precedenti del compilatore ignorano automaticamente determinati usi di un nodiscard attributo. Ignoravano l'attributo se si trovava in una posizione sintattica che non si applicava alla funzione o alla classe dichiarata. Ad esempio:

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

In Visual Studio 2019 versione 16.10 e successive il compilatore genera invece l'avviso di livello 4 C5240:

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

Per risolvere questo problema, spostare l'attributo nella posizione sintattica corretta:

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

Avviso per include le direttive con nomi di intestazione di sistema nel modulo purview

In Visual Studio 2019 versione 16.10 e successive il compilatore genera un avviso per evitare un errore comune di creazione dell'interfaccia del modulo. Se si include un'intestazione di libreria standard dopo un'istruzione export module , il compilatore genera l'avviso C5244. Ecco un esempio:

export module m;
#include <vector>

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

Probabilmente lo sviluppatore non intendeva m possedere il contenuto di <vector>. Il compilatore genera ora un avviso per individuare e risolvere il problema:

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

Per risolvere questo problema, spostarsi #include <vector> prima di export module m;:

#include <vector>
export module m;

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

Avviso per le funzioni di collegamento interno inutilizzate

In Visual Studio 2019 versione 16.10 e successive, il compilatore avvisa in più situazioni in cui è stata rimossa una funzione senza riferimenti con collegamento interno. Le versioni precedenti del compilatore generano l'avviso C4505 per il codice seguente:

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

Il compilatore ora avvisa anche sulle funzioni non referenziate auto e sulle funzioni non referenziate negli spazi dei nomi anonimi. Genera un avviso off-by-default C5245 per entrambe le funzioni seguenti:

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

Avviso sull'elisione parentesi graffa

In Visual Studio 2019 versione 16.10 e successive il compilatore avvisa sugli elenchi di inizializzazione che non usano parentesi graffe per gli oggetti secondari. Il compilatore genera un avviso disattivato per impostazione predefinita C5246.

Ecco un esempio:

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

Per risolvere questo problema, eseguire il wrapping dell'inizializzazione del sottooggetto tra parentesi graffe:

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

Rilevare correttamente se un const oggetto non è inizializzato

In Visual Studio 2019 versione 16.10 e successive il compilatore genera ora l'errore C2737 quando si tenta di definire un const oggetto che non è completamente inizializzato:

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

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

Le versioni precedenti del compilatore hanno consentito la compilazione di questo codice, anche se S::i non è inizializzato.

Per risolvere questo problema, inizializzare tutti i membri prima di creare un'istanza const di un oggetto :

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

Miglioramenti della conformità in Visual Studio 2019 versione 16.11

/std:c++20 modalità del compilatore

In Visual Studio 2019 versione 16.11 e successive il compilatore supporta ora la /std:c++20 modalità del compilatore. In precedenza, le funzionalità di C++20 erano disponibili solo in /std:c++latest modalità in Visual Studio 2019. Le funzionalità di C++20 che in origine richiedevano /std:c++latest la modalità funzionano in /std:c++20 modalità o versioni successive nelle versioni più recenti di Visual Studio.

Vedi anche

Conformità del linguaggio Microsoft C/C++