Peningkatan Kesesuaian C++, perubahan perilaku, dan perbaikan bug pada Visual Studio 2019

Microsoft C/C++ di Visual Studio (MSVC) melakukan peningkatan kesesuaian dan perbaikan bug di setiap rilis. Artikel ini mencantumkan peningkatan menurut rilis utama, lalu menurut versi. Untuk langsung beralih ke perubahan untuk versi tertentu, gunakan daftar di bawah ini Dalam artikel ini.

Dokumen ini mencantumkan perubahan di Visual Studio 2019. Untuk panduan perubahan di Visual Studio 2022, lihat Peningkatan kesesuaian C++ di Visual Studio 2022. Untuk perubahan di Visual Studio 2017, lihat Peningkatan kesesuaian C++ di Visual Studio 2017. Untuk daftar lengkap peningkatan kesesuaian sebelumnya, lihat Visual C++ Apa yang Baru di 2003 hingga 2015.

Peningkatan kesesuaian di Visual Studio 2019 RTW (versi 16.0)

Visual Studio 2019 RTW berisi peningkatan kesesuaian berikut, perbaikan bug, dan perubahan perilaku dalam pengompilasi Microsoft C++.

Catatan

Fitur C++20 hanya tersedia dalam mode /std:c++latest di Visual Studio 2019 hingga implementasi C++20 dianggap selesai. Visual Studio 2019 versi 16.11 memperkenalkan /std:c++20 mode pengompilasi. Dalam artikel ini, fitur yang awalnya memerlukan mode /std:c++latest sekarang berfungsi dalam mode /std:c++20 atau yang lebih baru dalam versi terbaru Visual Studio. Kami telah memperbarui dokumentasi untuk menyebutkan /std:c++20, meskipun opsi ini tidak tersedia saat fitur pertama kali dirilis.

Dukungan modul yang disempurnakan untuk templat dan deteksi kesalahan

Sekarang modul secara resmi dalam standar C++20. Dukungan yang ditingkatkan ditambahkan di Visual Studio 2017 versi 15.9. Untuk informasi selengkapnya, lihat Dukungan templat yang lebih baik dan deteksi kesalahan dalam Modul C++ dengan MSVC 2017 versi 15.9.

Spesifikasi yang dimodifikasi dari tipe agregat

Spesifikasi tipe agregat telah berubah di C++20 (lihat Melarang agregat dengan konstruktor yang dideklarasikan pengguna). Di Visual Studio 2019, di bawah /std:c++latest (atau /std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru), kelas dengan konstruktor yang dideklarasikan pengguna (misalnya, termasuk konstruktor yang dideklarasikan = default atau = delete) bukan agregat. Sebelumnya, hanya konstruktor yang disediakan pengguna yang akan mendiskualifikasi kelas agar tidak menjadi agregat. Perubahan ini menempatkan lebih banyak batasan tentang cara tipe tersebut dapat diinisialisasi.

Kode berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi menimbulkan kesalahan C2280 dan C2440 di Visual Studio 2019 di bawah /std:c++20 atau /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

Dukungan parsial untuk operator <=>

P0515R3 C++20 memperkenalkan <=> operator perbandingan tiga arah, juga dikenal sebagai "operator pesawat ruang angkasa". Visual Studio 2019 versi 16.0 dalam mode /std:c++latest memperkenalkan dukungan parsial untuk operator dengan memunculkan kesalahan untuk sintaks yang sekarang tidak diizinkan. Misalnya, kode berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi menimbulkan beberapa kesalahan di Visual Studio 2019 di bawah /std:c++20 atau /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.
}

Untuk menghindari kesalahan, masukkan spasi di baris yang menyinggung sebelum kurung sudut akhir: U<&S::operator<= > u;.

Referensi ke tipe dengan pengkualifikasi cv yang tidak cocok

Catatan

Perubahan ini hanya memengaruhi Visual Studio 2019 versi 16.0 hingga 16.8. Ini dikembalikan mulai di Visual Studio 2019 versi 16.9

Sebelumnya, MSVC mengizinkan pengikatan langsung referensi dari tipe dengan pengkualifikasi cv yang tidak cocok di bawah tingkat atas. Pengikatan ini dapat memungkinkan modifikasi data konstituen yang dirujuk oleh referensi.

Pengkompilasi untuk Visual Studio 2019 versi 16.0 hingga 16.8 sebagai gantinya membuat sementara, seperti yang diperlukan oleh standar pada saat itu. Kemudian, standar berubah secara retroaktif membuat perilaku Visual Studio 2017 sebelumnya dan yang lebih lama benar, dan perilaku Visual Studio 2019 versi 16.0 hingga 16.8 salah. Akibatnya, perubahan ini dikembalikan mulai Visual Studio 2019 versi 16.9.

Lihat Jenis dan pengikatan referensi serupa untuk perubahan terkait.

Sebagai contoh, di Visual Studio 2017, kode berikut dikompilasi tanpa peringatan. Di Visual Studio 2019 versi 16.0 hingga 16.8, pengkompilasi meningkatkan peringatan C4172. Dimulai dengan Visual Studio 2019 versi 16.9, kode sekali lagi dikompilasi tanpa peringatan:

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 dari fungsi overload

Argumen ke reinterpret_cast bukan salah satu konteks di mana alamat fungsi overload diizinkan. Kode berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi di Visual Studio 2019, ini menimbulkan kesalahan 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'
}

Untuk menghindari kesalahan, gunakan transmisi yang diizinkan untuk skenario ini:

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

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

Penutupan Lambda

Di C++14, tipe penutupan lambda tidak harfiah. Konsekuensi utama dari aturan ini adalah bahwa lambda mungkin tidak ditetapkan ke constexpr variabel. Kode berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi di Visual Studio 2019, ini menimbulkan kesalahan C2127:

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

Untuk menghindari kesalahan, hapus pengkualifikasi constexpr, atau ubah mode kesesuaian ke /std:c++17 atau yang lebih baru.

std::create_directory kode kegagalan

Menerapkan P1164 dari C++20 tanpa syarat. Perubahan std::create_directory ini ditujukan untuk memeriksa apakah target sudah menjadi direktori yang gagal. Sebelumnya, semua kesalahan tipe ERROR_ALREADY_EXISTS diubah menjadi kode success-but-directory-not-created.

operator<<(std::ostream, nullptr_t)

Per LWG 2221, menambahkan operator<<(std::ostream, nullptr_t) untuk menulis nullptr ke aliran.

Lebih banyak algoritma paralel

Versi paralel baru dari is_sorted, is_sorted_until, is_partitioned, set_difference, set_intersection, is_heap, dan is_heap_until.

Perbaikan dalam inisialisasi atom

P0883 "Memperbaiki inisialisasi atom" mengubah std::atomic menjadi menginisialisasi nilai T yang terkandung daripada menginisialisasi default. Perbaikan diaktifkan saat menggunakan Clang/LLVM dengan pustaka standar Microsoft. Saat ini dinonaktifkan untuk pengompilasi Microsoft C++, sebagai solusi untuk bug dalam pemrosesan constexpr.

remove_cvref dan remove_cvref_t

Menerapkan sifat tipe remove_cvref dan remove_cvref_t dari P0550. Ini menghapus referensi-ness dan cv-kualifikasi dari suatu tipe tanpa merusak fungsi dan array ke pointer (tidak seperti std::decay dan std::decay_t).

Makro uji fitur

P0941R2 - makro uji fitur selesai, dengan dukungan untuk __has_cpp_attribute. Makro uji fitur didukung di semua mode standar.

Melarang agregat dengan konstruktor yang dideklarasikan pengguna

C++20 P1008R1 - melarang agregat dengan konstruktor yang dideklarasikan pengguna selesai.

reinterpret_cast dalam fungsi constexpr

reinterpret_cast ilegal dalam fungsi constexpr. Pengompilasi Microsoft C++ sebelumnya hanya akan menolak reinterpret_cast jika digunakan dalam konteks constexpr. Di Visual Studio 2019, dalam semua mode standar bahasa, pengompilasi mendiagnosis reinterpret_cast dengan benar dalam definisi fungsi constexpr. Kode berikut sekarang menghasilkan C3615:

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

Untuk menghindari kesalahan, hapus pengubah constexpr dari deklarasi fungsi.

Diagnostik yang benar untuk konstruktor rentang basic_string

Di Visual Studio 2019, konstruktor rentang basic_string tidak lagi menekan diagnostik pengompilasi dengan static_cast. Kode berikut mengompilasi tanpa peringatan di Visual Studio 2017, meskipun kemungkinan kehilangan data dari wchar_t ke char saat menginisialisasi out:

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

Visual Studio 2019 memunculkan peringatan C4244 dengan benar. Untuk menghindari peringatan, Anda dapat menginisialisasi std::string seperti yang ditunjukkan dalam contoh ini:

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

Panggilan yang salah ke += dan -= di bawah /clr atau /ZW sekarang terdeteksi dengan benar

Bug diperkenalkan di Visual Studio 2017 yang menyebabkan pengompilasi secara diam-diam mengabaikan kesalahan dan tidak menghasilkan kode untuk panggilan yang tidak valid ke += dan -= di bawah /clr atau /ZW. Kode berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi di Visual Studio 2019, ini menimbulkan kesalahan C2845 dengan benar:

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

Untuk menghindari kesalahan dalam contoh ini, gunakan operator += dengan metode ToString(): s += E::e.ToString();.

Penginisialisasi untuk anggota data statis sebaris

Akses anggota yang tidak valid di dalam penginisialisasi inline dan static constexpr sekarang terdeteksi dengan benar. Contoh berikut mengompilasi tanpa kesalahan di Visual Studio 2017, tetapi di Visual Studio 2019 di bawah mode /std:c++17 atau yang lebih baru menimbulkan kesalahan 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'.
};

Untuk menghindari kesalahan, nyatakan anggota X::c sebagai dilindungi:

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

C4800 dipulihkan

