Mengapa std :: swap bertanda constexpr sebelum C ++ 20?

14

Di C ++ 20, std::swapmenjadi constexprfungsi.

Saya tahu bahwa perpustakaan standar benar-benar tertinggal di belakang bahasa dalam menandai hal-hal constexpr, tetapi pada 2017, <algorithm>cukup banyak constexpr seperti banyak hal lainnya. Namun - std::swaptidak. Samar-samar saya ingat ada beberapa cacat bahasa aneh yang mencegah penandaan itu, tapi saya lupa detailnya.

Adakah yang bisa menjelaskan hal ini dengan ringkas dan jelas?

Motivasi: Perlu memahami mengapa ide yang buruk untuk menandai std::swap()fungsi -seperti constexprdalam kode C ++ 11 / C ++ 14.

einpoklum
sumber

Jawaban:

11

Masalah bahasa yang aneh adalah CWG 1581 :

Ayat 15 [khusus] sangat jelas bahwa fungsi anggota khusus hanya didefinisikan secara implisit ketika mereka digunakan odr. Ini menciptakan masalah untuk ekspresi konstan dalam konteks yang tidak dievaluasi:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

Masalahnya di sini adalah bahwa kita tidak diizinkan untuk mendefinisikan secara implisit constexpr duration::duration(duration&&)dalam program ini, jadi ekspresi dalam daftar penginisialisasi bukan merupakan ekspresi konstan (karena memanggil fungsi constexpr yang belum didefinisikan), sehingga penginisialisasi bracing berisi konversi penyempitan , jadi programnya salah bentuk.

Jika kita batalkan komentar pada baris # 1, konstruktor pemindahan secara implisit didefinisikan dan program tersebut valid. Aksi seram di kejauhan ini sangat disayangkan. Implementasi berbeda pada titik ini.

Anda dapat membaca deskripsi masalah lainnya.

Resolusi untuk masalah ini diadopsi di P0859 di Albuquerque pada 2017 (setelah C ++ 17 dikirim). Masalah itu adalah pemblokir untuk keduanya dapat memiliki constexpr std::swap(diselesaikan dalam P0879 ) dan constexpr std::invoke(diselesaikan dalam P1065 , yang juga memiliki contoh CWG1581), baik untuk C ++ 20.


Contoh paling sederhana untuk dipahami di sini, menurut pendapat saya, adalah kode dari laporan bug LLVM yang ditunjukkan dalam P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 adalah tentang kapan fungsi anggota constexpr didefinisikan, dan resolusi memastikan bahwa mereka hanya ditentukan saat digunakan. Setelah P0859, di atas terbentuk dengan baik (tipe bis int).

Karena std::swapdan std::invokekeduanya harus bergantung pada memeriksa fungsi anggota (memindahkan konstruksi / penugasan di yang sebelumnya dan operator panggilan / panggilan pengganti di yang terakhir), keduanya bergantung pada resolusi masalah ini.

Barry
sumber
Jadi, mengapa CWG-1581 mencegah / membuatnya tidak diinginkan untuk menandai fungsi swap sebagai constexpr?
einpoklum
3
Swap @einpoklum membutuhkan std::is_move_constructible_v<T> && std::is_move_assignable_v<T>adalah true. Itu tidak dapat terjadi jika fungsi anggota khusus belum dibuat.
NathanOliver
@NathanOliver: Menambahkan ini pada jawaban saya.
einpoklum
5

Alasannya

(karena @NathanOliver)

Untuk memungkinkan constexprfungsi swap, Anda harus memeriksa - sebelum membuat contoh templat untuk fungsi ini - bahwa tipe yang ditukar adalah gerakan-konstruktif dan dapat ditetapkan-pindah. Sayangnya, karena cacat bahasa hanya diselesaikan dalam C ++ 20, Anda tidak dapat memeriksa itu, karena fungsi anggota yang relevan mungkin belum didefinisikan, sejauh menyangkut kompiler.

Kronologi

  • 2016: Antony Polukhin mengajukan proposal P0202 , untuk menandai semua <algorithm>fungsi sebagai constexpr.
  • Kelompok kerja inti dari komite standar membahas cacat CWG-1581 . Masalah ini membuatnya bermasalah untuk memiliki constexpr std::swap()dan juga constexpr std::invoke()- lihat penjelasan di atas.
  • 2017: Antony merevisi proposal beberapa kali untuk mengecualikan std::swapdan beberapa konstruksi lainnya, dan ini diterima ke dalam C ++ 17.
  • 2017: Resolusi untuk masalah CWG-1581 diajukan sebagai P0859 dan diterima oleh komite standar pada 2017 (tetapi setelah C ++ 17 dikirimkan).
  • Akhir 2017: Antony mengajukan proposal pelengkap, P0879 , untuk membuat std::swap()constexpr setelah resolusi CWG-1581.
  • 2018: Proposal pelengkap diterima (?) Ke dalam C ++ 20. Seperti yang ditunjukkan Barry, begitu juga std::invoke()perbaikan constexpr .

Kasus spesifik Anda

Anda dapat menggunakan constexprswapping jika Anda tidak memeriksa move-constructibility dan move-assignability, melainkan langsung memeriksa beberapa fitur tipe lain yang memastikannya. mis. hanya tipe primitif dan tidak ada kelas atau struct. Atau, secara teoritis, Anda bisa melepaskan cek dan hanya berurusan dengan kesalahan kompilasi yang mungkin Anda temui, dan dengan perilaku flaky beralih di antara kompiler. Bagaimanapun, jangan ganti std::swap()dengan hal semacam itu.

einpoklum
sumber