1 पॉइंट द्वारा GN⁺ 18 시간 전 | 2 टिप्पणियां | WhatsApp पर शेयर करें
  • C++ standard library ने C++11 के बाद खराब डिज़ाइनों को औपचारिक रूप से deprecate करने या नए replacement features के साथ उन्हें छोड़ देने का काम बार-बार किया है, और अब संरचना ऐसी हो गई है जिसमें developers को यह जानना पड़ता है कि “किस layer का इस्तेमाल नहीं करना चाहिए”
  • औपचारिक रूप से वापस ली गई layer में std::auto_ptr, dynamic exception specifications, C++11 garbage collection interface, std::aligned_storage जैसे items शामिल हैं जिन पर deprecation/removal papers लगे; std::function भी std::move_only_function, std::copyable_function, std::function_ref से घिरे 15 साल लंबे replacement flow में रखा गया है
  • अनौपचारिक रूप से टाली जाने वाली layer में धीमा std::regex, destructor में wait करके deadlock trap बनाने वाला std::async, <iostream>, std::list, std::deque, std::vector<bool> जैसे features आते हैं, जो standard में तो बचे हैं लेकिन production code अक्सर इन्हें bypass करता है
  • default containers की समस्या std::unordered_map, std::map, std::list में सबसे साफ दिखती है; एक ही workload benchmark में C++ naive implementation का P99 302,653 cycles और Rust naive implementation का 5,177 cycles रहा, यानी 58x का अंतर
  • ABI stability का चुनाव वह मुख्य फर्क है जिसकी वजह से दूसरी भाषाएँ delete, editions, या major version transitions के ज़रिए गलतियों को कम कर लेती हैं, जबकि C++ अपने खराब defaults को std:: के भीतर लगभग स्थायी रूप से बचाकर रखता है

शुरुआत: std::function को “legacy” मानना

  • Sandor Dargo की std::copyable_function quick reference table std::function को “Legacy. Avoid in new code.” के रूप में वर्गीकृत करती है
  • std::function C++11 में आया था, और उसका latest replacement wrapper std::copyable_function C++26 में आता है; नए feature की सिफारिश का भाव “जब copyable callable object चाहिए तो इसे इस्तेमाल करो” से ज़्यादा “पुराने वाले का इस्तेमाल मत करो” के करीब है
  • std::function का const operator() एक const-correctness defect रखता है, क्योंकि यह non-const callable object को call कर सकता है, और ABI तोड़े बिना इसे ठीक नहीं किया जा सकता
  • इसी defect के जवाब में std::move_only_function C++23 के P0288R9, std::copyable_function C++26 के P2548R6, और std::function_ref C++26 के P0792R14 वाले flow में रखे गए हैं

वे standard features जिन्हें औपचारिक रूप से वापस लिया गया

  • std::auto_ptr की copy-move semantics generic code और standard containers को तोड़ देती थीं; इसे C++11 में deprecated किया गया और C++17 में N4190 से हटा दिया गया; इसी paper ने C++98 <functional> adapters और std::random_shuffle को भी हटाया
  • std::random_shuffle std::rand और global state पर निर्भर था, इसलिए इसे std::shuffle से replace किया गया
  • dynamic exception specification throw(X, Y) को C++11 में deprecated किया गया, C++17 में P0003R5 से हटाया गया, और throw() alias को C++20 के P1152 से हटा दिया गया
  • std::iterator को C++17 में P0174R2 से deprecated किया गया, और C++26 removal P3365R1 में आगे बढ़ रही है; replacement तरीका पाँच typedefs को सीधे define करना है
  • std::aligned_storage और std::aligned_union C++11 में आए, फिर C++23 के P1413R3 से deprecated हुए; समस्याओं में typename ::type boilerplate, reinterpret_cast, Len == 0 पर undefined behavior, और constexpr की अनुपस्थिति शामिल थीं
  • std::not1, std::not2, unary_negate, binary_negate C++17 में deprecated हुए, C++20 में हटाए गए, और P0005 के std::not_fn से replace हुए
  • C++11 garbage collection interface की std::declare_reachable family को C++23 के P2186R2 से हटा दिया गया, क्योंकि major implementations ने कोई वास्तविक garbage collector दिया ही नहीं
  • Concepts TS, Modules TS, Coroutines TS, Reflection TS, Executors TS, Networking TS भी merge होने से पहले redesign, replacement, या delay से गुज़रे; Reflection P2996 की दिशा में बदला और Executors P2300 के sender/receiver flow में बदल गए