MSVC digunakan untuk memiliki peringatan performa C4800 tentang konversi implisit ke bool. Itu terlalu berisik dan tidak bisa ditekan, sehingga kami menghapusnya di Visual Studio 2017. Namun, selama siklus hidup Visual Studio 2017, kami mendapatkan banyak umpan balik tentang kasus-kasus berguna yang diselesaikannya. Kami menghadirkannya kembali di Visual Studio 2019 C4800 yang disesuaikan dengan hati-hati, bersama dengan penjelasan C4165. Kedua peringatan ini mudah ditekan: baik dengan menggunakan transmisi eksplisit, atau dibandingkan dengan 0 dari tipe yang sesuai. C4800 adalah peringatan tingkat 4 nonaktifkan secara default, dan C4165 adalah peringatan tingkat 3 nonaktifkan secara default. Keduanya dapat ditemukan dengan menggunakan opsi pengompilasi /Wall.

Contoh berikut menampilkan C4800 dan C4165 di bawah /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?
}

Untuk menghindari peringatan dalam contoh sebelumnya, Anda dapat menulis kode seperti ini:

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
}

Fungsi anggota kelas lokal tidak memiliki isi

Di Visual Studio 2017, peringatan C4822 hanya dimunculkan ketika opsi pengompilasi /w14822 diatur secara eksplisit. Ini tidak ditampilkan dengan /Wall. Di Visual Studio 2019, C4822 adalah peringatan nonaktifkan secara default, yang membuatnya dapat ditemukan di bawah /Wall tanpa harus mengatur /w14822 secara eksplisit.

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

Badan templat fungsi yang berisi pernyataan if constexpr

Di Visual Studio 2019 di bawah /std:c++20 atau /std:c++latest, badan fungsi templat yang memiliki pernyataan if constexpr mengaktifkan pemeriksaan terkait penguraian tambahan. Misalnya, di Visual Studio 2017, kode berikut menghasilkan C7510 hanya jika opsi /permissive- diatur. Di Visual Studio 2019, kode yang sama menimbulkan kesalahan bahkan ketika opsi /permissive diatur:

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

Untuk menghindari kesalahan, tambahkan kata kunci typename ke deklarasi a: typename T::Type a;.

Kode rakitan sebaris tidak didukung dalam ekspresi lambda

Tim Microsoft C++ baru-baru ini menyadari masalah keamanan di mana penggunaan perakit sebaris dalam lambda dapat menyebabkan kerusakan ebp (register alamat pengirim) saat runtime. Penyerang jahat mungkin bisa memanfaatkan skenario ini. Perakit sebaris hanya didukung pada x86, dan interaksi antara perakit sebaris dan pengompilasi lainnya buruk. Mengingat fakta-fakta ini dan sifat masalahnya, solusi paling aman untuk masalah ini adalah melarang perakit sebaris dalam ekspresi lambda.

Satu-satunya penggunaan perakit sebaris dalam ekspresi lambda yang telah kami temukan 'di alam liar' adalah untuk menangkap alamat pengembalian. Dalam skenario ini, Anda dapat menangkap alamat pengembalian di semua platform hanya dengan menggunakan intrinsik kompilator _ReturnAddress().

Kode berikut menghasilkan C7553 di Visual Studio 2017 15.9 dan versi Visual Studio yang lebih baru:

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

Untuk menghindari kesalahan, pindahkan kode rakitan ke fungsi bernama seperti yang ditunjukkan dalam contoh berikut:

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

Penelusuran kesalahan iterator dan std::move_iterator

Fitur penelusuran kesalahan iterator telah diajarkan untuk membuka bungkus std::move_iteratordengan benar. Misalnya, std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) sekarang dapat melibatkan jalur cepat memcpy.

Perbaikan untuk penerapan kata kunci <xkeycheck.h>

Penerapan pustaka standar di <xkeycheck.h> untuk makro yang menggantikan kata kunci telah diperbaiki. Pustaka sekarang mengeluarkan kata kunci masalah aktual yang terdeteksi daripada pesan umum. Ini juga mendukung kata kunci C++20, dan menghindari menipu IntelliSense mengatakan kata kunci acak adalah makro.

Tipe pengalokasi tidak digunakan lagi

std::allocator<void>, std::allocator::size_type, dan std::allocator::difference_type tidak digunakan lagi.

Peringatan yang benar untuk mempersempit konversi string

Menghapus static_cast palsu dari std::string yang tidak diminta oleh standar, dan yang secara tidak sengaja menekan peringatan penyempitan C4244. Upaya untuk memanggil std::string::string(const wchar_t*, const wchar_t*) sekarang mengeluarkan C4244 dengan benar tentang mempersempit wchar_t menjadi char.

Berbagai perbaikan untuk kebenaran <sistem file>

  • Gagal memperbaiki std::filesystem::last_write_time saat mencoba mengubah waktu penulisan terakhir direktori.
  • Konstruktor std::filesystem::directory_entry sekarang menyimpan hasil yang gagal, daripada melempar pengecualian, ketika diberikan jalur target yang tidak ada.
  • Versi std::filesystem::create_directory 2-parameter diubah untuk memanggil versi 1-parameter, karena fungsi CreateDirectoryExW yang mendasarinya akan digunakan copy_symlink ketika existing_p adalah symlink.
  • std::filesystem::directory_iterator tidak lagi gagal ketika symlink yang rusak ditemukan.
  • std::filesystem::space sekarang menerima jalur relatif.
  • std::filesystem::path::lexically_relative tidak lagi dikacaukan dengan garis miring di belakang, yang dilaporkan sebagai LWG 3096.
  • Bekerja di sekitar CreateSymbolicLinkW menolak jalur dengan garis miring ke depan di std::filesystem::create_symlink.
  • Bekerja di sekitar fungsi delete mode penghapusan POSIX yang ada di Windows 10 LTSB 1609, tetapi tidak dapat benar-benar menghapus file.
  • Konstruktor salin std::boyer_moore_searcher dan std::boyer_moore_horspool_searcher serta operator penetapan salin sekarang benar-benar menyalin sesuatu.

Algoritma paralel pada Windows 8 dan yang lebih baru

Pustaka algoritma paralel sekarang menggunakan kelompok WaitOnAddress asli dengan benar pada Windows 8 dan yang lebih baru, daripada selalu menggunakan Windows 7 dan versi palsu yang lebih lama.

std::system_category::message() spasi kosong

std::system_category::message() sekarang memotong spasi kosong di belakang dari pesan yang dikembalikan.

std::linear_congruential_engine dibagi nol

Beberapa kondisi yang akan menyebabkan std::linear_congruential_engine memicu pembagian dengan 0 telah diperbaiki.

Perbaikan untuk pembongkaran iterator

Beberapa mesin pembongkar iterator pertama kali diekspos untuk integrasi pengguna programmer di Visual Studio 2017 15.8. Hal ini dijelaskan dalam artikel Blog Tim C++ Fitur dan Perbaikan STL di VS 2017 15.8. Mesin ini tidak lagi membuka bungkus iterator yang berasal dari iterator pustaka standar. Misalnya, pengguna yang berasal dari std::vector<int>::iterator dan mencoba menyesuaikan perilaku sekarang mendapatkan perilaku yang disesuaikan saat memanggil algoritma pustaka standar, daripada perilaku penunjuk.

Fungsi kontainer reserve yang tidak diurutkan sekarang sebenarnya dicadangkan untuk elemen N, seperti yang dijelaskan dalam LWG 2156.

Penanganan waktu

  • Sebelumnya, beberapa nilai waktu yang diteruskan ke pustaka konkurensi akan meluap, misalnya, condition_variable::wait_for(seconds::max()). Sekarang diperbaiki, luapan mengubah perilaku pada siklus 29 hari yang tampaknya acak (ketika uint32_t milidetik diterima oleh API Win32 yang mendasarinya meluap).

  • Header <ctime> sekarang mendeklarasikan timespec dan timespec_get di namespace std dengan benar, dan juga mendeklarasikannya di namespace global.

Berbagai perbaikan untuk kontainer

  • Banyak fungsi kontainer internal pustaka standar telah dibuat private untuk pengalaman IntelliSense yang ditingkatkan. Lebih banyak perbaikan untuk menandai anggota seperti private yang diharapkan dalam rilis MSVC nanti.

  • Kami memperbaiki masalah kebenaran keamanan pengecualian yang menyebabkan kontainer berbasis node, seperti list, map, dan unordered_map, menjadi rusak. Selama operasi penetapan ulang propagate_on_container_copy_assignment atau propagate_on_container_move_assignment, kami akan membebaskan node sentinel kontainer dengan pengalokasi lama, melakukan penetapan POCCA/POCMA pada pengalokasi lama, lalu mencoba mendapatkan node sentinel dari pengalokasi baru. Jika alokasi ini gagal, kontainer rusak. Ini bahkan tidak bisa dihancurkan, karena memiliki node sentinel adalah struktur data yang tidak berubah. Kode ini diperbaiki untuk membuat node sentinel baru dengan menggunakan pengalokasi kontainer sumber sebelum menghancurkan node sentinel yang ada.

  • Kontainer diperbaiki untuk selalu menyalin/memindahkan/menukar pengalokasi sesuai dengan propagate_on_container_copy_assignment, propagate_on_container_move_assignment, dan propagate_on_container_swap, bahkan untuk pengalokasi yang dinyatakan is_always_equal.

  • Menambahkan overload untuk penggabungan kontainer dan mengekstrak fungsi anggota yang menerima kontainer rvalue. Untuk informasi selengkapnya, lihat P0083 "Menyambungkan Peta dan Set"

