1 poin oleh GN⁺ 18 jam lalu | 2 komentar | Bagikan ke WhatsApp
  • Pustaka standar C++ berulang kali secara resmi men-deprecate desain yang keliru atau membiarkannya tetap ada di samping fitur pengganti baru sejak C++11, sehingga pengembang harus mengetahui kapan sebuah lapisan termasuk “lapisan yang sebaiknya tidak digunakan”
  • Lapisan penarikan resmi mencakup item seperti std::auto_ptr, spesifikasi pengecualian dinamis, antarmuka garbage collection C++11, dan std::aligned_storage, yang disertai paper deprecate/penghapusan, sementara std::function juga berada dalam arus penggantian selama 15 tahun yang dikelilingi oleh std::move_only_function, std::copyable_function, dan std::function_ref
  • Lapisan penghindaran tidak resmi mencakup fitur yang tetap ada di standar tetapi dihindari dalam kode produksi, seperti std::regex yang lambat, std::async yang menunggu di destruktor dan menciptakan jebakan deadlock, <iostream>, std::list, std::deque, dan std::vector<bool>
  • Masalah kontainer default paling menonjol pada std::unordered_map, std::map, dan std::list; dalam benchmark workload yang sama, P99 implementasi naif C++ tercatat 302.653 cycles, sedangkan implementasi naif Rust 5.177 cycles, selisih 58 kali
  • Pilihan stabilitas ABI menjadi perbedaan inti karena, tidak seperti bahasa lain yang mengurangi kesalahan lewat penghapusan, edition, atau transisi versi mayor, C++ pada praktiknya mempertahankan default yang keliru hampir permanen di dalam std::

Titik awal: penilaian “legacy” untuk std::function

  • Tabel referensi cepat std::copyable_function milik Sandor Dargo mengklasifikasikan std::function sebagai “Legacy. Avoid in new code.”
  • std::function masuk di C++11, sedangkan wrapper pengganti terbaru std::copyable_function masuk di C++26; rekomendasi untuk fitur baru ini lebih dekat ke “jangan pakai yang lama” daripada “pakai ini saat Anda butuh callable yang bisa disalin”
  • const operator() pada std::function memiliki cacat const-correctness karena dapat memanggil callable non-const, dan hal ini tak bisa diperbaiki tanpa merusak ABI
  • Sebagai respons atas cacat tersebut, std::move_only_function berada pada alur P0288R9 di C++23, std::copyable_function pada P2548R6 di C++26, dan std::function_ref pada P0792R14 di C++26

Fitur standar yang secara resmi ditarik kembali

  • std::auto_ptr di-deprecate pada C++11 karena semantik copy-as-move merusak kode generik dan kontainer standar, lalu dihapus pada C++17 lewat N4190; paper yang sama juga menghapus adaptor C++98 di <functional> dan std::random_shuffle
  • std::random_shuffle digantikan oleh std::shuffle karena bergantung pada std::rand dan state global
  • Spesifikasi pengecualian dinamis throw(X, Y) di-deprecate pada C++11, dihapus pada C++17 lewat P0003R5, dan alias throw() dihapus oleh P1152 di C++20
  • std::iterator di-deprecate pada C++17 lewat P0174R2, dan penghapusannya di C++26 didorong lewat P3365R1; penggantinya adalah mendefinisikan langsung lima typedef
  • std::aligned_storage dan std::aligned_union masuk pada C++11 lalu di-deprecate pada C++23 lewat P1413R3; masalah yang disebut mencakup boilerplate typename ::type, reinterpret_cast, undefined behavior saat Len == 0, dan ketiadaan constexpr
  • std::not1, std::not2, unary_negate, dan binary_negate di-deprecate pada C++17, dihapus pada C++20, dan digantikan oleh std::not_fn dari P0005
  • Keluarga std::declare_reachable dari antarmuka garbage collection C++11 dihapus pada C++23 lewat P2186R2 setelah implementasi utama tak pernah menyediakan garbage collector nyata
  • Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS, dan Networking TS juga mengalami redesain, penggantian, atau penundaan sebelum digabungkan; Reflection beralih ke P2996, dan Executors berubah ke arus sender/receiver P2300