वे features जो standard में हैं, लेकिन ज़मीन पर लोग उनसे बचते हैं

  • std::regex C++11 में आया, लेकिन P1844R1 ने committee record में यह दर्ज किया कि इसका performance “दूसरे उपलब्ध समाधानों की तुलना में बहुत खराब” है; replacement flow में CTRE और P1433R0 आते हैं, और standard के बाहर Boost.Regex, RE2, PCRE2 हैं
  • std::async में returned future का destructor asynchronous task के पूरा होने तक block करता है, और N3679 ने इससे बनने वाले deadlock trap को दर्ज किया
  • <iostream> धीमा है, locale से बँधा है, formatting में thread-safe नहीं है, और इसके error messages बदनाम हैं; फिर भी C++20 के P0645 std::format और C++23 के P2093 std::print·std::println आने के बाद भी इसे deprecated नहीं किया गया
  • std::list वह item है जिसके बारे में Bjarne Stroustrup ने 2012 GoingNative keynote में दिखाया कि middle insertion workload में भी std::vector जीत सकता है; बाद की post Are lists evil? का जवाब भी लगभग “हाँ” के करीब है
  • std::deque Microsoft STL की public issue microsoft/STL#147 में ऐसा item दर्ज है जहाँ standard द्वारा तय block size बहुत छोटा है, और अगली ABI break पर बड़े performance redesign की ज़रूरत होगी
  • std::valarray 1998 में एक numeric container के रूप में आया था, लेकिन expression template optimization साकार नहीं हुई, और cppreference के अनुसार implementations में सामान्य containers से बढ़कर कोई खास codepath नज़र नहीं आता
  • std::vector<bool> पर Howard Hinnant की On vector<bool> एक प्रतिनिधि analysis है; bit-packed storage अपने आप में उपयोगी है, लेकिन std::vector specialization की तरह इसका नाम generic code में T = bool होने पर गलत behavior की trap बनाता है
  • volatile को C++20 के P1152R4 से compound operations और parameter/return positions में deprecated किया गया था, फिर C++23 के P2327R1 से कुछ rollback हुआ, और C++26 के P2866R0 में आगे rollback तय है

वे default containers जिन्हें ABI के कारण ठीक नहीं किया जा सकता

  • std::unordered_map में C++11 spec के bucket और iterator stability rules open addressing को लगभग असंभव बना देते हैं; Google SwissTable design को std::unordered_map पर लगभग 3x performance advantage दिखाने वाला बताया गया है
  • Folly F14, Boost unordered_flat_map, ankerl::unordered_dense जैसी alternatives भी इसी दिशा की हैं; Rust HashMap standard library default के रूप में hashbrown SwissTable port का उपयोग करता है
  • std::map और std::set node-based red-black tree हैं, इसलिए हर node पर heap allocation चाहिए और हर traversal पर pointer chasing होती है; Abseil btree_map और Rust BTreeMap B-tree आधारित होने के कारण इस समस्या से बचते हैं
  • C++23 ने P0429R9 से std::flat_map और std::flat_set जोड़े, लेकिन std::unordered_map, std::map, std::list की मूल default design नहीं बदल सका
  • multi-symbol order book benchmark में एक ही workload, एक ही seed, और एक ही isolated core पर C++ के std::unordered_map + std::map + std::list की तुलना Rust के HashMap + BTreeMap + VecDeque से की गई
Implementation 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
  • सिर्फ std::list से std::vector पर जाने से लगभग 70x सुधार दिखा, std::unordered_map से flat_hash_map पर जाने से 3–5x, और std::map से btree_map पर जाने से 1.09x का, यानी noise range के भीतर रहने वाला असर दिखा
  • तुलना का मकसद यह कहना नहीं है कि Rust भाषा खुद C++ से 58x तेज़ है, बल्कि यह कि Rust standard library ने सही defaults चुने, जबकि C++ standard library ABI की वजह से अपने तीन defaults नहीं बदल सकती