std::basic_istream::read pemrosesan dari \r\n`` =>\n`

std::basic_istream::read diperbaiki untuk tidak menulis ke dalam bagian buffer yang disediakan untuk sementara sebagai bagian dari pemrosesan \r\n hingga \n. Perubahan ini memberikan beberapa keuntungan performa yang diperoleh di Visual Studio 2017 15.8 untuk bacaan yang lebih besar dari ukuran 4K. Namun, peningkatan efisiensi dari menghindari tiga panggilan virtual per karakter masih ada.

Konstruktor std::bitset

Konstruktor std::bitset tidak lagi membaca satu dan nol dalam urutan terbalik untuk bitset besar.

Regresi std::pair::operator=

Kami memperbaiki regresi di operator penugasan yang std::pair diperkenalkan saat menerapkan LWG 2729 "SFINAE hilang pada std::pair::operator=";. Sekarang ini dengan benar menerima tipe yang dapat dikonversi ke std::pair lagi.

Konteks yang tidak disimpulkan untuk add_const_t

Kami memperbaiki bug sifat jenis minor, di mana add_const_t dan fungsi terkait seharusnya menjadi konteks yang tidak disimpulkan. Dengan kata lain, add_const_t harus menjadi alias untuk typename add_const<T>::type, bukan const T.

Penyempurnaan kesuaian di 16.1

char8_t

P0482r6. C++20 menambahkan tipe karakter baru yang digunakan untuk mewakili unit kode UTF-8. u8 literal string dalam C++20 memiliki tipe const char8_t[N] alih-alih const char[N], yang merupakan kasus sebelumnya. Perubahan serupa telah diusulkan untuk standar C di N2231. Saran untuk char8_t remediasi kompatibilitas mundur diberikan dalam P1423r3. Pengompilasi Microsoft C++ menambahkan dukungan untuk char8_t di Visual Studio 2019 versi 16.1 saat Anda menentukan /Zc:char8_t opsi pengompilasi. Ini dapat dikembalikan ke perilaku C++17 melalui /Zc:char8_t-. Pengompilasi EDG yang mendukung IntelliSense belum mendukungnya di Visual Studio 2019 versi 16.1. Anda mungkin melihat kesalahan khusus IntelliSense palsu yang tidak memengaruhi kompilasi yang sebenarnya.

Contoh

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

std::type_identity metafungsi dan std::identity objek fungsi

P0887R1 type_identity. Ekstensi templat kelas std::identity yang tidak digunakan lagi telah dihapus, dan diganti dengan metafungsi C++20 std::type_identity dan objek fungsi std::identity. Keduanya hanya tersedia di bawah /std:c++latest (/std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru).

Contoh berikut menghasilkan peringatan penghentian C4996 untuk std::identity (ditentukan dalam <type_traits>) di 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);

Contoh berikut menunjukkan cara menggunakan std::identity yang baru (ditentukan dalam <fungsional>) bersama dengan std::type_identity yang baru:

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

Pemeriksaan sintaks untuk lambda umum

Prosesor lambda baru memungkinkan beberapa pemeriksaan sindikat mode kesesuaian di lambda generik, di bawah /std:c++latest (/std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru) atau di bawah mode bahasa lain dengan /Zc:lambda di Visual Studio 2019 versi 16.9 atau yang lebih baru (sebelumnya tersedia sebagai /experimental:newLambdaProcessor awal di Visual Studio 2019 versi 16.3).

Prosesor lambda warisan mengompilasi contoh ini tanpa peringatan, tetapi prosesor lambda baru menghasilkan kesalahan C2760:

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

Contoh ini menunjukkan sintaks yang benar, sekarang diberlakukan oleh pengompilasi:

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

Pencarian dependen argumen untuk panggilan fungsi

P0846R0 (C++20) Peningkatan kemampuan untuk menemukan templat fungsi melalui pencarian dependen argumen untuk ekspresi panggilan fungsi dengan argumen templat eksplisit. Memerlukan /std:c++latest (atau /std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru).

Inisialisasi yang ditentukan

P0329R4 (C++20) Inisialisasi yang ditentukan memungkinkan anggota tertentu dipilih dalam inisialisasi agregat dengan menggunakan sintaks Type t { .member = expr }. Memerlukan /std:c++latest (atau /std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru).

Peringkat konversi enum ke tipe dasar tetapnya

Pengompilasi sekarang memberi peringkat konversi enum menurut N4800 11.3.3.2 Peringkat urutan konversi implisit (4.2):

  • Konversi yang mempromosikan enumerasi yang tipe dasarnya ditetapkan ke tipe dasarnya lebih baik daripada konversi yang mempromosikan ke tipe dasar yang dipromosikan, jika keduanya berbeda.

Peringkat konversi ini tidak diterapkan dengan benar sebelum Visual Studio 2019 versi 16.1. Perilaku yang sesuai dapat mengubah perilaku resolusi overload atau mengekspos ambiguitas di mana yang sebelumnya tidak terdeteksi.

Perubahan perilaku pengompilasi ini berlaku untuk semua mode /std dan merupakan perubahan pemecahan sumber serta biner.

Contoh berikut menunjukkan bagaimana perilaku pengompilasi berubah di 16.1 dan versi yang lebih baru:

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

Fungsi pustaka standar baru dan yang diperbarui (C++20)

  • starts_with() dan ends_with() untuk basic_string dan basic_string_view.
  • contains() untuk kontainer asosiatif.
  • remove(), remove_if(), dan unique() untuk list dan forward_list sekarang mengembalikan size_type.
  • shift_left() dan shift_right() ditambahkan ke <algoritma>.

Penyempurnaan kesuaian di 16.2

Fungsi noexceptconstexpr

fungsi constexpr tidak lagi dipertimbangkan noexcept secara default saat digunakan dalam ekspresi konstanta. Perubahan perilaku ini berasal dari resolusi Core Working Group (CWG) CWG 1351 dan diaktifkan di /permissive-. Contoh berikut ini dikompilasi di Visual Studio 2019 versi 16.1 dan yang lebih lama, tetapi menghasilkan C2338 di Visual Studio 2019 versi 16.2:

constexpr int f() { return 0; }

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

Untuk memperbaiki kesalahan, tambahkan ekspresi noexcept ke deklarasi fungsi:

constexpr int f() noexcept { return 0; }

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

Ekspresi biner dengan tipe enum yang berbeda

C++20 telah menghentikan konversi aritmetika biasa pada operand, di mana:

  • Satu operand adalah tipe enumerasi, dan

  • yang lain adalah tipe enumerasi yang berbeda atau tipe floating-point.

Untuk informasi selengkapnya, lihat P1120R0.

Di Visual Studio 2019 versi 16.2 dan yang lebih baru, kode berikut menghasilkan peringatan C5054 tingkat 4 saat /std:c++latest opsi pengkompilasi diaktifkan (/std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru):

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

Untuk menghindari peringatan, gunakan static_cast untuk mengonversi operand kedua:

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

Menggunakan operasi biner antara enumerasi dan jenis floating-point sekarang menjadi peringatan C5055 tingkat 1 ketika /std:c++latest opsi kompilator diaktifkan (/std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru):

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

Untuk menghindari peringatan, gunakan static_cast untuk mengonversi operand kedua:

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

Perbandingan kesetaraan dan relasional array

Perbandingan kesetaraan dan relasional di antara dua operand tipe array tidak digunakan lagi di C++20 (P1120R0). Dengan kata lain, operasi perbandingan di antara dua array (meskipun kesamaan peringkat dan tingkat) sekarang menjadi peringatan. Di Visual Studio 2019 versi 16.2 dan yang lebih baru, kode berikut menghasilkan C5056 peringatan tingkat 1 saat /std:c++latest opsi pengkompilasi diaktifkan (/std:c++20 di Visual Studio 2019 versi 16.11 dan yang lebih baru):

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

Untuk menghindari peringatan, Anda dapat membandingkan alamat elemen pertama:

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

Untuk menentukan apakah konten dari dua array sama, gunakan fungsi std::equal:

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

Efek menentukan operator pesawat ruang angkasa pada == dan !=

Definisi operator pesawat ruang angkasa (<=>) saja tidak akan lagi menulis ulang ekspresi yang melibatkan == atau != kecuali operator pesawat ruang angkasa ditandai sebagai = default (P1185R2). Contoh berikut dikompilasi di Visual Studio 2019 RTW dan versi 16.1, tetapi menghasilkan C2678 di Visual Studio 2019 versi 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
}

Untuk menghindari kesalahan, tentukan operator== atau nyatakan sebagai default:

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

Peningkatan Pustaka Standar

  • <charconv>to_chars() dengan presisi tetap/ilmiah. (Presisi umum saat ini direncanakan untuk 16.4.)
  • P0020R6: atomic<float>, atomic<double>, atomic<long double>
  • P0463R1: endian
  • P0482R6: Dukungan Pustaka untuk char8_t
  • P0600R1: [[nodiscard]] Untuk STL, Bagian 1
  • P0653R2: to_address()
  • P0754R2: <versi>
  • P0771R1: noexcept Untuk konstruktor pemindahan std::function

Pembanding const untuk kontainer asosiatif

Kode untuk pencarian dan penyisipan di set, map, multiset, dan multimap telah digabungkan untuk pengurangan ukuran kode. Operasi penyisipan sekarang memanggil perbandingan kurang dari pada fungsi perbandingan const, dengan cara yang sama seperti yang telah dilakukan operasi pencarian sebelumnya. Kode berikut dikompilasi di Visual Studio 2019 versi 16.1 dan yang lebih lama, tetapi memunculkan C3848 di Visual Studio 2019 versi 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);

}

Untuk menghindari kesalahan, buat operator perbandingan const:

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

Peningkatan kesesuaian di Visual Studio 2019 versi 16.3

Operator ekstraksi aliran untuk char* dihapus

Operator ekstraksi aliran untuk pointer-to-characters telah dihapus dan digantikan oleh operator ekstraksi untuk array-of-characters (per P0487R1). WG21 menganggap overload yang dihapus tidak aman. Dalam mode /std:c++20 atau /std:c++latest, contoh berikut sekarang menghasilkan 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)
}

Untuk menghindari kesalahan, gunakan operator ekstraksi dengan variabel char[]:

#include <iostream>
#include <iomanip>

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

Kata kunci baru requires dan concept

Kata kunci baru requires dan concept telah ditambahkan ke pengompilasi Microsoft C++. Jika Anda mencoba menggunakan salah satu sebagai pengidentifikasi dalam /std:c++20 mode atau /std:c++latest , pengkompilasi akan menaikkan C2059 untuk menunjukkan kesalahan sintaks.

Konstruktor sebagai nama tipe tidak diizinkan

Pengompilasi tidak lagi menganggap nama konstruktor sebagai nama kelas yang disuntikkan dalam hal ini: ketika muncul dalam nama yang memenuhi syarat setelah alias ke spesialisasi templat kelas. Sebelumnya, konstruktor dapat digunakan sebagai nama tipe untuk mendeklarasikan entitas lain. Contoh berikut sekarang menghasilkan C3646:

#include <chrono>

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

Untuk menghindari kesalahan, nyatakan TotalDuration seperti yang ditunjukkan di sini:

#include <chrono>

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

Pemeriksaan fungsi extern "C" yang lebih ketat

Jika fungsi extern "C" dideklarasikan di namespace yang berbeda, versi pengompilasi Microsoft C++ sebelumnya tidak memeriksa apakah deklarasi kompatibel. Di Visual Studio 2019 versi 16.3 dan yang lebih baru, kompilator memeriksa kompatibilitas. Dalam mode /permissive-, kode berikut menghasilkan kesalahan C2371 dan 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

Untuk menghindari kesalahan dalam contoh sebelumnya, gunakan bool alih-alih BOOL secara konsisten dalam kedua deklarasi f.

Peningkatan Pustaka Standar

Header non-standar <stdexcpt.h> dan <typeinfo.h> telah dihapus. Kode yang menyertakannya harus menyertakan header standar <pengecualian> dan <typeinfo>, masing-masing.

Peningkatan kesesuaian di Visual Studio 2019 versi 16.4

Penegakan pencarian nama dua fase yang lebih baik untuk id yang memenuhi syarat di /permissive-

Pencarian nama dua fase mengharuskan nama nondependen yang digunakan dalam isi template harus terlihat oleh template pada waktu definisi. Sebelumnya, nama-nama tersebut mungkin telah ditemukan ketika templat dibuat. Perubahan ini memudahkan untuk menulis kode portabel dan yang sesuai di MSVC di bawah bendera /permissive-.

Di Visual Studio 2019 versi 16.4 dengan set bendera /permissive-, contoh berikut menghasilkan kesalahan, karena N::f tidak terlihat saat templat f<T> ditentukan:

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

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

Biasanya, kesalahan ini dapat diperbaiki dengan menyertakan header yang hilang atau fungsi atau pun variabel deklarasi maju, seperti yang ditunjukkan dalam contoh berikut:

namespace N {
    int f();
}

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

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

Konversi implisit ekspresi konstanta integral ke penunjuk null

Pengompilasi MSVC sekarang mengimplementasikan CWG Issue 903 dalam mode kesesuaian (/permissive-). Aturan ini melarang konversi implisit ekspresi konstanta integral (kecuali untuk literal bilangan bulat '0') ke konstanta penunjuk null. Contoh berikut menghasilkan C2440 dalam mode kesesuaian:

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

Untuk memperbaiki kesalahan, gunakan nullptr alih-alih false. 0 literal masih diizinkan:

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

Aturan standar untuk tipe literal bilangan bulat

Dalam mode kesuaian (diaktifkan oleh /permissive-), MSVC menggunakan aturan standar untuk tipe literal bilangan bulat. Literal desimal yang terlalu besar untuk dimasukkan ke dalam signed int sebelumnya diberi tipe unsigned int. Sekarang literal tersebut diberikan tipe bilangan bulat signed terbesar berikutnya, long long. Selain itu, literal dengan akhiran 'll' yang terlalu besar untuk dimasukkan ke dalam tipe signed diberikan tipe unsigned long long.

Perubahan ini dapat menyebabkan diagnostik peringatan yang berbeda dihasilkan, dan perbedaan perilaku untuk operasi aritmetika pada literal.

Contoh berikut menunjukkan perilaku baru di Visual Studio 2019 versi 16.4. Variabel i sekarang berjenis unsigned int, sehingga peringatan dinaikkan. Bit urutan tinggi dari variabel j diatur ke 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
}

Contoh berikut menunjukkan cara menjaga perilaku lama dan menghindari peringatan serta perubahan perilaku run-time:

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

Parameter fungsi yang membayangi parameter templat

Pengompilasi MSVC sekarang menimbulkan kesalahan saat parameter fungsi membayangi parameter templat:

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

Untuk memperbaiki kesalahan, ubah nama salah satu parameter:

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

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

Spesialisasi sifat tipe yang disediakan pengguna

Sesuai dengan subklausul meta.rqmts dari Standar, pengompilasi MSVC sekarang memunculkan kesalahan saat menemukan spesialisasi yang ditentukan pengguna dari salah satu templat type_traits yang ditentukan di namespace std. Kecuali ditentukan lain, spesialisasi tersebut mengakibatkan perilaku yang tidak terdefinisi. Contoh berikut memiliki perilaku yang tidak terdefinisi karena melanggar aturan, dan static_assert gagal dengan kesalahan C2338.

#include <type_traits>
struct S;

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

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

Untuk menghindari kesalahan, tentukan struktur yang mewarisi dari type_trait pilihan, dan khususkan bahwa:

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

Perubahan pada operator perbandingan yang disediakan pengompilasi

Pengompilasi MSVC sekarang menerapkan perubahan berikut pada operator perbandingan per P1630R1 bila opsi /std:c++20 atau /std:c++latest diaktifkan:

Pengompilasi tidak lagi menulis ulang ekspresi menggunakan operator== jika melibatkan tipe pengembalian yang bukan.bool Kode berikut sekarang menghasilkan kesalahan 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
}

Untuk menghindari kesalahan, Anda harus secara eksplisit menentukan operator yang diperlukan:

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

Pengompilasi tidak lagi mendefinisikan operator perbandingan default jika merupakan anggota kelas union-like. Contoh berikut sekarang menghasilkan kesalahan 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;
}

Untuk menghindari kesalahan, tentukan isi untuk operator:

#include <compare>

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

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

Pengompilasi tidak akan lagi menentukan operator perbandingan default jika kelas berisi anggota referensi. Kode berikut sekarang menghasilkan kesalahan C2120:

#include <compare>

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

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

Untuk menghindari kesalahan, tentukan isi untuk operator:

#include <compare>

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

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

Peningkatan kesesuaian di Visual Studio 2019 versi 16.5

Deklarasi spesialisasi eksplisit tanpa penginisialisasi bukanlah definisi

Di bawah /permissive-, MSVC sekarang memberlakukan aturan standar bahwa deklarasi spesialisasi eksplisit tanpa inisialisasi bukanlah definisi. Sebelumnya, deklarasi akan dianggap sebagai definisi dengan penginisialisasi default. Efeknya dapat diamati pada waktu tautan, karena program yang bergantung pada perilaku ini sekarang mungkin memiliki simbol yang belum terselesaikan. Contoh ini sekarang menghasilkan kesalahan:

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.

Untuk mengatasi masalah ini, tambahkan penginisialisasi:

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

Output pra-prosesor mempertahankan baris baru

Pra-prosesor eksperimental sekarang mempertahankan garis baru dan spasi kosong saat menggunakan /P atau /E dengan /experimental:preprocessor.

Mengingat contoh sumber ini,

#define m()
line m(
) line

Output sebelumnya dari /E adalah:

line line
#line 2

Output baru dari /E sekarang adalah:

line
 line

Kata kunci import dan module bergantung pada konteks

Per P1857R1, arahan praprosesor import dan module memiliki batasan baru pada sintaksnya. Contoh ini tidak lagi mengompilasi:

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

Untuk mengatasi masalah ini, simpan impor pada baris yang sama:

import m; // OK

Penghapusan std::weak_equality dan std::strong_equality

Penggabungan P1959R0 mengharuskan pengompilasi menghapus perilaku dan referensi ke tipe std::weak_equality dan std::strong_equality.

Kode dalam contoh ini tidak lagi dikompilasi:

#include <compare>

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

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

Contohnya sekarang menyebabkan kesalahan ini:

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'

Untuk mengatasi masalah ini, perbarui untuk lebih memilih operator relasional bawaan dan ganti tipe yang dihapus:

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

Perubahan TLS Guard

Sebelumnya, variabel lokal utas dalam DLL tidak diinisialisasi dengan benar. Selain pada utas yang memuat DLL, variabel tersebut tidak diinisialisasi sebelum digunakan terlebih dahulu pada utas yang ada sebelum DLL dimuat. Cacat ini sekarang telah diperbaiki. Variabel lokal utas dalam DLL seperti itu segera diinisialisasi sebelum penggunaan pertamanya pada utas tersebut.

Perilaku baru pengujian untuk inisialisasi pada penggunaan variabel lokal utas ini dapat dinonaktifkan dengan menggunakan opsi pengompilasi /Zc:tlsGuards-. Atau, dengan menambahkan atribut [[msvc:no_tls_guard]] ke variabel lokal utas tertentu.

Diagnosis panggilan yang lebih baik ke fungsi yang dihapus

Pengompilasi kami lebih permisif tentang panggilan ke fungsi yang dihapus sebelumnya. Misalnya, jika panggilan terjadi dalam konteks isi templat, kami tidak akan mendiagnosis panggilan. Selain itu, jika ada beberapa instans panggilan ke fungsi yang dihapus, kami hanya akan mengeluarkan satu diagnostik. Sekarang kami mengeluarkan diagnostik untuk masing-masing.

Salah satu konsekuensi dari perilaku baru dapat menghasilkan perubahan kecil yang melanggar: Kode yang disebut fungsi yang dihapus tidak akan didiagnosis jika tidak pernah diperlukan untuk pembuatan kode. Sekarang kami mendiagnosisnya di depan.

Contoh ini menunjukkan kode yang sekarang menghasilkan kesalahan:

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

Untuk mengatasi masalah ini, hapus panggilan ke fungsi yang dihapus:

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

Peningkatan kesesuaian di Visual Studio 2019 versi 16.6

Aliran pustaka standar menolak penyisipan tipe karakter yang salah dikodekan

Biasanya, menyisipkan wchar_t ke dalam std::ostream, dan menyisipkan char16_t atau char32_t ke dalam std::ostream atau std::wostream, menghasilkan nilai integralnya. Menyisipkan penunjuk ke tipe karakter tersebut menghasilkan nilai penunjuk. Programmer tidak menemukan kedua kasus intuitif. Mereka sering mengharapkan pustaka standar untuk mentranskode karakter atau string karakter yang dihentikan null sebagai gantinya, dan untuk menghasilkan hasilnya.

Proposal C++20 P1423R3 menambahkan overload operator penyisipan aliran yang dihapus untuk kombinasi aliran dan karakter atau tipe penunjuk karakter. Di bawah /std:c++20 atau /std:c++latest, overload membuat penyisipan ini tidak tepat, alih-alih berperilaku dengan cara yang mungkin tidak disengaja. Pengompilasi menimbulkan kesalahan C2280 ketika ditemukan. Anda dapat menentukan makro "escape hatch" _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20 ke 1 untuk memulihkan perilaku lama. (Proposal tersebut juga menghapus operator penyisipan aliran untuk char8_t. Pustaka standar kami menerapkan overload yang serupa saat kami menambahkan dukungan char8_t, sehingga perilaku "wrong" tidak pernah tersedia untuk char8_t.)

Sampel ini menunjukkan perilaku dengan perubahan ini:

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

Kode sekarang menghasilkan pesan diagnostik ini:

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

Anda dapat mencapai efek perilaku lama di semua mode bahasa dengan mengonversi tipe karakter menjadi unsigned int, atau tipe pointer-to-character menjadi 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"
}

Mengubah tipe pengembalian std::pow() untuk std::complex

Sebelumnya, implementasi MSVC dari aturan promosi untuk tipe pengembalian templat fungsi std::pow() salah. Misalnya, sebelumnya pow(complex<float>, int) mengembalikan complex<float>. Sekarang mengembalikan complex<double> dengan benar. Perbaikan telah diterapkan tanpa syarat untuk semua mode standar di Visual Studio 2019 versi 16.6.

Perubahan ini dapat menyebabkan kesalahan pengompilasi. Misalnya, sebelumnya Anda dapat mengalikan pow(complex<float>, int) dengan float. Karena complex<T> operator* mengharapkan argumen dengan tipe yang sama, contoh berikut sekarang mengeluarkan kesalahan pengompilasi 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

Ada banyak kemungkinan perbaikan:

  • Ubah tipe float multiplicand menjadi double. Argumen ini dapat dikonversi langsung menjadi complex<double> untuk mencocokkan tipe yang dikembalikan oleh pow.

  • Persempit hasil pow menjadi complex<float> dengan mengucapkan complex<float>{pow(ARG, ARG)}. Kemudian Anda dapat terus mengalikannya dengan nilai float.

  • Teruskan float alih-alih int ke pow. Operasi ini mungkin lebih lambat.

  • Dalam beberapa kasus, Anda dapat menghindari pow sepenuhnya. Misalnya, pow(cf, -1) dapat digantikan oleh divisi.

switch peringatan untuk C

Di Visual Studio 2019 versi 16.6 dan yang lebih baru, kompilator mengimplementasikan beberapa peringatan C++ yang sudah ada sebelumnya untuk kode yang dikompilasi sebagai C. Peringatan berikut sekarang diaktifkan pada tingkat yang berbeda: C4060, C4061, C4062, C4063, C4064, C4065, C4808, dan C4809. Peringatan C4065 dan C4060 dinonaktifkan secara default di C.

Peringatan dipicu pada pernyataan case yang hilang, enum yang tidak terdefinisi, dan pernyataan peralihan bool yang buruk (yaitu, pernyataan yang berisi terlalu banyak kasus). Contohnya:

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

Untuk memperbaiki kode ini, hapus kasus default redundan:

#include <stdbool.h>

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

Kelas yang tidak disebutkan namanya dalam deklarasi typedef

Di Visual Studio 2019 versi 16.6 dan yang lebih baru, perilaku typedef deklarasi telah dibatasi agar sesuai dengan P1766R1. Dengan pembaruan ini, kelas yang tidak disebutkan namanya dalam deklarasi typedef tidak dapat memiliki anggota selain:

  • anggota data non-statis tanpa penginisialisasi anggota default,
  • kelas anggota, atau
  • enumerasi anggota.

Batasan yang sama diterapkan secara rekursif ke setiap kelas bertumpuk. Batasan dimaksudkan untuk memastikan kesederhanaan struktur yang memiliki typedef nama untuk tujuan tautan. Nama harus cukup sederhana sehingga tidak ada penghitungan tautan yang diperlukan sebelum pengompilasi sampai ke nama typedef untuk tautan.

Perubahan ini memengaruhi semua mode standar pengompilasi. Dalam mode (/std:c++14) dan /std:c++17 default, pengompilasi mengeluarkan peringatan C5208 untuk kode yang tidak sesuai. Jika /permissive- ditentukan, pengompilasi mengeluarkan peringatan C5208 sebagai kesalahan di bawah /std:c++14 dan mengeluarkan kesalahan C7626 di bawah /std:c++17. Pengompilasi mengeluarkan kesalahan C7626 untuk kode yang tidak sesuai saat /std:c++20 atau /std:c++latest ditentukan.

Sampel berikut menunjukkan konstruksi yang tidak lagi diizinkan dalam struktur yang tidak disebutkan namanya. Tergantung pada mode standar yang ditentukan, kesalahan atau peringatan C5208 atau C7626 akan muncul:

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;

Kode di atas dapat diperbaiki dengan memberi nama kelas yang tidak disebutkan namanya:

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

Impor argumen default di C++/CLI

Peningkatan jumlah API memiliki argumen default di .NET Core. Jadi, kami sekarang mendukung impor argumen default di C++/CLI. Perubahan ini dapat merusak kode yang ada di mana beberapa overload dinyatakan, seperti dalam contoh ini:

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

Ketika kelas ini diimpor ke C++/CLI, panggilan ke salah satu overload menyebabkan kesalahan:

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

Pengompilasi mengeluarkan kesalahan C2668 karena kedua overload cocok dengan daftar argumen ini. Dalam overload kedua, argumen kedua diisi oleh argumen default. Untuk mengatasi masalah ini, Anda dapat menghapus overload redundan (1). Atau, gunakan daftar argumen lengkap dan berikan argumen default secara eksplisit.

Peningkatan kesesuaian di Visual Studio 2019 versi 16.7

Definisi yang mudah disalin

C++20 mengubah definisi yang mudah disalin. Ketika kelas memiliki anggota data non-statis dengan volatile tipw yang memenuhi syarat, itu tidak lagi menyiratkan bahwa setiap konstruktor salinan atau pemindahan yang dihasilkan pengompilasi, atau pun menyalin atau memindahkan operator penerapan, non-trivial. Komite Standar C++ menerapkan perubahan ini secara retroaktif sebagai Defect Report. Di MSVC, perilaku pengompilasi tidak berubah dalam mode bahasa yang berbeda, seperti /std:c++14 atau /std:c++latest.

Berikut adalah contoh perilaku baru:

#include <type_traits>

struct S
{
    volatile int m;
};

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

Kode ini tidak dikompilasi dalam versi MSVC sebelum Visual Studio 2019 versi 16.7. Ada peringatan pengompilasi nonaktif secara default yang dapat Anda gunakan untuk mendeteksi perubahan ini. Jika Anda mengompilasi kode di atas dengan menggunakan cl /W4 /w45220, Anda akan melihat peringatan berikut:

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`