Fitur yang tetap ada di standar tetapi dihindari di lapangan

  • std::regex masuk pada C++11, tetapi P1844R1 mencatat di komite bahwa performanya “sangat buruk dibanding solusi lain yang tersedia”; arus penggantinya mencakup CTRE dan P1433R0, sementara di luar standar ada Boost.Regex, RE2, dan PCRE2
  • std::async memblokir di destruktor future yang dikembalikan hingga tugas asinkron selesai, dan N3679 mencatat jebakan deadlock yang ditimbulkannya
  • <iostream> lambat, terikat pada locale, tidak thread-safe untuk formatting, dan terkenal dengan pesan error yang buruk; namun bahkan setelah hadirnya std::format dari P0645 di C++20 serta std::print dan std::println dari P2093 di C++23, ia belum di-deprecate
  • std::list adalah salah satu item yang ditunjukkan Bjarne Stroustrup dalam keynote GoingNative 2012 bahkan untuk workload penyisipan di tengah std::vector tetap menang, dan jawaban dalam tulisan lanjutan Are lists evil? juga mendekati “ya”
  • std::deque tercatat dalam isu publik Microsoft STL microsoft/STL#147 sebagai item dengan ukuran blok yang diwajibkan standar terlalu kecil dan memerlukan perombakan performa besar pada ABI break berikutnya
  • std::valarray masuk pada 1998 sebagai kontainer numerik, tetapi optimisasi expression template tak pernah terwujud, dan menurut cppreference implementasi yang ada tampaknya tidak memiliki kode khusus di luar kontainer umum
  • std::vector<bool> dibahas secara representatif dalam On vector<bool> oleh Howard Hinnant; penyimpanan bit-packed itu sendiri berguna, tetapi penamaannya sebagai spesialisasi std::vector menciptakan jebakan yang memicu perilaku salah dalam kode generik saat T = bool
  • volatile di-deprecate pada operasi majemuk serta posisi parameter/pengembalian oleh P1152R4 di C++20, lalu sebagian dibatalkan kembali oleh P2327R1 di C++23, dan pembatalan tambahan dijadwalkan lewat P2866R0 di C++26

Kontainer default yang tak bisa diperbaiki lewat ABI

  • std::unordered_map praktis melarang open addressing karena spesifikasi bucket dan stabilitas iterator pada C++11, dan struktur Google SwissTable disebut menunjukkan keunggulan performa sekitar 3 kali dibanding std::unordered_map
  • Folly F14, Boost unordered_flat_map, dan ankerl::unordered_dense juga merupakan alternatif ke arah yang sama, sementara Rust HashMap memakai port SwissTable hashbrown sebagai default pustaka standarnya
  • std::map dan std::set berbasis red-black tree bertipe node, sehingga memerlukan alokasi heap per node dan pointer chasing di setiap iterasi; Abseil btree_map dan Rust BTreeMap menghindari masalah yang sama dengan basis B-tree
  • C++23 menambahkan std::flat_map dan std::flat_set lewat P0429R9, tetapi tidak bisa mengubah desain dasar std::unordered_map, std::map, dan std::list itu sendiri
  • Benchmark multi-symbol order book membandingkan std::unordered_map + std::map + std::list di C++ dengan HashMap + BTreeMap + VecDeque di Rust pada workload yang sama, seed yang sama, dan core terisolasi yang sama
Implementasi P99 cycles
C++ naive (unordered_map + map + list) 302,653
C++ step 1 (flat_hash_map + map + deque) 9,951
C++ step 2 (flat_hash_map + btree_map + deque) 9,114
C++ step 3 (flat_hash_map + btree_map + vector) 4,268
Rust naive (HashMap + BTreeMap + VecDeque) 5,177
  • Hanya dengan mengganti std::list ke std::vector didapat sekitar 70 kali peningkatan, mengganti std::unordered_map ke flat_hash_map memberi 3–5 kali, dan mengganti std::map ke btree_map memberi 1,09 kali yang masih dalam kisaran noise
  • Fokus perbandingan ini bukan bahwa bahasa Rust sendiri 58 kali lebih cepat dari C++, melainkan bahwa pustaka standar Rust memilih default yang benar sementara pustaka standar C++ tak bisa memperbaiki tiga default tersebut karena ABI

