De twee facties van C++
Er lijkt veel strijd en discussie te zijn over de toekomst van C++. Op Reddit en een bepaalde oranje website zeker, maar ongetwijfeld ook tijdens de officiële bijeenkomsten van het C++-standaardcomité. Je hoeft niet ver te zoeken.
De absolute staat van C++
Het lijkt erop dat we in de volgende situatie verkeren:
De Evolution Working Group (EWG) van C++ heeft zojuist consensus bereikt over de adoptie van P3466 R0 - (Re)affirm design principles for future C++ evolution. Dit betekent:
- Geen ABI-breuken (Application Binary Interface).
- Behoud van link-compatibiliteit met C en vorige versies van C++.
- Geen 'virale annotaties' (bijvoorbeeld geen lifetime-annotaties) [1].
Hiermee wordt ingezet op een set incompatibele doelen, namelijk: geen ABI-breuk én het zero-overhead-principe [2]. Of dit nu goed of slecht is, het is een letterlijke verdubbeling van de huidige koers van de C++-taal.
Ondertussen gebeurt het volgende:
De Amerikaanse overheid wil dat mensen stoppen met het gebruik van C++: Verschillende branches van de Amerikaanse overheid, waaronder de CISA, de NSA en blijkbaar het Witte Huis, hebben documenten, rapporten en aanbevelingen gepubliceerd om de industrie te waarschuwen tegen het gebruik van talen die niet geheugenveilig (memory-unsafe) zijn.
Grote tech-spelers stappen over op Rust:
- Microsoft herschrijft blijkbaar kernbibliotheken in Rust.
- Google lijkt zich te committeren aan Rust en is zelfs begonnen met het ontwikkelen van een bidirectionele C++/Rust interop-tool.
- AWS maakt gebruik van Rust.
Daarnaast is opvallend dat Herb Sutter Microsoft verlaat en dat MSVC traag lijkt in het implementeren van C++23-functies, waarbij de gemeenschap wordt gevraagd om prioriteiten te stellen.
De beruchte Prague ABI-stemming heeft plaatsgevonden (kort samengevat: "C++23 zal de ABI niet breken, het is onduidelijk of dat ooit zal gebeuren"). Google heeft naar verluidt zijn deelname aan het C++-ontwikkelingsproces aanzienlijk verminderd en is in plaats daarvan begonnen met het werken aan hun eigen opvolger van C++. Ze hebben zelfs een samenvatting gepubliceerd waarin alle problemen worden geschetst die ze ondervonden bij het proberen te verbeteren van C++ [3].
Verhalen over mensen die jarenlang hun best hebben gedaan om deel te nemen aan het proces van het C++-standaardcomité, om vervolgens 'opgegeten en uitgespuugd' te worden, zijn wijdverspreid in de gemeenschap. (Het feit dat een functie vaak eerst in C landt, helpt ook niet.)
Modules zijn nog steeds niet geïmplementeerd. Zijn we daar nu eindelijk? [4]
'Safety Profiles' verkeren nog in een vreemde staat zonder bestaande implementatie; men probeert een zekere mate van veiligheid achteraf toe te voegen aan bestaande C++-code, terwijl wijzigingen in die code tot een minimum worden beperkt. Sean Baxter heeft zelf een standpunt ingenomen tegen profielen en beschreef C++ als "ondergespecificeerd".
Als je als buitenstaander naar dit alles kijkt, zou het er zeker uitzien alsof C++ in feite uit elkaar valt, en alsof een groot aantal mensen het vertrouwen is verloren in het vermogen van het C++-comité om dit onder controle te houden [5].
Twee culturen
Mensen lijken naar andere oplossingen te zoeken. Neem Google. Google is duidelijk het vertrouwen in 'het proces' verloren sinds de ABI-stemming. Dit is geen verlies van vertrouwen in de taal zelf—Google heeft een van de grootste C++-codebases ter wereld, en die heeft hen uitstekend gediend. Het is een verlies van vertrouwen in het vermogen van de taal om te evolueren terwijl de druk vanuit verschillende hoeken toeneemt (potentiële overheidsregulering, concurrerende talen, een verlangen naar betere prestatie- en veiligheidsgaranties van belangrijke spelers, etc.).
Dus wat is het probleem? Waarom verandert C++ niet gewoon?
Het antwoord is eenvoudig te vinden in wat Herb Sutter schreef in zijn paper over profielen:
"We moeten de noodzaak om bestaande code te wijzigen minimaliseren. Voor adoptie in bestaande code heeft decennia aan ervaring consistent aangetoond dat de meeste klanten met grote codebases geen 1% van hun regels code zullen wijzigen om aan striktheidsregels te voldoen, zelfs niet om veiligheidsredenen, tenzij wettelijke vereisten hen daartoe dwingen."
— Herb Sutter
Nu kunnen we dit contrasteren met de biografie van Chandler Carruth op de WG21-ledenpagina:
"I led the design of C++ tooling and automated refactoring systems built on top of Clang and now part of the Clang project. [...] Within Google, I led the effort to scale the automated Clang-based refactoring tools up to our entire codebase, over 100 million lines of C++ code. We can analyze and apply refactorings across the entire codebase in 20 minutes."
Hier zien we een conflict tussen twee radicaal verschillende kampen C++-gebruikers:
- Relatief moderne, capabele tech-corporaties die begrijpen dat hun code een activa (asset) is. (Dit gaat niet alleen om 'big tech'. Elke verstandige groene-veld C++-startup valt ook in deze categorie.)
- Iedereen anders. Elke oude corporatie waar mensen nog steeds ruziën over hoe ze hun code moeten inspringen, en waar een jonge engineer het management smeekt om hem toe te staan een linter op te zetten.
Een van deze groepen is in staat om een migratie enigszins gracieus af te handelen, en dat is de groep die in staat is om hun C++-stack te bouwen vanuit versiebeheerde broncode, niet de groep die nog steeds gebruikmaakt van eeuwenoude, vooraf gecompileerde bibliotheken uit 1998.
Het vermogen om de volledige dependency-stack te bouwen vanuit versiebeheerde broncode (bij voorkeur met geautomatiseerde tests) is waarschijnlijk de meest kritieke scheidslijn tussen de twee kampen. In de praktijk is dit natuurlijk een gradiënt. Ik kan me alleen voorstellen hoeveel zweet, tranen en bloed er is gekost om big-tech-codebases te transformeren van angstaanjagende "balls of mud" naar semi-beheersbare, bouwbare, gelintte en correct versiebeheerde, iets-minder-angstaanjagende "balls of mud".
Met de wijsheid van achteraf is het makkelijk om dit als onvermijdelijk te zien: er was een duidelijke disconnect tussen de behoeften van bedrijven zoals Google (die relatief modern C++ gebruiken, geautomatiseerde tooling en testing hebben en een moderne infrastructuur) en het (zeer sterke) verlangen naar achterwaartse compatibiliteit.
Om een gewaagde stelling in te nemen: het idee van één enkele, dialect-vrije en uniforme C++ lijkt al jaren dood [6].
We hebben in ieder geval twee grote smaken van C++:
Modern C++ Elke remotely moderne versie. Alles kan worden gebouwd vanuit versiebeheerde broncode met een specifiek, schoon en uniform build-proces dat tenminste iets geavanceerder is dan rauwe CMake. Er is gebruik van statische analyzers, formatters en linters. Er is overeenstemming dat het schoon en modern houden van een codebase de moeite waard is. Waarschijnlijk minimaal C++17, met unique_ptr, constexpr, lambda's en optional, maar dat is niet het punt. Wat telt, is de tooling.
Legacy C++ Alles wat dat niet is. C++ die op stoffige, oude servers van een middelgrote bank staat. C++ die vertrouwt op een volstrekt oud stuk gecompileerde code waarvan de broncode verloren is gegaan en waarvan de oorspronkelijke auteurs onbereikbaar zijn. C++ die is uitgerold op 'pet-type' servers, tot het punt dat het opstarten op een andere plek een engineer een volle maand kost om alle impliciete dependencies, configuraties en omgevingsvariabelen te begrijpen. Elke codebase die primair wordt geclassificeerd als een kostenpost (cost-center). Elke code waarbij het bouwen van een gebruikte binary vanuit broncode meer dan een paar drukjes op een knop vereist, of simpelweg onmogelijk is.
Je zult merken dat het belangrijkste verschil helemaal niet over C++ zelf gaat. Het verschil zit in de tooling en het vermogen om vanuit versiebeheerde broncode te bouwen op een schone, goed gedefinieerde manier. Idealiter is dat ook het vermogen om te deployen zonder te hoeven onthouden welke ene vlag of omgevingsvariabele de vorige collega instelde om te voorkomen dat alles implodeerde.
Hoeveel van bijvoorbeeld de codebase van Google 'modern' C++-idiomen volgt, is secundair aan de vraag of de tooling goed is en of het vanuit broncode gebouwd kan worden.
Veel mensen zullen zeggen dat tooling niet de verantwoordelijkheid is van het C++-standaardcomité, en ze hebben gelijk. Tooling is niet de verantwoordelijkheid van het comité, omdat het comité elke verantwoordelijkheid daarvoor afstaat (het richt zich op specificaties voor de C++-taal, niet op concrete implementaties) [7]. Dit is bewust zo gedaan, en het is moeilijk hen te verwijten gezien de legacy-bagage. C++ is een standaard die verschillende implementaties verenigt.
Dat gezegd hebbende: als er één ding is dat Go goed heeft gedaan, is het dat tooling ertoe doet. C++ is in vergelijking daarmee uit een prehistorisch tijdperk voordat linters werden uitgevonden. C++ heeft geen uniform build-systeem, niets dat in de buurt komt van een uniform package-management-systeem, is ongelooflijk moeilijk te parsen en te analyseren (wat funest is voor tooling), en voert een verschrikkelijke strijd tegen de Wet van Hyrum voor elke wijziging die moet worden doorgevoerd.
Er is een enorme, groeiende kloof tussen deze twee facties (goede tooling, moeiteloos bouwen vanuit broncode vs. slechte tooling, niet kunnen bouwen vanuit broncode), en ik zie deze eerlijk gezegd niet snel dichten. Het C++-comité lijkt zeer vastbesloten om achterwaartse compatibiliteit te behouden, ongeacht de kosten.
Ik ben het hier overigens niet noodzakelijkerwijs mee oneens! Achterwaartse compatibiliteit is voor veel mensen om zeer goede redenen van groot belang. Andere mensen geven er veel minder om. Het maakt niet uit welke groep "gelijk" heeft: het zijn simpelweg incompatibele visies.
Gevolgen
Dit is waarom profielen zijn zoals ze zijn: Safety Profiles zijn niet bedoeld om de problemen van moderne, tech-savvy C++-corporaties op te lossen. Ze zijn bedoeld om verbeteringen aan te brengen zonder dat er wijzigingen in oude code nodig zijn.
Hetzelfde geldt voor modules. Je moet "gewoon" een header-bestand als module kunnen importeren, en er mogen geen achterwaartse compatibiliteitsproblemen ontstaan. Natuurlijk houdt iedereen van functies die je simpelweg kunt implementeren en die verbeteringen brengen zonder oude code te wijzigen. Maar het is vrij duidelijk dat deze functies (in eerste instantie) zijn ontworpen met 'legacy C++' in gedachten. Elke functie die een migratie van legacy C++ zou vereisen, is een non-starter voor het C++-comité, omdat je, zoals Herb Sutter zei, in feite niet kunt verwachten dat mensen migreren. (Nogmaals: functies ontwerpen met 'legacy C++' in gedachten is niet slecht; het is een volstrekt zinvolle beslissing.)
Dit is iets wat ik probeer in gedachten te houden wanneer ik C++-papers lees: er zijn hier twee grote doelgroepen. De één is die van modern C++, de ander is die van legacy C++. Deze twee kampen zijn het fel oneens, en veel papers zijn geschreven met de behoeften van één specifieke groep in gedachten.
Dit leidt er logischerwijs toe dat mensen langs elkaar heen praten: ondanks wat men denkt, proberen Safety Profiles en Safe C++ totaal verschillende problemen op te lossen voor verschillende doelgroepen, niet hetzelfde probleem.
Het C++-comité probeert te voorkomen dat deze kloof verder wordt. Dat is waarschijnlijk waarom alles in de richting van Safe C++ van Sean Baxter een non-starter voor hen is. Dit is een radicale, ingrijpende verandering die een fundamenteel nieuwe manier van C++ schrijven zou kunnen creëren.
Natuurlijk is er ook de vraag of specifieke leden van het C++-standaardcomité gewoon erg koppig zijn en zich vastklampen aan strohalmen om een evolutie te voorkomen waar ze persoonlijk een esthetische afkeer van hebben.
Ik wil niemand beschuldigen, maar het is niet de eerste keer dat ik hoor dat het C++-comité dubbele standaarden hanteert, zoals: "We verwachten van u een volledige, werkende implementatie over verschillende compilers voordat we dit voorstel goedkeuren, maar we zijn wel gelukkig om ons te committeren aan bepaalde enorme projecten (bijv. modules, profielen) die geen functionerende proof of concept-implementatie hebben."
Als dit het geval is (ik kan dat oprecht niet zeggen), dan weet ik echt niet hoe lang C++ dit pad nog kan volgen zonder een veel dramatischer splitsing. En dat allemaal nog zonder in te gaan op de enorme problemen die zouden ontstaan door de ABI-compatibiliteit te breken.
Als je cynisch wilt zijn, kun je dit interpreteren als een expliciet " la la" naar de lifetime-annotaties van Rust en het 'Safe C++'-voorstel van Sean Baxter. Als je minder cynisch wilt zijn, is het in ieder geval een bitter besef van het feit dat de industrie er niet om geeft om bestaande code te refactoren.
***
Voetnoten
[1] Een cynische interpretatie is dat dit een expliciete afwijzing is van Rust's lifetime-annotaties en Sean Baxters 'Safe C++'-voorstel. Minder cynisch gezien is het een besef dat de industrie niet bereid is bestaande code te refactoren.
[2] "Je betaalt niet voor wat je niet gebruikt." In essentie kan een bestaande C++-functie je runtime-prestaties alleen beïnvloeden als je deze actief gebruikt. Dit is niet geheel compatibel met een stabiele ABI, aangezien een stabiele ABI (begrepen als een kenmerk van C++) bepaalde prestatieverbeteringen uitsluit.
[3] Ik denk dat Carbon interessanter is dan de meeste mensen hem krediet geven. (Auteur: ik heb hier inmiddels een post over geschreven).
[4] We komen er bijna! GCC heeft blijkbaar vlak voor de publicatie van dit artikel ondersteuning toegevoegd voor de std module. Het kan nog een tijd duren, maar er is progressie.
[5] Valt het uit elkaar? Eh, dat hangt ervan af wat je bedoelt. Al die C++-code gaat nergens heen. In die zin: nee. C++-code zal in ieder geval blijven bestaan.
[6] Of het ooit echt "één" taal was, met al die verschillende compilers en hun eigen compiler-extensies, is een andere vraag.
[7] Dit is enigszins oneerlijk. Er is een studiegroep gewijd aan tooling, SG15. Natuurlijk is het hele proces nog steeds gericht op het schrijven van papers, niet op bijvoorbeeld het leveren van een canonieke package manager.
Groetjes,