Konversi literal pointer-to-member dan string ke bool dipersempit.

Komite Standar C++ baru-baru ini mengadopsi Defect Report P1957R2, yang menganggap T* ke bool sebagai konversi yang menyempit. MSVC memperbaiki bug dalam penerapannya, yang sebelumnya akan mendiagnosis T* menjadi bool sebagai penyempitan, tetapi tidak mendiagnosis konversi string literal ke bool atau pointer-to-member ke bool.

Program berikut ini tidak berfungsi dengan baik di Visual Studio 2019 versi 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
}

Untuk memperbaiki kode ini, tambahkan perbandingan eksplisit ke nullptr, atau hindari konteks yang mempersempit konversi:

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 hanya dapat dikonversi menjadi bool sebagai inisialisasi langsung

Di C++11, nullptr hanya dapat dikonversi menjadi bool sebagai konversi langsung; misalnya, saat Anda menginisialisasi bool dengan menggunakan daftar penginisialisasi yang diperkuat. Batasan ini tidak pernah diberlakukan oleh MSVC. MSVC sekarang menerapkan aturan di bawah /permissive-. Pertobatan implisit sekarang didiagnosis sebagai salah bentuk. Konversi kontekstual ke bool masih diperbolehkan, karena inisialisasi langsung bool b(nullptr) valid.

