Ulepszenia zgodności języka C++, zmiany zachowania i poprawki błędów w programie Visual Studio 2019
Język Microsoft C/C++ w programie Visual Studio (MSVC) wprowadza ulepszenia zgodności i poprawki błędów w każdej wersji. W tym artykule wymieniono ulepszenia według wersji głównej, a następnie według wersji. Aby przejść bezpośrednio do zmian dla określonej wersji, użyj poniższej listy w tym artykule.
Ten dokument zawiera listę zmian w programie Visual Studio 2019. Aby zapoznać się ze zmianami w programie Visual Studio 2022, zobacz Ulepszenia zgodności języka C++ w programie Visual Studio 2022. Aby uzyskać informacje o zmianach w programie Visual Studio 2017, zobacz Ulepszenia zgodności języka C++ w programie Visual Studio 2017. Aby uzyskać pełną listę poprzednich ulepszeń zgodności, zobacz Visual C++ What's New 2003–2015 (Co nowego w programie Visual C++ do 2015).
Ulepszenia zgodności w programie Visual Studio 2019 RTW (wersja 16.0)
Program Visual Studio 2019 RTW zawiera następujące ulepszenia zgodności, poprawki błędów i zmiany zachowania w kompilatorze języka Microsoft C++.
Uwaga
Funkcje języka C++20 były dostępne tylko w /std:c++latest
trybie w programie Visual Studio 2019 do momentu zakończenia implementacji języka C++20. Program Visual Studio 2019 w wersji 16.11 wprowadza tryb kompilatora /std:c++20
. W tym artykule funkcje, które pierwotnie wymagały /std:c++latest
trybu, działają teraz w /std:c++20
trybie lub nowszym w najnowszych wersjach programu Visual Studio. Zaktualizowaliśmy dokumentację, aby wspomnieć , /std:c++20
mimo że ta opcja nie była dostępna, gdy funkcje zostały wydane po raz pierwszy.
Ulepszona obsługa modułów dla szablonów i wykrywania błędów
Moduły są teraz oficjalnie w standardzie C++20. Ulepszona obsługa została dodana w programie Visual Studio 2017 w wersji 15.9. Aby uzyskać więcej informacji, zobacz Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (Lepsza obsługa szablonów i wykrywanie błędów w modułach języka C++ z programem MSVC 2017 w wersji 15.9).
Zmodyfikowana specyfikacja typu agregacji
Specyfikacja typu agregującego została zmieniona w języku C++20 (zobacz Zakaz agregacji za pomocą konstruktorów zadeklarowanych przez użytkownika). W programie Visual Studio 2019 w obszarze /std:c++latest
(lub /std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej) klasa z dowolnym konstruktorem zadeklarowanym przez użytkownika (na przykład z zadeklarowanym = default
konstruktorem lub = delete
) nie jest agregacja. Wcześniej tylko konstruktory dostarczane przez użytkownika zdyskwalifikują klasę z bycia agregacji. Ta zmiana nakłada więcej ograniczeń na sposób inicjowania takich typów.
Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale zgłasza błędy C2280 i C2440 w programie Visual Studio 2019 w obszarze /std:c++20
lub /std:c++latest
:
struct A
{
A() = delete; // user-declared ctor
};
struct B
{
B() = default; // user-declared ctor
int i = 0;
};
A a{}; // ill-formed in C++20, previously well-formed
B b = { 1 }; // ill-formed in C++20, previously well-formed
Częściowa obsługa dla operator <=>
P0515R3 C++20 wprowadza <=>
trójkierunkowy operator porównania, znany również jako "operator statku kosmicznego". Program Visual Studio 2019 w wersji 16.0 w /std:c++latest
trybie wprowadza częściową obsługę operatora przez zgłaszanie błędów składni, które są teraz niedozwolone. Na przykład następujący kod kompiluje się bez błędów w programie Visual Studio 2017, ale zgłasza wiele błędów w programie Visual Studio 2019 w programie /std:c++20
Lub /std:c++latest
:
struct S
{
bool operator<=(const S&) const { return true; }
};
template <bool (S::*)(const S&) const>
struct U { };
int main(int argc, char** argv)
{
U<&S::operator<=> u; // In Visual Studio 2019 raises C2039, 2065, 2146.
}
Aby uniknąć błędów, wstaw spację w linii obrażającej przed końcowym nawiasem kątowym: U<&S::operator<= > u;
.
Odwołania do typów z niezgodnymi kwalifikatorami cv
Uwaga
Ta zmiana dotyczy tylko programu Visual Studio 2019 w wersji od 16.0 do 16.8. Został przywrócony, począwszy od programu Visual Studio 2019 w wersji 16.9
Wcześniej MSVC zezwolił na bezpośrednie powiązanie odwołania z typu z niezgodnymi kwalifikatorami cv poniżej najwyższego poziomu. To powiązanie może umożliwić modyfikację rzekomo const danych, do których odwołuje się odwołanie.
Kompilator dla programu Visual Studio 2019 w wersji od 16.0 do 16.8 zamiast tego tworzy tymczasowy element, zgodnie z wymaganiami standardu w tym czasie. Później standardowo zmienił się, wprowadzając poprzednie zachowanie programu Visual Studio 2017 i jego wcześniejsze wersje oraz zachowanie programu Visual Studio 2019 w wersji 16.0 do 16.8. W związku z tym ta zmiana została przywrócona, począwszy od programu Visual Studio 2019 w wersji 16.9.
Zobacz Podobne typy i powiązanie odwołania, aby uzyskać powiązaną zmianę.
Na przykład w programie Visual Studio 2017 poniższy kod kompiluje się bez ostrzeżeń. W programie Visual Studio 2019 w wersji od 16.0 do 16.8 kompilator zgłasza ostrzeżenie C4172. Począwszy od programu Visual Studio 2019 w wersji 16.9, kod po raz kolejny kompiluje się bez ostrzeżeń:
struct X
{
const void* const& PData() const
{
return _pv;
}
void* _pv;
};
int main()
{
X x;
auto p = x.PData(); // C4172 <func:#1 "?PData@X@@QBEABQBXXZ"> returning address of local variable or temporary
}
reinterpret_cast
z przeciążonej funkcji
Argument to reinterpret_cast
nie jest jednym z kontekstów, w których dozwolony jest adres przeciążonej funkcji. Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 zgłasza błąd C2440:
int f(int) { return 1; }
int f(float) { return .1f; }
using fp = int(*)(int);
int main()
{
fp r = reinterpret_cast<fp>(&f); // C2440: cannot convert from 'overloaded-function' to 'fp'
}
Aby uniknąć tego błędu, użyj dozwolonego rzutowania dla tego scenariusza:
int f(int);
int f(float);
using fp = int(*)(int);
int main()
{
fp r = static_cast<fp>(&f); // or just &f;
}
Zamknięcia lambda
W języku C++14 typy zamknięcia lambda nie są literałami. Podstawową konsekwencją tej reguły jest to, że lambda może nie być przypisana do zmiennej constexpr
. Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 zgłasza błąd C2127:
int main()
{
constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}
Aby uniknąć błędu, usuń constexpr
kwalifikator lub zmień tryb zgodności na /std:c++17
lub nowszy.
std::create_directory
kody błędów
Zaimplementowano język P1164 z języka C++20 bezwarunkowo. Te zmiany std::create_directory
umożliwiają sprawdzenie, czy obiekt docelowy był już katalogiem po awarii. Wcześniej wszystkie błędy typu ERROR_ALREADY_EXISTS zostały przekształcone w kody success-but-directory-not-created.
operator<<(std::ostream, nullptr_t)
Na LWG 2221 dodano operator<<(std::ostream, nullptr_t)
zapis nullptr
do strumieni.
Bardziej równoległe algorytmy
Nowe równoległe wersje is_sorted
, , is_sorted_until
, set_intersection
is_partitioned
set_difference
, , is_heap
i is_heap_until
.
Poprawki w inicjalizacji niepodzielnej
P0883 "Naprawianie inicjowania niepodzielnego" zmienia std::atomic
się w celu zainicjowania zawartej T
zamiast inicjowania domyślnego. Poprawka jest włączona w przypadku korzystania z języka Clang/LLVM z biblioteką standardową firmy Microsoft. Obecnie jest on wyłączony dla kompilatora języka Microsoft C++, ponieważ jest to obejście problemu podczas constexpr
przetwarzania.
remove_cvref
i remove_cvref_t
remove_cvref
Zaimplementowano cechy typów i remove_cvref_t
z P0550. Usuwają one odwołania i kwalifikacje cv z typu bez rozkładania funkcji i tablic do wskaźników (w przeciwieństwie std::decay
do i std::decay_t
).
Makra testu funkcji
P0941R2 — makra testów funkcji są kompletne z obsługą programu __has_cpp_attribute
. Makra testów funkcji są obsługiwane we wszystkich trybach standardowych.
Zakaz agregacji za pomocą konstruktorów zadeklarowanych przez użytkownika
C++20 P1008R1 — kończy się zakaz agregacji z konstruktorami zadeklarowanymi przez użytkownika.
reinterpret_cast
constexpr
w funkcji
Element A reinterpret_cast
jest niedozwolony constexpr
w funkcji. Kompilator języka Microsoft C++ zostałby wcześniej odrzucony reinterpret_cast
tylko wtedy, gdy był używany w constexpr
kontekście. W programie Visual Studio 2019 we wszystkich trybach standardów językowych kompilator poprawnie diagnozuje reinterpret_cast
element w definicji constexpr
funkcji. Poniższy kod generuje teraz kod C3615:
long long i = 0;
constexpr void f() {
int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}
Aby uniknąć błędu, usuń constexpr
modyfikator z deklaracji funkcji.
Poprawna diagnostyka konstruktora zakresu basic_string
W programie Visual Studio 2019 basic_string
konstruktor zakresu nie pomija już diagnostyki kompilatora za pomocą polecenia static_cast
. Poniższy kod kompiluje się bez ostrzeżeń w programie Visual Studio 2017, pomimo możliwej utraty danych z wchar_t
do char
podczas inicjowania out
programu :
std::wstring ws = /* . . . */;
std::string out(ws.begin(), ws.end()); // VS2019 C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data.
Program Visual Studio 2019 poprawnie zgłasza ostrzeżenie C4244. Aby uniknąć ostrzeżenia, możesz zainicjować element std::string
, jak pokazano w tym przykładzie:
std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
out.push_back(static_cast<char>(ch));
}
Niepoprawne wywołania do +=
i -=
w obszarze /clr
lub /ZW
są teraz prawidłowo wykrywane
Wprowadzono usterkę w programie Visual Studio 2017, która spowodowała, że kompilator dyskretnie ignorował błędy i nie wygenerował żadnego kodu dla nieprawidłowych wywołań metody +=
i -=
w obszarze /clr
lub /ZW
. Poniższy kod kompiluje się bez błędów w programie Visual Studio 2017, ale w programie Visual Studio 2019 poprawnie zgłasza błąd C2845:
public enum class E { e };
void f(System::String ^s)
{
s += E::e; // in VS2019 C2845: 'System::String ^': pointer arithmetic not allowed on this type.
}
Aby uniknąć błędu w tym przykładzie, użyj +=
operatora z ToString()
metodą : s += E::e.ToString();
.
Inicjatory dla wbudowanych składowych danych statycznych
Nieprawidłowe dostępy do składowych w ramach inline
inicjatorów i static constexpr
są teraz poprawnie wykrywane. Poniższy przykład kompiluje bez błędu w programie Visual Studio 2017, ale w programie Visual Studio 2019 /std:c++17
w trybie lub nowszym zgłasza błąd C2248:
struct X
{
private:
static inline const int c = 1000;
};
struct Y : X
{
static inline int d = c; // VS2019 C2248: cannot access private member declared in class 'X'.
};
Aby uniknąć błędu, zadeklaruj element członkowski X::c
jako chroniony:
struct X
{
protected:
static inline const int c = 1000;
};
Przywrócono C4800
MSVC używane do ostrzeżenia o wydajności C4800 o niejawnej konwersji na bool
. To było zbyt hałaśliwe i nie można go pominąć, co doprowadziło nas do usunięcia go w programie Visual Studio 2017. Jednak w całym cyklu życia programu Visual Studio 2017 uzyskaliśmy wiele opinii na temat przydatnych przypadków rozwiązywania problemów. Przywracamy program Visual Studio 2019 starannie dostosowany C4800 wraz z objaśnieniami C4165. Oba te ostrzeżenia są łatwe do pomijania: przy użyciu jawnego rzutowania lub w porównaniu do 0 odpowiedniego typu. C4800 jest ostrzeżeniem poza domyślnym poziomem 4, a C4165 jest domyślnie ostrzeżeniem poziomu 3 poza. Oba są wykrywalne przy użyciu opcji kompilatora /Wall
.
Poniższy przykład zgłasza C4800 i C4165 w obszarze /Wall
:
bool test(IUnknown* p)
{
bool valid = p; // warning C4800: Implicit conversion from 'IUnknown*' to bool. Possible information loss
IDispatch* d = nullptr;
HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
return hr; // warning C4165: 'HRESULT' is being converted to 'bool'; are you sure this is what you want?
}
Aby uniknąć ostrzeżeń w poprzednim przykładzie, możesz napisać kod w następujący sposób:
bool test(IUnknown* p)
{
bool valid = p != nullptr; // OK
IDispatch* d = nullptr;
HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
return SUCCEEDED(hr); // OK
}
Funkcja składowa klasy lokalnej nie ma treści
W programie Visual Studio 2017 ostrzeżenie C4822 jest zgłaszane tylko wtedy, gdy opcja /w14822
kompilatora jest jawnie ustawiona. Nie jest on wyświetlany za pomocą polecenia /Wall
. W programie Visual Studio 2019 C4822 jest domyślnym ostrzeżeniem, które sprawia, że można /Wall
je odnaleźć bez konieczności jawnego ustawiania/w14822
.
void example()
{
struct A
{
int boo(); // warning C4822: Local class member function doesn't have a body
};
}
Elementy szablonu funkcji zawierające instrukcje if constexpr
W programie Visual Studio 2019 w obszarze /std:c++20
lub /std:c++latest
funkcje szablonu zawierające instrukcje mają if constexpr
włączone dodatkowe kontrole związane z analizowaniem. Na przykład w programie Visual Studio 2017 poniższy kod generuje kod C7510 tylko wtedy, gdy opcja jest ustawiona /permissive-
. W programie Visual Studio 2019 ten sam kod zgłasza błędy nawet wtedy, gdy opcja jest ustawiona /permissive
:
// C7510.cpp
// compile using: cl /EHsc /W4 /permissive /std:c++latest C7510.cpp
#include <iostream>
template <typename T>
int f()
{
T::Type a; // error C7510: 'Type': use of dependent type name must be prefixed with 'typename'
// To fix the error, add the 'typename' keyword. Use this declaration instead:
// typename T::Type a;
if constexpr (a.val)
{
return 1;
}
else
{
return 2;
}
}
struct X
{
using Type = X;
constexpr static int val = 1;
};
int main()
{
std::cout << f<X>() << "\n";
}
Aby uniknąć błędu, dodaj typename
słowo kluczowe do deklaracji a
: typename T::Type a;
.
Wbudowany kod zestawu nie jest obsługiwany w wyrażeniu lambda
Zespół microsoft C++ został niedawno poinformowany o problemie z zabezpieczeniami, w którym użycie asemblera wbudowanego w środowisku lambda może prowadzić do uszkodzenia (rejestru adresów zwrotnych ebp
) w czasie wykonywania. Złośliwy atakujący może skorzystać z tego scenariusza. Wbudowany asembler jest obsługiwany tylko w architekturze x86, a interakcja między wbudowanym asemblerem a resztą kompilatora jest słaba. Biorąc pod uwagę te fakty i charakter problemu, najbezpieczniejszym rozwiązaniem tego problemu było uniemożliwienie asemblera wbudowanego w wyrażeniu lambda.
Jedynym użyciem wbudowanego asemblera w wyrażeniu lambda, które znaleźliśmy "na wolności", było przechwycenie adresu zwrotnego. W tym scenariuszu można przechwycić adres zwrotny na wszystkich platformach, używając wbudowanego kompilatora _ReturnAddress()
.
Poniższy kod generuje kod C7553 w programie Visual Studio 2017 15.9 i nowszych wersjach programu Visual Studio:
#include <cstdio>
int f()
{
int y = 1724;
int x = 0xdeadbeef;
auto lambda = [&]
{
__asm { // C7553: inline assembler is not supported in a lambda
mov eax, x
mov y, eax
}
};
lambda();
return y;
}
Aby uniknąć błędu, przenieś kod zestawu do nazwanej funkcji, jak pokazano w poniższym przykładzie:
#include <cstdio>
void g(int& x, int& y)
{
__asm {
mov eax, x
mov y, eax
}
}
int f()
{
int y = 1724;
int x = 0xdeadbeef;
auto lambda = [&]
{
g(x, y);
};
lambda();
return y;
}
int main()
{
std::printf("%d\n", f());
}
Debugowanie iteratora i std::move_iterator
Funkcja debugowania iteratora została nauczona prawidłowego odpakowania std::move_iterator
. Na przykład std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*)
można teraz zaangażować szybką ścieżkę memcpy
.
Poprawki wymuszania <słów kluczowych xkeycheck.h>
Naprawiono wymuszanie biblioteki standardowej w <pliku xkeycheck.h> dla makr zastępujących słowo kluczowe. Biblioteka emituje teraz rzeczywiste słowo kluczowe problemu wykryte, a nie ogólny komunikat. Obsługuje również słowa kluczowe języka C++20 i pozwala uniknąć podstępowania funkcji IntelliSense, mówiąc, że słowa kluczowe losowe są makrami.
Typy alokatora nie są już przestarzałe
std::allocator<void>
, std::allocator::size_type
i std::allocator::difference_type
nie są już przestarzałe.
Poprawne ostrzeżenie dotyczące konwersji ciągów zawężających
Usunięto fałszywe static_cast
z std::string
tego nie zostało wywołane przez standard, i że przypadkowo pominięte ostrzeżenia C4244 zawężające. Próby wywołania std::string::string(const wchar_t*, const wchar_t*)
teraz prawidłowo emitują C4244 o zawężaniu wchar_t
do elementu char
.
Różne poprawki <poprawności systemu> plików
- Rozwiązano problem powodujący
std::filesystem::last_write_time
niepowodzenie podczas próby zmiany czasu ostatniego zapisu katalogu. - Konstruktor
std::filesystem::directory_entry
przechowuje teraz wynik niepowodzenia, zamiast zgłaszać wyjątek, gdy podano nieistniejącą ścieżkę docelową. - Wersja
std::filesystem::create_directory
2-parametru została zmieniona, aby wywołać wersję 1-parametru, ponieważ funkcja bazowaCreateDirectoryExW
będzie używanacopy_symlink
, gdyexisting_p
był to sylink. std::filesystem::directory_iterator
nie kończy się już niepowodzeniem, gdy zostanie znaleziony uszkodzony link syymlinku.std::filesystem::space
teraz akceptuje ścieżki względne.std::filesystem::path::lexically_relative
nie jest już mylony przez końcowe ukośniki, zgłaszane jako LWG 3096.CreateSymbolicLinkW
Obejśliśmy odrzucanie ścieżek za pomocą ukośników w elemeciestd::filesystem::create_symlink
.- Obejście funkcji trybu
delete
usuwania POSIX, która istniała w systemie Windows 10 LTSB 1609, ale nie mogła w rzeczywistości usunąć plików. - Konstruktory
std::boyer_moore_searcher
istd::boyer_moore_horspool_searcher
konstruktory kopii i operatory przypisania kopiowania teraz faktycznie kopiują elementy.
Algorytmy równoległe w systemie Windows 8 lub nowszym
Biblioteka algorytmów równoległych teraz prawidłowo używa prawdziwej WaitOnAddress
rodziny w systemie Windows 8 i nowszych, a nie zawsze przy użyciu systemu Windows 7 i wcześniejszych fałszywych wersji.
std::system_category::message()
Odstępu
std::system_category::message()
teraz przycina końcowe białe znaki z zwróconego komunikatu.
std::linear_congruential_engine
dzielenie przez zero
Naprawiono niektóre warunki, które spowodowałyby std::linear_congruential_engine
wyzwolenie dzielenia przez 0.
Poprawki dotyczące unwrapping iteratora
Niektóre maszyny iteracyjne rozpatkujące zostały po raz pierwszy uwidocznione na potrzeby integracji programisty-użytkownik w programie Visual Studio 2017 15.8. Został on opisany w artykule C++ Team Blog (Blog zespołu języka C++) Dotyczący funkcji i poprawek biblioteki STL w programie VS 2017 15.8. Te maszyny nie rozpakowuje już iteratorów pochodzących z iteratorów bibliotek standardowych. Na przykład użytkownik, który pochodzi z std::vector<int>::iterator
i próbuje dostosować zachowanie, teraz pobiera dostosowane zachowanie podczas wywoływania standardowych algorytmów biblioteki, a nie zachowania wskaźnika.
Nieurządkowana funkcja kontenera reserve
faktycznie rezerwuje elementy N zgodnie z opisem w lwg 2156.
Obsługa czasu
Wcześniej niektóre wartości czasu przekazane do biblioteki współbieżności przepełniły się, na przykład
condition_variable::wait_for(seconds::max())
. Teraz naprawiono, przepełnienia zmieniły zachowanie w pozornie losowym cyklu 29-dniowym (gdy uint32_t milisekund zaakceptowane przez bazowe interfejsy API Win32 przepełnione).<Nagłówek ctime> teraz poprawnie deklaruje
timespec
itimespec_get
w przestrzeni nazwstd
, a także deklaruje je w globalnej przestrzeni nazw.
Różne poprawki dla kontenerów
Wiele standardowych funkcji kontenera wewnętrznego biblioteki zostało wykonanych
private
w celu uzyskania ulepszonego środowiska funkcji IntelliSense. Więcej poprawek oznaczania elementów członkowskich zgodnieprivate
z oczekiwaniami w kolejnych wersjach programu MSVC.Rozwiązano problemy z poprawnością bezpieczeństwa wyjątków, które spowodowały uszkodzenie kontenerów opartych na węzłach, takich jak
list
,map
iunordered_map
, . Podczas operacji ponownegopropagate_on_container_copy_assignment
przypisania lubpropagate_on_container_move_assignment
zwolnilibyśmy węzeł sentinel kontenera ze starym alokatorem, wykonaj przypisanie POCCA/POCMA za pośrednictwem starego alokatora, a następnie spróbujemy uzyskać węzeł sentinel z nowego alokatora. Jeśli ta alokacja nie powiodła się, kontener został uszkodzony. Nie można go nawet zniszczyć, ponieważ posiadanie węzła sentinel jest niezmienną strukturą danych. Ten kod został naprawiony w celu utworzenia nowego węzła sentinel przy użyciu alokatora kontenera źródłowego przed zniszczeniem istniejącego węzła sentinel.Kontenery zostały naprawione tak, aby zawsze kopiować/przenosić/zamieniać alokatory zgodnie z
propagate_on_container_copy_assignment
propagate_on_container_move_assignment
, i , nawetpropagate_on_container_swap
dla alokatorów zadeklarowanychis_always_equal
.Dodano przeciążenia dla funkcji scalania kontenerów i wyodrębniania elementów członkowskich, które akceptują kontenery rvalue. Aby uzyskać więcej informacji, zobacz P0083 "Splicing Maps And Sets" (Splicing Maps And Sets)
std::basic_istream::read
\r\n
przetwarzanie =>\n
std::basic_istream::read
został naprawiony, aby nie zapisywać części dostarczonego buforu tymczasowo w ramach przetwarzania \r\n
\n
. Ta zmiana daje pewne korzyści z wydajności uzyskane w programie Visual Studio 2017 15.8 w przypadku operacji odczytu większych niż 4K. Jednak ulepszenia wydajności przed unikaniem trzech wywołań wirtualnych na znak są nadal obecne.
std::bitset
konstruktor
Konstruktor std::bitset
nie odczytuje już tych i zer w odwrotnej kolejności dla dużych zestawów bitów.
std::pair::operator=
regresja
Usunęliśmy regresję w operatorze std::pair
przypisania wprowadzonym podczas implementowania lwg 2729 "Brak SFINAE w " std::pair::operator=
;". Teraz poprawnie akceptuje typy konwertowane na std::pair
ponownie.
Konteksty niezwiązane z add_const_t
Usunęliśmy usterkę cech drobnego typu, w której add_const_t
i powiązane funkcje powinny być kontekstem nieudukowanym. Innymi słowy, add_const_t
powinien być aliasem dla typename add_const<T>::type
elementu , a nie const T
.
Ulepszenia zgodności w wersji 16.1
char8_t
P0482r6. Język C++20 dodaje nowy typ znaku używany do reprezentowania jednostek kodu UTF-8. u8
literały ciągu w języku C++20 mają typ const char8_t[N]
zamiast const char[N]
, co miało miejsce wcześniej. Podobne zmiany zostały zaproponowane dla standardu C w N2231. Sugestie dotyczące char8_t
korygowania zgodności z poprzednimi wersjami podano w wersji P1423r3. Kompilator języka Microsoft C++ dodaje obsługę char8_t
programu Visual Studio 2019 w wersji 16.1 podczas określania opcji kompilatora /Zc:char8_t
. Można go przywrócić do zachowania języka C++17 za pomocą polecenia /Zc:char8_t-
. Kompilator EDG, który obsługuje funkcję IntelliSense, nie obsługuje go jeszcze w programie Visual Studio 2019 w wersji 16.1. Mogą pojawić się fałszywe błędy tylko dla funkcji IntelliSense, które nie wpływają na rzeczywistą kompilację.
Przykład
const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20
std::type_identity
metafunction i std::identity
obiekt funkcji
P0887R1 type_identity. Przestarzałe std::identity
rozszerzenie szablonu klasy zostało usunięte i zastąpione metafunction języka C++20 std::type_identity
i std::identity
obiektem funkcji. Oba są dostępne tylko w obszarze /std:c++latest
(/std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej).
Poniższy przykład generuje ostrzeżenie o wycofaniu kodu C4996 dla std::identity
(zdefiniowane w type_traits>) w <programie Visual Studio 2017:
#include <type_traits>
using T = std::identity<int>::type;
T x, y = std::identity<T>{}(x);
int i = 42;
long j = std::identity<long>{}(i);
W poniższym przykładzie pokazano, jak używać nowego std::identity
(zdefiniowanego w <funkcjonalności>) wraz z nowym std::type_identity
elementem :
#include <type_traits>
#include <functional>
using T = std::type_identity<int>::type;
T x, y = std::identity{}(x);
int i = 42;
long j = static_cast<long>(i);
Sprawdzanie składni dla ogólnych wyrażeń lambd
Nowy procesor lambda umożliwia sprawdzanie składniowe w trybie zgodności w ogólnych wyrażeniach lambda w obszarze /std:c++latest
(/std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej) lub w innym trybie /Zc:lambda
językowym w programie Visual Studio 2019 w wersji 16.9 lub nowszej (wcześniej dostępnej jako /experimental:newLambdaProcessor
począwszy od programu Visual Studio 2019 w wersji 16.3).
Starszy procesor lambda kompiluje ten przykład bez ostrzeżeń, ale nowy procesor lambda generuje błąd C2760:
void f() {
auto a = [](auto arg) {
decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
};
}
W tym przykładzie przedstawiono poprawną składnię, która jest wymuszana przez kompilator:
void f() {
auto a = [](auto arg) {
typename decltype(arg)::Type t;
};
}
Wyszukiwanie zależne od argumentów dla wywołań funkcji
P0846R0 (C++20) Zwiększona możliwość znajdowania szablonów funkcji za pomocą wyszukiwania zależnego od argumentów dla wyrażeń wywołań funkcji z jawnymi argumentami szablonu. Wymaga /std:c++latest
(lub /std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej).
Wyznaczona inicjalizacja
P0329R4 (C++20) Wyznaczone inicjowanie umożliwia wybranie określonych elementów członkowskich w zagregowanej inicjalizacji Type t { .member = expr }
przy użyciu składni. Wymaga /std:c++latest
(lub /std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej).
Klasyfikacja konwersji wyliczenia na stały typ bazowy
Kompilator klasyfikuje teraz konwersje wyliczenia zgodnie z N4800 11.3.3.2 Klasyfikowanie niejawnych sekwencji konwersji (4.2):
- Konwersja, która promuje wyliczenie, którego typ bazowy jest stały dla jego typu bazowego, jest lepszy niż taki, który promuje promowany typ bazowy, jeśli te dwa są różne.
Ten ranking konwersji nie został poprawnie zaimplementowany przed programem Visual Studio 2019 w wersji 16.1. Zachowanie zgodne może spowodować zmianę zachowania rozwiązywania przeciążenia lub uwidocznienie niejednoznaczności, gdy wcześniej nie wykryto tego zachowania.
Ta zmiana zachowania kompilatora dotyczy wszystkich /std
trybów i jest zarówno zmianą powodującą niezgodność źródłową, jak i binarną.
W poniższym przykładzie pokazano, jak zmienia się zachowanie kompilatora w wersji 16.1 lub nowszej:
#include <type_traits>
enum E : unsigned char { e };
int f(unsigned int)
{
return 1;
}
int f(unsigned char)
{
return 2;
}
struct A {};
struct B : public A {};
int f(unsigned int, const B&)
{
return 3;
}
int f(unsigned char, const A&)
{
return 4;
}
int main()
{
// Calls f(unsigned char) in 16.1 and later. Called f(unsigned int) in earlier versions.
// The conversion from 'E' to the fixed underlying type 'unsigned char' is better than the
// conversion from 'E' to the promoted type 'unsigned int'.
f(e);
// Error C2666. This call is ambiguous, but previously called f(unsigned int, const B&).
f(e, B{});
}
Nowe i zaktualizowane funkcje biblioteki standardowej (C++20)
starts_with()
i dlabasic_string
ibasic_string_view
ends_with()
.contains()
dla kontenerów asocjacyjnych.remove()
,remove_if()
iunique()
dla obiektówlist
iforward_list
zwracają teraz obiektsize_type
.shift_left()
ishift_right()
dodane do algorytmu<>.
Ulepszenia zgodności w wersji 16.2
noexcept
constexpr
Funkcje
constexpr
funkcje nie są już traktowane noexcept
domyślnie, gdy są używane w wyrażeniu stałym. Ta zmiana zachowania wynika z rozwiązania podstawowej grupy roboczej (CWG) CWG 1351 i jest włączona w systemie /permissive-
. Poniższy przykład kompiluje się w programie Visual Studio 2019 w wersji 16.1 lub starszej, ale tworzy C2338 w programie Visual Studio 2019 w wersji 16.2:
constexpr int f() { return 0; }
int main() {
static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}
Aby naprawić błąd, dodaj noexcept
wyrażenie do deklaracji funkcji:
constexpr int f() noexcept { return 0; }
int main() {
static_assert(noexcept(f()), "f should be noexcept");
}
Wyrażenia binarne z różnymi typami wyliczeniowymi
Język C++20 wycofał zwykłe konwersje arytmetyczne na operandach, gdzie:
Jeden operand ma typ wyliczenia i
druga jest innego typu wyliczenia lub typu zmiennoprzecinkowego.
Aby uzyskać więcej informacji, zobacz P1120R0.
W programie Visual Studio 2019 w wersji 16.2 lub nowszej poniższy kod generuje ostrzeżenie poziomu 4 C5054 po włączeniu /std:c++latest
opcji kompilatora (/std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej):
enum E1 { a };
enum E2 { b };
int main() {
int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}
Aby uniknąć ostrzeżenia, użyj polecenia static_cast
, aby przekonwertować drugi operand:
enum E1 { a };
enum E2 { b };
int main() {
int i = a | static_cast<int>(b);
}
Użycie operacji binarnej między wyliczeniem a typem zmiennoprzecinkowa jest teraz ostrzeżeniem poziomu 1 C5055 po włączeniu /std:c++latest
opcji kompilatora (/std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej):
enum E1 { a };
int main() {
double i = a * 1.1;
}
Aby uniknąć ostrzeżenia, użyj polecenia static_cast
, aby przekonwertować drugi operand:
enum E1 { a };
int main() {
double i = static_cast<int>(a) * 1.1;
}
Porównanie równości i relacyjnych tablic
Porównania równości i relacyjne między dwoma operandami typu tablicy są przestarzałe w języku C++20 (P1120R0). Innymi słowy, operacja porównania między dwiema tablicami (pomimo podobieństw rangi i zakresu) jest teraz ostrzeżeniem. W programie Visual Studio 2019 w wersji 16.2 lub nowszej poniższy kod generuje ostrzeżenie poziomu 1 C5056 po włączeniu /std:c++latest
opcji kompilatora (/std:c++20
w programie Visual Studio 2019 w wersji 16.11 lub nowszej):
int main() {
int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3 };
if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}
Aby uniknąć ostrzeżenia, możesz porównać adresy pierwszych elementów:
int main() {
int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3 };
if (&a[0] == &b[0]) { return 1; }
}
Aby określić, czy zawartość dwóch tablic jest równa, użyj std::equal
funkcji :
std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
Wpływ definiowania operatora statku kosmicznego na ==
i !=
Definicja operatora statku kosmicznego (<=>
) nie będzie już ponownie pisać wyrażeń z udziałem ==
lub !=
chyba że operator statku kosmicznego jest oznaczony jako = default
(P1185R2). Poniższy przykład kompiluje w programie Visual Studio 2019 RTW i wersji 16.1, ale tworzy C2678 w programie Visual Studio 2019 w wersji 16.2:
#include <compare>
struct S {
int a;
auto operator<=>(const S& rhs) const {
return a <=> rhs.a;
}
};
bool eq(const S& lhs, const S& rhs) {
return lhs == rhs; // error C2676
}
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs; // error C2676
}
Aby uniknąć błędu, zdefiniuj operator==
go lub zadeklaruj jako domyślny:
#include <compare>
struct S {
int a;
auto operator<=>(const S& rhs) const {
return a <=> rhs.a;
}
bool operator==(const S&) const = default;
};
bool eq(const S& lhs, const S& rhs) {
return lhs == rhs;
}
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs;
}
Ulepszenia biblioteki standardowej
- <znak z>
to_chars()
stałą/naukową precyzją. (Ogólna precyzja jest obecnie planowana dla wersji 16.4). - P0020R6:
atomic<float>
, ,atomic<double>
atomic<long double>
- P0463R1: endian
- P0482R6: obsługa biblioteki dla
char8_t
- P0600R1:
[[nodiscard]]
dla biblioteki STL, część 1 - P0653R2:
to_address()
- P0754R2: <wersja>
- P0771R1:
noexcept
dlastd::function
konstruktora przenoszenia
Komparatory const dla kontenerów asocjacyjnych
Kod do wyszukiwania i wstawiania w pliku set
, multiset
map
, i multimap
został scalony w celu zmniejszenia rozmiaru kodu. Operacje wstawiania teraz wywołają porównanie mniejsze niż w narzędziu const
functor porównania w taki sam sposób, jak wcześniej wykonywane operacje wyszukiwania. Poniższy kod kompiluje się w programie Visual Studio 2019 w wersji 16.1 lub starszej, ale zgłasza język C3848 w programie Visual Studio 2019 w wersji 16.2:
#include <iostream>
#include <map>
using namespace std;
struct K
{
int a;
string b = "label";
};
struct Comparer {
bool operator() (K a, K b) {
return a.a < b.a;
}
};
map<K, double, Comparer> m;
K const s1{1};
K const s2{2};
K const s3{3};
int main() {
m.emplace(s1, 1.08);
m.emplace(s2, 3.14);
m.emplace(s3, 5.21);
}
Aby uniknąć błędu, wykonaj operator const
porównania :
struct Comparer {
bool operator() (K a, K b) const {
return a.a < b.a;
}
};
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.3
Operatory wyodrębniania strumieni do char*
usunięcia
Operatory wyodrębniania strumieni dla znaków wskaźnika zostały usunięte i zastąpione przez operatory wyodrębniania dla tablicy znaków (na P0487R1). WG21 uważa, że usunięte przeciążenia są niebezpieczne. W /std:c++20
trybie lub /std:c++latest
poniższy przykład generuje teraz kod C2679:
// stream_extraction.cpp
// compile by using: cl /std:c++latest stream_extraction.cpp
#include <iostream>
#include <iomanip>
int main() {
char x[42];
char* p = x;
std::cin >> std::setw(42);
std::cin >> p; // C2679: binary '>>': no operator found which takes a right-hand operand of type 'char *' (or there is no acceptable conversion)
}
Aby uniknąć błędu, użyj operatora wyodrębniania ze zmienną char[]
:
#include <iostream>
#include <iomanip>
int main() {
char x[42];
std::cin >> std::setw(42);
std::cin >> x; // OK
}
Nowe słowa kluczowe requires
i concept
Nowe słowa kluczowe requires
i concept
zostały dodane do kompilatora języka Microsoft C++. Jeśli próbujesz użyć jednego jako identyfikatora w /std:c++20
trybie lub /std:c++latest
, kompilator zgłasza błąd składniowy C2059.
Konstruktory jako niedozwolone nazwy typów
W tym przypadku kompilator nie uwzględnia już nazw konstruktorów jako wstrzykiwanych nazw klas: po pojawieniu się w kwalifikowanej nazwie po aliasie specjalizacji szablonu klasy. Wcześniej konstruktory były używane jako nazwa typu do deklarowania innych jednostek. Poniższy przykład generuje teraz kod C3646:
#include <chrono>
class Foo {
std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};
Aby uniknąć błędu, zadeklaruj TotalDuration
, jak pokazano poniżej:
#include <chrono>
class Foo {
std::chrono::milliseconds TotalDuration {};
};
Bardziej rygorystyczne sprawdzanie extern "C"
funkcji
extern "C"
Jeśli funkcja została zadeklarowana w różnych przestrzeniach nazw, poprzednie wersje kompilatora Microsoft C++ nie sprawdziły, czy deklaracje są zgodne. W programie Visual Studio 2019 w wersji 16.3 lub nowszej kompilator sprawdza zgodność. W /permissive-
trybie następujący kod generuje błędy C2371 i C2733:
using BOOL = int;
namespace N
{
extern "C" void f(int, int, int, bool);
}
void g()
{
N::f(0, 1, 2, false);
}
extern "C" void f(int, int, int, BOOL){}
// C2116: 'N::f': function parameter lists do not match between declarations
// C2733: 'f': you cannot overload a function with 'extern "C"' linkage
Aby uniknąć błędów w poprzednim przykładzie, należy użyć bool
zamiast BOOL
spójnie w obu deklaracjach f
.
Ulepszenia biblioteki standardowej
Niestandardowe nagłówki <stdexcpt.h> i <typeinfo.h> zostały usunięte. Kod, który je zawiera, powinien zawierać odpowiednio wyjątek nagłówków <standardowych i <informacje> o typie.>
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.4
Lepsze wymuszanie wyszukiwania nazw dwufazowych dla kwalifikowanych identyfikatorów w /permissive-
Wyszukiwanie nazw dwufazowych wymaga, aby nazwy inne niż zależne używane w treści szablonu były widoczne dla szablonu w czasie definicji. Wcześniej takie nazwy mogły zostać znalezione po utworzeniu wystąpienia szablonu. Ta zmiana ułatwia pisanie przenośnego i zgodnego kodu w MSVC pod flagą /permissive-
.
W programie Visual Studio 2019 w wersji 16.4 z ustawionym /permissive-
flagą poniższy przykład generuje błąd, ponieważ N::f
nie jest widoczny, gdy f<T>
szablon jest zdefiniowany:
template <class T>
int f() {
return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}
namespace N {
int f() { return 42; }
}
Zazwyczaj ten błąd można naprawić, włączając brakujące nagłówki lub funkcje lub zmienne deklaratywne, jak pokazano w poniższym przykładzie:
namespace N {
int f();
}
template <class T>
int f() {
return N::f() + T{};
}
namespace N {
int f() { return 42; }
}
Niejawna konwersja wyrażeń stałych całkowitych na wskaźnik o wartości null
Kompilator MSVC implementuje teraz problem CWG 903 w trybie zgodności (/permissive-
). Ta reguła nie zezwala na niejawną konwersję wyrażeń stałych całkowitych (z wyjątkiem literału całkowitego "0") na stałe wskaźnika null. Poniższy przykład generuje kod C2440 w trybie zgodności:
int* f(bool* p) {
p = false; // error C2440: '=': cannot convert from 'bool' to 'bool *'
p = 0; // OK
return false; // error C2440: 'return': cannot convert from 'bool' to 'int *'
}
Aby naprawić błąd, użyj polecenia nullptr
zamiast false
. Literał 0 jest nadal dozwolony:
int* f(bool* p) {
p = nullptr; // OK
p = 0; // OK
return nullptr; // OK
}
Standardowe reguły dla typów literałów liczb całkowitych
W trybie zgodności (włączonym przez /permissive-
program ) MSVC używa standardowych reguł dla typów literałów całkowitych. Literały dziesiętne są zbyt duże, aby zmieściły się w obiekcie signed int
, wcześniej podano typ unsigned int
. Teraz takie literały otrzymują następny największy signed
typ liczb całkowitych: long long
. Ponadto literały z sufiksem "ll", które są zbyt duże, aby zmieścić się w typie signed
, mają typ unsigned long long
.
Ta zmiana może prowadzić do wygenerowania innej diagnostyki ostrzegawczej oraz różnic w zachowaniu operacji arytmetycznych na literałach.
W poniższym przykładzie przedstawiono nowe zachowanie w programie Visual Studio 2019 w wersji 16.4. Zmienna i
ma teraz typ unsigned int
, więc jest wywoływane ostrzeżenie. Bity o wysokiej kolejności zmiennej j
są ustawione na 0.
void f(int r) {
int i = 2964557531; // warning C4309: truncation of constant value
long long j = 0x8000000000000000ll >> r; // literal is now unsigned, shift will fill high-order bits with 0
}
W poniższym przykładzie pokazano, jak zachować stare zachowanie i uniknąć ostrzeżeń i zmian zachowania w czasie wykonywania:
void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}
Parametry funkcji, które parametry szablonu w tle
Kompilator MSVC zgłasza teraz błąd, gdy parametr funkcji w tle parametru szablonu:
template<typename T>
void f(T* buffer, int size, int& size_read);
template<typename T, int Size>
void f(T(&buffer)[Size], int& Size) // error C7576: declaration of 'Size' shadows a template parameter
{
return f(buffer, Size, Size);
}
Aby naprawić błąd, zmień nazwę jednego z parametrów:
template<typename T>
void f(T* buffer, int size, int& size_read);
template<typename T, int Size>
void f(T (&buffer)[Size], int& size_read)
{
return f(buffer, Size, size_read);
}
Specjalizacje typu udostępniane przez użytkownika
W przypadku zgodności z podkatalizami meta.rqmts w warstwie Standardowa kompilator MSVC zgłasza teraz błąd w przypadku znalezienia specjalizacji zdefiniowanej przez użytkownika jednego z określonych type_traits
szablonów w std
przestrzeni nazw. O ile nie określono inaczej, takie specjalizacje powodują niezdefiniowane zachowanie. Poniższy przykład ma niezdefiniowane zachowanie, ponieważ narusza regułę, a static_assert
błąd C2338 kończy się niepowodzeniem.
#include <type_traits>
struct S;
template<>
struct std::is_fundamental<S> : std::true_type {};
static_assert(std::is_fundamental<S>::value, "fail");
Aby uniknąć błędu, zdefiniuj strukturę dziedziczą z preferowanego type_trait
elementu i specjalizuj się, że:
#include <type_traits>
struct S;
template<typename T>
struct my_is_fundamental : std::is_fundamental<T> {};
template<>
struct my_is_fundamental<S> : std::true_type { };
static_assert(my_is_fundamental<S>::value, "fail");
Zmiany w operatorach porównania dostarczonych przez kompilator
Kompilator MSVC implementuje teraz następujące zmiany dla operatorów porównania na P1630R1 po włączeniu /std:c++20
opcji lub /std:c++latest
:
Kompilator nie ponownie zapisuje już wyrażeń przy użyciuoperator==
, jeśli obejmują typ zwracany, który nie jest .bool
Poniższy kod generuje teraz błąd C2088:
struct U {
operator bool() const;
};
struct S {
U operator==(const S&) const;
};
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs; // C2088: '!=': illegal for struct
}
Aby uniknąć błędu, należy jawnie zdefiniować wymagany operator:
struct U {
operator bool() const;
};
struct S {
U operator==(const S&) const;
U operator!=(const S&) const;
};
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs;
}
Kompilator nie definiuje już domyślnego operatora porównania, jeśli jest członkiem klasy podobnej do unii. Poniższy przykład generuje teraz błąd C2120:
#include <compare>
union S {
int a;
char b;
auto operator<=>(const S&) const = default;
};
bool lt(const S& lhs, const S& rhs) {
return lhs < rhs;
}
Aby uniknąć błędu, zdefiniuj treść operatora:
#include <compare>
union S {
int a;
char b;
auto operator<=>(const S&) const { ... }
};
bool lt(const S& lhs, const S& rhs) {
return lhs < rhs;
}
Kompilator nie będzie już definiować domyślnego operatora porównania, jeśli klasa zawiera składową odwołania. Poniższy kod generuje teraz błąd C2120:
#include <compare>
struct U {
int& a;
auto operator<=>(const U&) const = default;
};
bool lt(const U& lhs, const U& rhs) {
return lhs < rhs;
}
Aby uniknąć błędu, zdefiniuj treść operatora:
#include <compare>
struct U {
int& a;
auto operator<=>(const U&) const { ... };
};
bool lt(const U& lhs, const U& rhs) {
return lhs < rhs;
}
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.5
Jawna deklaracja specjalizacji bez inicjatora nie jest definicją
W obszarze /permissive-
program MSVC wymusza teraz standardową regułę, że jawne deklaracje specjalizacji bez inicjatorów nie są definicjami. Wcześniej deklaracja zostałaby uznana za definicję z inicjatorem domyślnym. Efekt jest zauważalny w czasie połączenia, ponieważ program w zależności od tego zachowania może teraz mieć nierozwiązane symbole. W tym przykładzie zostanie wyświetlony błąd:
template <typename> struct S {
static int a;
};
// In permissive-, this declaration isn't a definition, and the program won't link.
template <> int S<char>::a;
int main() {
return S<char>::a;
}
error LNK2019: unresolved external symbol "public: static int S<char>::a" (?a@?$S@D@@2HA) referenced in function _main at link time.
Aby rozwiązać ten problem, dodaj inicjator:
template <typename> struct S {
static int a;
};
// Add an initializer for the declaration to be a definition.
template <> int S<char>::a{};
int main() {
return S<char>::a;
}
Dane wyjściowe preprocesora zachowują nowe linie
Eksperymentalny preprocesor zachowuje teraz nowe linie i białe znaki w przypadku używania polecenia /P
lub /E
z /experimental:preprocessor
programem .
Biorąc pod uwagę to przykładowe źródło,
#define m()
line m(
) line
Poprzednie dane wyjściowe /E
:
line line
#line 2
Nowe dane wyjściowe polecenia /E
to teraz:
line
line
import
i module
słowa kluczowe są zależne od kontekstu
Na P1857R1import
i module
dyrektywy preprocesora mają nowe ograniczenia dotyczące ich składni. W tym przykładzie nie są już kompilowane:
import // Invalid
m; // error C2146: syntax error: missing ';' before identifier 'm'
Aby rozwiązać ten problem, zachowaj import w tym samym wierszu:
import m; // OK
Usunięcie i std::weak_equality
std::strong_equality
Scalanie P1959R0 wymaga od kompilatora usunięcia zachowania i odwołań do std::weak_equality
typów i std::strong_equality
.
Kod w tym przykładzie nie jest już kompilowany:
#include <compare>
struct S {
std::strong_equality operator<=>(const S&) const = default;
};
void f() {
nullptr<=>nullptr;
&f <=> &f;
&S::operator<=> <=> &S::operator<=>;
}
Przykład prowadzi teraz do następujących błędów:
error C2039: 'strong_equality': is not a member of 'std'
error C2143: syntax error: missing ';' before '<=>'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C7546: binary operator '<=>': unsupported operand types 'nullptr' and 'nullptr'
error C7546: binary operator '<=>': unsupported operand types 'void (__cdecl *)(void)' and 'void (__cdecl *)(void)'
error C7546: binary operator '<=>': unsupported operand types 'int (__thiscall S::* )(const S &) const' and 'int (__thiscall S::* )(const S &) const'
Aby rozwiązać ten problem, zaktualizuj element w celu preferowania wbudowanych operatorów relacyjnych i zastąp usunięte typy:
#include <compare>
struct S {
std::strong_ordering operator<=>(const S&) const = default; // prefer 'std::strong_ordering'
};
void f() {
nullptr != nullptr; // use pre-existing builtin operator != or ==.
&f != &f;
&S::operator<=> != &S::operator<=>;
}
Zmiany funkcji TLS Guard
Wcześniej zmienne lokalne wątku w bibliotekach DLL nie zostały poprawnie zainicjowane. Poza wątkiem, który załadował bibliotekę DLL, nie zostały zainicjowane przed pierwszym użyciem w wątkach, które istniały przed załadowaniem biblioteki DLL. Ta usterka została poprawiona. Zmienne lokalne wątku w takiej biblioteki DLL są inicjowane bezpośrednio przed ich pierwszym użyciem w takich wątkach.
To nowe zachowanie testowania na potrzeby inicjowania użycia zmiennych lokalnych wątku może zostać wyłączone przy użyciu opcji kompilatora /Zc:tlsGuards-
. Lub przez dodanie atrybutu [[msvc:no_tls_guard]]
do określonych zmiennych lokalnych wątku.
Lepsza diagnostyka wywołań do usuniętych funkcji
Nasz kompilator był bardziej permissive o wywołaniach do usuniętych funkcji wcześniej. Jeśli na przykład wywołania wystąpiły w kontekście treści szablonu, nie zdiagnozowalibyśmy wywołania. Ponadto jeśli było wiele wystąpień wywołań do usuniętych funkcji, wydalibyśmy tylko jedną diagnostykę. Teraz wydajemy diagnostykę dla każdego z nich.
Jedną z konsekwencji nowego zachowania może być mała zmiana powodująca niezgodność: kod, który nazwał usuniętą funkcję, nie zostanie zdiagnozowany, jeśli nigdy nie był potrzebny do generowania kodu. Teraz diagnozujemy go z góry.
W tym przykładzie pokazano kod, który teraz generuje błąd:
struct S {
S() = delete;
S(int) { }
};
struct U {
U() = delete;
U(int i): s{ i } { }
S s{};
};
U u{ 0 };
error C2280: 'S::S(void)': attempting to reference a deleted function
note: see declaration of 'S::S'
note: 'S::S(void)': function was explicitly deleted
Aby rozwiązać ten problem, usuń wywołania funkcji usuniętych:
struct S {
S() = delete;
S(int) { }
};
struct U {
U() = delete;
U(int i): s{ i } { }
S s; // Do not call the deleted ctor of 'S'.
};
U u{ 0 };
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.6
Strumienie biblioteki standardowej odrzucają wstawiania nieprawidłowo zakodowanych typów znaków
Tradycyjnie wstawianie elementu wchar_t
std::ostream
do elementu i wstawianie elementu lub char32_t
do elementu std::ostream
lub std::wostream
powoduje wyprowadzenie char16_t
jej wartości całkowitej. Wstawianie wskaźników do tych typów znaków powoduje wyprowadzenie wartości wskaźnika. Programiści nie znajdą żadnej intuicyjnej wielkości liter. Często oczekuje się, że zamiast tego biblioteka standardowa transkoduje znak lub ciąg znaków zakończony o wartości null i wyświetli wynik.
Propozycja języka C++20 P1423R3 dodaje przeciążenia operatora wstawiania usuniętego strumienia dla tych kombinacji typów strumienia i znaków lub wskaźników znaków. W obszarze /std:c++20
lub /std:c++latest
przeciążenia sprawiają, że te wstawki są źle sformułowane, zamiast zachowywać się w sposób prawdopodobny niezamierzony. Kompilator zgłasza błąd C2280 po znalezieniu. Aby przywrócić stare zachowanie, można zdefiniować makro _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20
1
"ucieczka kreskowania". (Propozycja usuwa również operatory wstawiania strumieni dla elementu char8_t
. Nasza biblioteka standardowa zaimplementowała podobne przeciążenia, gdy dodaliśmy char8_t
obsługę, więc "nieprawidłowe" zachowanie nigdy nie było dostępne dla elementu char8_t
.
W tym przykładzie pokazano zachowanie z tą zmianą:
#include <iostream>
int main() {
const wchar_t cw = L'x', *pw = L"meow";
const char16_t c16 = u'x', *p16 = u"meow";
const char32_t c32 = U'x', *p32 = U"meow";
std::cout << cw << ' ' << pw << '\n';
std::cout << c16 << ' ' << p16 << '\n';
std::cout << c32 << ' ' << p32 << '\n';
std::wcout << c16 << ' ' << p16 << '\n';
std::wcout << c32 << ' ' << p32 << '\n';
}
Kod generuje teraz następujące komunikaty diagnostyczne:
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,wchar_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char32_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char32_t)': attempting to reference a deleted function
Można osiągnąć efekt starego zachowania we wszystkich trybach języka, konwertując typy znaków na unsigned int
typy znaków na , lub wskaźniki do :const void*
#include <iostream>
int main() {
const wchar_t cw = L'x', *pw = L"meow";
const char16_t c16 = u'x', *p16 = u"meow";
const char32_t c32 = U'x', *p32 = U"meow";
std::cout << (unsigned)cw << ' ' << (const void*)pw << '\n'; // Outputs "120 0052B1C0"
std::cout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
std::cout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
std::wcout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
std::wcout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
}
Zmieniono typ zwracany std::pow()
dla std::complex
Wcześniej implementacja MSVC reguł podwyższania poziomu dla zwracanego typu szablonu std::pow()
funkcji była niepoprawna. Na przykład wcześniej pow(complex<float>, int)
zwrócono complex<float>
wartość . Teraz poprawnie zwraca wartość complex<double>
. Poprawka została zaimplementowana bezwarunkowo dla wszystkich trybów standardów w programie Visual Studio 2019 w wersji 16.6.
Ta zmiana może spowodować błędy kompilatora. Na przykład wcześniej można było pomnożyć pow(complex<float>, int)
przez element float
. Ponieważ complex<T> operator*
oczekuje argumentów tego samego typu, poniższy przykład emituje teraz błąd kompilatora C2676:
// pow_error.cpp
// compile by using: cl /EHsc /nologo /W4 pow_error.cpp
#include <complex>
int main() {
std::complex<float> cf(2.0f, 0.0f);
(void) (std::pow(cf, -1) * 3.0f);
}
pow_error.cpp(7): error C2676: binary '*': 'std::complex<double>' does not define this operator or a conversion to a type acceptable to the predefined operator
Istnieje wiele możliwych poprawek:
Zmień typ mnożenia na
float
double
. Ten argument można przekonwertować bezpośrednio na element , aby był zgodny z typemcomplex<double>
zwracanym przezpow
element .Zawęź wynik do
complex<float>
,pow
mówiąccomplex<float>{pow(ARG, ARG)}
. Następnie możesz kontynuować mnożenie przezfloat
wartość.Przekaż
float
zamiastint
dopow
. Ta operacja może być wolniejsza.W niektórych przypadkach można go całkowicie uniknąć
pow
. Na przykładpow(cf, -1)
można zastąpić podziałem.
switch
ostrzeżenia dla języka C
W programie Visual Studio 2019 w wersji 16.6 lub nowszej kompilator implementuje niektóre istniejące ostrzeżenia języka C++ dla kodu skompilowanego jako C. Następujące ostrzeżenia są teraz włączone na różnych poziomach: C4060, C4061, C4062, C4063, C4064, C4065, C4808 i C4809. Ostrzeżenia C4065 i C4060 są domyślnie wyłączone w języku C.
Ostrzeżenia wyzwalają brakujące case
instrukcje, niezdefiniowane enum
i nieprawidłowe bool
instrukcje przełącznika (czyli te, które zawierają zbyt wiele przypadków). Na przykład:
#include <stdbool.h>
int main() {
bool b = true;
switch (b) {
case true: break;
case false: break;
default: break; // C4809: switch statement has redundant 'default' label;
// all possible 'case' labels are given
}
}
Aby naprawić ten kod, usuń nadmiarowy default
przypadek:
#include <stdbool.h>
int main() {
bool b = true;
switch (b) {
case true: break;
case false: break;
}
}
Nienazwane klasy w typedef
deklaracjach
W programie Visual Studio 2019 w wersji 16.6 lub nowszej typedef
zachowanie deklaracji zostało ograniczone do zgodności z P1766R1. W przypadku tej aktualizacji klasy nienazwane w typedef
deklaracji nie mogą mieć żadnych elementów członkowskich innych niż:
- niestatyczne składowe danych bez domyślnych inicjatorów składowych,
- klasy składowe lub
- wyliczenia składowych.
Te same ograniczenia są stosowane rekursywnie do każdej zagnieżdżonej klasy. Ograniczenie to ma zapewnić prostotę struktur, które mają typedef
nazwy do celów powiązania. Muszą być na tyle proste, że nie są konieczne żadne obliczenia łączenia, zanim kompilator przejdzie do typedef
nazwy połączenia.
Ta zmiana ma wpływ na wszystkie tryby standardów kompilatora. W domyślnych (/std:c++14
) i /std:c++17
trybach kompilator emituje ostrzeżenie C5208 dla niezgodnego kodu. Jeśli /permissive-
zostanie określony, kompilator emituje ostrzeżenie C5208 jako błąd w obszarze /std:c++14
i emituje błąd C7626 w obszarze /std:c++17
. Kompilator emituje błąd C7626 dla niezgodnego kodu, gdy /std:c++20
jest określony lub /std:c++latest
.
W poniższym przykładzie przedstawiono konstrukcje, które nie są już dozwolone w strukturach bez nazw. W zależności od określonego trybu standardów emitowane są błędy C5208 lub C7626 lub ostrzeżenia:
struct B { };
typedef struct : B { // inheriting from 'B'; ill-formed
void f(); // ill-formed
static int i; // ill-formed
struct U {
void f(); // nested class has non-data member; ill-formed
};
int j = 10; // default member initializer; ill-formed
} S;
Powyższy kod można naprawić, nadając nienazwanej klasie nazwę:
struct B { };
typedef struct S_ : B {
void f();
static int i;
struct U {
void f();
};
int j = 10;
} S;
Domyślny import argumentów w języku C++/interfejsie wiersza polecenia
Rosnąca liczba interfejsów API ma domyślne argumenty na platformie .NET Core. Dlatego teraz obsługujemy domyślny import argumentów w języku C++/CLI. Ta zmiana może przerwać istniejący kod, w którym zadeklarowano wiele przeciążeń, jak w tym przykładzie:
public class R {
public void Func(string s) {} // overload 1
public void Func(string s, string s2 = "") {} // overload 2;
}
Po zaimportowaniu tej klasy do języka C++/interfejsu wiersza polecenia wywołanie do jednego z przeciążeń powoduje błąd:
(gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function
Kompilator emituje błąd C2668, ponieważ oba przeciążenia pasują do tej listy argumentów. W drugim przeciążeniu drugi argument jest wypełniany przez domyślny argument. Aby obejść ten problem, możesz usunąć nadmiarowe przeciążenie (1). Możesz też użyć pełnej listy argumentów i jawnie podać argumenty domyślne.
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.7
jest trywialnie kopiowalną definicją
Język C++20 zmienił definicję jest trywialnie kopiowalny. Jeśli klasa ma niestatyczny element członkowski danych z volatile
kwalifikowanym typem, nie oznacza już, że każdy konstruktor kopii lub przenoszenia generowany przez kompilator albo operator przypisania kopiowania lub przenoszenia nie jest trywialny. Komitet standardowy języka C++ zastosował tę zmianę wstecznie jako raport o wadach. W środowisku MSVC zachowanie kompilatora nie zmienia się w różnych trybach języka, takich jak /std:c++14
lub /std:c++latest
.
Oto przykład nowego zachowania:
#include <type_traits>
struct S
{
volatile int m;
};
static_assert(std::is_trivially_copyable_v<S>, "Meow!");
Ten kod nie jest kompilowany w wersjach MSVC przed programem Visual Studio 2019 w wersji 16.7. Istnieje domyślne ostrzeżenie kompilatora, za pomocą którego można wykryć tę zmianę. Jeśli skompilujesz powyższy kod przy użyciu polecenia cl /W4 /w45220
, zobaczysz następujące ostrzeżenie:
warning C5220: `'S::m': a non-static data member with a volatile qualified type no longer implies that compiler generated copy/move constructors and copy/move assignment operators are non trivial`
Konwersje literału wskaźnika do składowej i ciągu w celu bool
zawężenia
Komisja Standardowa języka C++ niedawno przyjęła P1957R2 Raport wad, który jest uważany za T*
bool
konwersję zawężającą. Program MSVC usunęł usterkę w implementacji, która wcześniej zdiagnozowała T*
bool
jako zawężenie, ale nie zdiagnozowała konwersji literału ciągu na bool
lub wskaźnika do elementu członkowskiego .bool
Następujący program jest nieprawidłowo sformułowany w programie Visual Studio 2019 w wersji 16.7:
struct X { bool b; };
void f(X);
int main() {
f(X { "whoops?" }); // error: conversion from 'const char [8]' to 'bool' requires a narrowing conversion
int (X::* p) = nullptr;
f(X { p }); // error: conversion from 'int X::*' to 'bool' requires a narrowing conversion
}
Aby poprawić ten kod, dodaj jawne porównania do nullptr
elementu lub unikaj kontekstów, w których konwersje zawężające są źle sformułowane:
struct X { bool b; };
void f(X);
int main() {
f(X { "whoops?" != nullptr }); // Absurd, but OK
int (X::* p) = nullptr;
f(X { p != nullptr }); // OK
}
nullptr_t
jest konwertowany tylko na bool
jako bezpośrednie inicjowanie
W języku C++11 nullptr
jest konwertowany tylko na bool
konwersję bezpośrednią, na przykład podczas inicjowania bool
elementu przy użyciu listy inicjatora nawiasu klamrowego. To ograniczenie nigdy nie zostało wymuszone przez MSVC. Program MSVC implementuje teraz regułę w obszarze /permissive-
. Niejawne konwersje są teraz diagnozowane jako źle sformułowane. Konwersja kontekstowa na bool
jest nadal dozwolona, ponieważ inicjowanie bool b(nullptr)
bezpośrednie jest prawidłowe.
W większości przypadków błąd można naprawić, zastępując element nullptr
, false
jak pokazano w tym przykładzie:
struct S { bool b; };
void g(bool);
bool h() { return nullptr; } // error, should be 'return false;'
int main() {
bool b1 = nullptr; // error: cannot convert from 'nullptr' to 'bool'
S s { nullptr }; // error: cannot convert from 'nullptr' to 'bool'
g(nullptr); // error: cannot convert argument 1 from 'nullptr' to 'bool'
bool b2 { nullptr }; // OK: Direct-initialization
if (!nullptr) {} // OK: Contextual conversion to bool
}
Zgodne zachowanie inicjowania dla inicjowania tablicy z brakującymi inicjatorami
Wcześniej program MSVC miał niezgodne zachowanie w przypadku inicjowania tablicy, które nie zawierały inicjatorów. Program MSVC zawsze nazywał się konstruktorem domyślnym dla każdego elementu tablicy, który nie miał inicjatora. Standardowe zachowanie polega na zainicjowaniu każdego elementu z pustą listą inicjowania nawiasów klamrowych ({}
). Kontekst inicjowania pustej listy inicjatora-nawiasu klamrowego to inicjowanie kopiowania, które nie zezwala na wywołania jawnych konstruktorów. Mogą również występować różnice w czasie wykonywania, ponieważ użycie polecenia {}
do inicjowania może wywołać konstruktor, który przyjmuje std::initializer_list
element , zamiast konstruktora domyślnego. Zachowanie zgodne jest włączone w obszarze /permissive-
.
Oto przykład zmienionego zachowania:
struct B {
explicit B() {}
};
void f() {
B b1[1]{}; // Error in /permissive-, because aggregate init calls explicit ctor
B b2[1]; // OK: calls default ctor for each array element
}
Inicjowanie składowych klas z przeciążonymi nazwami jest poprawnie sekwencjonowane
Zidentyfikowaliśmy usterkę w wewnętrznej reprezentacji składowych danych klasy, gdy nazwa typu jest również przeciążona jako nazwa elementu członkowskiego danych. Ta usterka spowodowała niespójności w zagregowanej kolejności inicjowania i inicjowania składowych. Wygenerowany kod inicjowania jest teraz poprawny. Jednak ta zmiana może prowadzić do błędów lub ostrzeżeń w źródle, które przypadkowo polegały na błędnie uporządkowanych elementach członkowskich, jak w tym przykładzie:
// Compiling with /w15038 now gives:
// warning C5038: data member 'Outer::Inner' will be initialized after data member 'Outer::v'
struct Outer {
Outer(int i, int j) : Inner{ i }, v{ j } {}
struct Inner { int x; };
int v;
Inner Inner; // 'Inner' is both a type name and data member name in the same scope
};
W poprzednich wersjach konstruktor niepoprawnie zainicjował element członkowski Inner
danych przed elementem członkowskim v
danych . (Standard C++ wymaga kolejności inicjowania, która jest taka sama jak kolejność deklaracji elementów członkowskich). Teraz, gdy wygenerowany kod jest zgodny ze standardem, lista elementów członkowskich nie jest w porządku. Kompilator generuje ostrzeżenie dla tego przykładu. Aby rozwiązać ten problem, zmień kolejność listy member-initializer-list, aby odzwierciedlić kolejność deklaracji.
Rozpoznawanie przeciążeń obejmujące przeciążenia całkowite i long
argumenty
Standard C++ wymaga klasyfikacji long
klasy do int
konwersji jako standardowej konwersji. Poprzednie kompilatory MSVC niepoprawnie klasyfikują ją jako integralną promocję, która plasuje się wyżej w przypadku rozpoznawania przeciążeń. Ta klasyfikacja może spowodować pomyślne rozwiązanie przeciążenia, gdy należy je uznać za niejednoznaczne.
Kompilator uwzględnia teraz klasyfikację prawidłowo w /permissive-
trybie. Nieprawidłowy kod jest prawidłowo diagnozowany, jak w tym przykładzie:
void f(long long);
void f(int);
int main() {
long x {};
f(x); // error: 'f': ambiguous call to overloaded function
f(static_cast<int>(x)); // OK
}
Ten problem można rozwiązać na kilka sposobów:
W lokacji wywołania zmień typ przekazanego argumentu na
int
. Możesz zmienić typ zmiennej lub rzutować go.Jeśli istnieje wiele witryn wywołań, możesz dodać kolejne przeciążenie, które przyjmuje
long
argument. W tej funkcji rzutuj i przekaż argument do przeciążeniaint
.
Używanie niezdefiniowanej zmiennej z wewnętrznym połączeniem
Wersje MSVC przed programem Visual Studio 2019 w wersji 16.7 zaakceptowały użycie zmiennej zadeklarowanej extern
, która miała połączenie wewnętrzne i nie została zdefiniowana. Takich zmiennych nie można zdefiniować w żadnej innej jednostce tłumaczenia i nie można utworzyć prawidłowego programu. Kompilator diagnozuje teraz ten przypadek w czasie kompilacji. Błąd jest podobny do błędu dla niezdefiniowanych funkcji statycznych.
namespace {
extern int x; // Not a definition, but has internal linkage because of the anonymous namespace
}
int main()
{
return x; // Use of 'x' that no other translation unit can possibly define.
}
Ten program wcześniej niepoprawnie skompilowany i połączony, ale teraz emituje błąd C7631.
error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined
Takie zmienne muszą być zdefiniowane w tej samej jednostce tłumaczenia, w której są używane. Można na przykład podać jawny inicjator lub oddzielną definicję.
Kompletność typu i konwersje wskaźnika pochodnego na podstawowy
W standardach języka C++ przed C++20 konwersja z klasy pochodnej na klasę bazową nie wymagała, aby klasa pochodna byłaby kompletnym typem klasy. Standardowy komitet języka C++ zatwierdził zmianę raportu wad wstecznych, która ma zastosowanie do wszystkich wersji języka C++. Ta zmiana jest zgodna z procesem konwersji z cechami typów, takimi jak std::is_base_of
, które wymagają, aby klasa pochodna była kompletnym typem klasy.
Oto przykład:
template<typename A, typename B>
struct check_derived_from
{
static A a;
static constexpr B* p = &a;
};
struct W { };
struct X { };
struct Y { };
// With this change this code will fail as Z1 is not a complete class type
struct Z1 : X, check_derived_from<Z1, X>
{
};
// This code failed before and it will still fail after this change
struct Z2 : check_derived_from<Z2, Y>, Y
{
};
// With this change this code will fail as Z3 is not a complete class type
struct Z3 : W
{
check_derived_from<Z3, W> cdf;
};
Ta zmiana zachowania dotyczy wszystkich trybów języka C++ MSVC, a nie tylko /std:c++20
lub /std:c++latest
.
Konwersje zawężające są bardziej spójnie diagnozowane
Program MSVC emituje ostrzeżenie dotyczące zawężania konwersji w inicjatorze listy nawiasów klamrowych. Wcześniej kompilator nie diagnozował zawężających konwersji z większych enum
typów bazowych do węższych typów całkowitych. (Kompilator nieprawidłowo uznał je za integralną podwyższanie poziomu zamiast konwersji). Jeśli konwersja zawężania jest celowa, można uniknąć ostrzeżenia przy użyciu static_cast
argumentu inicjatora. Możesz też wybrać większy typ całkowity miejsca docelowego.
Oto przykład użycia jawnego static_cast
do rozwiązania ostrzeżenia:
enum E : long long { e1 };
struct S { int i; };
void f(E e) {
S s = { e }; // warning: conversion from 'E' to 'int' requires a narrowing conversion
S s1 = { static_cast<int>(e) }; // Suppress warning with explicit conversion
}
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.8
Rozszerzenie "Class rvalue used as lvalue" (Klasa rvalue używana jako lvalue)
MSVC ma rozszerzenie, które umożliwia używanie wartości rvalue klasy jako lvalue. Rozszerzenie nie wydłuża okresu istnienia wartości rvalue klasy i może prowadzić do niezdefiniowanego zachowania w czasie wykonywania. Teraz wymuszamy regułę standardową i nie zezwalamy na to rozszerzenie w obszarze /permissive-
.
Jeśli nie możesz jeszcze używać /permissive-
, możesz użyć /we4238
polecenia , aby jawnie uniemożliwić rozszerzenie. Oto przykład:
// Compiling with /permissive- now gives:
// error C2102: '&' requires l-value
struct S {};
S f();
void g()
{
auto p1 = &(f()); // The temporary returned by 'f' is destructed after this statement. So 'p1' points to an invalid object.
const auto &r = f(); // This extends the lifetime of the temporary returned by 'f'
auto p2 = &r; // 'p2' points to a valid object
}
Rozszerzenie "Jawna specjalizacja w zakresie przestrzeni nazw"
MSVC miał rozszerzenie, które zezwalało na jawną specjalizację w zakresie przestrzeni nazw. Jest to teraz część standardu, po rozwiązaniu CWG 727. Istnieją jednak różnice w zachowaniu. Dostosowaliśmy zachowanie naszego kompilatora, aby dopasować je do standardu.
// Compiling with 'cl a.cpp b.cpp /permissive-' now gives:
// error LNK2005: "public: void __thiscall S::f<int>(int)" (??$f@H@S@@QAEXH@Z) already defined in a.obj
// To fix the linker error,
// 1. Mark the explicit specialization with 'inline' explicitly. Or,
// 2. Move its definition to a source file.
// common.h
struct S {
template<typename T> void f(T);
template<> void f(int);
};
// This explicit specialization is implicitly inline in the default mode.
template<> void S::f(int) {}
// a.cpp
#include "common.h"
int main() {}
// b.cpp
#include "common.h"
Sprawdzanie pod kątem typów klas abstrakcyjnych
Język C++20 Standard zmienił kompilatory procesu używane do wykrywania użycia typu klasy abstrakcyjnej jako parametru funkcji. W szczególności nie jest to już błąd SFINAE. Wcześniej, jeśli kompilator wykrył, że specjalizacja szablonu funkcji będzie miała wystąpienie typu klasy abstrakcyjnej jako parametr funkcji, specjalizacja ta zostanie uznana za źle sformułowaną. Nie zostanie on dodany do zestawu realnych funkcji kandydatów. W języku C++20 sprawdzanie parametru typu klasy abstrakcyjnej nie następuje, dopóki funkcja nie zostanie wywołana. Efekt polega na tym, że kod używany do kompilowania nie spowoduje błędu. Oto przykład:
class Node {
public:
int index() const;
};
class String : public Node {
public:
virtual int size() const = 0;
};
class Identifier : public Node {
public:
const String& string() const;
};
template<typename T>
int compare(T x, T y)
{
return x < y ? -1 : (x > y ? 1 : 0);
}
int compare(const Node& x, const Node& y)
{
return compare(x.index(), y.index());
}
int f(const Identifier& x, const String& y)
{
return compare(x.string(), y);
}
Wcześniej wywołanie compare
metody próbowałoby specjalizować szablon compare
funkcji przy użyciu String
argumentu szablonu szablonu dla elementu T
. Nie udałoby się wygenerować prawidłowej specjalizacji, ponieważ String
jest to klasa abstrakcyjna. Jedynym realnym kandydatem byłby compare(const Node&, const Node&)
. Jednak w języku C++20 sprawdzanie typu klasy abstrakcyjnej nie nastąpi, dopóki funkcja nie zostanie wywołana. Dlatego specjalizacja compare(String, String)
jest dodawana do zestawu realnych kandydatów i jest wybierana jako najlepszy kandydat, ponieważ konwersja z const String&
na String
jest lepszą sekwencją konwersji niż konwersja z const String&
do const Node&
.
W języku C++20 jedną z możliwych poprawek dla tego przykładu jest użycie pojęć; oznacza to, że zmień definicję na compare
:
template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
return x < y ? -1 : (x > y ? 1 : 0);
}
Lub jeśli pojęcia języka C++ nie są dostępne, możesz wrócić do sfINAE:
template<typename T, std::enable_if_t<!std::is_abstract_v<T>, int> = 0>
int compare(T x, T y)
{
return x < y ? -1 : (x > y ? 1 : 0);
}
Obsługa P0960R3 — zezwalaj na inicjowanie agregacji z nawiasowej listy wartości
Język C++20 P0960R3 dodaje obsługę inicjowania agregacji przy użyciu listy inicjatora nawiasu. Na przykład następujący kod jest prawidłowy w języku C++20:
struct S {
int i;
int j;
};
S s(1, 2);
Większość tej funkcji jest addytywna, czyli kod kompiluje kod, który nie został skompilowany wcześniej. Jednak zmienia zachowanie elementu std::is_constructible
. W trybie C++17 kończy się to static_assert
niepowodzeniem, ale w trybie C++20 kończy się powodzeniem:
static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");
Jeśli używasz tej cechy typu do kontrolowania rozpoznawania przeciążeń, może to prowadzić do zmiany zachowania między C++17 i C++20.
Rozpoznawanie przeciążenia obejmujące szablony funkcji
Wcześniej kompilator zezwolił na skompilowanie kodu, który /permissive-
nie powinien być kompilowany. Efekt był, kompilator nazwał niewłaściwą funkcją prowadzącą do zmiany zachowania środowiska uruchomieniowego:
int f(int);
namespace N
{
using ::f;
template<typename T>
T f(T);
}
template<typename T>
void g(T&& t)
{
}
void h()
{
using namespace N;
g(f);
}
Wywołanie do g
używa zestawu przeciążenia zawierającego dwie funkcje ::f
i N::f
. Ponieważ N::f
jest szablonem funkcji, kompilator powinien traktować argument funkcji jako kontekst niezwiązany. Oznacza to, że w tym przypadku wywołanie metody powinno zakończyć się g
niepowodzeniem, ponieważ kompilator nie może wywieść typu parametru T
szablonu . Niestety, kompilator nie odrzucił faktu, że już zdecydował, że ::f
jest to dobry mecz dla wywołania funkcji. Zamiast emitować błąd, kompilator wygeneruje kod do wywołania g
przy użyciu ::f
jako argumentu.
Biorąc pod uwagę, że w wielu przypadkach użycie ::f
jako argumentu funkcji jest oczekiwane przez użytkownika, emitujemy błąd tylko wtedy, gdy kod jest kompilowany za pomocą /permissive-
polecenia .
Migrowanie z /await
do C++20 coroutines
Coroutines języka C++20 w warstwie Standardowa jest teraz domyślnie włączona w obszarze /std:c++20
i /std:c++latest
. Różnią się one od TS coroutines i obsługi w /await
ramach opcji . Migracja z /await
do standardowych kohroutyn może wymagać pewnych zmian w źródle.
Niestandardowe słowa kluczowe
Stare await
i yield
słowa kluczowe nie są obsługiwane w trybie C++20. Kod musi być używany co_await
i co_yield
zamiast tego. Tryb standardowy nie zezwala również na korzystanie z return
metody w kohroutynie. Każdy return
w kohroutynie musi używać .co_return
// /await
task f_legacy() {
...
await g();
return n;
}
// /std:c++latest
task f() {
...
co_await g();
co_return n;
}
Typy initial_suspend/final_suspend
W obszarze /await
obietnicy funkcje początkowe i wstrzymane mogą być zadeklarowane jako zwracane bool
. To zachowanie nie jest standardowe. W języku C++20 te funkcje muszą zwracać oczekiwany typ klasy, często jeden z trywialnych typów oczekujących: std::suspend_always
jeśli funkcja wcześniej zwróciła true
wartość , lub std::suspend_never
jeśli zwróciła false
wartość .
// /await
struct promise_type_legacy {
bool initial_suspend() noexcept { return false; }
bool final_suspend() noexcept { return true; }
...
};
// /std:c++latest
struct promise_type {
auto initial_suspend() noexcept { return std::suspend_never{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
...
};
Typ yield_value
W języku C++20 funkcja promise yield_value
musi zwrócić oczekiwany typ. W /await
trybie yield_value
funkcja mogła zwrócić void
funkcję i zawsze zawieszała się. Takie funkcje można zastąpić funkcją zwracającą wartość std::suspend_always
.
// /await
struct promise_type_legacy {
...
void yield_value(int x) { next = x; };
};
// /std:c++latest
struct promise_type {
...
auto yield_value(int x) { next = x; return std::suspend_always{}; }
};
Funkcja obsługi wyjątków
/await
obsługuje typ obietnicy bez funkcji obsługi wyjątków lub funkcji obsługi wyjątków o nazwie set_exception
, która przyjmuje std::exception_ptr
wartość . W języku C++20 typ obietnicy musi mieć funkcję o nazwie unhandled_exception
, która nie przyjmuje żadnych argumentów. Obiekt wyjątku można uzyskać w std::current_exception
razie potrzeby.
// /await
struct promise_type_legacy {
void set_exception(std::exception_ptr e) { saved_exception = e; }
...
};
// /std:c++latest
struct promise_type {
void unhandled_exception() { saved_exception = std::current_exception(); }
...
};
Dedukowane typy zwracane coroutines nie są obsługiwane
Język C++20 nie obsługuje kohroutyn z typem zwrotnym, który zawiera typ symbolu zastępczego, taki jak auto
. Typy zwracane coroutines muszą być jawnie zadeklarowane. W obszarze /await
typy te zawsze wiążą się z typem eksperymentalnym i wymagają dołączenia nagłówka definiującego wymagany typ: jeden z std::experimental::task<T>
, std::experimental::generator<T>
lub std::experimental::async_stream<T>
.
// /await
auto my_generator() {
...
co_yield next;
};
// /std:c++latest
#include <experimental/generator>
std::experimental::generator<int> my_generator() {
...
co_yield next;
};
Zwracany typ return_value
Zwracany typ funkcji obietnicy return_value
musi mieć wartość void
. W /await
trybie zwracany typ może być dowolny i jest ignorowany. Ta diagnostyka może pomóc wykrywać subtelne błędy, takie jak wtedy, gdy autor niepoprawnie zakłada, że zwracana jest wartość return_value
zwracana do obiektu wywołującego.
// /await
struct promise_type_legacy {
...
int return_value(int x) { return x; } // incorrect, the return value of this function is unused and the value is lost.
};
// /std:c++latest
struct promise_type {
...
void return_value(int x) { value = x; }; // save return value
};
Zwracanie zachowania konwersji obiektu
Jeśli zadeklarowany typ zwracany coroutine nie jest zgodny z zwracanym typem funkcji obietnicy get_return_object
, obiekt zwrócony z get_return_object
zostanie przekonwertowany na zwracany typ coroutine. W obszarze /await
ta konwersja jest wykonywana wcześnie, zanim treść koprutynowa ma szansę wykonać. W /std:c++20
programie lub /std:c++latest
ta konwersja jest wykonywana, gdy wartość jest zwracana do elementu wywołującego. Umożliwia ona współistnienie, które nie są zawieszone w początkowym punkcie wstrzymania, aby korzystać z obiektu zwróconego przez get_return_object
wewnątrz ciała koprutynowego.
Parametry obietnicy coroutine
W języku C++20 kompilator próbuje przekazać parametry coroutine (jeśli istnieją) do konstruktora typu obietnicy. Jeśli zakończy się niepowodzeniem, ponawia próbę z domyślnym konstruktorem. W /await
trybie użyto tylko domyślnego konstruktora. Ta zmiana może prowadzić do różnicy w zachowaniu, jeśli obietnica ma wiele konstruktorów. Lub, jeśli istnieje konwersja z parametru coroutine na typ obietnicy.
struct coro {
struct promise_type {
promise_type() { ... }
promise_type(int x) { ... }
...
};
};
coro f1(int x);
// Under /await the promise gets constructed using the default constructor.
// Under /std:c++latest the promise gets constructed using the 1-argument constructor.
f1(0);
struct Object {
template <typename T> operator T() { ... } // Converts to anything!
};
coro f2(Object o);
// Under /await the promise gets constructed using the default constructor
// Under /std:c++latest the promise gets copy- or move-constructed from the result of
// Object::operator coro::promise_type().
f2(Object{});
/permissive-
Moduły i C++20 są domyślnie włączone w obszarze /std:c++20
Obsługa modułów języka C++20 jest domyślnie włączona w obszarze /std:c++20
i /std:c++latest
. Aby uzyskać więcej informacji na temat tej zmiany oraz scenariuszy, w których module
i import
są warunkowo traktowane jako słowa kluczowe, zobacz Standard C++20 Modules support with MSVC in Visual Studio 2019 version 16.8 (Obsługa modułów Standard C++20 w programie Visual Studio 2019 w wersji 16.8).
W ramach wymagań wstępnych dotyczących obsługi modułów funkcja jest teraz włączona, permissive-
gdy /std:c++20
określono funkcję lub /std:c++latest
. Aby uzyskać więcej informacji, zobacz /permissive-
.
W przypadku kodu, który został wcześniej skompilowany w ramach /std:c++latest
programu i wymaga niezgodnych zachowań kompilatora, można określić, /permissive
aby wyłączyć tryb ścisłej zgodności w kompilatorze. Opcja kompilatora musi pojawić się po /std:c++latest
na liście argumentów wiersza polecenia. Jednak /permissive
powoduje wystąpienie błędu, jeśli zostanie wykryte użycie modułów:
błąd C1214: Moduły powodują konflikt z nietypowym zachowaniem żądanym za pośrednictwem opcji
Najbardziej typowe wartości opcji to:
Opcja | Opis |
---|---|
/Zc:twoPhase- |
Wyszukiwanie dwufazowej nazwy jest wymagane dla modułów języka C++20 i implikowane przez program /permissive- . |
/Zc:hiddenFriend- |
Standardowe reguły wyszukiwania ukrytych nazw znajomych są wymagane dla modułów języka C++20 i implikowane przez /permissive- program . |
/Zc:lambda- |
Standardowe przetwarzanie lambda jest wymagane dla modułów języka C++20 i jest implikowane w /std:c++20 trybie lub nowszym. |
/Zc:preprocessor- |
Zgodny preprocesor jest wymagany tylko do użycia jednostek nagłówka języka C++20 i tworzenia. Nazwane moduły nie wymagają tej opcji. |
Opcja jest nadal wymagana /experimental:module
do używania std.*
modułów dostarczonych z programem Visual Studio, ponieważ nie są one jeszcze ustandaryzowane.
Opcja /experimental:module
oznacza /Zc:twoPhase
również , /Zc:lambda
i /Zc:hiddenFriend
. Wcześniej kod skompilowany za pomocą modułów może być czasami kompilowany, /Zc:twoPhase-
jeśli moduł był używany tylko. To zachowanie nie jest już obsługiwane.
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.9
Inicjalizacja kopiowania tymczasowego w odwołaniu do inicjowania bezpośredniego
Problem z podstawową grupą roboczą CWG 2267 dotyczył niespójności między listą inicjatora nawiasów a listą inicjatorów nawiasów klamrowych. Rozdzielczość zharmonizuje te dwie formy.
Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std
trybach kompilatora. Jednak ponieważ jest to potencjalnie zmiana powodująca niezgodność źródła, jest obsługiwana tylko wtedy, gdy kod jest kompilowany przy użyciu polecenia /permissive-
.
W tym przykładzie pokazano zmianę zachowania:
struct A { };
struct B {
explicit B(const A&);
};
void f()
{
A a;
const B& b1(a); // Always an error
const B& b2{ a }; // Allowed before resolution to CWG 2267 was adopted: now an error
}
Cechy destruktora i potencjalnie skonstruowane podobiekty
Problem z podstawową grupą roboczą CWG 2336 obejmuje pominięcie niejawnych specyfikacji wyjątków destruktorów w klasach, które mają wirtualne klasy bazowe. Pominięcie oznaczało, że destruktor w klasie pochodnej może mieć słabszą specyfikację wyjątku niż klasa bazowa, jeśli ta baza była abstrakcyjna i miała virtual
bazę.
Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std
trybach kompilatora.
W tym przykładzie pokazano, jak zmieniła się interpretacja:
class V {
public:
virtual ~V() noexcept(false);
};
class B : virtual V {
virtual void foo () = 0;
// BEFORE: implicitly defined virtual ~B() noexcept(true);
// AFTER: implicitly defined virtual ~B() noexcept(false);
};
class D : B {
virtual void foo ();
// implicitly defined virtual ~D () noexcept(false);
};
Przed tą zmianą niejawnie zdefiniowany destruktor for B
to noexcept
, ponieważ rozważane są tylko potencjalnie konstruowane podobiekty. Klasa bazowa V
nie jest potencjalnie skonstruowanym podobiektem, ponieważ jest bazą virtual
i B
jest abstrakcyjna. Jednak klasa V
bazowa jest potencjalnie skonstruowanym podobiektem klasy D
, a więc D::~D
jest określana jako noexcept(false)
, co prowadzi do klasy pochodnej ze słabszą specyfikacją wyjątku niż jej podstawowa. Ta interpretacja jest niebezpieczna. Może to prowadzić do nieprawidłowego zachowania środowiska uruchomieniowego, jeśli wyjątek zostanie zgłoszony z destruktora klasy pochodzącej z klasy B.
Dzięki tej zmianie destruktor jest również potencjalnie zgłaszany, jeśli ma wirtualny destruktor, a każda wirtualna klasa bazowa ma potencjalnie zgłaszający destruktor.
Podobne typy i powiązanie odwołania
Podstawowy problem z grupą roboczą CWG 2352 dotyczy niespójności między regułami powiązania odwołania a zmianami podobieństwa typu. Niespójność została wprowadzona we wcześniejszych raportach o wadach (takich jak CWG 330). Dotyczy to programu Visual Studio 2019 w wersji od 16.0 do 16.8.
Po tej zmianie, począwszy od programu Visual Studio 2019 w wersji 16.9, kod, który wcześniej powiązał odwołanie do tymczasowego odwołania w programie Visual Studio 2019 w wersji 16.0 do 16.8, może teraz wiązać się bezpośrednio, gdy powiązane typy różnią się tylko przez kwalifikatory cv.
Program Visual Studio 2019 w wersji 16.9 implementuje zmienione zachowanie we wszystkich /std
trybach kompilatora. Jest to potencjalnie zmiana powodująca niezgodność źródła.
Zobacz Odwołania do typów z niezgodnymi kwalifikatorami cv, aby uzyskać powiązaną zmianę.
W tym przykładzie pokazano zmienione zachowanie:
int *ptr;
const int *const &f() {
return ptr; // Now returns a reference to 'ptr' directly.
// Previously returned a reference to a temporary and emitted C4172
}
Aktualizacja może zmienić zachowanie programu, które polegało na wprowadzonym tymczasowym:
int func() {
int i1 = 13;
int i2 = 23;
int* iptr = &i1;
int const * const& iptrcref = iptr;
// iptrcref is a reference to a pointer to i1 with value 13.
if (*iptrcref != 13)
{
return 1;
}
// Now change what iptr points to.
// Prior to CWG 2352 iptrcref should be bound to a temporary and still points to the value 13.
// After CWG 2352 it is bound directly to iptr and now points to the value 23.
iptr = &i2;
if (*iptrcref != 23)
{
return 1;
}
return 0;
}
/Zc:twoPhase
i /Zc:twoPhase-
zmiana zachowania opcji
Zwykle opcje kompilatora MSVC działają na zasadzie, że ostatni widział wygrywa. Niestety, tak nie było w przypadku opcji /Zc:twoPhase
i /Zc:twoPhase-
. Te opcje były "lepkie", więc późniejsze opcje nie mogły je zastąpić. Na przykład:
cl /Zc:twoPhase /permissive a.cpp
W tym przypadku pierwsza /Zc:twoPhase
opcja umożliwia ścisłe wyszukiwanie nazw dwufazowych. Druga opcja jest przeznaczona do wyłączenia trybu ścisłej zgodności (jest to przeciwieństwo /permissive-
), ale nie wyłączył /Zc:twoPhase
.
Program Visual Studio 2019 w wersji 16.9 zmienia to zachowanie we wszystkich /std
trybach kompilatora. /Zc:twoPhase
i /Zc:twoPhase-
nie są już "lepkie", a późniejsze opcje mogą je zastąpić.
Jawne specyfikatory noexcept w szablonach destruktorów
Kompilator wcześniej zaakceptował szablon destruktora zadeklarowany przy użyciu specyfikacji wyjątku niezrzucania, ale zdefiniowany bez jawnego specyfikatora noexcept. Niejawna specyfikacja wyjątku destruktora zależy od właściwości klasy — właściwości, które mogą nie być znane w punkcie definicji szablonu. Standard C++ wymaga również tego zachowania: jeśli destruktor jest zadeklarowany bez specyfikatora noexcept, ma niejawną specyfikację wyjątku, a żadna inna deklaracja funkcji może mieć specyfikator noexcept-.
Program Visual Studio 2019 w wersji 16.9 zmienia zachowanie zgodne ze wszystkimi /std
trybami kompilatora.
W tym przykładzie przedstawiono zmianę zachowania kompilatora:
template <typename T>
class B {
virtual ~B() noexcept; // or throw()
};
template <typename T>
B<T>::~B() { /* ... */ } // Before: no diagnostic.
// Now diagnoses a definition mismatch. To fix, define the implementation by
// using the same noexcept-specifier. For example,
// B<T>::~B() noexcept { /* ... */ }
Ponowne napisanie wyrażeń w języku C++20
Od wersji 16.2 programu Visual Studio 2019 w obszarze /std:c++latest
kompilator zaakceptował kod podobny do następującego przykładu:
#include <compare>
struct S {
auto operator<=>(const S&) const = default;
operator bool() const;
};
bool f(S a, S b) {
return a < b;
}
Jednak kompilator nie wywoła funkcji porównania, która może oczekiwać autor. Powyższy kod powinien zostać przepisany a < b
jako (a <=> b) < 0
. Zamiast tego kompilator użył funkcji konwersji zdefiniowanej operator bool()
przez użytkownika i porównał bool(a) < bool(b)
element . W programie Visual Studio 2019 w wersji 16.9 lub nowszej kompilator ponownie zapisuje wyrażenie przy użyciu oczekiwanego wyrażenia operatora statków kosmicznych.
Zmiana powodująca niezgodność źródła
Prawidłowe stosowanie konwersji do wyrażeń przepisanych ma inny efekt: kompilator poprawnie diagnozuje niejednoznaczności z prób ponownego zapisania wyrażenia. Rozważ taki przykład:
struct Base {
bool operator==(const Base&) const;
};
struct Derived : Base {
Derived();
Derived(const Base&);
bool operator==(const Derived& rhs) const;
};
bool b = Base{} == Derived{};
W języku C++17 ten kod zostanie zaakceptowany z powodu pochodnej konwersji Derived
na bazę po prawej stronie wyrażenia. W języku C++20 dodawany jest również syntetyzowany kandydat wyrażenia: Derived{} == Base{}
. Ze względu na reguły w standardzie o tym, która funkcja wygrywa na podstawie konwersji, okazuje się, że wybór między Base::operator==
i Derived::operator==
jest niezdecydowalny. Ponieważ sekwencje konwersji w dwóch wyrażeniach nie są lepsze lub gorsze od siebie, przykładowy kod powoduje niejednoznaczność.
Aby rozwiązać niejednoznaczność, dodaj nowego kandydata, który nie będzie podlegać dwóm sekwencjom konwersji:
bool operator==(const Derived&, const Base&);
Zmiana powodująca niezgodność w czasie wykonywania
Ze względu na reguły ponownego zapisywania operatorów w języku C++20 istnieje możliwość znalezienia nowego kandydata, który w przeciwnym razie nie znajdzie się w trybie niższego języka. A nowy kandydat może być lepszym dopasowaniem niż starszy kandydat. Rozważ taki przykład:
struct iterator;
struct const_iterator {
const_iterator(const iterator&);
bool operator==(const const_iterator &ci) const;
};
struct iterator {
bool operator==(const const_iterator &ci) const { return ci == *this; }
};
W języku C++17 jedynym kandydatem jest ci == *this
const_iterator::operator==
. Jest to dopasowanie, ponieważ *this
przechodzi przez konwersję pochodną na bazę na const_iterator
wartość . W języku C++20 dodawany jest inny ponownie napisany kandydat: *this == ci
, który wywołuje element iterator::operator==
. Ten kandydat nie wymaga konwersji, więc jest to lepsze dopasowanie niż const_iterator::operator==
. Problem z nowym kandydatem polega na tym, że funkcja jest obecnie zdefiniowana, więc nowa semantyka funkcji powoduje nieskończenie rekursywną definicję iterator::operator==
.
Aby ułatwić kod podobny do przykładu, kompilator implementuje nowe ostrzeżenie:
$ cl /std:c++latest /c t.cpp
t.cpp
t.cpp(8): warning C5232: in C++20 this comparison calls 'bool iterator::operator ==(const const_iterator &) const' recursively
Aby naprawić kod, należy jawnie ustalić, której konwersji użyć:
struct iterator {
bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.10
Nieprawidłowe przeciążenie wybrane do inicjowania kopiowania klasy
Biorąc pod uwagę ten przykładowy kod:
struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});
Wcześniejsze wersje kompilatora niepoprawnie przekonwertowałyby argument typu f
na C
A
element przy użyciu szablonowego konstruktora konwertującego A
wartość . Zamiast tego język C++ w warstwie Standardowa wymaga użycia operatora B::operator A
konwersji. W programie Visual Studio 2019 w wersji 16.10 lub nowszej zachowanie rozpoznawania przeciążenia zostanie zmienione w celu użycia poprawnego przeciążenia.
Ta zmiana może również poprawić wybrane przeciążenie w niektórych innych sytuacjach:
struct Base
{
operator char *();
};
struct Derived : public Base
{
operator bool() const;
};
void f(Derived &d)
{
// Implicit conversion to bool previously used Derived::operator bool(), now uses Base::operator char*.
// The Base function is preferred because operator bool() is declared 'const' and requires a qualification
// adjustment for the implicit object parameter, while the Base function does not.
if (d)
{
// ...
}
}
Niepoprawne analizowanie literałów zmiennoprzecinkowych
W programie Visual Studio 2019 w wersji 16.10 lub nowszej literały zmiennoprzecinkowe są analizowane na podstawie ich rzeczywistego typu. Wcześniejsze wersje kompilatora zawsze analizowały literał zmiennoprzecinkowa tak, jakby miał typ double
, a następnie przekonwertował wynik na rzeczywisty typ. To zachowanie może prowadzić do nieprawidłowego zaokrąglenia i odrzucenia prawidłowych wartości:
// The binary representation is '0x15AE43FE' in VS2019 16.9
// The binary representation is '0x15AE43FD' in VS2019 16.10
// You can use 'static_cast<float>(7.038531E-26)' if you want the old behavior.
float f = 7.038531E-26f;
Niepoprawny punkt deklaracji
Wcześniejsze wersje kompilatora nie mogły skompilować kodu odwołującego się do siebie w następujący przykład:
struct S {
S(int, const S*);
int value() const;
};
S s(4, &s);
Kompilator nie zadeklarowałby zmiennej s
, dopóki nie przeanalizuje całej deklaracji, w tym argumentów konstruktora. Wyszukiwanie elementu s
na liście argumentów konstruktora zakończy się niepowodzeniem. W programie Visual Studio 2019 w wersji 16.10 lub nowszej ten przykład jest teraz poprawnie kompilowany.
Niestety ta zmiana może uszkodzić istniejący kod, jak w tym przykładzie:
S s(1, nullptr); // outer s
// ...
{
S s(s.value(), nullptr); // inner s
}
We wcześniejszych wersjach kompilatora, gdy wyszukuje s
on argumenty konstruktora dla deklaracji "wewnętrznej" , znajduje poprzednią deklarację s
("outer" s
) i kompiluje kod. Począwszy od wersji 16.10, kompilator emituje ostrzeżenie C4700 zamiast tego. Jest to spowodowane tym, że kompilator deklaruje teraz "wewnętrzny" s
przed przeanalizowanie argumentów konstruktora. s
Dlatego wyszukiwanie znajduje element "wewnętrzny"s
, który nie został jeszcze zainicjowany.
Jawnie wyspecjalizowany element członkowski szablonu klasy
Wcześniejsze wersje kompilatora niepoprawnie oznaczyły jawną specjalizację składowej szablonu klasy, tak jakby inline
została również zdefiniowana w szablonie podstawowym. To zachowanie oznaczało, że kompilator czasami odrzuca kod zgodny. W programie Visual Studio 2019 w wersji 16.10 lub nowszej jawna specjalizacja nie jest już niejawnie oznaczona jako inline
w /permissive-
trybie. Rozważ taki przykład:
Plik s.h
źródłowy:
// s.h
template<typename T>
struct S {
int f() { return 1; }
};
template<> int S<int>::f() { return 2; }
Plik s.cpp
źródłowy:
// s.cpp
#include "s.h"
Plik main.cpp
źródłowy:
// main.cpp
#include "s.h"
int main()
{
}
Aby rozwiązać problem z błędem konsolidatora w powyższym przykładzie, dodaj inline
jawnie do S<int>::f
elementu :
template<> inline int S<int>::f() { return 2; }
Dedukowana zwracana nazwa typu mangling
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator zmienił sposób generowania nazw mangled dla funkcji, które spowodowały zwracanie typów. Rozważmy na przykład następujące funkcje:
auto f() { return 0; }
auto g() { []{}; return 0; }
Wcześniejsze wersje kompilatora wygenerowałyby te nazwy konsolidatora:
f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)
Co zaskakujące, typ zwracany zostanie pominięty z g
powodu innego zachowania semantycznego spowodowanego przez lokalną lambdę w treści funkcji. Ta niespójność utrudniała zaimplementowanie wyeksportowanych funkcji, które mają typ zwracany: interfejs modułu wymaga informacji na temat sposobu kompilowania treści funkcji. Potrzebuje informacji, aby utworzyć funkcję po stronie importu, która może prawidłowo połączyć się z definicją.
Kompilator pomija teraz zwracany typ funkcji zwracanego typu. To zachowanie jest zgodne z innymi głównymi implementacjami. Istnieje wyjątek dla szablonów funkcji: ta wersja kompilatora wprowadza nowe zachowanie mangled-name dla szablonów funkcji, które mają typ zwracany:
template <typename T>
auto f(T) { return 1; }
template <typename T>
decltype(auto) g(T) { return 1.; }
int (*fp1)(int) = &f;
double (*fp2)(int) = &g;
Nazwy mangled dla auto
i decltype(auto)
teraz pojawiają się w pliku binarnym, a nie wywoływanego typu zwracanego:
f: ??$f@H@@YA?A_PH@Z -> auto __cdecl f<int>(int)
g: ??$g@H@@YA?A_TH@Z -> decltype(auto) __cdecl g<int>(int)
Wcześniejsze wersje kompilatora obejmowałyby wywołany typ zwracany w ramach podpisu. Gdy kompilator uwzględnił typ zwracany w nazwie mangled, może to spowodować problemy z konsolidatorem. Niektóre w przeciwnym razie dobrze sformułowane scenariusze stałyby się niejednoznaczne dla konsolidatora.
Nowe zachowanie kompilatora może spowodować zmianę powodującą niezgodność binarną. Rozważ taki przykład:
Plik a.cpp
źródłowy:
// a.cpp
auto f() { return 1; }
Plik main.cpp
źródłowy:
// main.cpp
int f();
int main() { f(); }
W wersjach wcześniejszych niż 16.10 kompilator opracował nazwę, auto f()
która wyglądała jak int f()
, mimo że są one semantycznie odrębnymi funkcjami. Oznacza to, że przykład zostanie skompilowany. Aby rozwiązać ten problem, nie należy polegać na auto
oryginalnej definicji elementu f
. Zamiast tego zapisz go jako int f()
. Ponieważ funkcje, które wywoływały typy zwracane, są zawsze kompilowane, implikacje ABI są zminimalizowane.
Ostrzeżenie dotyczące ignorowanych nodiscard
atrybutów
Poprzednie wersje kompilatora dyskretnie ignorują pewne zastosowania atrybutu nodiscard
. Zignorowali atrybut, jeśli znajdował się on w pozycji składniowej, która nie miała zastosowania do zadeklarowanej funkcji ani klasy. Na przykład:
static [[nodiscard]] int f() { return 1; }
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje ostrzeżenie poziomu 4 C5240 zamiast:
a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position
Aby rozwiązać ten problem, przenieś atrybut do poprawnej pozycji składni:
[[nodiscard]] static int f() { return 1; }
Ostrzeżenie dotyczące include
dyrektyw z nazwami nagłówków systemu w module purview
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje ostrzeżenie, aby zapobiec błędowi tworzenia wspólnego interfejsu modułu. Jeśli po instrukcji export module
dołączysz nagłówek biblioteki standardowej, kompilator emituje ostrzeżenie C5244. Oto przykład:
export module m;
#include <vector>
export
void f(std::vector<int>);
Deweloper prawdopodobnie nie zamierzał, aby moduł m
był właścicielem zawartości .<vector>
Kompilator emituje teraz ostrzeżenie ułatwiające znalezienie i rozwiązanie problemu:
m.ixx(2): warning C5244: '#include <vector>' in the purview of module 'm' appears erroneous. Consider moving that directive before the module declaration, or replace the textual inclusion with an "import <vector>;".
m.ixx(1): note: see module 'm' declaration
Aby rozwiązać ten problem, przejdź #include <vector>
przed export module m;
:
#include <vector>
export module m;
export
void f(std::vector<int>);
Ostrzeżenie dotyczące nieużywanych funkcji połączenia wewnętrznego
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator ostrzega w kolejnych sytuacjach, w których usunięto funkcję bez wnioskowania z wewnętrznym połączeniem. Wcześniejsze wersje kompilatora emitują ostrzeżenie C4505 dla następującego kodu:
static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}
Kompilator ostrzega teraz również o funkcjach nieużywanych auto
i nieużywanych funkcjach w anonimowych przestrzeniach nazw. Emituje ostrzeżenie off-by-default C5245 dla obu następujących funkcji:
namespace
{
void f1() // warning C5245: '`anonymous-namespace'::f1': unreferenced function with internal linkage has been removed
{
}
}
auto f2() // warning C5245: 'f2': unreferenced function with internal linkage has been removed
{
return []{ return 13; };
}
Ostrzeżenie dotyczące elizji nawiasu klamrowego
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator ostrzega przed listami inicjowania, które nie używają nawiasów klamrowych dla podobiektów. Kompilator emituje domyślnie ostrzeżenie off-by-default C5246.
Oto przykład:
struct S1 {
int i, j;
};
struct S2 {
S1 s1;
int k;
};
S2 s2{ 1, 2, 3 }; // warning C5246: 'S2::s1': the initialization of a subobject should be wrapped in braces
Aby rozwiązać ten problem, opakuj inicjowanie podobiektu w nawiasach klamrowych:
S2 s2{ { 1, 2 }, 3 };
Poprawnie wykryj, czy const
obiekt nie został zainicjowany
W programie Visual Studio 2019 w wersji 16.10 lub nowszej kompilator emituje teraz błąd C2737 podczas próby zdefiniowania const
obiektu, który nie jest w pełni zainicjowany:
struct S {
int i;
int j = 2;
};
const S s; // error C2737: 's': const object must be initialized
Wcześniejsze wersje kompilatora umożliwiły kompilowanie tego kodu, mimo że S::i
nie zostało zainicjowane.
Aby rozwiązać ten problem, zainicjuj wszystkie elementy członkowskie przed utworzeniem const
wystąpienia obiektu:
struct S {
int i = 1;
int j = 2;
};
Ulepszenia zgodności w programie Visual Studio 2019 w wersji 16.11
/std:c++20
tryb kompilatora
W programie Visual Studio 2019 w wersji 16.11 lub nowszej kompilator obsługuje teraz tryb kompilatora /std:c++20
. Wcześniej funkcje języka C++20 były dostępne tylko w /std:c++latest
trybie w programie Visual Studio 2019. Funkcje języka C++20, które pierwotnie wymagały /std:c++latest
trybu, działają teraz w /std:c++20
trybie lub nowszym w najnowszych wersjach programu Visual Studio.