Masalah Vasa dan penumpukan fitur

  • Dokumen WG21 tahun 2018 P0977R0 “Remember the Vasa!” dari Bjarne Stroustrup memakai metafora tenggelamnya kapal perang Swedia Vasa pada 1628, dan menilai komite memiliki “sekitar 150 koki” tanpa cukup membahas dampak tiap fitur terhadap keseluruhan sistem
  • std::simd dibahas sebagai contoh representatif pola yang sama dalam std::simd Is a Solution to the Wrong Problem; fitur ini dimulai oleh Matthias Kretz dari pustaka Vc, lalu melalui P0214, Parallelism TS 2, dan P1928 sebelum masuk ke C++26
  • Saat std::simd masuk ke standar, di luar standar sudah ada Google Highway, ISPC, EVE, xsimd, dan SIMDe; auto-vectorizer GCC dan Clang juga telah membaik, sehingga dikemukakan hasil bahwa loop skalar -O3 -march=native lebih baik daripada std::simd
  • std::simd memiliki masalah seperti waktu kompilasi 10 kali lebih lambat dibanding kode skalar setara, lebih lambat dari auto-vectorizer yang hendak digantikannya, serta tak mampu merepresentasikan vektor scalable-width ARM SVE dan runtime dispatch
  • Tiga implementasi libstdc++, libc++, dan MSVC STL masing-masing dipelihara oleh tim engineer berukuran satu digit; setiap fitur standar baru menambah matriks pengujian, bug kesesuaian, interaksi antarf fitur, dan item bug tracker yang harus diwarisi perawat berikutnya
  • std::regex membawa masalah yang sudah dikenal selama 15 tahun, std::deque memiliki isu yang menuntut redesain, dan modules C++20 digambarkan belum bekerja rapi di ketiga implementasi bahkan 6 tahun setelah distandarkan
  • Pengetahuan operasional nyata tentang standar C++ modern akhirnya terkonsentrasi pada segelintir spesialis penuh waktu yang memahami kapan lapisan yang salah muncul, workaround pihak ketiga, perbedaan tiga implementasi pustaka standar, dan kesenjangan antara teori dan praktik

Perbedaan dengan bahasa lain: bukan ada tidaknya kesalahan, melainkan tingkat pelestarian

  • Python menghapus lebih dari 20 modul pustaka standar lewat PEP 594, menghapus distutils di Python 3.12 lewat PEP 632, dan PEP 387 secara eksplisit memberi wewenang memperpendek siklus deprecate untuk fitur berbahaya atau rusak
  • Java memproses Applet API lewat jalur 8 tahun: di-deprecate pada Java 9, dijadwalkan dihapus pada Java 17, lalu benar-benar dihapus lewat JEP 504; Nashorn dihapus pada Java 15 lewat JEP 372
  • Java SecurityManager menjadi deprecate-for-removal lewat JEP 411 dan dinonaktifkan permanen lewat JEP 486, sementara JEP 398 membahas jalur menuju penghapusan Applet API
  • Rust memilih edition 2015, 2018, 2021, dan 2024 per crate lewat opt-in di Cargo.toml; mem::uninitialized diganti oleh MaybeUninit, std::error::Error::description oleh source, dan makro try! oleh operator ?
  • C# menerima transisi versi mayor dari .NET Framework ke .NET Core dengan melepas BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server, dan WebForms
  • JavaScript hampir tak pernah menghapus karena kendala kompatibilitas web, tetapi cancelable promises ditarik pada Stage 1, SIMD.js ditinggalkan ke arah WebAssembly SIMD, dan Go memilih hanya meninggalkan io/ioutil dalam status deprecate karena janji kompatibilitas Go 1
  • Perbedaan C++ bukan pada apakah ia pernah membuat kesalahan, melainkan pada tingkat pelestariannya terhadap item seperti std::regex, std::unordered_map, std::vector<bool>, std::valarray, dan cacat const-correctness pada std::function