Dalam kebanyakan kasus, kesalahan dapat diperbaiki dengan mengganti nullptr dengan false, seperti yang ditunjukkan dalam contoh ini:

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
}

Menyesuaikan perilaku inisialisasi untuk inisialisasi array dengan penginisialisasi yang hilang

Sebelumnya, MSVC memiliki perilaku yang tidak sesuai untuk inisialisasi array yang kehilangan penginisialisasi. MSVC selalu disebut konstruktor default untuk setiap elemen array yang tidak memiliki penginisialisasi. Perilaku standarnya adalah menginisialisasi setiap elemen dengan braced-initializer-list kosong ({}). Konteks inisialisasi untuk braced-initializer-list kosong adalah inisialisasi penyalinan, yang tidak mengizinkan panggilan ke konstruktor eksplisit. Mungkin juga ada perbedaan runtime, karena penggunaan {} untuk menginisialisasi dapat memanggil konstruktor yang menggunakan std::initializer_list, bukan konstruktor default. Perilaku yang sesuai diaktifkan di bawah /permissive-.

Berikut adalah contoh perilaku yang diubah:

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
}

Inisialisasi anggota kelas dengan nama overload diurutkan dengan benar

Kami mengidentifikasi bug dalam representasi internal anggota data kelas ketika nama tipe juga overload sebagai nama anggota data. Bug ini menyebabkan inkonsistensi dalam inisialisasi agregat dan urutan inisialisasi anggota. Kode inisialisasi yang dihasilkan sekarang sudah benar. Namun, perubahan ini dapat menyebabkan kesalahan atau peringatan di sumber yang secara tidak sengaja bergantung pada anggota yang salah diurutkan, seperti dalam contoh ini:

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

Di versi sebelumnya, konstruktor akan salah menginisialisasi anggota data Inner sebelum anggota data v. (Standar C++ memerlukan urutan inisialisasi yang sama dengan urutan deklarasi anggota). Sekarang kode yang dihasilkan mengikuti standar, daftar anggota-init rusak. Pengompilasi menghasilkan peringatan untuk contoh ini. Untuk memperbaikinya, susun ulang member-initializer-list untuk mencerminkan urutan deklarasi.

Resolusi overload yang melibatkan overload integral dan long argumen

Standar C++ memerlukan peringkat konversi long hingga int sebagai konversi standar. Pengompilasi MSVC sebelumnya salah memberi peringkat sebagai promosi integral, yang berperingkat lebih tinggi untuk resolusi overload. Peringkat ini dapat menyebabkan resolusi overload berhasil diselesaikan ketika harus dianggap ambigu.