Vasa समस्या और features का जमाव

  • Bjarne Stroustrup के 2018 WG21 document P0977R0 “Remember the Vasa!” में 1628 के Swedish warship Vasa के डूबने की उपमा देते हुए कहा गया कि committee में “लगभग 150 cooks” हैं, और individual features का पूरे system पर असर पर्याप्त रूप से नहीं देखा जाता
  • std::simd को std::simd Is a Solution to the Wrong Problem में उसी pattern के प्रतिनिधि item के रूप में लिया गया है; Matthias Kretz ने इसे Vc library से शुरू कर P0214, Parallelism TS 2, और P1928 से होते हुए C++26 तक पहुँचाया
  • जब std::simd standard में आया, तब standard के बाहर Google Highway, ISPC, EVE, xsimd, SIMDe मौजूद थे, और GCC तथा Clang के auto-vectorizers भी बेहतर हो चुके थे; दावे के अनुसार -O3 -march=native scalar loop कभी-कभी std::simd से बेहतर निकला
  • std::simd में समस्याएँ यह बताई गईं कि equivalent scalar code की तुलना में compile time 10x धीमा है, यह उस auto-vectorizer से भी धीमा पड़ सकता है जिसे replace करना था, और यह ARM SVE के scalable-width vectors तथा runtime dispatch को व्यक्त नहीं कर पाता
  • libstdc++, libc++, और MSVC STL — इन तीनों implementations को single-digit size की engineering teams maintain करती हैं; हर नया standard feature test matrix, conformance bugs, feature interaction, और अगले maintainer के लिए bug tracker entries बढ़ाता है
  • std::regex 15 साल से ज्ञात समस्याओं के साथ मौजूद है, std::deque पर redesign-needed issue है, और C++20 modules को standardization के 6 साल बाद भी तीनों implementations में साफ़-सुथरे तरीके से काम न करने वाली स्थिति के रूप में बताया गया है
  • आधुनिक C++ standard की वास्तविक operational knowledge कुछ गिने-चुने full-time experts में सिमट जाती है, जिन्हें गलत layers के समयक्रम, third-party workarounds, तीन standard library implementations के फर्क, और theory बनाम practice का अंतर समझना पड़ता है

दूसरी भाषाओं से फर्क: गलती नहीं, preservation rate

  • Python ने PEP 594 से 20 से अधिक standard library modules हटाए, PEP 632 से distutils को Python 3.12 में हटाया, और PEP 387 खतरनाक या टूटी हुई features के लिए deprecation cycle छोटा करने का अधिकार देती है
  • Java ने Applet API को Java 9 में deprecated किया, Java 17 में removal path पर रखा, और JEP 504 के तहत 8 साल की प्रक्रिया में वास्तविक removal तक पहुँचाया; Nashorn को JEP 372 से Java 15 में हटा दिया गया
  • Java SecurityManager को JEP 411 से deprecation-for-removal दिया गया और JEP 486 से स्थायी रूप से disable किया गया; JEP 398 Applet API removal path को कवर करता है
  • Rust 2015, 2018, 2021, 2024 editions को Cargo.toml में crate-by-crate opt-in के रूप में चुनने देता है; mem::uninitialized को MaybeUninit, std::error::Error::description को source, और try! macro को ? operator ने replace किया
  • C# ने .NET Framework से .NET Core की ओर जाते समय BinaryFormatter, AppDomains, Remoting, Code Access Security, WCF server, WebForms जैसी चीज़ें छोड़ने वाले major version transition को स्वीकार किया
  • JavaScript web compatibility constraints के कारण बहुत कम remove करता है, लेकिन cancelable promises Stage 1 पर वापस ले लिए गए, SIMD.js को WebAssembly SIMD की दिशा में छोड़ दिया गया, और Go ने Go 1 compatibility promise के कारण io/ioutil को सिर्फ deprecated छोड़ने का रास्ता चुना
  • C++ का फर्क यह नहीं कि उससे गलतियाँ हुईं या नहीं, बल्कि यह है कि std::regex, std::unordered_map, std::vector<bool>, std::valarray, std::function का const-correctness defect जैसी चीज़ों को वह लगभग हटा ही नहीं पाता

ABI stability कैसे स्थायी संरक्षण बनाती है

  • P1863R1 “ABI - Now or Never” ऐसा flow था जिसने पूछा कि C++23 में ABI break स्वीकार की जाए या स्थायी ABI stability चुनी जाए; committee ने व्यवहार में स्थायी ABI stability ही चुनी
  • इसी चुनाव की वजह से std::regex को ठीक करना, std::unordered_map को open-addressing में बदलना, और std::list, std::map, std::deque की संरचना बदलना कठिन हो गया
  • C++ standard library ABI को dynamic linker enforce करता है; एक libstdc++ release से compile हुआ object दूसरे release के objects से link हो सके, इसलिए std::string layout और std::regex_traits configuration जैसी details distributed binaries में जड़ हो जाती हैं
  • यह constraint libstdc++ ABI policy और Itanium C++ ABI जैसे documents में concretely दर्ज है
  • Python user python==3.12 चुन सकता है, Rust user Cargo.toml edition, Java user JDK version, और C# user net6.0 या net8.0 TFM चुन सकता है, लेकिन C++ में std:: के लिए कोई Cargo.toml नहीं है
  • -std=c++26 यह चुनता है कि कौन-से headers और language rules इस्तेमाल होंगे, लेकिन यह कोई अलग std::string या redesign किया गया std::unordered_map उपलब्ध नहीं कराता
  • इसलिए 2026 में production में भेजी जा रही C++ standard library, 1998 के बाद committee द्वारा स्वीकार किए गए खराब defaults को design और enforcement — दोनों के कारण — अब भी साथ लेकर चलती है
  • tier-one trading firm, search engine, और browser जैसी आधुनिक C++ codebases Boost, Abseil, Folly, EASTL, Chromium //base, custom containers, custom allocators, CTRE, Outcome, और coroutine libraries जैसी non-standard libraries पर भारी निर्भरता रखती हैं