Pelestarian permanen yang dibentuk stabilitas ABI

  • P1863R1 “ABI - Now or Never” adalah arus yang mempertanyakan apakah C++23 harus menerima ABI break atau memilih stabilitas ABI permanen, dan komite pada praktiknya memilih stabilitas ABI permanen
  • Pilihan ini membuat perbaikan std::regex, perpindahan std::unordered_map ke open addressing, serta perubahan struktur std::list, std::map, dan std::deque menjadi sulit
  • ABI pustaka standar C++ dipaksakan oleh dynamic linker; objek yang dikompilasi dengan satu rilis libstdc++ harus bisa ditautkan dengan objek dari rilis lain, sehingga detail seperti layout std::string dan konfigurasi std::regex_traits terkunci ke binary yang didistribusikan
  • Kendala ini dirinci dalam dokumen seperti kebijakan ABI libstdc++ dan Itanium C++ ABI
  • Pengguna Python bisa memilih python==3.12, pengguna Rust memilih edition di Cargo.toml, dan pengguna Java memilih versi JDK, sementara pengguna C# memilih TFM seperti net6.0 atau net8.0, tetapi C++ tidak memiliki Cargo.toml untuk std::
  • -std=c++26 memilih header dan aturan bahasa yang ingin dipakai, tetapi tidak menyediakan std::string yang berbeda atau std::unordered_map yang didesain ulang
  • Karena itu, pustaka standar C++ yang dikirim ke produksi pada 2026 tetap memuat default yang keliru yang diterima komite sejak 1998, dipertahankan oleh desain dan mekanisme pemaksaan
  • Codebase C++ modern di tier-one trading firm, search engine, dan browser sangat bergantung pada pustaka non-standar seperti Boost, Abseil, Folly, EASTL, Chromium //base, kontainer buatan sendiri, custom allocator, CTRE, Outcome, dan pustaka coroutine

2 komentar

 
dieafterwork 3 jam lalu