Pengompilasi sekarang mempertimbangkan peringkat dengan benar dalam mode /permissive-. Kode yang tidak valid didiagnosis dengan benar, seperti dalam contoh ini:

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
}

Anda dapat memperbaiki masalah ini dengan beberapa cara:

  • Di situs panggilan, ubah tipe argumen yang diteruskan menjadi int. Anda dapat mengubah tipe variabel, atau mentransmisiannya.

  • Jika ada banyak situs panggilan, Anda dapat menambahkan overload lain yang mengambil argumen long. Dalam fungsi ini, transmisikan dan teruskan argumen ke overload int.

Penggunaan variabel yang tidak ditentukan dengan tautan internal

Versi MSVC sebelum Visual Studio 2019 versi 16.7 menerima penggunaan variabel yang mendeklarasikan extern yang memiliki tautan internal dan tidak ditentukan. Variabel tersebut tidak dapat didefinisikan dalam unit terjemahan lainnya dan tidak dapat membentuk program yang valid. Pengompilasi sekarang mendiagnosis kasus ini pada waktu kompilasi. Kesalahan ini mirip dengan kesalahan untuk fungsi statis yang tidak didefinisikan.

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

Program ini sebelumnya salah dikompilasi dan ditautkan, tetapi sekarang akan memunculkan kesalahan C7631.

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

Variabel tersebut harus didefinisikan dalam unit terjemahan yang sama dengan yang digunakan. Misalnya, Anda dapat memberikan penginisialisasi eksplisit atau definisi terpisah.

Tipe kelengkapan dan konversi penunjuk derived-to-base

Dalam standar C++ sebelum C++20, konversi dari kelas turunan ke kelas dasar tidak mengharuskan kelas turunan menjadi tipe kelas lengkap. Komite standar C++ telah menyetujui perubahan Defect Report retroaktif yang berlaku untuk semua versi bahasa C++. Perubahan ini menyelaraskan proses konversi dengan sifat tipe, seperti std::is_base_of, yang mengharuskan kelas turunan bertipe kelas lengkap.

Berikut contohnya:

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

Perubahan perilaku ini berlaku untuk semua mode bahasa C++ MSVC, bukan hanya /std:c++20 atau /std:c++latest.

Konversi yang dipersempit didiagnosis secara lebih konsisten

MSVC mengeluarkan peringatan untuk mempersempit konversi dalam penginisialisasi braced-list. Sebelumnya, pengompilasi tidak akan mendiagnosis penyempitan konversi dari tipe dasar enum yang lebih besar ke tipe integral yang lebih sempit. (Pengompilasi salah menganggap merekasebagai promosi integral alih-alih konversi). Jika konversi penyempitan disengaja, Anda dapat menghindari peringatan dengan menggunakan static_cast pada argumen penginisialisasi. Atau, pilih tipe integral tujuan yang lebih besar.

Berikut adalah contoh penggunaan static_cast eksplisit untuk mengatasi peringatan:

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
}

Peningkatan kesesuaian di Visual Studio 2019 versi 16.8

Ekstensi 'class rvalue used as lvalue'

MSVC memiliki ekstensi yang memungkinkan penggunaan rvalue kelas sebagai lvalue. Ekstensi tidak memperpanjang masa pakai rvalue kelas dan dapat menyebabkan perilaku yang tidak ditentukan saat runtime. Kami sekarang memberlakukan aturan standar dan melarang ekstensi ini di bawah /permissive-. Jika Anda belum dapat menggunakan /permissive- , Anda dapat menggunakan /we4238 untuk secara eksplisit melarang ekstensi. Berikut contohnya:

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

Ekstensi 'explicit specialization in non-namespace scope'

MSVC memiliki ekstensi yang memungkinkan spesialisasi eksplisit dalam cakupan non-namespace. Sekarang menjadi bagian dari standar, setelah resolusi CWG 727. Namun, ada perbedaan perilaku. Kami telah menyesuaikan perilaku pengompilasi agar selaras dengan standar.

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

Memeriksa tipe kelas abstrak

Standar C++20 mengubah pengompilasi proses yang digunakan untuk mendeteksi penggunaan tipe kelas abstrak sebagai parameter fungsi. Secara khusus, ini bukan lagi kesalahan SFINAE. Sebelumnya, jika pengompilasi mendeteksi bahwa spesialisasi templat fungsi akan memiliki instans tipe kelas abstrak sebagai parameter fungsi, spesialisasi itu akan dianggap tidak tepat. Ini tidak akan ditambahkan ke set fungsi kandidat yang layak. Di C++20, pemeriksaan parameter tipe kelas abstrak tidak terjadi sampai fungsi dipanggil. Efeknya adalah, kode yang digunakan untuk mengompilasi tidak akan menyebabkan kesalahan. Berikut contohnya:

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

Sebelumnya, panggilan ke compare akan mencoba untuk mengkhususkan templat fungsi compare dengan menggunakan argumen templat String untuk T. Ini akan gagal menghasilkan spesialisasi yang valid, karena String merupakan kelas abstrak. Satu-satunya kandidat yang layak adalah compare(const Node&, const Node&). Namun, di bawah C++ 20, pemeriksaan untuk tipe kelas abstrak tidak terjadi sampai fungsi dipanggil. Jadi, spesialisasi compare(String, String) ditambahkan ke kumpulan kandidat yang layak, dan itu dipilih sebagai kandidat terbaik karena konversi dari const String& ke String adalah urutan konversi yang lebih baik daripada konversi dari const String& ke const Node& .

Di bawah C++20, satu kemungkinan perbaikan untuk contoh ini adalah dengan menggunakan konsep; yaitu, ubah definisi compare menjadi:

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

Atau, jika konsep C++ tidak tersedia, Anda dapat kembali ke 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);
}

Dukungan untuk P0960R3 - memungkinkan inisialisasi agregat dari daftar nilai yang dikurung

C++20 P0960R3 menambahkan dukungan untuk menginisialisasi agregat menggunakan daftar penginisialisasi yang diberi tanda kurung. Misalnya, kode berikut valid di C++20:

struct S {
    int i;
    int j;
};

S s(1, 2);

Sebagian besar fitur ini bersifat aditif, yaitu, kode sekarang mengompilasi yang tidak dikompilasi sebelumnya. Namun, itu mengubah perilaku std::is_constructible. Dalam mode C++17 static_assert ini gagal, tetapi dalam mode C++20 berhasil:

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

Jika Anda menggunakan sifat tipe ini untuk mengontrol resolusi overload, itu dapat menyebabkan perubahan perilaku antara C++17 dan C++20.

Resolusi overload yang melibatkan templat fungsi

Sebelumnya, pengompilasi memungkinkan beberapa kode untuk dikompilasi di bawah /permissive- yang seharusnya tidak dikompilasi. Efeknya adalah, pengompilasi yang disebut fungsi yang salah yang mengarah ke perubahan perilaku runtime:

int f(int);

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

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

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

Panggilan ke g menggunakan rangkaian overload yang berisi dua fungsi, ::f dan N::f. Karena N::f adalah templat fungsi, pengompilasi harus memperlakukan argumen fungsi sebagai konteks yang tidak dideduksi. Artinya, dalam kasus ini, panggilan ke g akan gagal, karena pengompilasi tidak dapat menyimpulkan tipe untuk parameter template T. Sayangnya, pengompilasi tidak mengabaikan fakta bahwa ini telah memutuskan bahwa ::f cocok untuk pemanggilan fungsi. Alih-alih mengeluarkan kesalahan, pengompilasi akan menghasilkan kode untuk memanggil g menggunakan ::f sebagai argumen.

Mengingat bahwa dalam banyak kasus yang menggunakan ::f sebagai argumen fungsi adalah apa yang diharapkan pengguna, kami hanya mengeluarkan kesalahan jika kode dikompilasi dengan /permissive-.

Bermigrasi dari /await ke C++20 coroutine

Coroutine C++20 standar sekarang aktif secara default di bawah /std:c++20 dan /std:c++latest. Mereka berbeda dari Coroutine TS dan dukungan di bawah opsi /await. Migrasi dari /await ke coroutine standar mungkin memerlukan beberapa perubahan sumber.

Kata kunci non-standar

Kata kunci await dan yield lama tidak didukung dalam mode C++20. Kode harus menggunakan co_await dan co_yield sebagai gantinya. Mode standar juga tidak mengizinkan penggunaan return dalam coroutine. Setiap return dalam coroutine harus menggunakan co_return.

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

Tipe initial_suspend/final_suspend

Di bawah /await, fungsi janji awal dan penangguhan dapat dinyatakan sebagai pengembalian bool. Perilaku ini tidak standar. Dalam C++20, fungsi-fungsi ini harus mengembalikan tipe kelas yang dapat ditunggu, biasanya salah satu tipe trivial yang dapat ditunggu: std::suspend_always jika fungsi sebelumnya mengembalikan true, atau std::suspend_never jika mengembalikan false.

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

Tipe yield_value

Di C++20, fungsi promise yield_value harus mengembalikan jenis yang dapat ditunggu. Dalam mode /await, fungsi yield_value diizinkan untuk mengembalikan void, dan akan selalu ditangguhkan. Fungsi tersebut dapat diganti dengan fungsi yang mengembalikan 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{}; }
};

Fungsi penanganan pengecualian

/await mendukung tipe janji tanpa fungsi penanganan pengecualian atau fungsi penanganan pengecualian bernama set_exception yang menggunakan std::exception_ptr. Di C++20, tipe janji harus memiliki fungsi bernama unhandled_exception yang tidak membutuhkan argumen. Objek pengecualian dapat diperoleh dari std::current_exception jika diperlukan.

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

Tipe pengembalian coroutine yang disimpulkan tidak didukung

C++20 tidak mendukung coroutine dengan tipe pengembalian yang menyertakan tipe placeholder seperti auto. Tipe coroutine yang dikembalikan harus dideklarasikan secara eksplisit. Di bawah /await, tipe yang disimpulkan ini selalu melibatkan tipe eksperimental dan memerlukan penyertaan header yang menentukan tipe yang diperlukan: Salah satu dari std::experimental::task<T>, std::experimental::generator<T>, atau 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;
};

Tipe pengembalian return_value

Tipe pengembalian fungsi return_value janji harus void. Dalam mode /await, tipe pengembalian dapat berupa apa pun, dan diabaikan. Diagnostik ini dapat membantu mendeteksi kesalahan yang tidak terlihat, seperti ketika penulis salah mengasumsikan nilai pengembalian return_value dikembalikan ke pemanggil.

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