2 टिप्पणियां

 
dieafterwork 3 시간 전

मूल लेख काफ़ी भारी-भरकम है, लेकिन अंत तक पढ़ते-पढ़ते इसमें rust के प्रबल समर्थक जैसा एहसास काफ़ी आने लगा।
फिर भी, मुझे बहुत-सी ऐसी जानकारियाँ मिलीं जो पहले नहीं पता थीं। अच्छे लेख के लिए धन्यवाद।

 
Lobste.rs की राय
  • अगर Rust ecosystem में भी ऐसे ही churn को याद करने की कोशिश करूँ, तो बड़े मामले शायद गिनती के ही थे
    Leakpocalypse के समय यह निष्कर्ष निकला था कि Drop destructor पर इस बात के लिए हमेशा भरोसा नहीं किया जा सकता कि वह safety invariants बनाए रखने हेतु ज़रूर चलेगा, और वास्तविक API बदलाव लगभग नहीं के बराबर थे; बस std::thread::scoped को हटाया गया था। बाद में ऐसा विकल्प आ गया जो वही काम sound तरीके से करता है
    std::mem::uninitialized को deprecated कर दिया गया, और अब उसे unsound माना जाता है। मौजूदा Range types को अपेक्षाकृत छोटे API मुद्दे ठीक करने के लिए लगभग वैसी ही नई types से धीरे-धीरे बदला जाना है। std::error::Error::description को deprecated किया गया क्योंकि ज़्यादातर error types string स्टोर नहीं करना चाहतीं, और उसका सीधा विकल्प Display implementation है
    यह सोचकर काफ़ी हैरानी होती है कि std 11 साल तक स्थिर रहा, और बाकी std अब भी मौजूद है, काम करता है, और उसका 98% हिस्सा आज भी idiomatic Rust माना जाता है। इसके उलट C++ standard library feature जोड़ने में कुछ ज़्यादा ही जल्दबाज़ लगती है, और किसी भी स्थिति में deprecate करने को लेकर हैरतअंगेज़ रूप से conservative और ख़तरनाक स्थिति में दिखती है

    • मुझे तो Leakpocalypse के बारे में किसी तरह बिल्कुल पता ही नहीं था: faultlore (2015)
    • Iterator trait में अपने ही contents को borrow करने वाली समस्या भी याद आती है। Rust की चर्चाओं में यह बार-बार आने वाली पुरानी दिक्कत है: “मैं यह क्यों नहीं इस्तेमाल कर सकता और workaround क्यों चाहिए?”
      इसी तरह f32 और f64 का Cmp implement न करना और उसकी जगह f32::total_cmp method देना भी नई engineers को अक्सर टकराने वाली झुंझलाहट है, तो फिर लंबी साँस लेकर उसका background समझाना पड़ता है
      panic formatting machinery भी कुछ खास अच्छी नहीं है, और इस पर कई blog posts हैं कि default panic handler formatting का उपयोग करता है, उसे बंद करना मुश्किल है, और वह executable size का अच्छा-खासा हिस्सा खा जाता है
  • मेरी निजी राय में standard library की पुरानी design C++ की लोकप्रियता और usability को काफ़ी कम कर देती है
    जिन कई समस्याओं का दोष लोग भाषा पर डालते हैं, उनका निशाना दरअसल standard library होना चाहिए
    उदाहरण के लिए, “C++ compile होने में धीमा है” यह बात सटीक नहीं है। C++ features इस्तेमाल करना अपने-आप में मूलतः धीमा नहीं है; उसे धीमा बनाती है standard library, जिसमें भारी header bloat और dependencies हैं, और साधारण abstractions के लिए भी templates का अत्यधिक इस्तेमाल है
    “C++ सुरक्षित नहीं है” यह भी कुछ हद तक सही है, लेकिन standard library की design उसे और बदतर बना देती है। Rust API design में इस्तेमाल होने वाले अधिक सुरक्षित patterns को नई standard library पर लागू न कर पाने की कोई वजह नहीं है। हाँ, C++ की सबसे बड़ी ताकतों में से एक backward compatibility है, इसलिए यह बहुत जटिल समस्या है

    • कुछ मामलों में यह सही है। vec[idx] को out-of-bounds access/undefined behavior की जगह exception throw करने या abort करने वाला बनाया जा सकता है। लेकिन language differences की वजह से कई मामलों में C++ में सुरक्षित API बनाना कहीं ज़्यादा कठिन है
      Rust में मूल रूप से destructive move है, लेकिन C++ में नहीं। इसलिए smart pointer APIs या तो unsafe होंगी, या कम-से-कम चौंकाने वाली और crash-y बनेंगी। उदाहरण के लिए, moved-from smart pointer को access करने पर program abort कर देना
      Rust में lifetime annotations हैं, लेकिन C++ में नहीं। इसलिए Rust iterator API design में iterator invalidation जैसी चीज़ों को रोक सकता है, जबकि C++ में यह व्यावहारिक रूप से बहुत कठिन है। Rust में pattern matching भी है, इसलिए Option जैसी APIs “पहले जाँचो, फिर तुरंत उपयोग करो” वाला तरीका ergonomically दे सकती हैं। C++ ऐसा std::option version दे सकता है जिसमें empty value access करने पर UB न हो, लेकिन वह आज के C++ या Rust दोनों से कहीं ज़्यादा झंझट वाला होगा। Rust का ? operator भी यहाँ बहुत मदद करता है
      मुझे पता है कि std::variant जैसी चीज़ों में overload set के ज़रिए pattern matching जैसा कुछ C++ में जोड़ा जा सकता है, लेकिन वह कहीं ज़्यादा awkward है और उसमें गलती की संभावना भी अधिक है
    • मेरे हिसाब से C में भी यही बात लागू होती है। C की बहुत-सी समस्याएँ stdlib के ख़राब होने से पैदा होती हैं
      अगर बस एक आधुनिक library मिल जाए जिसमें strings और arrays की library, कुछ generic containers, और allocator के लिए native support हो, तो C काफ़ी ज़्यादा ergonomic और इस्तेमाल में आसान हो सकती है। बेशक, भाषा की कुछ कमियाँ सिर्फ library बदल देने से गायब नहीं होंगी, लेकिन फिर भी इससे बहुत दूर तक जाया जा सकता है
      आधुनिक C codebases को देखें तो वे allocator, strings, vector, hash table, और filesystem operations के लिए custom libraries का बहुत व्यापक उपयोग करते हैं, और अगर आपको C या manual resource management का अनुभव है तो उस दिशा में जाना इतना मुश्किल नहीं है
    • हमारी company में slice<T, N> का implementation इस्तेमाल होता है, जो “ठीक N bytes को point करने वाला pointer” या “मनचाही संख्या के bytes को point करने वाला pointer” व्यक्त कर सकता है
      इसमें head(n), tail(n), slice(start, end), और index operator हैं, और ये सभी bounds checking करते हैं
      ऐसी abstractions के साथ काम करना सचमुच आनंददायक है, लेकिन आधुनिक और कुछ हद तक सुरक्षित भाषा जैसी चीज़ पाने के लिए आपको असल में Rust और Zig standard libraries को C++ में port करना पड़ता है। फिर भी, आख़िरकार यह मेहनत के लायक है
    • अगर सरल abstractions के लिए templates का इस्तेमाल कम किया जाए, तो क्या performance नहीं चली जाएगी?
  • अगर आप ऐसा लेख लिखने वाले हैं, तो कृपया उसे खुद लिखें। सूची आपने शायद खुद बनाई हो, लेकिन उसे LLM में डालकर परिणाम को वेबपेज पर इंसानों के पढ़ने के लिए उछाल देना बेहद असभ्य है। अगर मैं “काम करने वाला engineer” “पहले दिन” से “feature X” से बचना सीखता है—ऐसा वाक्य एक बार और देखूँ, तो शायद पागल हो जाऊँगा
    शर्म की बात यह है कि यहाँ कहने को सच में बहुत कुछ है, लेकिन असल में कुछ कहा ही नहीं जा रहा। इस लेख को बनाने की कोई वजह रही होगी, तो वह वजह बतानी चाहिए थी। C++ के किस हिस्से ने आपको नाराज़ किया, किन features ने आपको उलझाया—यह बताया जाना चाहिए था। ये features सिर्फ़ objective design failures की वजह से बुरे नहीं हैं, बल्कि इसलिए भी कि इनका हम पर क्या असर पड़ता है
    क्या आपने std::iterator इस्तेमाल करके Slack पर डाँट खाई थी, या reinterpret_cast 16 अक्षरों का होने से line formatting थोड़ा बिगड़ जाएगी इस डर से cast न करने का फैसला किया था? ऐसी बातें अगर Lobsters पर आतीं तो बेहतर होता। अगर ऐसी कहानियाँ नहीं हैं, तो उन्हें ज़बरदस्ती मत गढ़िए, और GPU से matrix multiplication करवाकर वही वाक्य 10 बार मत लिखवाइए। जिन हिस्सों पर टिप्पणी करनी है, बस उन पर annotation डालिए, और बाकी को table और bullet point में लिख दीजिए

    • यह लेख LLM से लिखा हुआ नहीं लगता
  • मैं C++ को 20 साल से इस्तेमाल कर रहा हूँ और अभी भी करता हूँ, लेकिन इस लेख की बहुत-सी बातों से सहमत हूँ। आजकल Rust इस्तेमाल करते समय जो चीज़ सच में बहुत अच्छी लगती है, वह memory safety से भी ज़्यादा इसकी बेहतरीन standard library और package ecosystem है
    इसका एक प्रमुख उदाहरण ranges library है। इसे standardize हुए 6 साल हो चुके हैं, फिर भी प्रमुख standard libraries अभी तक इसे पूरी तरह implement नहीं कर पाई हैं, और implement होने पर भी combinators बस कुछ ही हैं। Rust में इसका समकक्ष Iterator methods हैं, जो 76 हैं, और cargo add एक बार चलाने पर itertools trait के ज़रिए 130 और मिल जाते हैं
    एक और चीज़ जिसकी सच में बहुत कमी महसूस होती है, वह है pattern matching। इससे std::variant जैसे union types को ergonomic तरीके से बनाया जा सकता है। इसका proposal चर्चा में है, लेकिन C++26 में भी अभी तक शामिल नहीं हुआ, और यह अफसोस की बात है। दूसरी ओर contracts और executors आ रहे हैं, जबकि सच कहूँ तो मैंने अपने आसपास किसी को इन्हें माँगते नहीं देखा

    • C++ की समस्याओं में से एक यह है कि कौन-सी सुविधा language feature होनी चाहिए और कौन-सी standard library feature, इसके लिए औपचारिक और documented criteria ही नहीं हैं
      आम तौर पर मेरा मानदंड यह है। अगर कोई सुविधा वांछनीय use cases को support करती है और उसे standard library के रूप में व्यक्त नहीं किया जा सकता, तो उसे language में जाना चाहिए। जहाँ तक संभव हो, वांछित सुविधा को ऐसे न्यूनतम स्वतंत्र हिस्सों में बाँटना चाहिए जिन्हें दूसरे उद्देश्यों के लिए भी इस्तेमाल किया जा सके
      जो सुविधाएँ लगभग हर codebase में इस्तेमाल होती हैं, उन्हें standard library में होना चाहिए। अगर कोई type libraries के बीच interface के रूप में आम तौर पर इस्तेमाल होता है, तो वह standard library में होना चाहिए। हम यह नहीं चाहेंगे कि हर library अपना अलग tuple type या string define करे। C++ में पहले मामले में C++11 से पहले व्यवहारिक रूप से यही स्थिति थी, और दूसरे मामले में std::string एक disaster होने के कारण आज भी कुछ हद तक ऐसा है। यह interface types पर भी लागू होता है, और C++ आजकल इसे ज़्यादातर concepts से संभालता है
      बाकी चीज़ें reusable modular libraries में जानी चाहिए। Rust स्थिर और blessed external libraries के संग्रह को बनाए रखने में काफ़ी अच्छा है, इसलिए “Rust में लिखे हर game को इस data structure की ज़रूरत है, तो इसे standard library में डाल देते हैं” जैसा दबाव बहुत कम है। game developers ज़रूरत के हिसाब से crate ले आते हैं। C++ ने “ऐसे अच्छे packages की सिफारिश, जो बहुत-से लेकिन बहुमत नहीं रखने वाले लोगों की समस्याओं के काम आएँ” इस विचार को कभी ठीक से अपनाया ही नहीं
  • जो बात चिंताजनक लगती है, वह यह है कि अभी जो चीज़ें जोड़ी जा रही हैं उनमें से कौन-सी आगे चलकर फिर वापस लेनी पड़ेगी। Contracts अभी-अभी C++26 में शामिल हुए हैं, और उन पर पहले से ही गंभीर design flaws की ओर इशारा किया जा रहा है
    मैं सामान्य रूप से “committee design” की निंदा नहीं करना चाहता। मेरा मानना है कि ऐसे निकाय महत्वपूर्ण उद्देश्य पूरे करते हैं और उनकी अपनी विशिष्ट ताकतें होती हैं। बस उनकी ताकत बिल्कुल नई सुविधाओं को एकदम खाली मैदान से design करने में नहीं है
    WG21 और WG14 सच में वहाँ चमकते हैं जहाँ design space का कुछ हद तक अन्वेषण हो चुका हो, और जहाँ संभव हो, कई मौजूदा implementations वाली किसी सुविधा को उठाकर उसे ऐसा standard feature बनाया जाए जिसे ज़्यादातर users और implementers स्वीकार कर सकें। std::embed इसका एक उदाहरण है
    इसके उलट, लेख में बताए गए GC extension, std::memory_order_consume, और C++20 modules जैसी चीज़ों को अगर किसी के ठीक से implement कर पाने से पहले ही standardize कर दिया जाए, तो हालात अक्सर बहुत खराब हो जाते हैं

    • C++ और Haskell दोनों ही committee द्वारा design किए गए थे, लेकिन दोनों भाषाएँ लगभग एक-दूसरे के उलट हैं। जब भी यह सोचने का मन हो कि “$X को committee ने design किया” से $X के बारे में कुछ निष्कर्ष निकलता है, तो इसे याद रखना चाहिए
  • पहले मुझे यह जानकर काफ़ी झटका लगा था कि C++ अपनी standard library का versioning नहीं करता। यह लेख ठीक इसी बात पर उंगली रखेगा, यह उम्मीद नहीं थी
    यह बात भी दिलचस्प लगी कि Go को forward compatibility के मामले में कुछ हद तक इसी तरह conservative बताया गया है। लेकिन Go नई features जोड़ने में भी उतना ही conservative है, इसलिए लगता है कि उसने C++ की ज़्यादातर समस्याओं से बचाव कर लिया। Stable ABI न होना भी शायद मददगार रहा होगा
    जितनी लोकप्रिय libraries मैं जानता हूँ, उनमें C++ ABI को explicitly expose करने वाली सिर्फ libcamera है, और यह काफ़ी झंझटभरा है। मेरे अनुभव में C++ libraries भी आम तौर पर symbols को C ABI के रूप में export करती हैं, और इससे दूसरी भाषाओं के साथ interoperability भी आसान हो जाती है। हो सकता है मैंने यहाँ कुछ मिस किया हो
    और Clang और MSVC के बीच ABI compatibility में कुछ quirks नहीं हैं क्या? मुझे याद है Conan compiler mixing को explicitly discourage करता था या मना करता था, इसलिए समझ नहीं आता कि C++ committee ABI stability बचाए रखने की इतनी कोशिश क्यों करती है

    • यह पूरी तरह सही नहीं है। C++ standard library का versioning भाषा से स्वतंत्र रूप में नहीं करता, बस इतना ही
      यहाँ दो चीज़ें हैं जो काफ़ी क़रीब से जुड़ी हुई हैं: standard library specification और implementation। Specification पूरी language+library combination के लिए होती है, और implementation आम तौर पर specification के कम-से-कम एक या उससे अधिक versions को support करने की कोशिश करता है
      C++ interface expose करने वाली libraries बहुत हैं, और उनमें Qt जैसी बहुत बड़ी libraries भी शामिल हैं
      असली समस्या यह है कि C++ abstract machine linking process को define नहीं करती। इसलिए यह define ही नहीं किया जा सकता कि dynamic libraries कैसे काम करेंगी। UNIX systems पर C++ dynamic linking, C model का अनुसरण करता है। यानी dynamic linking होने का दिखावा, लेकिन असली बात loader की समस्या बताकर टाल देना। इसी वजह से copy relocation जैसी भयानक चीज़ें पैदा होती हैं। Windows के पास shared library क्या होती है, इसका कहीं ज़्यादा principled concept है, लेकिन इसी कारण UNIX C++ libraries के कुछ idioms Windows पर काम नहीं कर पाते
      Shared libraries, C++ templates जैसी सुविधाओं के साथ बड़ी समस्या पैदा करती हैं। अगर templates को user types के साथ instantiate करना है, तो compiler compilation-unit boundaries के पार नहीं देख सकता, इसलिए पूरी definition header में होनी चाहिए। Shared libraries में वही code कई जगह instantiate होता है। अगर program और library दोनों एक ही parameters के साथ एक ही template को instantiate करें, तो दोनों के पास उसकी copy होगी, और फिर linker और loader को यह सुनिश्चित करना होगा कि अंतिम loaded program में सिर्फ एक ही इस्तेमाल हो
      Swift से तुलना करें तो Swift साफ़ कहता है कि “shared libraries मौजूद हैं, और भाषा-स्तर की संरचनाएँ उन्हें व्यक्त करती हैं।” अगर आप shared library boundaries के पार generics expose करना चाहते हैं, तो कर सकते हैं, लेकिन सभी external callers के लिए वे dynamic dispatch version में lower हो जाते हैं। C++ में भी इसे हाथ से implement किया जा सकता है। Type-erased wrapper इस्तेमाल करने वाला template का एक general version बना लीजिए, और अलग-अलग concrete instantiations को explicitly लिखिए। लेकिन यह कठिन और manual है। Swift में बस यही नियम है कि “shared library boundary पर चीज़ें ऐसे काम करती हैं”
      Type hiding के साथ भी यही बात है। C++ में library boundary के पार behavior expose करते हुए implementation छिपाने वाली public interface बनाने के लिए pImpl pattern इस्तेमाल होता है। Swift के पास ऐसी abstract machine है जो जानती है कि library boundary कहाँ है, और वह कहती है कि “जो types ABI-stable के रूप में चिह्नित नहीं हैं, उनका size shared library boundary के पार compile-time constant नहीं होता”
      Standard का वास्तविकता से इनकार करने का यह भी एक रूप है। जिन लगभग सभी non-trivial C++ codebases पर मैंने काम किया है, वे -fno-rtti -fno-exceptions या CL.EXE के समकक्ष options के साथ compile हुए थे। Standard इसे एक संभावना के रूप में मान्यता ही नहीं देता। Standard library की ज़्यादातर functions अभी भी error reporting के लिए exceptions पर निर्भर करती हैं, इसलिए -fno-exception के साथ compile करने पर वे बस abort कॉल करती हैं। इसका मतलब है कि dynamic memory allocation करने वाले standard library components embedded में उपयोग नहीं किए जा सकते। std::vector<T>::push_back program को crash करा सकता है
      लेख में यह जो कहा गया है कि “committee न सिर्फ़ खराब features हटा नहीं पाती, बल्कि ऐसे नए features जोड़ती रहती है जिनकी working engineers ने माँग भी नहीं की,” यह contracts के बनने के तरीके से 100% मेल खाता है। Verus दिखाता है कि C++ जैसे environments को target करने वाली language में एक अच्छा contracts system क्या संभव बना सकता है। P2900 contracts परस्पर टकराती requirements का मिश्रण हैं, इसलिए contracts जिन भी समस्याओं में सही बैठ सकते थे, उन सबको यह और बदतर बना देता है
      यह निष्कर्ष कि “C++ engineer” को “ऐसा engineer जो programming कर सकता है” से कहीं अधिक वेतन मिलता है, मुझे सही नहीं लगता। हक़ीक़त में कोई भी C++ standard के बिल्कुल अनुसार code नहीं लिखता; हर कोई अपने पसंदीदा in-house subset-of-a-superset के मुताबिक लिखता है
    • यहाँ go vet का भी मूल्य है। क्योंकि यह API सुधार के लिए automatic upgrades देता है
  • पिछले साल से मैंने C++ लगभग छोड़ ही दिया है; पहले Kotlin पर गया, फिर Swift पर। कंपनी में अभी भी C++ maintenance करनी पड़ती है, लेकिन जो नया code लिखता हूँ वह काफ़ी साफ़, संक्षिप्त और सुरक्षित है। Code size और शायद performance में कुछ tradeoff है, लेकिन उसके लायक है

  • मुझे याद था कि Go के for loop semantics backward compatibility तोड़ते हुए बदले थे, इसलिए मुझे यह कथन ग़लत लगा था: https://go.dev/blog/loopvar-preview
    लेकिन बाद में पता चला कि Go यहाँ Rust editions जैसा तरीका अपनाता है। Semantics तभी बदलती हैं जब आप Go version 1.22 या उससे ऊपर declare करते हैं। शायद io/ioutil को भी इसी तरह हटाया जा सकता था, लेकिन शायद edition boundary पार करके code तोड़ने जितना महत्वपूर्ण मामला वह नहीं था

  • अगर C++ ने इन बुरे विचारों को वास्तव में आज़मा कर यह साबित न किया होता कि वे बुरे विचार हैं, तो शायद Rust आज जिस रूप में है, उस रूप में मौजूद ही न होता। Big Thank You!

  • मुझे C++ के लिए Rust-जैसे standard library replacement में दिलचस्पी है। इस लक्ष्य की ओर जाने वाला rpp मुझे पता है: https://github.com/TheNumbat/rpp
    क्या और विकल्प हैं? EASTL जैसी C++ stdlib की दूसरी implementations नहीं, बल्कि ऐसी libraries जो Rust का और नज़दीकी अनुसरण करती हों। मुझे पता है कि std::initializer_list जैसी कुछ चीज़ें syntax में baked-in हैं, लेकिन बाक़ी सब कुछ बदला जा सकता है