Tulisan aslinya sangat panjang, tetapi setelah dibaca sampai selesai memang terasa cukup kuat nuansa pemuja Rust-nya.
Meski begitu, saya juga jadi tahu banyak informasi yang sebelumnya tidak saya ketahui. Terima kasih untuk tulisan yang bagus ini.

 
GN⁺ 18 jam lalu
Pendapat di Lobste.rs
  • Kalau dipikir-pikir apakah ekosistem Rust juga mengalami churn serupa, sepertinya yang besar hanya beberapa
    Saat Leakpocalypse, disimpulkan bahwa destructor Drop tidak bisa selalu diandalkan akan dijalankan demi menjaga invarians keamanan, dan perubahan API nyatanya hampir tidak ada selain penghapusan std::thread::scoped. Setelah itu muncul pengganti yang bisa melakukan hal serupa secara sound
    std::mem::uninitialized telah deprecated dan sekarang dianggap unsound. Tipe-tipe Range yang lama rencananya akan perlahan digantikan oleh tipe baru yang nyaris sama untuk memperbaiki masalah API yang relatif kecil. std::error::Error::description dideprecated karena sebagian besar tipe error tidak ingin menyimpan string, dan ada pengganti langsung berupa implementasi Display
    Mengingat std sudah stabil selama 11 tahun, ini cukup mengejutkan; bagian std lainnya masih ada dan tetap berfungsi, dan 98% darinya masih dianggap Rust idiomatis. Sebaliknya, standard library C++ tampak terlalu ringan tangan dalam menambah fitur, tetapi berada di posisi berbahaya karena sangat konservatif dalam melakukan deprecate apa pun kondisinya

    • Ternyata saya sama sekali tidak tahu soal Leakpocalypse: faultlore (2015)
    • Saya juga teringat masalah trait Iterator yang meminjam isinya sendiri. Ini masalah kronis yang terus muncul dalam diskusi Rust sebagai “kenapa ini tidak bisa dipakai dan butuh jalan memutar”
      Demikian juga f32 dan f64 yang tidak mengimplementasikan Cmp dan malah menyediakan metode f32::total_cmp, juga jadi bagian menyebalkan yang sering bikin engineer baru tersandung, sehingga kita harus menghela napas lalu menjelaskan latar belakangnya
      mekanisme formatting panic juga kurang bagus, dan ada banyak tulisan blog yang mengeluhkan bahwa panic handler bawaan memakai formatting dan sulit dimatikan, sehingga cukup memakan ukuran executable
  • Menurut saya pribadi, desain standard library yang usang sangat mengurangi popularitas dan kegunaan C++
    Banyak masalah yang sering disalahkan ke bahasanya sendiri sebenarnya lebih tepat diarahkan ke standard library
    Misalnya, pernyataan “C++ lambat dikompilasi” itu tidak tepat. Memakai fitur C++ tidak secara inheren lambat; yang membuatnya lambat adalah standard library yang membengkak dengan header dan dependensi dalam jumlah besar, serta terlalu banyak memakai template bahkan untuk abstraksi sederhana
    Pernyataan “C++ tidak aman” juga sebagian benar, tetapi desain standard library memperparahnya. Tidak ada alasan pola yang lebih aman dari desain API Rust tidak bisa diterapkan ke standard library baru. Tentu saja, salah satu kelebihan besar C++ adalah kompatibilitas ke belakang, jadi ini masalah yang sangat kompleks

    • Dalam beberapa kasus, iya. Kita bisa membuat vec[idx] melempar exception atau abort alih-alih menghasilkan akses di luar batas/perilaku tak terdefinisi. Tetapi karena perbedaan bahasa, dalam banyak kasus API aman jauh lebih sulit dibuat di C++
      Rust pada dasarnya punya destructive move, sedangkan C++ tidak. Karena itu API smart pointer mau tak mau jadi unsafe, atau setidaknya mengejutkan dan mudah bikin crash. Misalnya dengan membuat program abort saat smart pointer yang sudah dipindahkan diakses
      Rust punya anotasi lifetime, C++ tidak. Karena itu Rust bisa mencegah hal-hal seperti invalidasi iterator dalam desain API iterator, sedangkan di C++ itu pada praktiknya sangat sulit. Rust juga punya pattern matching, jadi API seperti Option bisa memberi pendekatan “cek lalu langsung pakai” dengan ergonomis. C++ juga bisa menyediakan versi std::option yang tidak menghasilkan UB saat nilai kosong diakses, tetapi penggunaannya akan jauh lebih merepotkan dibanding C++ sekarang maupun Rust. Operator ? di Rust juga sangat membantu di sini
      Saya tahu C++ bisa menambahkan sesuatu yang mirip pattern matching lewat overload set seperti std::variant, tetapi menurut saya itu jauh lebih sulit dipakai dan lebih mudah bikin salah
    • Saya rasa hal yang sama juga berlaku untuk C. Banyak masalah C muncul karena stdlib-nya payah
      Hanya dengan library modern yang punya string dan array library, beberapa generic container, serta dukungan native untuk allocator, C bisa jadi jauh lebih ergonomis dan lebih mudah dipakai. Tentu beberapa cacat bahasa tidak akan hilang hanya dengan mengganti library, tetapi tetap bisa membawa banyak perbaikan
      Kalau melihat codebase C modern, banyak yang luas memakai library kustom untuk allocator, string, vector, hash table, dan operasi file system, dan kalau punya pengalaman dengan C atau pengelolaan resource manual, mengikuti pola itu tidak terlalu sulit
    • Di kantor, kami memakai implementasi slice<T, N> yang bisa merepresentasikan “pointer ke tepat N byte” atau “pointer ke sejumlah byte berapa pun”
      Ada head(n), tail(n), slice(start, end), dan operator indeks, dan semuanya melakukan pemeriksaan batas
      Bekerja dengan abstraksi seperti ini benar-benar menyenangkan, tetapi untuk mendapatkan bahasa yang modern dan agak aman, pada dasarnya kita harus mem-port standard library Rust dan Zig ke C++. Meski begitu, hasilnya tetap sepadan dengan usaha yang dikeluarkan
    • Kalau template dipakai lebih sedikit untuk abstraksi sederhana, bukankah kita jadi kehilangan kinerja?
  • Kalau mau menulis tulisan seperti ini, tolong tulis sendiri. Mungkin daftarnya memang dibuat sendiri, tetapi memasukkannya ke LLM lalu menayangkan hasilnya di halaman web untuk dibaca orang terasa sangat tidak sopan. Kalau saya melihat satu kali lagi kalimat seperti engineer “yang benar-benar bekerja” diajari menghindari “fitur X” sejak “hari pertama”, rasanya saya bisa gila
    Yang memalukan adalah sebenarnya ada banyak hal yang bisa dibahas di sini, tetapi justru tidak mengatakan apa-apa. Pasti ada alasan tulisan ini dibuat, jadi sampaikan alasannya. Pasti ada bagian dari C++ yang membuat penulis kesal, dan ada fitur yang membingungkan. Fitur-fitur ini buruk bukan hanya karena kegagalan desain yang objektif, tetapi juga karena dampaknya pada kita
    Pernahkah Anda memakai std::iterator lalu dihajar di Slack, atau tidak memakai cast karena reinterpret_cast panjangnya 16 karakter dan sedikit merusak format baris? Akan lebih bagus kalau cerita seperti itu yang muncul di Lobsters. Kalau memang tidak ada cerita seperti itu, jangan dipaksakan, dan jangan suruh GPU menghasilkan kalimat yang sama 10 kali lewat perkalian matriks. Cukup beri anotasi pada bagian yang perlu dikomentari, sisanya tulis sebagai tabel dan bullet point

    • Tulisan ini tidak terasa seperti ditulis oleh LLM
  • Saya sudah memakai C++ selama 20 tahun dan masih memakainya sekarang, tapi saya sangat setuju dengan banyak hal di tulisan ini. Hal yang benar-benar bagus saat memakai Rust belakangan ini, lebih dari sekadar keamanan memori, adalah pustaka standar dan ekosistem paket yang luar biasa
    Contoh yang mewakili adalah pustaka ranges. Sudah 6 tahun sejak distandardisasi, tetapi pustaka standar utama masih belum mengimplementasikannya secara penuh, dan bahkan ketika ada implementasi pun, combiner yang tersedia hanya sedikit. Padanan Rust-nya, metode Iterator, berjumlah 76, dan dengan sekali cargo add, trait itertools menambahkan 130 lagi
    Hal lain yang sangat saya rindukan adalah pattern matching. Ini bisa membuat tipe union seperti std::variant menjadi lebih ergonomis. Usulannya memang sedang dibahas, tetapi bahkan di C++26 pun belum masuk, dan itu disayangkan. Sebaliknya, contracts dan executors justru masuk, padahal sejujurnya saya belum pernah melihat orang di sekitar saya yang memintanya

    • Salah satu masalah C++ adalah tidak adanya kriteria resmi dan terdokumentasi tentang fitur mana yang seharusnya menjadi fitur bahasa dan mana yang seharusnya menjadi fitur pustaka standar
      Secara umum, kriteria yang saya pakai seperti ini. Jika sebuah fitur mendukung use case yang diinginkan dan tidak bisa diekspresikan sebagai pustaka standar, maka itu harus masuk ke bahasa. Kalau memungkinkan, fitur yang diinginkan itu sebaiknya dipecah menjadi elemen-elemen mandiri seminimal mungkin yang juga bisa dipakai untuk tujuan lain
      Fitur yang dipakai di hampir semua codebase harus masuk ke pustaka standar. Jika suatu tipe umum dipakai sebagai antarmuka antar pustaka, maka itu juga harus masuk ke pustaka standar. Kita tidak ingin setiap pustaka mendefinisikan tipe tuple atau string versinya sendiri. Dalam C++, yang pertama pada praktiknya memang seperti itu sampai sebelum C++11, dan yang kedua masih begitu karena std::string adalah disaster. Ini juga berlaku untuk tipe antarmuka, dan C++ belakangan ini kebanyakan menanganinya dengan concepts
      Sisanya harus masuk ke pustaka modular yang dapat digunakan ulang. Rust cukup bagus dalam menyediakan kumpulan pustaka eksternal yang stabil dan blessed, jadi tekanan untuk berkata, “semua game yang ditulis dengan Rust butuh struktur data ini, jadi mari masukkan ke pustaka standar” jauh lebih kecil. Orang yang menulis game tinggal mengambil crate yang mereka butuhkan. C++ tidak pernah benar-benar menerima gagasan “paket yang bagus untuk direkomendasikan bagi masalah yang dimiliki banyak orang, meski bukan mayoritas”
  • Yang mengkhawatirkan adalah hal-hal yang sedang ditambahkan sekarang mana yang pada akhirnya nanti akan ditarik kembali. Contracts baru saja masuk ke C++26, tetapi cacat desain yang serius sudah mulai ditunjukkan
    Saya tidak ingin mencela “desain oleh komite” secara umum. Menurut saya, organisasi seperti ini punya tujuan penting dan kekuatan uniknya sendiri. Hanya saja, kekuatan itu bukan pada merancang fitur yang sepenuhnya baru dari green field
    Titik di mana WG21 dan WG14 benar-benar bersinar adalah ketika ruang desainnya sudah cukup dieksplorasi dan, kalau bisa, ada beberapa implementasi yang sudah ada, lalu mereka mengangkatnya menjadi fitur standar yang bisa diterima sebagian besar pengguna dan implementator. std::embed adalah contoh seperti itu
    Sebaliknya, jika sesuatu seperti ekstensi GC yang disebut di artikel, std::memory_order_consume, atau C++20 modules distandardisasi sebelum ada yang benar-benar berhasil mengimplementasikannya dengan baik, hasilnya cenderung jadi sangat buruk

    • C++ dan Haskell sama-sama dirancang oleh komite, tetapi kedua bahasa itu hampir seperti berlawanan kutub. Saya mengingat hal ini setiap kali tergoda untuk menganggap “$X dirancang oleh komite” menyiratkan sesuatu tentang $X
  • Dulu saya cukup terkejut saat sadar bahwa C++ tidak melakukan versioning pada standard library-nya. Saya tidak menyangka tulisan ini akan menyoroti hal itu secara tepat
    Menarik juga bahwa disebutkan Go bersikap konservatif dengan cara yang mirip soal forward compatibility. Namun Go juga tampaknya sama konservatifnya dalam menambahkan fitur, sehingga berhasil menghindari sebagian besar masalah C++. Ketiadaan ABI yang stabil mungkin juga membantu
    Dari library populer yang saya tahu, hanya libcamera yang secara eksplisit mengekspos ABI C++, dan itu cukup merepotkan. Dari pengalaman saya, library C++ pun biasanya mengekspor simbol lewat ABI C, dan itu juga mempermudah interoperabilitas dengan bahasa lain. Mungkin saya yang ketinggalan perkembangan
    Dan bukankah ada beberapa quirks dalam kompatibilitas ABI antara Clang dan MSVC? Saya ingat Conan secara eksplisit tidak menganjurkan atau melarang mencampur compiler, jadi saya jadi bertanya-tanya kenapa komite C++ begitu berusaha mempertahankan stabilitas ABI

    • Itu tidak sepenuhnya benar. C++ hanya tidak melakukan versioning pada standard library secara terpisah dari bahasanya
      Ada dua hal yang sangat terkait di sini: spesifikasi standard library dan implementasinya. Spesifikasinya berlaku untuk kombinasi bahasa+library yang utuh, sedangkan implementasi biasanya berusaha mendukung setidaknya satu atau lebih versi spesifikasi
      Ada banyak library yang mengekspos antarmuka C++, termasuk yang sangat besar seperti Qt
      Masalahnya adalah mesin abstrak C++ tidak mendefinisikan proses linking. Karena itu, ia tidak bisa mendefinisikan bagaimana dynamic library bekerja. Dynamic linking C++ di sistem UNIX mengikuti model C. Pura-pura seolah itu dynamic linking lalu melempar urusannya ke masalah loader. Akibatnya muncullah hal-hal mengerikan seperti copy relocation. Windows punya konsep yang jauh lebih berprinsip tentang apa itu shared library, tetapi karena itu beberapa idiom library C++ di UNIX tidak bisa berjalan di Windows
      Shared library menimbulkan masalah besar untuk fitur seperti template C++. Agar template bisa di-instantiate dengan tipe buatan pengguna, definisi penuhnya harus ada di header karena compiler tidak bisa melihat batas antar compilation unit. Dalam shared library, kode yang sama di-instantiate di banyak tempat. Jika program dan library sama-sama meng-instantiate template yang sama dengan parameter yang sama, keduanya akan memiliki salinan, dan linker serta loader harus memastikan hanya satu yang dipakai dalam program akhir yang dimuat
      Jika dibandingkan dengan Swift, Swift secara eksplisit menyatakan bahwa “shared library itu ada, dan bahasa mengekspos struktur tingkat bahasa untuk merepresentasikannya”. Jika ingin mengekspos generic melintasi batas shared library, itu bisa, tetapi akan diturunkan menjadi versi dynamic dispatch untuk semua pemanggil eksternal. Di C++ ini juga bisa dibuat manual. Anda bisa membuat template versi umum yang memakai type-erasure wrapper, lalu secara eksplisit menggunakan instantiation konkret lain. Namun ini sulit dan serba manual. Di Swift, ini cukup menjadi “beginilah perilakunya di batas shared library”
      Penyembunyian tipe juga serupa. C++ memakai pola pImpl untuk membuat public interface yang mengekspos perilaku melewati batas library tetapi menyembunyikan implementasinya. Swift memiliki mesin abstrak yang tahu di mana batas library berada, dan mengatakan bahwa “ukuran tipe yang tidak dinyatakan ABI-stable bukan konstanta waktu kompilasi di seberang batas shared library”
      Standar juga menyangkal realitas dalam bentuk lain. Hampir setiap codebase C++ non-trivial yang pernah saya kerjakan dikompilasi dengan -fno-rtti -fno-exceptions atau opsi setara di CL.EXE. Standar tidak mengakui ini sebagai kemungkinan. Sebagian besar fungsi standard library masih mengandalkan exception untuk pelaporan error, jadi bila dikompilasi dengan -fno-exception ia hanya akan memanggil abort. Ini membuat elemen standard library yang melakukan alokasi memori dinamis tidak bisa dipakai di embedded. std::vector<T>::push_back bisa membuat program crash
      Bagian artikel yang mengatakan “komite bukan hanya gagal menghapus fitur buruk, tetapi juga terus menambahkan fitur baru yang tidak diminta engineer praktis” 100% sama dengan cara contracts muncul. Verus menunjukkan apa yang dimungkinkan oleh sistem contracts yang bagus dalam bahasa yang menargetkan lingkungan seperti C++. Contracts P2900 adalah gabungan kebutuhan yang saling bertentangan, sehingga memperburuk semua masalah yang mungkin cocok ditangani contracts
      Saya rasa kesimpulan bahwa “engineer C++” dibayar jauh lebih tinggi daripada “engineer yang bisa memrogram” itu tidak benar. Kenyataannya, tidak ada yang benar-benar menulis kode sesuai standar C++ apa adanya; semua orang menulis sesuai subset-of-a-superset internal favorit masing-masing
    • go vet juga bernilai di sini. Itu menyediakan upgrade otomatis untuk perbaikan API
  • Sejak tahun lalu saya hampir meninggalkan C++ sepenuhnya, awalnya pindah ke Kotlin lalu ke Swift. Di kantor saya masih harus memelihara C++, tetapi kode baru yang saya tulis jauh lebih rapi, ringkas, dan aman. Ada tradeoff pada ukuran kode dan mungkin performa, tetapi menurut saya itu sepadan

  • Saya ingat semantik for loop di Go pernah berubah dengan cara yang mematahkan kompatibilitas ke belakang, jadi saya sempat mengira kalimat ini salah: https://go.dev/blog/loopvar-preview
    Namun ternyata Go juga memakai pendekatan yang mirip dengan editions di Rust. Semantik hanya berubah jika Anda menyatakan versi Go 1.22 atau lebih baru. Mungkin io/ioutil juga bisa dihapus dengan cara seperti ini, tetapi tampaknya tidak cukup layak untuk sampai merusak kode lintas batas edition

  • Jika C++ tidak benar-benar mencoba ide-ide buruk ini dan membuktikan bahwa itu memang ide buruk, mungkin Rust tidak akan ada dalam bentuknya yang sekarang. Big Thank You!

  • Saya tertarik pada pengganti standard library bergaya Rust untuk C++. Saya tahu rpp yang memang mengejar tujuan itu: https://github.com/TheNumbat/rpp
    Apakah ada opsi lain? Maksud saya bukan implementasi lain dari stdlib C++ seperti EASTL, melainkan library yang lebih dekat mengikuti Rust. Saya paham beberapa hal seperti std::initializer_list sudah tertanam di sintaks, tetapi selebihnya semuanya bisa diganti