Mengembalikan perilaku konversi objek

Jika tipe pengembalian coroutine yang dideklarasikan tidak cocok dengan tipe pengembalian fungsi get_return_object janji, objek yang dikembalikan dari get_return_object akan dikonversi ke tipe pengembalian coroutine. Di bawah /await, konversi ini dilakukan lebih awal, sebelum isi coroutine memiliki kesempatan untuk dieksekusi. Di /std:c++20 atau /std:c++latest, konversi ini dilakukan saat nilai dikembalikan ke pemanggil. Ini memungkinkan coroutine yang tidak ditangguhkan pada titik penangguhan awal untuk menggunakan objek yang dikembalikan oleh get_return_object di dalam isi coroutine.

Parameter janji coroutine

Dalam C++20, pengompilasi mencoba meneruskan parameter coroutine (jika ada) ke konstruktor dari tipe janji. Jika gagal, ini mencoba lagi dengan konstruktor default. Dalam mode /await, hanya konstruktor default yang digunakan. Perubahan ini dapat menyebabkan perbedaan perilaku jika janji memiliki banyak konstruktor. Atau, jika ada konversi dari parameter coroutine ke tipe janji.

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

Modul /permissive- dan C++20 diaktifkan secara default di bawah /std:c++20

Dukungan Modul C++20 diaktifkan secara default di bawah /std:c++20 dan /std:c++latest. Untuk informasi selengkapnya tentang perubahan ini, dan skenario di mana module dan import diperlakukan secara kondisional sebagai kata kunci, lihat Dukungan Modul C++ 20 Standar dengan MSVC di Visual Studio 2019 versi 16.8.

Sebagai prasyarat untuk dukungan Modul, permissive- sekarang diaktifkan bila /std:c++20 atau /std:c++latest ditentukan. Untuk informasi selengkapnya, lihat /permissive- .

Untuk kode yang sebelumnya dikompilasi di bawah /std:c++latest dan memerlukan perilaku pengompilasi yang tidak sesuai, /permissive dapat ditentukan untuk menonaktifkan mode kesesuaian ketat di pengompilasi. Opsi pengompilasi harus muncul setelah /std:c++latest dalam daftar argumen baris perintah. Namun, /permissive menghasilkan kesalahan jika penggunaan Modul terdeteksi:

kesalahan C1214: Modul bertentangan dengan perilaku non-standar yang diminta melalui 'opsi'

Nilai yang paling umum untuk opsi adalah:

Opsi Deskripsi
/Zc:twoPhase- Pencarian nama dua fase diperlukan untuk Modul C++20 dan diimplikasikan oleh /permissive-.
/Zc:hiddenFriend- Aturan pencarian nama teman tersembunyi standar diperlukan untuk Modul C++20 dan diimplikasikan oleh /permissive-.
/Zc:lambda- Pemrosesan lambda standar diperlukan untuk Modul C++20 dan diimplikasikan oleh mode /std:c++20 atau yang lebih baru.
/Zc:preprocessor- Praprosesor yang sesuai diperlukan untuk penggunaan dan pembuatan unit header C++20 saja. Modul Bernama tidak memerlukan opsi ini.

Opsi /experimental:module masih diperlukan untuk menggunakan Modul std.* yang dikirimkan bersama Visual Studio, karena belum distandarisasi.

Opsi /experimental:module juga menyiratkan /Zc:twoPhase, /Zc:lambda, dan /Zc:hiddenFriend. Sebelumnya, kode yang dikompilasi dengan Modul terkadang dapat dikompilasi dengan /Zc:twoPhase- jika Modul hanya digunakan. Perilaku ini tidak lagi didukung.

Peningkatan kesesuaian di Visual Studio 2019 versi 16.9

Inisialisasi salin sementara dalam referensi inisialisasi langsung

Masalah Core Working Group CWG 2267 menangani ketidakkonsistenan antara daftar penginisialisasi yang diberi tanda kurung dan daftar penginisialisasi yang diberi kurung. Resolusi menyelaraskan dua bentuk.

Visual Studio 2019 versi 16.9 mengimplementasikan perubahan perilaku di semua mode pengompilasi /std. Namun, karena berpotensi mengubah sumber, itu hanya didukung jika kode dikompilasi dengan menggunakan /permissive-.

Sampel ini menunjukkan perubahan perilaku:

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
}

Karakteristik destruktor dan subobjek yang berpotensi dibangun

Masalah Core Working Group CWG 2336 mencakup penghilangan tentang spesifikasi pengecualian implisit dari destruktor di kelas yang memiliki kelas dasar virtual. Penghilangan tersebut berarti destruktor di kelas turunan dapat memiliki spesifikasi pengecualian yang lebih lemah daripada kelas dasar, jika basis itu abstrak dan memiliki basis virtual.

Visual Studio 2019 versi 16.9 mengimplementasikan perubahan perilaku di semua mode pengompilasi /std.

Sampel ini menunjukkan bagaimana interpretasi berubah:

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

Sebelum perubahan ini, destruktor yang didefinisikan secara implisit untuk B adalah noexcept, karena hanya subobjek yang berpotensi dibangun yang dipertimbangkan. Kelas dasar V juga bukan sub-objek yang berpotensi dibangun, karena merupakan dasar virtual dan B adalah abstrak. Namun, kelas dasar V adalah sub-objek yang berpotensi dibangun dari kelas D, sehingga D::~D ditentukan sebagai noexcept(false), yang mengarah ke kelas turunan dengan spesifikasi pengecualian yang lebih lemah daripada basisnya. Penafsiran ini tidak aman. Ini dapat menyebabkan perilaku runtime yang salah jika pengecualian dilemparkan dari destruktor kelas yang berasal dari B.

Dengan perubahan ini, destruktor juga berpotensi melempar jika memiliki destruktor virtual dan setiap kelas dasar virtual memiliki destruktor yang berpotensi melempar.

Tipe dan pengikatan referensi serupa

Masalah Core Working Group CWG 2352 berkaitan dengan inkonsistensi antara aturan pengikatan referensi dan perubahan pada kesamaan tipe. Inkonsistensi diperkenalkan di Defect Report sebelumnya (seperti CWG 330). Ini mempengaruhi Visual Studio 2019 versi 16.0 hingga 16.8.

Dengan perubahan ini, mulai Visual Studio 2019 versi 16.9, kode yang sebelumnya mengikat referensi ke sementara di Visual Studio 2019 versi 16.0 hingga 16.8 sekarang dapat mengikat secara langsung ketika jenis yang terlibat hanya berbeda oleh cv-qualifier.

Visual Studio 2019 versi 16.9 mengimplementasikan perubahan perilaku di semua mode pengompilasi /std. Ini berpotensi menjadi sumber yang melanggar perubahan.

Lihat Referensi ke jenis dengan cv-qualifier yang tidak cocok untuk perubahan terkait.

Sampel ini menunjukkan perilaku yang berubah:

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
}

Pembaruan dapat mengubah perilaku program yang mengandalkan sementara yang diperkenalkan:

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

Perubahan perilaku opsi /Zc:twoPhase dan /Zc:twoPhase-

Biasanya, opsi pengompilasi MSVC bekerja berdasarkan prinsip bahwa yang terakhir terlihat menang. Sayangnya, tidak demikian halnya dengan opsi /Zc:twoPhase dan /Zc:twoPhase-. Opsi ini "melekat", jadi opsi selanjutnya tidak dapat menimpanya. Contohnya:

cl /Zc:twoPhase /permissive a.cpp

Dalam hal ini, opsi /Zc:twoPhase pertama memungkinkan pencarian nama dua fase yang ketat. Opsi kedua dimaksudkan untuk menonaktifkan mode kepatuhan ketat (ini kebalikan dari /permissive-), tetapi tidak menonaktifkan /Zc:twoPhase.

Visual Studio 2019 versi 16.9 mengubah perilaku ini di semua mode pengompilasi /std. /Zc:twoPhase dan /Zc:twoPhase- tidak lagi "melekat", dan opsi selanjutnya dapat menimpanya.

Noexcept-specifiers eksplisit pada templat destruktor

Pengompilasi sebelumnya menerima templat destruktor yang dideklarasikan dengan spesifikasi pengecualian non-pelemparan, tetapi didefinisikan tanpa noexcept-specifier eksplisit. Spesifikasi pengecualian implisit dari destruktor bergantung pada properti kelas - properti yang mungkin tidak diketahui pada titik definisi templat. Standar C++ juga memerlukan perilaku ini: Jika destruktor dideklarasikan tanpa noexcept-specifier, ini memiliki spesifikasi pengecualian implisit, dan tidak ada deklarasi fungsi lain yang mungkin memiliki noexcept-specifier.

Visual Studio 2019 versi 16.9 mengubah perilaku yang sesuai di semua mode pengompilasi /std.

Sampel ini menunjukkan perubahan perilaku pengompilasi:

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

Ekspresi yang ditulis ulang dalam C++ 20

Sejak Visual Studio 2019 versi 16.2, di bawah /std:c++latest, pengompilasi telah menerima kode seperti contoh ini:

#include <compare>

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

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

Namun, pengompilasi tidak akan menjalankan fungsi perbandingan yang mungkin diharapkan oleh penulis. Kode di atas seharusnya menulis ulang a < b sebagai (a <=> b) < 0. Sebagai gantinya, pengompilasi menggunakan operator bool() fungsi konversi yang ditentukan pengguna dan membandingkan bool(a) < bool(b). Di Visual Studio 2019 versi 16.9 dan yang lebih baru, pengkompilasi menulis ulang ekspresi menggunakan ekspresi operator pesawat ruang angkasa yang diharapkan.

Perubahan pemutusan sumber

Menerapkan konversi dengan benar ke ekspresi yang ditulis ulang memiliki efek lain: Pengompilasi juga mendiagnosis ambiguitas dengan benar dari upaya untuk menulis ulang ekspresi. Pertimbangkan contoh ini:

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

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

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

Dalam C++17, kode ini akan diterima karena konversi derived-to-base dari Derived di sisi kanan ekspresi. Di C++20, kandidat ekspresi yang disintesis juga ditambahkan: Derived{} == Base{}. Karena aturan dalam standar tentang fungsi mana yang menang berdasarkan konversi, ternyata pilihan antara Base::operator== dan Derived::operator== tidak dapat diputuskan. Karena urutan konversi dalam dua ekspresi tidak lebih baik atau lebih buruk dari satu sama lain, contoh kode menghasilkan ambiguitas.

Untuk mengatasi ambiguitas, tambahkan kandidat baru yang tidak akan tunduk pada dua urutan konversi:

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

Perubahan pemutusan runtime

Karena aturan penulisan ulang operator di C++20, resolusi overload memungkinkan untuk menemukan kandidat baru yang tidak akan ditemukan dalam mode bahasa yang lebih rendah. Kandidat baru mungkin juga lebih cocok dari kandidat yang lebih lama. Pertimbangkan contoh ini:

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

Di C++17, satu-satunya kandidat untuk ci == *this adalah const_iterator::operator==. Ini cocok karena *this melalui konversi derived-to-base menjadi const_iterator. Di C++20, kandidat lain yang ditulis ulang akan ditambahkan: *this == ci, yang memanggil iterator::operator==. Kandidat ini tidak memerlukan konversi, jadi ini lebih cocok daripada const_iterator::operator==. Masalah dengan kandidat baru adalah bahwa itu adalah fungsi yang saat ini sedang didefinisikan, sehingga semantik baru dari fungsi tersebut menyebabkan definisi rekursif tak terhingga dari iterator::operator==.

Untuk membantu dalam kode seperti contoh, pengompilasi menerapkan peringatan baru:

$ 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

Untuk memperbaiki kode, lakukan secara eksplisit tentang konversi mana yang akan digunakan:

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

Peningkatan kesesuaian di Visual Studio 2019 versi 16.10

Overload yang salah dipilih untuk inisialisasi salinan kelas

Mengingat kode sampel ini:

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

Versi pengompilasi yang lebih lama akan salah mengonversi argumen f dari tipe C ke A dengan menggunakan konstruktor pengonversi templat dari A. C++ standar memerlukan penggunaan operator konversi B::operator A sebagai gantinya. Di Visual Studio 2019 versi 16.10 dan yang lebih baru, perilaku resolusi kelebihan beban diubah untuk menggunakan kelebihan beban yang benar.

Perubahan ini juga dapat memperbaiki overload yang dipilih dalam beberapa situasi lain:

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

Penguraian literal floating-point salah

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, literal floating-point diurai berdasarkan jenis sebenarnya. Versi pengompilasi sebelumnya selalu menguraikan literal floating-point seolah-olah memiliki tipe double, lalu mengonversi hasilnya ke tipe sebenarnya. Perilaku ini dapat menyebabkan pembulatan yang salah dan penolakan nilai yang valid:

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

Titik deklarasi yang salah

Versi pengompilasi yang lebih lama tidak dapat mengompilasi kode referensi mandiri seperti contoh ini:

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

    int value() const;
};

S s(4, &s);

Pengompilasi tidak akan mendeklarasikan variabel s sampai mengurai seluruh deklarasi, termasuk argumen konstruktor. Pencarian s dalam daftar argumen konstruktor akan gagal. Di Visual Studio 2019 versi 16.10 dan yang lebih baru, contoh ini sekarang dikompilasi dengan benar.

Sayangnya, perubahan ini dapat merusak kode yang ada, seperti dalam contoh ini:

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

Dalam versi pengompilasi sebelumnya, ketika mencari s dalam argumen konstruktor untuk deklarasi s "dalam", ini menemukan deklarasi sebelumnya (s "luar") dan kode dikompilasi. Mulai versi 16.10, pengompilasi mengeluarkan peringatan C4700 sebagai gantinya. Ini karena kompilator sekarang mendeklarasikan "dalam" s sebelum mengurai argumen konstruktor. Jadi, pencarian s menemukan s "dalam", yang belum diinisialisasi.

Anggota khusus templat kelas

Versi pengompilasi sebelumnya salah menandai spesialisasi eksplisit dari anggota templat kelas sebagai inline jika ini juga ditentukan di templat utama. Perilaku ini berarti pengompilasi terkadang menolak kode yang sesuai. Di Visual Studio 2019 versi 16.10 dan yang lebih baru, spesialisasi eksplisit tidak lagi secara implisit ditandai sebagai inline dalam /permissive- mode. Pertimbangkan contoh ini:

File sumber s.h:

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

File sumber s.cpp:

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

File sumber main.cpp:

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

int main()
{
}

Untuk mengatasi kesalahan penaut dalam contoh di atas, tambahkan inline secara eksplisit ke S<int>::f:

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

Perusakan nama tipe pengembalian yang dikurangi

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengkompilasi mengubah caranya menghasilkan nama mangled untuk fungsi yang telah menyimpulkan jenis pengembalian. Misalnya, pertimbangkan fungsi-fungsi ini:

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

Versi pengompilasi sebelumnya akan menghasilkan nama-nama ini untuk penaut:

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

Anehnya, tipe pengembalian akan dihilangkan dari g karena perilaku semantik lain yang disebabkan oleh lambda lokal di isi fungsi. Ketidakkonsistenan ini mempersulit penerapan fungsi yang diekspor yang memiliki tipe pengembalian yang disimpulkan: Antarmuka modul memerlukan informasi tentang bagaimana isi fungsi dikompilasi. Diperlukan informasi untuk menghasilkan fungsi di sisi impor yang dapat menautkan dengan benar ke definisi.

Pengompilasi sekarang menghilangkan tipe pengembalian dari fungsi tipe pengembalian yang disimpulkan. Perilaku ini konsisten dengan implementasi utama lainnya. Ada pengecualian untuk templat fungsi: versi pengompilasi ini memperkenalkan perilaku nama rusak baru untuk templat fungsi yang memiliki tipe pengembalian yang disimpulkan:

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;

Nama rusak untuk auto dan decltype(auto) sekarang muncul dalam biner, bukan tipe pengembalian yang disimpulkan:

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)

Versi pengompilasi sebelumnya akan menyertakan tipe pengembalian yang disimpulkan sebagai bagian dari tanda tangan. Ketika pengompilasi menyertakan tipe pengembalian dalam nama yang rusak, itu dapat menyebabkan masalah penaut. Beberapa skenario yang terbentuk dengan baik akan menjadi ambigu bagi penaut.

Perilaku pengompilasi baru dapat menghasilkan perubahan pemutusan biner. Pertimbangkan contoh ini:

File sumber a.cpp:

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

File sumber main.cpp:

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

Pada versi sebelum versi 16.10, pengompilasi menghasilkan nama untuk auto f() yang tampak seperti int f(), meskipun fungsi tersebut secara semantik berbeda. Itu berarti contoh akan dikompilasi. Untuk memperbaiki masalah ini, jangan mengandalkan auto dalam definisi asli dari f. Sebagai gantinya, tulis sebagai int f(). Karena fungsi yang telah menyimpulkan tipe pengembalian selalu dikompilasi, implikasi ABI diminimalkan.

Peringatan untuk atribut nodiscard yang diabaikan

Pengompilasi versi sebelumnya akan mengabaikan penggunaan atribut nodiscard tertentu secara diam-diam. Mereka mengabaikan atribut jika berada dalam posisi sintaksis yang tidak berlaku untuk fungsi atau kelas yang dideklarasikan. Contohnya:

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

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengkompilasi memancarkan peringatan tingkat 4 C5240 sebagai gantinya:

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

Untuk memperbaiki masalah ini, pindahkan atribut ke posisi sintaksis yang benar:

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

Peringatan untuk arahan include dengan nama header sistem dalam lingkup modul

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengkompilasi memancarkan peringatan untuk mencegah kesalahan penulisan antarmuka modul umum. Jika Anda menyertakan header pustaka standar setelah pernyataan export module, pengompilasi mengeluarkan peringatan C5244. Berikut contohnya:

export module m;
#include <vector>

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

Pengembang mungkin tidak bermaksud agar modul m memiliki konten <vector>. Pengompilasi sekarang mengeluarkan peringatan untuk membantu menemukan dan memperbaiki masalah:

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

Untuk memperbaiki masalah ini, pindahkan #include <vector> sebelum export module m;:

#include <vector>
export module m;

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

Peringatan untuk fungsi tautan internal yang tidak digunakan

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengkompilasi memperingatkan dalam situasi yang lebih banyak di mana fungsi yang tidak direferensikan dengan tautan internal telah dihapus. Versi pengompilasi sebelumnya akan mengeluarkan peringatan C4505 untuk kode berikut:

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

Pengompilasi sekarang juga memperingatkan tentang fungsi auto yang tidak direferensikan dan fungsi yang tidak direferensikan di namespace anonim. Ini mengeluarkan peringatan nonaktif secara default C5245 untuk kedua fungsi berikut:

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

Peringatan tentang penghapusan kurung

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengompilasi memperingatkan pada daftar inisialisasi yang tidak menggunakan kurung kurawal untuk subobjek. Pengompilasi mengeluarkan peringatan C5246 nonaktifkan secara default.

Berikut contohnya:

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

Untuk memperbaiki masalah ini, bungkus inisialisasi subobjek dalam kurung kurawal:

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

Deteksi dengan benar jika objek const tidak diinisialisasi

Di Visual Studio 2019 versi 16.10 dan yang lebih baru, pengkompilasi sekarang mengeluarkan kesalahan C2737 saat Anda mencoba menentukan const objek yang tidak sepenuhnya diinisialisasi:

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

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

Versi pengompilasi sebelumnya mengizinkan kode ini untuk dikompilasi, meskipun S::i tidak diinisialisasi.

Untuk memperbaiki masalah ini, inisialisasi semua anggota sebelum Anda membuat instans objek const:

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

Peningkatan kesesuaian di Visual Studio 2019 versi 16.11

Mode pengompilasi /std:c++20

Di Visual Studio 2019 versi 16.11 dan yang lebih baru, pengkompilasi sekarang mendukung /std:c++20 mode kompilator. Sebelumnya, fitur C++20 hanya tersedia dalam mode /std:c++latest di Visual Studio 2019. Fitur C++20 yang awalnya memerlukan /std:c++latest mode sekarang berfungsi dalam /std:c++20 mode atau yang lebih baru di versi terbaru Visual Studio.

Lihat juga

Kesesuaian bahasa Microsoft C/C++