Something went wrong. Try again.
A Tour of C++ for experienced programmers, as if C++26 is the only version that ever existed.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301<!DOCTYPE HTML><html lang="en" class="light sidebar-visible" dir="ltr"> <head> <!-- Book generated using mdBook --> <meta charset="UTF-8"> <title>A Tour of C++26</title> <meta name="robots" content="noindex">
<!-- Custom HTML head -->
<meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon-de23e50b.svg"> <link rel="shortcut icon" href="favicon-8114d1fc.png"> <link rel="stylesheet" href="css/variables-8adf115d.css"> <link rel="stylesheet" href="css/general-e96d0476.css"> <link rel="stylesheet" href="css/chrome-d279d366.css"> <link rel="stylesheet" href="css/print-9e4910d8.css" media="print">
<!-- Fonts --> <link rel="stylesheet" href="fonts/fonts-9644e21d.css">
<!-- Highlight.js Stylesheets --> <link rel="stylesheet" id="mdbook-highlight-css" href="highlight-493f70e1.css"> <link rel="stylesheet" id="mdbook-tomorrow-night-css" href="tomorrow-night-4c0ae647.css"> <link rel="stylesheet" id="mdbook-ayu-highlight-css" href="ayu-highlight-3fdfc3ac.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript --> <script> const path_to_root = ""; const default_light_theme = "light"; const default_dark_theme = "navy"; window.path_to_searchindex_js = "searchindex-7c7963f3.js"; </script> <!-- Start loading toc.js asap --> <script src="toc-0eddcac6.js"></script> </head> <body> <div id="mdbook-help-container"> <div id="mdbook-help-popup"> <h2 class="mdbook-help-title">Keyboard shortcuts</h2> <div> <p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p> <p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p> <p>Press <kbd>?</kbd> to show this help</p> <p>Press <kbd>Esc</kbd> to hide this help</p> </div> </div> </div> <div id="mdbook-body-container"> <!-- Work around some values being stored in localStorage wrapped in quotes --> <script> try { let theme = localStorage.getItem('mdbook-theme'); let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) { localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1)); }
if (sidebar.startsWith('"') && sidebar.endsWith('"')) { localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1)); } } catch (e) { } </script>
<!-- Set the theme before any content is loaded, prevents flash --> <script> const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme; let theme; try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { } if (theme === null || theme === undefined) { theme = default_theme; } const html = document.documentElement; html.classList.remove('light') html.classList.add(theme); html.classList.add("js"); </script>
<input type="checkbox" id="mdbook-sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed --> <script> let sidebar = null; const sidebar_toggle = document.getElementById("mdbook-sidebar-toggle-anchor"); if (document.body.clientWidth >= 1080) { try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { } sidebar = sidebar || 'visible'; } else { sidebar = 'hidden'; sidebar_toggle.checked = false; } if (sidebar === 'visible') { sidebar_toggle.checked = true; } else { html.classList.remove('sidebar-visible'); } </script>
<nav id="mdbook-sidebar" class="sidebar" aria-label="Table of contents"> <!-- populated by js --> <mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox> <noscript> <iframe class="sidebar-iframe-outer" src="toc.html"></iframe> </noscript> <div id="mdbook-sidebar-resize-handle" class="sidebar-resize-handle"> <div class="sidebar-resize-indicator"></div> </div> </nav>
<div id="mdbook-page-wrapper" class="page-wrapper">
<div class="page"> <div id="mdbook-menu-bar-hover-placeholder"></div> <div id="mdbook-menu-bar" class="menu-bar sticky"> <div class="left-buttons"> <label id="mdbook-sidebar-toggle" class="icon-button" for="mdbook-sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="mdbook-sidebar"> <span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></span> </label> <button id="mdbook-theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="mdbook-theme-list"> <span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M371.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L600.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S549.7-4.4 531.1 9.6L294.4 187.2c-24 18-38.2 46.1-38.4 76.1L371.3 367.1zm-19.6 25.4l-116-104.4C175.9 290.3 128 339.6 128 400c0 3.9 .2 7.8 .6 11.6c1.8 17.5-10.2 36.4-27.8 36.4H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H240c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z"/></svg></span> </button> <ul id="mdbook-theme-list" class="theme-popup" aria-label="Themes" role="menu"> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-default_theme">Auto</button></li> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-light">Light</button></li> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-rust">Rust</button></li> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-coal">Coal</button></li> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-navy">Navy</button></li> <li role="none"><button role="menuitem" class="theme" id="mdbook-theme-ayu">Ayu</button></li> </ul> <button id="mdbook-search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="mdbook-searchbar"> <span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z"/></svg></span> </button> </div>
<h1 class="menu-title">A Tour of C++26</h1>
<div class="right-buttons"> <a href="print.html" title="Print this book" aria-label="Print this book"> <span class=fa-svg id="print-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 368 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg></span> </a>
</div> </div>
<div id="mdbook-search-wrapper" class="hidden"> <form id="mdbook-searchbar-outer" class="searchbar-outer"> <div class="search-wrapper"> <input type="search" id="mdbook-searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="mdbook-searchresults-outer" aria-describedby="searchresults-header"> <div class="spinner-wrapper"> <span class=fa-svg id="fa-spin"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"/></svg></span> </div> </div> </form> <div id="mdbook-searchresults-outer" class="searchresults-outer hidden"> <div id="mdbook-searchresults-header" class="searchresults-header"></div> <ul id="mdbook-searchresults"> </ul> </div> </div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM --> <script> document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible'); document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible'); Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) { link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1); }); </script>
<div id="mdbook-content" class="content"> <main> <h1 id="preface"><a class="header" href="#preface">Preface</a></h1><p>This book teaches C++ as if it were always C++26, using only a small allow‑list of legacy constructs taught explicitly: raw pointers and <code>new</code>/<code>delete</code> for RAII, virtual functions for type erasure, C strings and <code>errno</code> for C interop, and SFINAE for reading existing code.</p><p>The book has four parts and a set of appendices. Part I covers the language. Part II covers the standard library. Part III covers generic and compile‑time programming. Part IV covers systems topics. The appendices hold reference material, including a feature table and a Core Guidelines index.</p><h2 id="who-this-book-is-for"><a class="header" href="#who-this-book-is-for">Who this book is for</a></h2><p>You are an experienced programmer who already knows C, Rust, Lisp, and Prolog, and you have never written C++. The book assumes you understand functions, structs, loops, ownership, and metaprogramming at a high level. It never wastes a sentence describing a basic function. Instead, every paragraph highlights what is specific to C++, or how C++ differs from the languages you already master.</p><p>Each chapter assumes you can read code in at least one of these languages. It does not re‑teach loops or structs. It focuses on the C++ way to express an idea you already know. You will learn the vocabulary of modern C++, the ownership model, and the tools that verify the examples.</p><h2 id="how-the-book-reads"><a class="header" href="#how-the-book-reads">How the book reads</a></h2><p>The book is a tour, not a reference. Each chapter introduces a cluster of related ideas, connects them to what came before, and ends with a single challenge under a “Try this” heading. There is no solutions appendix. The examples are real files, compiled and run by the toolchain that ships with the book, so what you read is exactly what the compiler accepted.</p><p>The tour is cumulative. Later chapters assume the vocabulary of earlier ones. The capstone in Part III ties the ideas together with a constexpr SQL interpreter. You can read the chapters in order, or jump to a topic and follow its cross‑references back.</p><h2 id="the-toolchain"><a class="header" href="#the-toolchain">The toolchain</a></h2><p>The book is built with a Nix dev shell that pins LLVM 22.1.8, its libc++, and the Clang tools. <code>nix develop</code> gives you the whole environment. <code>scripts/verify.sh</code> runs the complete acceptance pipeline. The pipeline builds the book and verifies that every example is correct. It follows this order:</p><ol><li><code>scripts/verify.sh</code> enters the Nix dev shell and runs <code>scripts/verify-inner.sh</code>. The inner script checks that <code>clang++</code> is version 22 or newer, because the lifetime‑safety analysis requires it.</li><li>CMake configures the build with <code>cmake --preset dev</code>. It probes the compiler for the lifetime‑safety flag and assembles the warning set.</li><li><code>cmake --build build</code> compiles every example with the warning set, <code>-Werror</code>, and clang‑tidy. A warning becomes a hard error, so a clean example stays clean.</li><li>CTest runs each compiled example under the sanitizers. Each <code>book_example</code> test must match its <code>EXPECT</code> regular expression.</li><li><code>mdbook build</code> renders the book. It includes each example file directly from <code>examples/</code>, so the printed code is the compiled code.</li></ol><p>A feature that is new in C++26 but not yet implemented by this compiler is taught prose‑first and marked with a “Not yet deployable” callout, so the book never presents code it cannot verify.</p><h2 id="a-note-on-style"><a class="header" href="#a-note-on-style">A note on style</a></h2><p>The code follows the C++ Core Guidelines. Types and functions use snake_case, functions declare their return type before their name, and errors are reported through exceptions or <code>std::expected</code>, never through <code>errno</code> or out‑parameters. Wherever a rule comes from the Core Guidelines, it is cited inline, for example (CG F.15), and collected in Appendix C. The prose emphasizes clarity, brevity, and direct comparison to your existing language experience.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="the-compiler-is-a-committee"><a class="header" href="#the-compiler-is-a-committee">The compiler is a committee</a></h1><h2 id="the-thesis"><a class="header" href="#the-thesis">The thesis</a></h2><p>This book treats the C++ toolchain as a single committee: clang++ front‑end, clang‑tidy, path‑sensitive static analyzer, address and undefined‑behavior sanitizers, and clangd. The clang++ front‑end, the clang‑tidy lint suite, the path‑sensitive static analyzer, the address and undefined‑behavior sanitizers, and the clangd language server together form the compiler committee. A diagnostic that originates from any of these components is a violation of the language law, not a mere suggestion. The Core Guidelines describe rules that the committee intends to make machine enforceable, and the committee enforces them.</p><pre><code>clang++ front-end compilerclang-tidy lint suiteclang-analyzer path-sensitive static analyzerASan / UBSan runtime sanitizersclangd language server</code></pre><p>The mental model mirrors the Rust experience where the borrow checker, clippy linter, and cargo‑test runner cooperate to keep code correct. In the Rust ecosystem the borrow checker enforces lifetimes, clippy provides style and correctness lints, and cargo test runs the unit test suite.</p><p>In the C++ world those responsibilities are split across clang++ flags, clang‑tidy checks, the static analyzer and sanitizers, but the book treats them as one cohesive compiler surface.</p><h2 id="getting-the-toolchain"><a class="header" href="#getting-the-toolchain">Getting the toolchain</a></h2><p>The canonical way to obtain the exact toolchain used by the book is to invoke the Nix flake that lives at the repository root. Running <code>nix develop</code> spawns a development shell that contains the pinned versions:</p><ul><li>LLVM 22.1.8 provides clang++, clang‑tidy, clang‑analyzer and clangd.</li><li>CMake 4.3.4 drives the builds.</li><li>mdBook 0.5.4 renders the final HTML.</li></ul><p>When Nix is unavailable the book falls back to system packages. On macOS the user can install the same LLVM version with <code>brew install llvm</code>. On Linux the user can follow the instructions at <code>apt.llvm.org</code> to obtain a recent clang. The script <code>scripts/verify.sh</code> builds every example, runs the tests, and builds the book. The script runs CMake to configure the build, builds all <code>book_example</code> targets, executes each test with CTest, and finally calls <code>mdbook build</code> to ensure the markdown renders without missing includes. The development shell also sets <code>CC=clang</code> and <code>CXX=clang++</code>. CMake locates clang‑tidy on its own with <code>find_program</code>. The same environment provides <code>clangd</code> for IDE integration.</p><h2 id="hello-world-honestly"><a class="header" href="#hello-world-honestly">Hello, world, honestly</a></h2><p>The book writes <code>#include <print></code> because the header <code><print></code> is the modern way to reach <code>std::println</code>. The directive <code>#include</code> is the build model the book uses, because the classic preprocessor workflow is what real multi‑file projects run today. The preprocessor expands <code>#include</code> before compilation.</p><p>The preprocessor gets full treatment in chapter 23, and modules in chapter 24.</p><p><code>std::println</code> replaced <code>std::cout</code> and <code>printf</code> as the default output mechanism in C++23. It checks format strings at compile time, writes directly to stdout, and returns void. The older facilities still work, but the book uses the modern one from the first example so the reader never has to unlearn a habit.</p><pre><code class="language-cpp">#include <print>
int main() { std::println("hello, world");}</code></pre><p>Compiling the file by hand looks like this:</p><pre><code>clang++ -std=c++26 -Wall -Wextra -Wpedantic -c ch01_hello.cppclang++ -std=c++26 -Wall -Wextra -Wpedantic ch01_hello.o -o ch01_hello</code></pre><p>The short command line shows the language version flag, the three warning groups that the book treats as law, and the absence of any additional options. The resulting binary prints <code>hello, world</code> on standard output. The header <code><print></code> became part of the standard library in C++23 and is fully supported by the libc++ shipped with the pinned toolchain.</p><h2 id="warnings-are-laws"><a class="header" href="#warnings-are-laws">Warnings are laws</a></h2><p>The next example illustrates a classic lifetime violation. The file returns a pointer to a local variable, a pattern that the compiler can diagnose.</p><pre><code class="language-cpp">// DEMO: deliberately wrong. This file exists to produce a diagnostic.// The function returns a pointer to a dead local. The lifetime-safety// analysis and the plain -Wreturn-stack-address warning both catch it.//// examples/ch01/ch01_dangling.cpp:9:13: warning: address of stack memory// associated with local variable 'x' returned [-Wreturn-stack-address]// 9 | return &x;// | ^
int* bad() { int x = 42; return &x;}
int main() { int* p = bad(); return *p;}</code></pre><p>The diagnostic that appears in the comment of the example appears again here verbatim:</p><pre><code>examples/ch01/ch01_dangling.cpp:9:13: warning: address of stack memory associated with local variable 'x' returned [-Wreturn-stack-address] 9 | return &x; | ^</code></pre><p>The <code>-Wreturn-stack-address</code> check emits the warning, and the check is part of the standard warning set. The book treats this warning as a language law. The book compiles its real examples with <code>-Werror</code>, which turns every warning into a hard error, and encourages the reader to do the same. The deliberately wrong demos keep their warning visible to serve as the exhibit, as described under <code>book_example</code> and <code>book_demo</code> in the section <code>## CMake As The Contract</code>. The larger ambition of the committee is to provide a unified lifetime‑safety profile. The flag that represents this profile in current clang builds is <code>-Wlifetime-safety</code>. On the pinned clang 22.1.8 this flag does not exist directly. Instead the compiler accepts the experimental spelling <code>-Xclang -fexperimental-lifetime-safety</code>, but it produces almost no diagnostics on the cases shown in chapter 7. The book therefore relies on the diagnostics that do fire in the shipped toolchain, while still naming the lifetime‑safety analysis as the law that the committee intends to enforce.</p><blockquote><p><strong>Not yet deployable.</strong> The lifetime‑safety analysis is young. The flag <code>-Wlifetime-safety</code> will become a full diagnostic source in future clang releases. Until then the analysis is visible only through the experimental option and the traditional warnings such as <code>-Wreturn-stack-address</code>. The flag <code>-Wuninitialized</code> also catches use of a variable before a value stores into it, and clang enables it by default.</p></blockquote><h2 id="sanitizers-as-compile-options"><a class="header" href="#sanitizers-as-compile-options">Sanitizers as Compile Options</a></h2><p>Dynamic checking is another member of the committee. The address sanitizer (ASan) and the undefined‑behavior sanitizer (UBSan) detect errors at runtime. The following example purposely reads past the end of a <code>std::vector</code> to trigger an ASan report.</p><pre><code class="language-cpp">// DEMO: deliberately wrong. This file exists to produce a sanitizer report.// Run under AddressSanitizer, the read past the end of the vector storage// reports heap-buffer-overflow://// ==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000fc// READ of size 4 at 0x6020000000fc thread T0// #0 ... std::__1::__format::__create_format_arg ... format_arg_store.h:191// ...// #8 ... in main ch01_overflow.cpp:13
#include <print>#include <vector>
int main() { std::vector<int> v{1, 2, 3}; volatile std::size_t past_the_end = v.size(); // volatile: defeat constant folding std::println("{}", v.data()[past_the_end]);}</code></pre><p>The report pasted in the file appears again exactly:</p><pre><code>==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000fcREAD of size 4 at 0x6020000000fc thread T0 #0 ... std::__1::__format::__create_format_arg ... format_arg_store.h:191 ... #8 ... in main ch01_overflow.cpp:13</code></pre><p>The build compiles the program with the sanitizer flags <code>-fsanitize=address,undefined</code>. The static members of the committee (clang‑tidy and the static analyzer) catch many issues at compile time, while sanitizers catch the remaining runtime bugs. The undefined‑behavior sanitizer reports a range of UB such as signed integer overflow, shift overflow and null‑pointer dereference. It inserts runtime checks that abort execution as soon as the offending operation occurs. The instrumentation stays in the finished binary: the address sanitizer roughly doubles runtime and memory use in typical programs, so sanitizer builds are a test configuration, not the shipping binary. The book treats both classes of diagnostics as equally binding.</p><h2 id="the-static-analyzer"><a class="header" href="#the-static-analyzer">The Static Analyzer</a></h2><p>The clang static analyzer runs a path‑sensitive analysis that discovers bugs that are plain warnings miss. The command <code>scan-build clang++ …</code> invokes the analyzer and prints a concise HTML report. Integrated development environments that use clangd can invoke the analyzer on the fly. An example that the analyzer catches but the usual warnings miss is a use‑after‑free of a heap object passed through a function pointer. The analyzer reports the error with a trace that shows the allocation, the free, and the later dereference. The analyzer works by symbolically executing the control‑flow graph of each function and exploring the paths that reach each statement. It can track heap allocations across function boundaries and can flag memory leaks even when the program frees memory in a different function. The analysis is conservative. It reports false positives on some code patterns, and a reported error is still worth the reader’s time.</p><h2 id="the-rulebook"><a class="header" href="#the-rulebook">The Rulebook</a></h2><p>The committee’s rulebook lives in the repository root as <code>.clang‑tidy</code>. The file appears verbatim below.</p><pre><code class="language-cpp"># The compiler committee's rulebook. Every book_example target builds with# these checks; ch01 quotes this file verbatim. clang-tidy runs as part of# compilation (see cmake/BookExample.cmake), so every retained check must stay# silent on the examples.## Stylistic checks are pruned: they either argue with the book's teaching# style or fire on idiomatic example code. The retained correctness checks# (bugprone, cppcoreguidelines, clang-analyzer, and the rest) stay on so a# real defect in an example is still caught.## Disabled checks are deliberate:# - *avoid-magic-numbers: teaching examples are full of small literal values# whose names would add noise, not safety.# - modernize-use-trailing-return-type: this book uses leading return types.# - bugprone-exception-escape: every example main() may throw from std::println;# this check would flag all of them for no safety gain.# - The stylistic checks below are pruned for the same reason: they fight the# book's style rather than find bugs.Checks: > bugprone-*, cppcoreguidelines-*, modernize-*, performance-*, readability-*, clang-analyzer-*, -readability-identifier-length, -readability-braces-around-statements, -readability-named-parameter, -readability-implicit-bool-conversion, -readability-math-missing-parentheses, -readability-simplify-subscript-expr, -readability-isolate-declaration, -readability-convert-member-functions-to-static, -readability-make-member-function-const, -readability-else-after-return, -readability-container-data-pointer, -modernize-use-designated-initializers, -modernize-use-nodiscard, -modernize-use-std-numbers, -modernize-use-ranges, -modernize-avoid-bind, -modernize-type-traits, -modernize-use-constraints, -modernize-avoid-c-arrays, -performance-avoid-endl, -performance-unnecessary-value-param, -performance-inefficient-vector-operation, -bugprone-easily-swappable-parameters, -bugprone-crtp-constructor-accessibility, -bugprone-unsafe-functions, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, -cppcoreguidelines-avoid-c-arrays, -cppcoreguidelines-owning-memory, -cppcoreguidelines-rvalue-reference-param-not-moved, -clang-analyzer-unix.Stream, -cppcoreguidelines-avoid-magic-numbers, -readability-magic-numbers, -modernize-use-trailing-return-type, -bugprone-exception-escapeWarningsAsErrors: '*'HeaderFilterRegex: '.*'FormatStyle: file</code></pre><p>The configuration enables a broad set of checks grouped under the headings <code>bugprone</code>, <code>cppcoreguidelines</code>, <code>modernize</code>, <code>performance</code>, <code>readability</code>, and <code>clang-analyzer</code>. The <code>bugprone</code> group catches patterns that frequently cause bugs, for example misuse of <code>std::move</code>. The <code>cppcoreguidelines</code> group enforces the Core Guidelines, such as requiring initialization of all members. The <code>modernize</code> group suggests modern C++ idioms, for example replacing raw arrays with <code>std::array</code>. The <code>performance</code> group flags inefficient constructions such as unnecessary copies. The <code>readability</code> group encourages clear code, for example by preferring range‑based loops over index arithmetic. The <code>clang-analyzer</code> group runs the static analyzer during compilation. Three checks are deliberately disabled:</p><ul><li><code>avoid-magic-numbers</code>: teaching examples contain many small literal values and naming each one adds noise.</li><li><code>modernize-use-trailing-return-type</code>: the book prefers leading return types for readability.</li><li><code>bugprone-exception-escape</code>: every example <code>main</code> can throw from <code>std::println</code>. This check flags all of them without improving safety.</li></ul><p>Only the enabled checks form part of the language law for the book. The <code>HeaderFilterRegex: '.*'</code> setting applies the checks to every file in the repository. The <code>WarningsAsErrors: '*'</code> setting promotes every retained clang-tidy warning to a hard error, so a <code>book_example</code> that trips a check fails the build. Compiler warnings become hard errors the same way through <code>-Werror</code> (see “Warnings are laws”). The deliberately-wrong <code>book_demo</code> targets run clang-tidy off, so their teaching diagnostics still reach the reader.</p><h2 id="cmake-as-the-contract"><a class="header" href="#cmake-as-the-contract">CMake As The Contract</a></h2><p>The build model for each example is deliberately simple. A CMake macro called <code>book_example</code> wraps every compiled example. The macro adds the source file as a target, attaches the clang‑tidy checks, enables the sanitizers, and registers a test with CTest. The macro also adds the flag <code>-Wlifetime-safety</code> when the compiler supports it, so the lifetime profile runs for every example that uses a reference or pointer. When the test runs the program, CTest verifies the expected output or the presence of a diagnostic in the standard output. The macro hides the details of the build system. The reader only needs to understand that the committee compiles each example once, checks it, and exercises it with a test. No deeper CMake teaching is necessary at this point. A typical invocation looks like this in <code>CMakeLists.txt</code>:</p><pre><code class="language-cmake">book_example(ch01_hello.cpp EXPECT "hello, world")</code></pre><p>The <code>EXPECT</code> argument tells CTest which regular expression to match on the program’s standard output. For the deliberately wrong demos the picture is different. They register a compile target but no test, because their exhibit is the compiler diagnostic itself, which the author pastes into the file’s header comment. The macro also defines a <code>book_gap</code> variant for features that are not yet supported by the pinned compiler. Those targets build only when the build passes the option <code>BOOK_ENABLE_GAPS=ON</code>.</p><h2 id="try-this"><a class="header" href="#try-this">Try This</a></h2><h3 id="trigger-a-ubsan-diagnostic"><a class="header" href="#trigger-a-ubsan-diagnostic">Trigger a UBSan diagnostic</a></h3><p>Write a program that adds two signed 32‑bit integers where the result overflows the representable range. Compile the program with the flags <code>-fsanitize=address,undefined</code>. Run the binary and observe that the undefined‑behavior sanitizer emits a diagnostic while the address sanitizer remains silent. Explain which member of the committee produced each message.</p><pre><code class="language-cpp">int main() { int a = 2'000'000'000; int b = 2'000'000'000; int sum = a + b; // signed overflow, undefined behavior return sum == 0;}</code></pre><h3 id="make-clangtidy-flag-a-cstyle-array"><a class="header" href="#make-clangtidy-flag-a-cstyle-array">Make clang‑tidy flag a C‑style array</a></h3><p>Create a tiny source file that defines a C‑style array of ten <code>int</code> values and fills it with a loop. Run <code>clang‑tidy</code> on the file using the repository configuration. The <code>modernize-avoid-c-arrays</code> check must produce a diagnostic suggesting the use of <code>std::array</code>. The program still compiles and runs, but the warning demonstrates how the committee enforces modern practices.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="values-and-functions"><a class="header" href="#values-and-functions">Values and functions</a></h1><p>A C++ program is a graph of functions that exchange values. This book treats thefunction as the atomic unit of design: a named operation on values, written onceand called from anywhere, not a method bolted to a class. Classes appear laterand stay rare. If you come from Rust, read “function” as <code>fn</code>. If you come from C, returning rich values (tuples, structs, containers) is cheap and normal, so C’s out‑parameters and pointer‑returning habits fall away.</p><h2 id="a-function-is-one-logical-operation"><a class="header" href="#a-function-is-one-logical-operation">A function is one logical operation</a></h2><p>A declaration tells the compiler a name’s type, and a definition supplies the bodyand must appear exactly once in the whole program (CG F.2). In a single-fileexample you write both at once. In a multi-file program the declaration lives ina header and the definition in one translation unit. Chapter 23 covers thatsplit, and the one-definition rule that enforces it.</p><p>The guideline is deliberately narrow: one function does one logical operation.</p><p>Design each function to perform a single logical operation. This makes the function easy to test in isolation and reduces hidden coupling. For example, <code>draw_triangle</code> is a function, while <code>draw_triangle_and_clear_screen_and_log</code> is not. The compiler cannot enforce this rule. It relies on the programmer. When a function grows, extract helper functions so each piece remains testable and readable. Small functions can be inlined, eliminating call overhead.</p><h2 id="return-by-value-is-the-default"><a class="header" href="#return-by-value-is-the-default">Return by value is the default</a></h2><p>C++ moves values, so returning a <code>std::vector</code> or a struct is not a copy ofmegabytes. The function builds the result in its own frame and the caller’svariable <em>takes ownership</em> of it through move, which for a temporary is elidedto no copy at all. Write <code>auto</code> for the return type and let the compiler deduceit from the <code>return</code> expression:</p><pre><code class="language-cpp">#include <print>#include <tuple>#include <cmath>
// Solve a*x^2 + b*x + c = 0 and return the two roots.// For the equation x^2 - 3*x + 2 = 0 the roots are 2 and 1.std::tuple<double, double> solve_quadratic(double a, double b, double c) { double discriminant = b * b - 4.0 * a * c; double sqrt_disc = std::sqrt(discriminant); double root1 = (-b + sqrt_disc) / (2.0 * a); double root2 = (-b - sqrt_disc) / (2.0 * a); return {root1, root2};}
int main() { // Structured binding decomposes the tuple into two distinct names. auto [root1, root2] = solve_quadratic(1.0, -3.0, 2.0); // Print exactly "roots: 2, 1". std::println("roots: {}, {}", root1, root2); return 0;}</code></pre><p>Multiple results come back as a <code>std::tuple</code> or, better, as a small named struct(CG F.21). You read them with a structured binding, which decomposes anyaggregate (a tuple, a <code>std::pair</code>, an array, or a user-defined struct) intonamed locals in one line. The binding <code>auto [root1, root2]</code> is not adestructuring of a special tuple type, it is generic aggregate decomposition andyou will lean on it for your own types in chapter 3.</p><p>Out-parameters (<code>void solve(double*, double*)</code>) are forbidden by the book’sstyle. They obscure the result in the argument list and break move semantics(CG F.20). Return the value.</p><h2 id="trailing-return-types-and-why-the-book-avoids-them"><a class="header" href="#trailing-return-types-and-why-the-book-avoids-them">Trailing return types, and why the book avoids them</a></h2><p>The book writes the return type first, before the parameter list:</p><pre><code class="language-cpp">std::vector<int> make_squares(int n);</code></pre><p>C++ also offers a trailing return type, written after the parameter list andpreceded by <code>-></code>:</p><pre><code class="language-cpp">auto make_squares(int n) -> std::vector<int>;</code></pre><p>The book uses the leading form as its default. The trailing form readsright-to-left and hides the result behind the parameters, so it is not thebook’s style. Yet the trailing form is required, or genuinely helpful, in afew real cases.</p><p>A trailing return type can name a parameter. In a leading return type theparameter names are not yet in scope, so you cannot write their types with<code>decltype</code>. The trailing form places the parameters first, which puts them inscope for the return type. This is the classic case where trailing isrequired:</p><pre><code class="language-cpp">template <typename T>auto describe(T const& t) -> decltype(t.size());</code></pre><p>The same scoping helps member function definitions. A trailing return typeappears after the class name, so names looked up inside the class body are inscope. A leading return type sits before the class name and cannot see them:</p><pre><code class="language-cpp">struct Counter { std::size_t count() const;};auto Counter::count() const -> std::size_t; // return type sees the class</code></pre><p>Returning a function pointer or an array reads badly with a leading type.The trailing form keeps the pointer or array syntax attached to the functionname:</p><pre><code class="language-cpp">auto choose(int) -> int(*)(int); // returns a pointer to a function</code></pre><p>Finally, the trailing form orders information the way the reader meets it:parameters first, result last. That ordering is the source of the “East EndFunctions” style. The book still prefers the leading return type for everydaycode, and reserves the trailing form for the cases above where it is requiredor clearer.</p><pre><code class="language-cpp">#include <print>#include <string_view>
// The return type is a trailing decltype of the parameter. Only the// parameter name is in scope at that point, so this form is required.template <typename T>auto describe(T const& t) -> decltype(t.size()) { return t.size();}
int main() { std::string_view greeting = "hello"; std::println("size: {}", describe(greeting)); return 0;}</code></pre><h2 id="overloading-one-name-many-shapes"><a class="header" href="#overloading-one-name-many-shapes">Overloading: one name, many shapes</a></h2><p>Overloading lets several functions share a name, distinguished by argumenttypes. C has no overloading. This is the first construct where C++ reads as adifferent language. The compiler picks the overload at compile time by bestmatch: an exact match beats a promotion, a promotion beats a standardconversion, and a user-defined conversion is the last resort. The “best matchwins” rule is all you need for everyday code. The full precedence machinery,including how templates enter the contest, waits for chapters 16 and 20.</p><pre><code class="language-cpp">#include <print>#include <string_view>
void describe(int) { std::println("int overload");}
void describe(double) { std::println("double overload");}
void describe(std::string_view) { std::println("string overload");}
int main() { describe(42); describe(3.14); describe(std::string_view{"hello"}); return 0;}</code></pre><p>When the call sites are <code>describe(42)</code>, <code>describe(3.14)</code>, and<code>describe("hello")</code>, the compiler resolves each to a distinct function, and thetest confirms the string overload fired. Overload resolution is zero-cost: thechoice is made entirely before the program runs.</p><p>Overloading is the first place C++ diverges from C. C solves the same problemwith name mangling: <code>describe_int</code> and <code>describe_string</code> are different names.C++ lets you use one name and trusts the compiler to pick. This is the samerule that templates extend in chapter 16, where the “shapes” become patternsand the compiler writes a new function for each type.</p><h2 id="default-arguments-state-the-common-case"><a class="header" href="#default-arguments-state-the-common-case">Default arguments state the common case</a></h2><p>A parameter can carry a default used when the caller omits it. Defaults sit onthe declaration and must be trailing: once a parameter has a default, everyparameter to its right must too. A default encodes the usual call. The unusualcall supplies the argument. Keep them for genuine common cases, not to mergeseveral unrelated functions into one.</p><pre><code class="language-cpp">void greet(std::string_view name = "world") { std::println("hello, {}", name);}</code></pre><p><code>greet()</code> prints <code>hello, world</code>. <code>greet("Alice")</code> prints <code>hello, Alice</code>. Thedefault is compiled into each call site, so the two forms cost the same. (Themulti-declaration rules for defaults across files belong to chapter 23.)</p><h2 id="const-is-the-default"><a class="header" href="#const-is-the-default">const is the default</a></h2><p><code>const</code> is a promise the compiler enforces. A <code>const</code> name cannot be reboundto a different value after initialization. Declare a local <code>const</code> unless itmust change (CG ES.25). This is the same split as Rust’s <code>let</code> versus<code>let mut</code>, and the same discipline: start immutable, relax only where thealgorithm demands it.</p><p>A const local variable binds once and never changes:</p><pre><code class="language-cpp">const double pi = 3.14159;const int max_attempts = 3;</code></pre><p>A const reference reads an object without copying or mutating it. Functionsthat merely read a value take it by <code>const</code> reference so they neither copynor mutate (CG Con.1):</p><pre><code class="language-cpp">void show(const std::string& name) { std::println("{}", name);}</code></pre><p>The placement of <code>const</code> on a pointer decides what is fixed. A pointer toconst (<code>const T*</code>) lets the pointer move but not the pointee. A const pointer(<code>T* const</code>) cannot be reseated, but the pointee stays mutable. A constpointer to const (<code>const T* const</code>) fixes both:</p><pre><code class="language-cpp">const int* p1; // pointer to const, pointee read‑onlyint* const p2 = &value; // const pointer, cannot be reseatedconst int* const p3 = &value; // const pointer to const</code></pre><p>A const member function promises not to modify the object it runs on. Thecompiler checks this, so a <code>const</code> object can call it:</p><pre><code class="language-cpp">struct Counter { int value() const { return count_; } int count_ = 0;};</code></pre><p>A const return value protects the result from mutation. Returning <code>const T</code>by value is rare, because it blocks move semantics. The book returns plainvalues and reserves <code>const</code> for references and pointers:</p><pre><code class="language-cpp">const std::string label() const; // const return value, const member</code></pre><p>A const function parameter promises the callee will not modify the argument.This is the default for read‑only parameters (chapter 8):</p><pre><code class="language-cpp">void draw(const Shape& shape);</code></pre><p>Immutability‑by‑default is the cheapest correctness tool the language offers,and the book applies it everywhere.</p><h2 id="function-naming-and-error-handling"><a class="header" href="#function-naming-and-error-handling">Function naming and error handling</a></h2><p>Name functions with a lower‑case verb phrase that describes the action, e.g., <code>read_file</code> or <code>calculate_checksum</code>. Avoid generic names. For recoverable errors return <code>std::expected<T, E></code>. For unrecoverable failures throw an exception that includes a message identifying the failed operation.</p><h2 id="references-are-aliases-not-pointers"><a class="header" href="#references-are-aliases-not-pointers">References are aliases, not pointers</a></h2><p><code>int& r = x;</code> gives <code>r</code> a second name for <code>x</code>. Reads and writes through <code>r</code>reach the same object. A reference is not a pointer you must dereference, and itcannot be reseated after initialization. In Rust terms it is a reborrow.</p><p>A reference parameter hands the caller’s object to the callee, which can read ormodify it. The full decision of <em>what</em> to pass (value, <code>const</code> reference, or<code>std::span</code>) is chapter 8. For now, the rule is that a reference means “I am”using your object,“ never “I own a copy.</p><h2 id="practical-tips-for-writing-functions"><a class="header" href="#practical-tips-for-writing-functions">Practical tips for writing functions</a></h2><p>When designing a function, choose a clear verb‑phrase name, keep parameters short, pass large objects by <code>const</code> reference and small trivially copyable types by value, and return a value when needed.</p><p>Use <code>void</code> for functions that produce no observable result.</p><h2 id="common-pitfalls"><a class="header" href="#common-pitfalls">Common pitfalls</a></h2><ul><li>Do not return a reference to a local variable. The reference dangles after the function returns (see the WARNING above).</li><li>Do not store a pointer to a parameter that the caller can destroy before the function returns.</li><li>Avoid default arguments that depend on mutable global state, as they create hidden dependencies.</li><li>Keep overload sets small and well‑documented. Overloads that differ only by const qualification can be confusing.</li></ul><h2 id="performance-considerations"><a class="header" href="#performance-considerations">Performance considerations</a></h2><p>When a function returns a large object by value the compiler can apply return value optimization. This eliminates the temporary copy by constructing the result directly in the caller’s storage. Modern compilers can also apply named return value optimization when the return variable is a named local. The generated code frequently reduces memory traffic and improves cache usage.</p><p>If a function takes a large argument by value the copy can be expensive. Prefer a const reference for read‑only parameters. For parameters that the function will modify and then return, take the argument by value and move it back. This pattern enables the caller to pass a temporary without an extra copy. The move operation transfers ownership of the internal resources with minimal overhead.</p><p>Inlining small functions removes the call overhead entirely. The compiler decides whether inlining is beneficial based on the function size and the context of the call site. Functions that consist of a single return statement or a few arithmetic operations are prime candidates for inlining. Developers can hint at inlining with the inline keyword, but the final decision rests with the optimizer.</p><p>Do not include unnecessary branches inside hot loops. Branch prediction failures can stall the pipeline. When possible restructure code to keep the hot path straight. Use constexpr when the result can be computed at compile time. This moves work from runtime to compile time and can produce faster executables.</p><p>Profile the code with a sampling profiler to locate bottlenecks. Measure the impact of changes rather than assuming improvement. The guidelines in this chapter aim to produce clear, maintainable code, and the performance impact of each choice must be evaluated in the context of the whole program.</p><blockquote><p><strong>WARNING</strong>Never return a reference to a local variable. The local is destroyed when thefunction returns, and the caller receives a dangling name. Chapter 1 showedthe compiler catching exactly this. Return-by-reference is reserved forassignment operators, which return <code>*this</code> (CG F.47).</p></blockquote><h2 id="try-this-1"><a class="header" href="#try-this-1">Try this</a></h2><p>Rewrite <code>solve_quadratic</code> to return a small user-defined struct<code>{ double first; double second; }</code> instead of a <code>std::tuple</code>, and keep the callsite a structured binding. In one sentence, state what this proves aboutstructured bindings.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="user-defined-types-structs-sums-and-expectations"><a class="header" href="#user-defined-types-structs-sums-and-expectations">User-defined types: structs, sums, and expectations</a></h1><h2 id="structs-are-product-types"><a class="header" href="#structs-are-product-types">Structs are product types</a></h2><p>In C++ a <code>struct</code> groups a fixed set of named fields. The compiler automatically provides a default constructor, a copy constructor, a move constructor, and a trivial destructor when the fields themselves support those operations. This mirrors the mathematical notion of a <em>product</em>: a value of the struct type contains one value of each field.</p><pre><code class="language-cpp">struct point { double x; double y;};</code></pre><p>The declaration above yields a type that can be constructed with brace initialisation: <code>point{1.0, 2.0}</code>. No user-written constructor is required, which satisfies the Core Guidelines recommendation to prefer aggregates (C.20). The fields are public by default. The type behaves like a plain data carrier, unlike the historic C <code>struct</code>, which cannot contain member functions or <code>const</code> qualifiers.</p><p>Member functions can be added without sacrificing the aggregate property. A read-only member function is marked <code>const</code> so the compiler forbids it from modifying the object (C.2). The <code>point</code> example below adds <code>double distance(const point&) const</code>, which computes the Euclidean distance without changing <code>*this</code> and can therefore be called on a <code>const point</code>.</p><p>An aggregate also supports designated initialisers. The syntax <code>point{.x = 1.0, .y = 2.0}</code> names each field explicitly. The compiler checks the order and the names, so a typo in a field name fails to compile. Designated initialisers make the intent of each value clear at the call site. They also survive a change in field order without silently swapping values. This form is the clearest way to build a small data carrier.</p><p>The <code>point</code> type with its distance function is a complete example. The test verifies that the distance between two points is correct.</p><pre><code class="language-cpp">#include <print>#include <cmath>
struct point { double x; double y; double distance(const point& other) const { double dx = x - other.x; double dy = y - other.y; return std::hypot(dx, dy); }};
int main() { point p1{0.0, 0.0}; point p2{3.0, 4.0}; double d = p1.distance(p2); std::println("distance: {:.2f}", d); return 0;}</code></pre><h2 id="invariants-and-the-small-private-we-allow"><a class="header" href="#invariants-and-the-small-private-we-allow">Invariants and the small private we allow</a></h2><p>A struct needs to enforce a relationship between its fields: an <em>invariant</em>. The book avoids class-based object orientation, yet a narrow use of <code>private</code> together with a public accessor is acceptable when the invariant cannot be expressed by the type system alone. For example, a normalized 3-D vector must always have length 1:</p><pre><code class="language-cpp">struct vec3 {private: double x, y, z; vec3(double a, double b, double c) : x(a), y(b), z(c) {}public: static vec3 make(double a, double b, double c) { double len = std::hypot(a, b, c); return vec3{a / len, b / len, c / len}; } double length() const { return std::hypot(x, y, z); }};</code></pre><p>The <code>private</code> section prevents accidental mutation that can break the invariant, while the static factory <code>make</code> guarantees a correctly normalised instance. This follows Core Guidelines C.21 to keep data encapsulation minimal and prefer plain functions over heavy OO machinery. The invariant, that a <code>vec3</code> constructed via <code>make</code> has length 1, is enforced by the factory. Direct use of the private constructor violates the contract, and the factory also checks for a zero‑length input and rejects it, avoiding a NaN result.</p><p>Because the type system cannot express this invariant, the responsibility rests with the factory and disciplined callers. This trades a small runtime check for the complexity of a large class hierarchy.</p><h2 id="defaulted-comparison-and-the-spaceship"><a class="header" href="#defaulted-comparison-and-the-spaceship">Defaulted comparison and the spaceship</a></h2><p>C++20 introduced the three-way comparison operator <code><=></code>, commonly called the <em>spaceship</em>. When a struct’s fields already support equality and ordering, the compiler can generate all six relational operators automatically:</p><pre><code class="language-cpp">struct point { double x; double y; auto operator<=>(const point&) const = default;};</code></pre><p>Before C++20 each relational operator had to be written manually, a source of boilerplate and possible inconsistencies. The defaulted spaceship satisfies the Core Guidelines rule that the compiler must generate “obvious” functions (C.10, C.87). The generated operators perform a lexicographic comparison of the fields in declaration order, mirroring the behaviour of <code>std::tie</code>.</p><p>The spaceship is the last piece the <code>point</code> type needs. With it, the type supports equality, ordering, and structured bindings, all without a hand-written operator. The compiler generates the comparisons from the fields, and the fields are the data. This is the Rule of Zero applied to comparisons: the type declares its intent (<code>= default</code>), and the compiler does the work.</p><p>Structured bindings let a caller unpack a <code>point</code> into its fields in one statement. The form <code>auto [px, py] = p</code> binds <code>px</code> to <code>p.x</code> and <code>py</code> to <code>p.y</code>. The binding respects the declaration order of the fields, which matches the order that the spaceship compares. So the ordering of the fields has two consequences: it fixes the order of comparison and the order of the bindings. A reader who knows the field order can predict both. This consistency is why the book keeps field order stable across the type.</p><p>The spaceship also reports the strength of the comparison. The defaulted operator returns a comparison category, and the compiler derives it from the field types. For a <code>point</code> of two <code>double</code> fields the category is <code>std::partial_ordering</code>, because floating-point values do not always compare as equal to themselves. The caller rarely names this category directly, yet it governs how the generated operators behave. This detail matters when a struct mixes fields of different comparison strength.</p><h2 id="stdvariant-as-a-sum-type"><a class="header" href="#stdvariant-as-a-sum-type"><code>std::variant</code> as a sum type</a></h2><p>A <em>sum type</em> represents a value that is exactly one of several alternatives. In C++ the standard library provides <code>std::variant</code> for this purpose. It stores a discriminated union together with a runtime tag, guaranteeing safe access. The expression-tree example below demonstrates a binary addition tree built from numbers and nested operations:</p><pre><code class="language-cpp">#include <variant>#include <memory>#include <iostream>
struct Node;struct Number { double value; };struct BinaryOp { char op; std::unique_ptr<Node> left; std::unique_ptr<Node> right;};struct Node { std::variant<Number, BinaryOp> expr; double eval() const { struct Visitor { double operator()(const Number& n) const { return n.value; } double operator()(const BinaryOp& b) const { double l = b.left->eval(); double r = b.right->eval(); return b.op == '+' ? l + r : 0.0; } }; return std::visit(Visitor{}, expr); }};
int main() { // Build (1 + 2) + 3 = 6 auto leaf1 = std::make_unique<Node>(Node{Number{1}}); auto leaf2 = std::make_unique<Node>(Node{Number{2}}); auto inner = std::make_unique<Node>(Node{BinaryOp{'+', std::move(leaf1), std::move(leaf2)}}); auto root = std::make_unique<Node>(Node{BinaryOp{'+', std::move(inner), std::make_unique<Node>(Node{Number{3}})}}); std::println("result: {}", root->eval());}</code></pre><p><code>std::visit</code> dispatches to the appropriate overload based on the active alternative. The helper <code>Visitor</code> aggregates the lambdas (a pattern often called <em>overloaded</em>) and makes the code concise. Compared with a traditional C <code>union</code>, <code>std::variant</code> carries a tag, eliminating the undefined behaviour that arises from reading the wrong member. Rust’s <code>enum</code> offers a similar safety guarantee. The C++ version fits naturally into the existing type system without requiring a separate language construct.</p><p>The expression tree is a Prolog term in C++ clothing. A <code>Node</code> is either a <code>Number</code> (a leaf) or a <code>BinaryOp</code> (a compound term with two subterms). The <code>eval</code> function is the interpreter: it walks the term and reduces it to a value. The <code>visit</code> call is the case dispatch, and the <code>Visitor</code> struct is the set of clauses, one per alternative. This is the pattern the book returns to in chapter 22, where a compile-time SQL parser builds a similar term and evaluates it at translation time.</p><p>The full expression-tree example is a complete program. The test verifies that the tree evaluates to the correct value.</p><pre><code class="language-cpp">#include <variant>#include <memory>#include <iostream>
struct Node;struct Number { double value; };struct BinaryOp { char op; std::unique_ptr<Node> left; std::unique_ptr<Node> right;};struct Node { std::variant<Number, BinaryOp> expr; double eval() const { struct Visitor { double operator()(const Number& n) const { return n.value; } double operator()(const BinaryOp& b) const { double l = b.left->eval(); double r = b.right->eval(); return b.op == '+' ? l + r : 0.0; } }; return std::visit(Visitor{}, expr); }};
int main() { // Build (1 + 2) + 3 = 6 auto leaf1 = std::make_unique<Node>(Node{Number{1}}); auto leaf2 = std::make_unique<Node>(Node{Number{2}}); auto inner = std::make_unique<Node>(Node{BinaryOp{'+', std::move(leaf1), std::move(leaf2)}}); auto root = std::make_unique<Node>(Node{BinaryOp{'+', std::move(inner), std::make_unique<Node>(Node{Number{3}})}}); std::println("result: {}", root->eval());}</code></pre><h2 id="stdoptional-for-the-maybe"><a class="header" href="#stdoptional-for-the-maybe"><code>std::optional</code> for the maybe</a></h2><p>A function that can return a value or nothing uses <code>std::optional<T></code>. An optional either holds a <code>T</code> or is empty. It replaces the raw-pointer-returns-null idiom, which a caller can forget to check. The <code>value()</code> accessor throws <code>std::bad_optional_access</code> if the object is empty, and <code>operator*</code> is undefined for an empty optional, so the type makes the empty state explicit in the API. This pattern aligns with the Core Guidelines principle that error-prone pointer use must be avoided (F.4).</p><p>An optional carries no information about why the value is absent. It answers the question “is there a value?” and nothing more. When the caller needs to know why the operation failed, <code>std::expected</code> is the right type, because it carries an error description alongside the absence.</p><p>The choice between <code>optional</code> and <code>expected</code> is a question of what the caller must know. Use <code>optional</code> when the absence is a normal state, not an error. A map lookup that finds no key is a good fit, because the caller can proceed without the value. Use <code>expected</code> when the absence is a failure that the caller must handle or report. A file read that fails needs a reason, so the caller can log it or retry. The two types share the same shape, yet they carry different meaning. The book states the rule plainly: absence without a reason is <code>optional</code>, absence with a reason is <code>expected</code>.</p><h2 id="stdexpected-for-fallible-computations"><a class="header" href="#stdexpected-for-fallible-computations"><code>std::expected</code> for fallible computations</a></h2><p>When a computation can fail with a recoverable error, <code>std::expected<T, E></code> conveys either a successful result of type <code>T</code> or an error description of type <code>E</code>. The parse-integer example returns an <code>int</code> on success or a <code>parse_error</code> struct describing the position of the first non-digit character.</p><pre><code class="language-cpp">#include <expected>struct parse_error { int position; std::string_view message; };std::expected<int, parse_error> parse_int(std::string_view sv);</code></pre><p>The caller inspects the returned object with <code>if (result)</code> and branches on success or failure. This approach is preferable to exceptions for anticipated failure such as malformed user input, because the control flow is explicit and does not incur the cost of stack unwinding. It also improves on <code>std::optional</code> by providing diagnostic information: the error type can carry a message, an error code, or any richer context the application needs. Chapter 9 expands on the trade-offs between exceptions, <code>expected</code>, and <code>optional</code>.</p><p>The parse-integer example is a complete program. The test verifies that a valid input produces the correct integer and an invalid input produces the error message.</p><p>The <code>expected</code> type also supports composition. A caller can chain operations so that a failure stops the chain early. The member function <code>and_then</code> applies a follow-up function only when the object holds a value. The member function <code>or_else</code> supplies a fallback when the object holds an error. These operations keep the control flow in the value domain instead of in a set of nested checks. The result reads like a pipeline of steps, and the error path stays visible in the type. This is the same spirit as the <code>visit</code> dispatch in the expression tree: the shape of the type drives the flow of the code.</p><p>The error type <code>E</code> is a full type, not a fixed string. A program can define an error that carries a code and a message, or a nested error from a lower layer. The caller can inspect the error and decide how to recover. This flexibility is what separates <code>expected</code> from a bare <code>bool</code> return. A <code>bool</code> tells the caller that something failed, while an <code>expected</code> tells the caller what failed and where. The extra information is the reason the book reaches for <code>expected</code> over a status flag.</p><pre><code class="language-cpp">#include <print>#include <string_view>#include <expected>
struct parse_error { int position; std::string_view message;};
std::expected<int, parse_error> parse_int(std::string_view sv) { int result = 0; int pos = 0; for (char c : sv) { if (c >= '0' && c <= '9') { result = result * 10 + (c - '0'); ++pos; } else { return std::unexpected(parse_error{pos, "non-digit character"}); } } return result;}
int main() { auto good = parse_int("123"); if (good) { std::println("value: {}", *good); } else { std::println("error at {}", good.error().position); } auto bad = parse_int("12a3"); if (bad) { std::println("value: {}", *bad); } else { std::println("error at {}", bad.error().position); } return 0;}</code></pre><h2 id="try-this-2"><a class="header" href="#try-this-2">Try this</a></h2><p>Extend the <code>point</code> example from the product-type section by adding a defaulted three-way comparison (<code>operator<=></code>). Create three <code>point</code> objects, store them in a <code>std::vector<point></code>, and call <code>std::sort</code>. State in one sentence which comparison the sort algorithm used.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="control-flow-modernly"><a class="header" href="#control-flow-modernly">Control flow, modernly</a></h1><p>This chapter examines modern control-flow constructs introduced in C++23 and C++26 that let the programmer keep the code that establishes a value adjacent to the test that uses it. We look at init-statements for if, switch, while and for, the ability to place structured bindings directly in a condition, the range-for loop as the preferred iteration form, compile-time conditionals with if constexpr, and the recommendation to replace manual loops with standard algorithms. By using these features the intent of the code stays close to the point of use, reducing accidental reuse and improving readability.</p><h2 id="init-statements"><a class="header" href="#init-statements">Init-statements</a></h2><p>C++23 introduced init‑statements so a temporary variable can be created, tested, and destroyed within the condition of <code>if</code>, <code>while</code>, or <code>for</code>. A simple call yields compact syntax. For more complex setup a helper returning an RAII object can be used directly.</p><p>Init‑statements also appear in <code>while</code> and range‑based <code>for</code>. In a <code>while</code> loop the initializer runs once before the first test, allowing resource acquisition followed by exhaustion testing. In a <code>for</code> loop the initializer can bind a temporary range object, ensuring the range lives exactly for the loop’s duration. This keeps lifetimes tightly scoped and prevents accidental reuse outside the loop body.</p><h3 id="example-initstatement-in-an-if"><a class="header" href="#example-initstatement-in-an-if">Example: init‑statement in an <code>if</code></a></h3><pre><code class="language-cpp">if (auto line = std::getline(std::cin, s); !line.empty()) { std::cout << "first line: " << s << '\n';}</code></pre><p>The variable <code>line</code> exists only while the condition is evaluated and the body runs. No other part of the function can mistakenly read it.</p><p>This tight scoping prevents accidental reuse of the temporary buffer later in the function, which is a common source of bugs in legacy code. By limiting the lifetime, the compiler can also apply stack‑slot reuse optimizations, reducing memory pressure in tight loops.</p><h2 id="structured-bindings-in-a-condition-c26"><a class="header" href="#structured-bindings-in-a-condition-c26">Structured bindings in a condition (C++26)</a></h2><p>C++26 extends init-statements by enabling structured bindings directly in the condition. A binding can decompose a tuple-like object and the boolean test can refer to any of the bound names:</p><pre><code class="language-cpp">if (auto [ok, n] = try_parse(s); ok) { std::cout << "got " << n << '\n';}</code></pre><p><code>try_parse</code> returns a <code>std::pair<bool,int></code> (or a <code>std::expected<int,std::string_view></code>). The binding extracts the success flag <code>ok</code> and the parsed value <code>n</code>. The subsequent test <code>ok</code> decides whether the body runs. Before C++26 the language only permitted a <em>single</em> simple declaration inside the condition, so a common pattern was a nested <code>if</code> pyramid:</p><pre><code class="language-cpp">auto result = try_parse(s);if (result.ok) { int n = result.value; // …}</code></pre><p>The new form collapses that pyramid into one line, reducing visual noise and keeping the “parse-then-use” logic together. It is the modern replacement for the nested-<code>if</code> pattern often used with <code>std::expected</code> or <code>std::pair</code>.</p><p>Using structured bindings in a condition also makes error handling more direct. When a function returns a <code>std::expected</code>, the success flag and the value can be examined immediately. This enables the error path to be written without an extra temporary variable. This leads to code that reads like a natural language description of the operation, which matches the goal of modern C++ to be expressive and intent-revealing.</p><pre><code class="language-cpp">#include <iostream>#include <utility>#include <string_view>
// Simple parser that returns {true,42} for the literal "42",// otherwise returns {false,0}.std::pair<bool,int> try_parse(std::string_view s) { if (s == "42") return {true, 42}; return {false, 0};}
int main() { std::string_view input = "42"; if (auto [ok, n] = try_parse(input); ok) { std::cout << "got " << n << '\n'; } return 0;}</code></pre><h2 id="switch-need-not-switch-on-an-integer"><a class="header" href="#switch-need-not-switch-on-an-integer"><code>switch</code> need not switch on an integer</a></h2><p>Although the classic <code>switch</code> works well with integral types, modern C++ encourages the use of <code>std::visit</code> for variant-like data. When a developer needs to dispatch based on a value that is not an integer, the <code>visit</code> pattern provides exhaustive handling and integrates with concepts for compile‑time checks.</p><p>In practice, a <code>switch</code> on an enum can still be useful when the set of cases is closed and the compiler can emit a jump table. However, for open‑ended sets such as <code>std::variant</code> the <code>visit</code> approach avoids the risk of missing a case and produces clearer error messages.</p><p>When performance is critical, a <code>switch</code> with contiguous case values can be faster than a series of <code>if‑else</code> checks because the compiler can generate a direct table lookup. Yet, readability and safety often outweigh micro‑optimisations, especially in high‑level code.</p><p>The guidelines suggest preferring <code>visit</code> when dealing with sum types and reserving <code>switch</code> for simple, closed enumerations where the intent is to map each constant to a distinct branch.</p><pre><code class="language-cpp">std::variant<int,double,std::string> v = 3.14;std::visit([](auto&& arg){ std::cout << arg << '\n';}, v);</code></pre><p>For range‑based algorithms replace a <code>switch</code> that branches on element values with a standard algorithm such as <code>std::count_if</code> or <code>std::transform</code>. The algorithm expresses <em>what</em> to compute, not <em>how</em> to iterate.</p><h2 id="ranges-for-is-the-only-loop-you-write"><a class="header" href="#ranges-for-is-the-only-loop-you-write">Ranges-<code>for</code> is the only loop you write</a></h2><p>A range-<code>for</code> iterates directly over the elements of a view or container:</p><pre><code class="language-cpp">for (auto&& x : container) { // use x}</code></pre><p>It eliminates the manual index variable, the off-by-one risk, and the need to look up <code>container[i]</code>. The Core Guidelines (ES.71) state: <em>write a range-<code>for</code> whenever you need to touch each element</em>. Index-based loops belong only to cases where the index itself is a required output.</p><p>The FizzBuzz program below runs over the view <code>iota(1,21)</code>, which generates the integers 1 through 20. No explicit index variable appears because the view owns the counting.</p><pre><code class="language-cpp">#include <iostream>#include <ranges>
int main() { for (int i : std::views::iota(1, 21)) { if (i % 15 == 0) std::cout << "fizzbuzz"; else if (i % 3 == 0) std::cout << "fizz"; else if (i % 5 == 0) std::cout << "buzz"; else std::cout << i; if (i != 20) std::cout << ' '; } std::cout << '\n'; return 0;}</code></pre><h2 id="if-constexpr"><a class="header" href="#if-constexpr"><code>if constexpr</code></a></h2><p>C++17 introduced <code>if constexpr</code>, a compile‑time conditional. The false branch is discarded before instantiation, so it need not compile. This lets generic code adapt to template arguments via constant‑expression predicates such as <code>std::is_integral_v<T></code>. The selected branch can be inlined, eliminating dead code and improving optimisation.</p><p>A typical use tests <code>std::ranges::range<T></code> to choose between printing a scalar value or iterating a range. Concepts can be combined, e.g. <code>requires { typename T::value_type; }</code>, to keep constraints close to the code they govern.</p><p>Overall, <code>if constexpr</code> provides clear, type‑safe compile‑time dispatch without separate specialisations.</p><h2 id="raw-loops-are-a-smell"><a class="header" href="#raw-loops-are-a-smell">Raw loops are a smell</a></h2><p>When an algorithm expresses the intent (e.g., <em>find the first element greater than 10</em>), writing a manual loop hides that intent. The guideline (ES.70) urges the programmer to replace a hand‑written loop with an appropriate standard algorithm, such as <code>std::find_if</code>, <code>std::count</code>, or <code>std::transform</code>, so that readers instantly recognise the operation. Chapter 12 will enumerate the algorithm zoo and show how each algorithm maps to a common pattern.</p><p>Standard algorithms also bring strong exception‑safety guarantees. Because the library implements the iteration and cleanup logic, edge cases such as early exits or thrown exceptions are handled uniformly. This reduces the likelihood of resource leaks compared with manually written loops that must explicitly manage cleanup. Moreover, many algorithms are annotated for vectorisation. The compiler can then generate SIMD instructions automatically when the iterator type supports it. The result is often faster code with the same expressive clarity.</p><p>When performance is critical, the programmer can still profile the algorithmic choice. The standard library provides overloads that accept execution policies. These enable parallel execution without changing the high‑level code. This flexibility means that the same source can be tuned for different hardware targets by swapping the policy argument.</p><p>Overall, preferring algorithms over hand‑written loops aligns with modern C++ philosophy: write what you want to achieve, let the library handle how to achieve it.</p><h2 id="try-this-3"><a class="header" href="#try-this-3">Try this</a></h2><p>Rewrite the FizzBuzz program so that the upper bound is supplied by a function parameter and the program prints only the count of numbers that are multiples of both 3 and 5. Use the <code>std::views::filter</code> adaptor to select the qualifying numbers and <code>std::ranges::distance</code> to obtain the count.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="ownership-move-and-raii"><a class="header" href="#ownership-move-and-raii">Ownership, move, and RAII</a></h1><h2 id="value-semantics-as-the-default-mental-model"><a class="header" href="#value-semantics-as-the-default-mental-model">Value semantics as the default mental model</a></h2><p>C++ treats a variable as the sole owner of the value it stores. The compiler creates the value when the variable’s defined and destroys it when the variable’s lifetime ends. This model matches the Rust model without the borrow checker: a name owns a value.</p><p>Passing the name to a function transfers the value or copies it. The transfer occurs by invoking a move constructor or a copy constructor. The compiler later checks that every move obeys the lifetime‑safety rules introduced in Chapter 1.</p><p>Contrast this model with raw C pointers. A pointer refers to memory that any code path can own. The language does not track that ownership. Programmers conventionally treat the pointer as borrowed, but the compiler cannot enforce that rule. Consequently, dangling pointers and double frees appear frequently in legacy code.</p><p>In modern C++ code, the default assumption is <strong>ownership per value</strong>. When a function receives a parameter by value, the caller gives up its ownership. When a function returns a value, the caller receives a fresh owner. The only time a value is duplicated is when a copy constructor executes.</p><p>The contrast with Rust is direct. Rust’s borrow checker proves at compile time that no two owners exist for the same value and that no reference outlives its referent. C++ has no borrow checker, so the same invariants rest on type design: owning types delete copy, non-owning views borrow without duplicating, and the lifetime analysis flags dangling references. The mental model is the same. The enforcement differs.</p><h2 id="special-member-functions-and-the-rule-of-zerofive"><a class="header" href="#special-member-functions-and-the-rule-of-zerofive">Special member functions and the Rule of Zero/Five</a></h2><p>A type can define up to six special member functions: default constructor, destructor, copy constructor, move constructor, copy-assignment operator, and move-assignment operator. If a class contains only owning standard‑library members (e.g., <code>std::vector</code>, <code>std::string</code>, <code>std::unique_ptr</code>), the compiler‑generated versions are correct. This is the <strong>Rule of Zero</strong>: write no special members and rely on the compiler.</p><p>If a class manages a raw resource unknown to the language (e.g., a C <code>FILE*</code>), it must provide a destructor, delete copy operations, and implement a move constructor that transfers ownership. This follows the <strong>Rule of Five</strong>. The Rule of Zero is the default, cheapest correct choice. The Rule of Five applies only at system boundaries where a raw resource is wrapped.</p><h2 id="value-categories-lvalues-and-rvalues"><a class="header" href="#value-categories-lvalues-and-rvalues">Value categories: lvalues and rvalues</a></h2><p>An expression that has a name and an addressable location is an <strong>lvalue</strong>. An unnamed temporary or a cast that produces a temporary is an <strong>rvalue</strong>. An rvalue is about to be destroyed, and the compiler can reuse its resources.</p><p>C++ refines rvalues into two subcategories. A <strong>prvalue</strong> is a pure temporary, the result of an expression like <code>42</code> or <code>std::string("hi")</code>. An <strong>xvalue</strong> is an expiring value, an object that has a name but is about to be destroyed, which is what <code>std::move</code> produces. Both are rvalues. The full ladder:</p><div class="table-wrapper"><table><thead><tr><th>Category</th><th>Has a name?</th><th>Can be moved from?</th><th>Example</th></tr></thead><tbody><tr><td>lvalue</td><td>yes</td><td>no</td><td><code>int x;</code> then <code>x</code></td></tr><tr><td>prvalue</td><td>no</td><td>yes</td><td><code>42</code>, <code>std::string("hi")</code></td></tr><tr><td>xvalue</td><td>yes</td><td>yes</td><td><code>std::move(x)</code></td></tr><tr><td>rvalue</td><td>(prvalue or xvalue)</td><td>yes</td><td>(either of the above)</td></tr></tbody></table></div><p>The distinction is not about what the expression is, but about what the program will do with it next. A named variable is an lvalue because the program will read it again. A temporary is an rvalue because nobody will read it again after the current expression. The move constructor is the mechanism that lets the compiler steal from the rvalue instead of copying it.</p><p><code>std::move</code> is a cast that converts an lvalue to an rvalue. It does <strong>not</strong> move any bytes. It merely tells the compiler that the object can be treated as a disposable source. If a move constructor exists, the compiler selects it. Otherwise it falls back to the copy constructor.</p><p>Because <code>std::move</code> is a cast, you can apply it repeatedly without side effects. The actual movement happens exactly once, when the move constructor runs.</p><p>The two categories matter because they decide what the compiler is allowed to do with the object. An lvalue has a name and a future: the program will use it again, so the compiler must keep its value intact. An rvalue is about to be destroyed: nobody will read it again, so the compiler is free to steal its resources. <code>std::move</code> is how you tell the compiler that an lvalue is now in the second category. The cast is safe only if you will not use the source again after the move, because the source is left in a valid-but-unspecified state.</p><h2 id="move-is-cheap-copy-is-honest"><a class="header" href="#move-is-cheap-copy-is-honest">Move is cheap. copy is honest</a></h2><p>Moving a <code>std::vector<int></code> copies three pointers: the data address, the size, and the capacity. The source vector becomes empty. Copying a <code>std::vector<int></code> allocates new storage and copies every element. The cost difference is usually orders of magnitude.</p><p>The same ratio holds for <code>std::string</code>, <code>std::list</code>, and every container that owns heap memory. A move is a pointer swap. A copy is an allocation plus a loop. This is why the book treats move as the default transfer mechanism and copy as the deliberate, expensive choice.</p><p>Standard containers require their move constructors to be <code>noexcept</code> (CG C.66). When a container reallocates during growth, it prefers to move elements rather than copy them. If the move constructor can throw, the container falls back to copying to preserve strong exception safety. Therefore you must mark move constructors <code>noexcept</code> whenever possible.</p><p>The noexcept guarantee is a contract between your type and the container. A type whose move constructor throws breaks the strong exception guarantee when the container grows, because a throw mid-reallocation leaves the container in a state where some elements moved and some did not. The container refuses to take that risk: it copies instead, which is correct but slow. Marking the move <code>noexcept</code> is the one-word fix that lets the container take the fast path.</p><h2 id="raii-as-the-central-idiom"><a class="header" href="#raii-as-the-central-idiom">RAII as the central idiom</a></h2><p><strong>Resource Acquisition Is Initialization</strong> (RAII) states that a resource is obtained in a constructor and released in the matching destructor (CG R.1, R.3). The <code>file_guard</code> struct illustrates this pattern: the constructor calls <code>std::fopen</code>. The destructor calls <code>std::fclose</code>.</p><p>Contrast the RAII version with manual management:</p><pre><code class="language-cpp">std::FILE* f = std::fopen("tmp.txt","w");std::fwrite("X",1,1,f);std::fclose(f);</code></pre><p>If the function returns early, the explicit <code>std::fclose</code> is skipped and the file leaks. The RAII wrapper eliminates that risk because the destructor runs automatically on every exit path, including exceptions.</p><h2 id="raii-wrapper-for-a-c-file-handle"><a class="header" href="#raii-wrapper-for-a-c-file-handle">RAII wrapper for a C file handle</a></h2><p>The following example compiles the target <code>ch05_file_guard</code>. The test expects the word <strong>closed</strong> on standard output, confirming that the destructor runs when the scope ends.</p><pre><code class="language-cpp">#include <cstdio>#include <print>
struct file_guard { std::FILE* f_ = nullptr; explicit file_guard(const char* path, const char* mode) : f_(std::fopen(path, mode)) { if (f_) std::println("opened"); } // Delete copy operations – a file handle cannot be duplicated safely. file_guard(const file_guard&) = delete; file_guard& operator=(const file_guard&) = delete; // Move transfers ownership; source is left null. file_guard(file_guard&& other) noexcept : f_(other.f_) { other.f_ = nullptr; if (f_) std::println("moved"); } file_guard& operator=(file_guard&& other) noexcept { if (this != &other) { if (f_) std::fclose(f_); f_ = other.f_; other.f_ = nullptr; if (f_) std::println("move‑assigned"); } return *this; } ~file_guard() { if (f_) { std::fclose(f_); std::println("closed"); } } void write_byte(char c) { if (f_) std::fputc(c, f_); }};
int main() { { file_guard fg("tmp.txt", "w"); fg.write_byte('A'); } return 0;}</code></pre><h2 id="instrumented-move-and-copy"><a class="header" href="#instrumented-move-and-copy">Instrumented move and copy</a></h2><p>The program prints <strong>copy</strong> when the line <code>blob b = a;</code> executes. The <code>std::move</code> call triggers the move constructor, which prints <strong>move</strong>. The test expects the word <strong>copy</strong>, proving that the first operation is a copy.</p><pre><code class="language-cpp">#include <vector>#include <print>
struct blob { std::vector<int> data; // Default constructor blob() = default; // Copy constructor – prints "copy" blob(const blob& other) : data(other.data) { std::println("copy"); } // Move constructor – prints "move" blob(blob&& other) noexcept : data(std::move(other.data)) { std::println("move"); } // Copy assignment – prints "copy assign" blob& operator=(const blob& other) { data = other.data; std::println("copy assign"); return *this; } // Move assignment – prints "move assign" blob& operator=(blob&& other) noexcept { data = std::move(other.data); std::println("move assign"); return *this; } // Destructor – defaulted; data cleans itself up. ~blob() = default;};
int main() { blob a; // default constructed a.data = {1,2,3}; blob b = a; // copy – should print "copy" blob c = std::move(a); // move – should print "move" (void)b; (void)c; // silence unused warnings return 0;}</code></pre><p>In this book, every resource lives inside an object whose destructor frees it. You never see a raw <code>fopen</code>/<code>fclose</code> pair outside a RAII wrapper.</p><p>RAII is the bridge between the C world of manual resource management and the C++ world of value semantics. The wrapper is the only place that touches the raw API. Everything else sees a type that moves, destructs, and cleans up automatically. This is why the chapter shows <code>new</code> and <code>delete</code> exactly once and then drops them: the raw primitives exist to explain what the wrapper replaces, not to be used directly.</p><h2 id="new-and-delete-appear-only-once-then-disappear"><a class="header" href="#new-and-delete-appear-only-once-then-disappear"><code>new</code> and <code>delete</code> appear only once, then disappear</a></h2><p>The classic C-style allocation pattern looks like this:</p><pre><code class="language-cpp">int* p = new int{42};delete p;</code></pre><p>That pattern appears only in the explanatory paragraph above. All other code relies on standard library containers, <code>std::unique_ptr</code>, or RAII wrappers. The guideline is <strong>never write <code>new</code> or <code>delete</code> in production code</strong>. Let the compiler generate those calls inside containers or smart pointers.</p><p>The reason is not aesthetic. A raw <code>new</code> is a leak waiting to happen: every early return, every exception, and every forgotten <code>delete</code> leaks the object. A container or smart pointer ties the deallocation to a destructor, which the compiler guarantees to run. The two lines above are the only place in the book where the raw primitives appear, and they exist to make this argument concrete.</p><h2 id="try-this-4"><a class="header" href="#try-this-4">Try this</a></h2><p>Write a <code>struct blob</code> that deletes its copy constructor and copy-assignment operator, but keeps the move constructor and move-assignment operator. Then call a function <code>void consume(blob b);</code> with <code>std::move</code> on a local <code>blob</code> variable. Verify that the call compiles, while a plain copy <code>blob c = b;</code> fails to compile. The exercise demonstrates that moving a value does not require a copy and that the compiler enforces the deletion.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="smart-pointers-and-owning-views"><a class="header" href="#smart-pointers-and-owning-views">Smart pointers and owning views</a></h1><h2 id="unique-ownership-is-the-default"><a class="header" href="#unique-ownership-is-the-default">Unique ownership is the default</a></h2><p>The Core Guidelines (R.20, R.21) require that a heap object have exactly one owning smart pointer. <code>std::unique_ptr<T></code> satisfies this rule. It holds a pointer, destroys the object when the <code>unique_ptr</code> itself is destroyed, and can be moved but never copied. The move operation transfers the stored pointer and leaves the source empty.</p><pre><code class="language-cpp">#include <memory>#include <vector>#include <print>
struct tree_node { int value{}; std::vector<std::unique_ptr<tree_node>> children;};
void print(const tree_node& node) { std::print("{} ", node.value); for (const auto& child : node.children) { print(*child); }}
int main() { auto root = std::make_unique<tree_node>(); root->value = 1; tree_node* cur = root.get(); for (int v = 2; v <= 5; ++v) { cur->children.emplace_back(std::make_unique<tree_node>()); cur->children.back()->value = v; cur = cur->children.back().get(); } print(*root); std::println(""); return 0;}</code></pre><p>In the tree example the root is a <code>std::unique_ptr<tree_node></code>. Each node stores its children in a <code>std::vector<std::unique_ptr<tree_node>></code>. Because every child is owned uniquely, the destruction of the root automatically destroys the whole subtree recursively. No manual <code>delete</code> appears, and the program cannot accidentally copy a node-owner. The compiler rejects any copy of a <code>unique_ptr</code>.</p><h2 id="transfer-of-ownership-by-value"><a class="header" href="#transfer-of-ownership-by-value">Transfer of ownership by value</a></h2><p>A function that receives a <code>unique_ptr<T></code> by value <em>takes</em> ownership. The caller must move the pointer into the parameter. After the call the caller’s pointer becomes empty. This pattern appears in many factory functions:</p><pre><code class="language-cpp">std::unique_ptr<tree_node> make_root(int v) { auto p = std::make_unique<tree_node>(); p->value = v; return p; // move-return, caller receives ownership}</code></pre><p>In the tree example the statement <code>auto root = std::make_unique<tree_node>()</code> creates the sole owner. When <code>main</code> ends, <code>root</code> goes out of scope, the move-return chain unwinds, and the destructor of each <code>unique_ptr</code> in the vectors frees the corresponding child. No memory leaks survive past <code>main</code>.</p><h3 id="stdmake_unique-versus-new"><a class="header" href="#stdmake_unique-versus-new"><code>std::make_unique</code> versus <code>new</code></a></h3><p>The guidelines (R.23) require that a <code>unique_ptr</code> be created with <code>std::make_unique</code> rather than a raw <code>new</code> expression. <code>std::make_unique<T>(args...)</code> constructs the object and wraps it in a <code>unique_ptr</code> in one step. This form is shorter and safer than the two-step alternative. The two-step form first calls <code>new T(args...)</code> and then passes the result to the <code>unique_ptr</code> constructor. If an exception occurs between these two steps, the raw pointer leaks. <code>std::make_unique</code> avoids this window, because the construction and the wrapping happen together. The function also deduces the type, so the code does not repeat the type name. Use <code>std::make_unique</code> whenever the object is created and owned immediately. Reserve a raw <code>new</code> for the rare case where a custom deleter or a pre-existing pointer is required.</p><p>Move semantics give <code>unique_ptr</code> its efficiency. Moving a <code>unique_ptr</code> transfers the pointer without copying the pointed-to object. The operation is a simple pointer assignment plus a null-out of the source. It never allocates and never touches the heap object. This property lets a function return a <code>unique_ptr</code> by value at no cost. The move-return in <code>make_root</code> does not copy the tree. It hands the same object to the caller. The same reasoning applies when a <code>unique_ptr</code> moves into a container or into another owner.</p><h2 id="shared-ownership-is-a-cost-aware-choice"><a class="header" href="#shared-ownership-is-a-cost-aware-choice">Shared ownership is a cost-aware choice</a></h2><p><code>std::shared_ptr<T></code> holds a control block with an atomic reference count. Every copy increments the counter. The last copy decrements to zero and destroys the object. The guidelines (R.22) caution that <code>shared_ptr</code> must be used <em>only</em> when at least two distinct owners truly need to keep the object alive.The cost model is higher than <code>unique_ptr</code>:</p><div class="table-wrapper"><table><thead><tr><th>Aspect</th><th><code>unique_ptr</code></th><th><code>shared_ptr</code></th></tr></thead><tbody><tr><td>Allocation</td><td>one block (object)</td><td>two blocks (object + control)</td></tr><tr><td>Reference count</td><td>none</td><td>atomic increment/decrement on every copy</td></tr><tr><td>Size of pointer object</td><td>≤ sizeof(void*)</td><td>≈ 2 × sizeof(void*)</td></tr><tr><td>Cache behavior</td><td>contiguous access</td><td>indirect control block</td></tr><tr><td>If the program never needs more than one owner, <code>unique_ptr</code> is the zero‑overhead choice. <code>shared_ptr</code> allocates an extra control block and incurs atomic reference‑count updates on each copy, doubling pointer size and reducing cache locality. Use <code>shared_ptr</code> only when at least two owners truly need to keep the object alive. Otherwise the extra cost is unnecessary.</td><td></td><td></td></tr></tbody></table></div><h2 id="weak-pointers-break-cycles"><a class="header" href="#weak-pointers-break-cycles">Weak pointers break cycles</a></h2><p><code>std::weak_ptr<T></code> provides a non‑owning view that does not affect the reference count, breaking reference cycles. Use <code>lock()</code> to obtain a temporary <code>shared_ptr</code> if the object is still alive. Otherwise <code>lock()</code> returns an empty pointer. <code>expired()</code> reports whether the object has been destroyed. In the graph example, <code>weak_ptr</code> allows the parent link to be observed without extending the child’s lifetime, preventing a cycle.</p><pre><code class="language-cpp">#include <memory>#include <vector>#include <print>
struct graph_node { int id{}; std::weak_ptr<graph_node> parent; std::vector<std::shared_ptr<graph_node>> children;};
void print_ids(const graph_node& node) { std::print("{} ", node.id); for (const auto& child : node.children) { print_ids(*child); }}
int main() { auto a = std::make_shared<graph_node>(); a->id = 1; auto b = std::make_shared<graph_node>(); b->id = 2; // create edge a -> b and back-edge parent weak_ptr a->children.push_back(b); b->parent = a; // weak, no cycle print_ids(*a); std::println(""); // when main exits, both a and b are reclaimed; no leak. return 0;}</code></pre><h2 id="gslownert-documents-raw-owning-pointers"><a class="header" href="#gslownert-documents-raw-owning-pointers"><code>gsl::owner<T></code> documents raw owning pointers</a></h2><p>Sometimes a C-language API requires a raw pointer that <strong>owns</strong> the pointed-to object. The Guidelines Support Library provides <code>gsl::owner<T></code> as a type alias that makes the ownership intent explicit to static analysis tools:</p><pre><code class="language-cpp">void c_api(gsl::owner<int*> p); // p must be freed by the caller</code></pre><p><code>gsl::owner<T></code> is a type alias that documents raw owning pointers for static analysis tools such as Clang’s lifetime‑safety analysis. It carries no runtime cost and does not alter program behavior. A function taking a <code>gsl::owner<T></code> parameter signals that it assumes ownership. A function returning <code>gsl::owner<T></code> signals that it transfers ownership to the caller. Static analysis can verify that each owner releases the object exactly once.</p><h2 id="when-pointers-are-the-wrong-tool"><a class="header" href="#when-pointers-are-the-wrong-tool">When pointers are the wrong tool</a></h2><p>Ownership and lifetime are not the only reasons to use a pointer. Frequently a value, a <code>std::span</code>, or a <code>std::string_view</code> conveys the required relationship without any ownership semantics.</p><ul><li><strong>Value</strong>: use when the object’s lifetime is confined to the current scope and copying is cheap.</li><li><strong><code>std::span<T></code></strong>: a non-owning view over a contiguous range. It is ideal for passing array slices to functions.</li><li><strong><code>std::string_view</code></strong>: a read-only view of a string. It is perfect for read-only parameters where the callee must not modify or own the data.Choosing a smart pointer when a simple view suffices adds unnecessary indirection and can hide bugs. The guidelines (R.3) advise to prefer plain values and views first. Smart pointers come into play only when the lifetime must outlive the current scope <strong>and</strong> no value can express the relation.A value owns its data within the current scope. Copying or moving it transfers ownership automatically. <code>std::span<T></code> provides a non‑owning view of a contiguous range, and <code>std::string_view</code> provides a read‑only view of a string. These types express the relationship without ownership overhead and avoid the need for raw pointers. Use them before considering a smart pointer.</li></ul><h2 id="try-this-5"><a class="header" href="#try-this-5">Try this</a></h2><p>Take the tree program from the previous section and add a function:</p><pre><code class="language-cpp">int height(const tree_node&);</code></pre><p><code>height</code> returns the length of the longest root-to-leaf path (the number of nodes on that path). Write the function recursively, using only the <code>tree_node</code> interface. When you run the program, observe that the tree is still freed automatically when <code>main</code> exits, even though <code>height</code> returns a plain <code>int</code>.<em>What does <code>std::unique_ptr</code> guarantee about the tree’s memory after <code>height</code> returns?</em></p><div style="break-before: page; page-break-before: always;"></div><h1 id="lifetimes-and-the-compiler-that-sees-them"><a class="header" href="#lifetimes-and-the-compiler-that-sees-them">Lifetimes, and the compiler that sees them</a></h1><h2 id="storage-durations-in-one-breath"><a class="header" href="#storage-durations-in-one-breath">Storage durations in one breath</a></h2><p>C++ classifies every object by its storage duration, which determines when memory is allocated and reclaimed. The three categories cover nearly all cases:</p><ul><li><strong>Automatic</strong> objects are created when control enters their block and destroyed on exit. Their lifetime matches the block’s scope.</li><li><strong>Static</strong> objects exist for the program’s lifetime, created before <code>main</code> and destroyed after it returns.</li><li><strong>Dynamic</strong> objects live on the heap, managed by containers or smart pointers that acquire and release the memory.</li></ul><p>The contrast with C is sharp. In C, <code>malloc</code> allocates heap memory without an automatic scope link. The programmer must call <code>free</code> at the exact moment, separating pointer and memory lifetimes. C++ eliminates this gap: containers or smart pointers own heap objects, and those owners have automatic or static lifetimes, so reasoning focuses on scope and ownership rather than matching allocation and deallocation.</p><p>RAII links storage duration to resource lifetime. An automatic object that owns a resource releases it in its destructor when the block ends, so the resource’s lifetime matches the object’s lifetime. This enables the compiler to understand and enforce lifetimes.</p><h2 id="the-dangling-taxonomy"><a class="header" href="#the-dangling-taxonomy">The dangling taxonomy</a></h2><p>The Core Guidelines Lifetime profile enumerates four ways a program can keep using a value after the value’s storage is gone. Each case has a fixed shape, and each can be caught by static analysis when the analysis runs.</p><ol><li>Return of a local reference or pointer. A function returns the address of a variable that goes out of scope when the function returns. The returned handle points at memory the compiler is about to reuse.</li><li>Use after end of scope or <code>delete</code>. Code reaches through a handle after the object’s destructor has run, or after <code>delete</code> has freed the memory. The handle is live but the value is dead.</li><li>Views that outlive their source. A <code>std::string_view</code>, an iterator, or a raw pointer keeps being used after the object it observes dies or moves. The view borrows storage that no longer holds the expected value.</li><li>Container invalidation. Inserting or erasing elements can trigger reallocation, which moves the underlying storage. Any pointer, reference, or iterator taken before the operation now points at freed or stale memory.</li></ol><p>These four cases are not arbitrary. They are the only ways a handle can survive its referent, because a handle can only outlive its referent through a return, through a delayed use, through a non-owning view, or through a container resize. The compiler can model each shape because each shape is a small, local pattern in the code.</p><h2 id="lifetime-safety-analysis-as-a-compiler-feature"><a class="header" href="#lifetime-safety-analysis-as-a-compiler-feature">Lifetime-safety analysis as a compiler feature</a></h2><p>The analysis is the Clang implementation of the Core Guidelines Lifetime Safety profile. The profile was written to make the rules above checkable, and the Clang work turned those rules into a dataflow pass over the abstract syntax tree. The stable flag is <code>-Wlifetime-safety</code>. On the pinned toolchain, Clang 22.1.8, that stable flag does not exist yet. The experimental form <code>-Xclang -fexperimental-lifetime-safety</code> is available, and it emits only the <code>-Wreturn-stack-address</code> warning. The deeper checks for cases two, three, and four remain silent in this build.</p><p>The book treats the analysis as a law the compiler will enforce when the feature matures. The same committee member who advanced the flag in Chapter 01 is behind the wider effort. The point of teaching the taxonomy now is that the discipline is correct regardless of whether the tool shouts at you. A program that obeys the lifetime rules is correct on today’s compiler and will stay correct on tomorrow’s.</p><h2 id="demo-return-of-a-stack-address"><a class="header" href="#demo-return-of-a-stack-address">Demo: return of a stack address</a></h2><pre><code class="language-cpp">// warning: address of stack memory associated with local variable 'x' returned [-Wreturn-stack-address]#include <cstddef>
int* dangling_pointer() { int x = 42; return &x; // returns address of a local variable}
int main() { int* p = dangling_pointer(); // Using *p here would be undefined behavior (void)p; return 0;}</code></pre><p>The warning at the top of the file is the diagnostic Clang emits when a function returns a reference to a local variable. The local lives in the function’s stack frame. When the function returns, that frame is reclaimed, and the returned reference points at storage the next call is free to overwrite. No runtime check can save this case. The value is gone before anyone reads it. The fix is to return by value, or to have the caller pass the destination in and let the function fill it through a reference the caller owns.</p><h2 id="demo-vector-invalidation"><a class="header" href="#demo-vector-invalidation">Demo: vector invalidation</a></h2><pre><code class="language-cpp">#include <vector>#include <iostream>
int main() { std::vector<int> v{1, 2, 3}; int* p = &v[0]; // capture pointer to first element v.push_back(4); // can cause reallocation, invalidating p // If compiled with AddressSanitizer, a heap-use-after-free error would be reported when *p is accessed. std::cout << *p << "\n"; // undefined behavior if reallocation occurred return 0;}</code></pre><p>The program captures a pointer to the first element, then calls <code>push_back</code>. A <code>std::vector</code> keeps its elements in a contiguous block. When that block is full, <code>push_back</code> allocates a larger block and moves every element into it. The old block is freed. The captured pointer still points at the old block, so reading through it is a use of freed memory. When compiled with AddressSanitizer the runtime reports <em>heap-use-after-free</em>.</p><p>The lesson is general. Any operation that can grow a <code>std::vector</code> can invalidate pointers, references, and iterators into it. The safe pattern is to take the address after the last growth, to call <code>reserve</code> up front when the final size is known, or to keep using the vector’s own indexing instead of a captured handle.</p><h2 id="demo-dangling-string_view"><a class="header" href="#demo-dangling-string_view">Demo: dangling <code>string_view</code></a></h2><pre><code class="language-cpp">#include <string_view>#include <string>
std::string_view dangling_view() { std::string temp = "temporary"; // lives only inside function return std::string_view{temp}; // view refers to destroyed string}
int main() { auto sv = dangling_view(); // Using sv here is undefined behavior because the source string has been destroyed. (void)sv; return 0;}</code></pre><p>The function returns a <code>std::string_view</code> that refers to a temporary <code>std::string</code>. The temporary is destroyed at the end of the full expression that created it, while the view is returned to the caller. The caller then reads through a view whose characters are already gone. This is undefined behavior, and it is silent. The code compiles and can even appear to work until the memory is reused.</p><p>The trap is that <code>std::string_view</code> is cheap to return, so it invites returning a view into a value the function just made. The rule is to return a <code>std::string</code> when the source is a temporary, and to return a <code>std::string_view</code> only when the caller already owns the backing storage for the whole time the view is used.</p><h2 id="demo-lifetimebound-annotation"><a class="header" href="#demo-lifetimebound-annotation">Demo: lifetime‑bound annotation</a></h2><pre><code class="language-cpp">#include <string_view>#include <string>
// First version: no lifetime annotation. The analysis does not warn when a temporary is passed.std::string_view first_word(const std::string& s) { // Return view to first word (up to first space) auto pos = s.find(' '); if (pos == std::string::npos) return std::string_view{s}; return std::string_view{s.data(), pos};}
int main() { // Passing a temporary string – the view dangles, but current clang gives no diagnostic. auto sv = first_word(std::string{"hello world"}); (void)sv; return 0;}
/*// Fixed version with lifetimebound annotation (Clang accepts the attribute).std::string_view first_word(const std::string& s) [[clang::lifetimebound]] { auto pos = s.find(' '); if (pos == std::string::npos) return std::string_view{s}; return std::string_view{s.data(), pos};}*/</code></pre><p>The first version returns a view into its parameter without any annotation, so the compiler does not warn when a temporary is passed. Adding <code>[[clang::lifetimebound]]</code> to the parameter tells the analysis that the returned view is tied to the argument’s lifetime. Current Clang accepts the attribute but emits no diagnostic. The programmer must still respect the contract, which future compilers will enforce.</p><p>The attribute can also be placed on the function itself, applying the rule to every return path, and on constructors: a constructor that stores a pointer or reference member must mark the source parameter <code>lifetimebound</code> so the member’s lifetime cannot outlive the argument. This links the member to the argument and enables static checks.</p><p>The pattern appears wherever a function hands back a handle into memory it was given. Accessors that return a reference or a view to a member, parsers that return a view into their input, and span factories all need the attribute to connect output to input.</p><h2 id="views-and-spans-are-lifetime-transparent"><a class="header" href="#views-and-spans-are-lifetime-transparent">Views and spans are lifetime-transparent</a></h2><p><code>std::string_view</code> and <code>std::span</code> are non-owning handles. They do not own storage. They merely borrow it. A view has no destructor that frees anything, because there is nothing for it to free. Its correctness depends entirely on the owner staying alive.</p><p>Keep the owner alive for at least as long as any view or span that refers to it. A view is a loan. The lender must outlive the loan. Creating the owner and view in the same expression makes the temporary owner die before the view is used. Bind the view to a named owner that lives in an enclosing scope to avoid the hazard.</p><p><code>std::span</code> generalizes the idea from characters to any <code>T</code>. A <code>std::span<const int></code> is a non-owning window over a row of <code>int</code> values. The same lifetime rule applies. The <code>std::vector<int></code> or array that backs the span must outlive every use of the span. Because a span is so light, functions must accept spans instead of a raw pointer plus a length. That change makes the non-owning intent explicit and removes a whole class of length mismatch bugs.</p><h2 id="gslnot_nullt"><a class="header" href="#gslnot_nullt"><code>gsl::not_null<T></code></a></h2><p>The guideline support library provides <code>gsl::not_null<T></code>. It wraps a raw pointer and guarantees that the pointer is never null. The wrapper adds a contract that the analysis can check. In practice, references already enforce non-nullness, so the book uses <code>gsl::not_null</code> only when a raw pointer is required for legacy interop. The lifetime rules still apply on top of the nullness rule. A <code>gsl::not_null</code> that points at a destroyed object is just as dangling as a plain pointer.</p><p>The two contracts are independent. <code>gsl::not_null</code> answers the question of whether the pointer is null, while the lifetime rules answer the question of whether the referent is still alive. A handle can satisfy both and still dangle, so the wrapper does not remove the need for the lifetime discipline taught in this chapter.</p><h2 id="try-this-6"><a class="header" href="#try-this-6">Try this</a></h2><p>Write a function <code>std::span<const int> middle(std::vector<int>& v)</code> that returns a span over the elements <code>v[1]</code> through <code>v[n-2]</code>. State the rule that the caller must keep <code>v</code> alive for the span’s lifetime. Explain what happens if <code>v</code> is a temporary, and show the <code>[[clang::lifetimebound]]</code> marking that makes the contract explicit.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="passing-arguments"><a class="header" href="#passing-arguments">Passing arguments</a></h1><h2 id="pass-by-value-vs-by-reference"><a class="header" href="#pass-by-value-vs-by-reference">Pass by value vs by reference</a></h2><p>In C++ a function parameter can be a value, a reference, or a pointer. The choice determines whether the callee receives its own copy of the argument or an alias to the caller’s object.</p><div class="table-wrapper"><table><thead><tr><th>Parameter</th><th>Typical use</th><th>Effect on caller</th></tr></thead><tbody><tr><td><code>T</code> (by value)</td><td>The function needs its own mutable object</td><td>The argument is copied or moved. The caller’s object is unchanged</td></tr><tr><td><code>const T&</code></td><td>The function only reads the argument</td><td>No copy is performed. The callee cannot modify the object</td></tr><tr><td><code>T&</code></td><td>The function must modify the caller’s object</td><td>The caller sees the mutation. No copy is performed</td></tr><tr><td><code>T*</code></td><td>Legacy C‑style API or optional argument</td><td>The pointer can be null. The callee can reassign the pointer</td></tr></tbody></table></div><p>The rule of thumb is:</p><ul><li><strong>Pass by value</strong> when the function will create a new object, store it, or move from it.</li><li><strong>Pass by <code>const</code> reference</strong> when the function only needs to observe the argument.</li><li><strong>Pass by non‑<code>const</code> reference</strong> only when the function must change the caller’s object.</li></ul><p>Contrast this with C. In C every argument is passed by value. To share an object the programmer writes a pointer parameter (<code>T* p</code>). The reader must locate <code>*</code> to know that the function can observe or mutate shared state. C++ hides that pointer indirection behind references. This makes the intent visible in the function signature.</p><p>Size is not the only driver. A type that is expensive or impossible to copy, such as <code>std::unique_ptr</code> or a large <code>std::vector</code>, must travel by reference or by rvalue move, never by value copy. For a value that the function only inspects, <code>const T&</code> is the default even when <code>T</code> is small, because it avoids a copy and works for every argument type. Pass by value is the right default only when the function keeps or modifies a copy of what it was given.</p><h3 id="arguments-of-differing-size"><a class="header" href="#arguments-of-differing-size">Arguments of differing size</a></h3><pre><code class="language-cpp">#include <iostream>#include <vector>#include <string>
struct Small { int x; Small(int v) : x(v) { std::cout << "Small ctor\n"; } Small(const Small& other) : x(other.x) { std::cout << "Small copy\n"; } Small(Small&& other) noexcept : x(other.x) { std::cout << "Small move\n"; } Small& operator=(const Small&) = delete; Small& operator=(Small&&) = delete; ~Small() = default;};
void by_value(Small s) { std::cout << "by_value address " << &s << "\n";}
void by_const_ref(const Small& s) { std::cout << "by_const_ref address " << &s << "\n";}
void by_mut_ref(Small& s) { std::cout << "by_mut_ref address " << &s << "\n"; s.x += 1;}
void vec_by_value(std::vector<int> v) { std::cout << "vec_by_value size " << v.size() << "\n";}
void vec_by_const_ref(const std::vector<int>& v) { std::cout << "vec_by_const_ref size " << v.size() << "\n";}
int main() { Small a(5); std::cout << "original address " << &a << "\n"; by_value(a); by_const_ref(a); by_mut_ref(a); std::cout << "after mutation value " << a.x << "\n";
std::vector<int> big(1000, 1); vec_by_value(big); vec_by_const_ref(big); return 0;}</code></pre><p>Running the program prints the address of each parameter. The <code>by_value</code> call receives a copy, therefore its address differs from the caller’s object. The <code>by_const_ref</code> and <code>by_mut_ref</code> calls receive the same address, confirming they are aliases. The <code>vector</code> examples show that a large container passed by value incurs a copy of its control block, while a <code>const</code> reference avoids any copy.</p><h2 id="const-correctness"><a class="header" href="#const-correctness">Const correctness</a></h2><p>A <code>const</code> qualifier promises not to modify the object it decorates. In a function signature:</p><pre><code class="language-cpp">void f(const T& arg);</code></pre><p>the callee can call only <code>const</code> member functions on <code>arg</code>. Attempting to call a non‑<code>const</code> member function triggers a compile‑time error because the implicit <code>this</code> pointer is <code>const</code>. The same rule applies to member functions:</p><pre><code class="language-cpp">struct S { void mutate() { ++value; } // non‑const void inspect() const { std::cout << value; } // const int value;};</code></pre><p>When <code>inspect</code> is called on a <code>const S</code> object, the compiler guarantees that <code>value</code> is not altered. If a <code>const</code> reference were bound to a temporary, the temporary becomes immutable for the duration of the reference. The compiler enforces this rule even though the temporary will soon be destroyed.</p><p>The same const placement rules apply to pointers. See Chapter 2.</p><p>If we modify <code>ch08_pass_by.cpp</code> to call a non‑<code>const</code> member on a <code>const</code> reference, the compilation fails, illustrating that <code>const</code> truly prevents mutation.</p><p>The discipline pays off via const overloading: a type can provide both <code>T& at(std::size_t)</code> and <code>const T& at(std::size_t) const</code>. The former binds to non‑const objects, and the latter binds to const objects and temporaries. This enables read‑only access such as <code>std::string{}.size()</code>.</p><h2 id="pass-by-value-then-move"><a class="header" href="#pass-by-value-then-move">Pass by value then move</a></h2><p>The “sink” idiom accepts a parameter by value and immediately moves it into a member or another container. This design gives two useful behaviors:</p><ul><li>When the caller supplies an lvalue, the argument is copied into the parameter and then moved into the destination. The copy costs one construction. The move costs no additional allocation.</li><li>When the caller supplies an rvalue (a temporary), the argument is constructed directly in the parameter slot and then moved. This results in zero copies.</li></ul><p>The diagram below shows both paths.</p><pre><code>caller lvalue caller rvalue | | v copy v constructparameter (value) <---> parameter (value) | move | movedestination destination</code></pre><h3 id="sink-idiom"><a class="header" href="#sink-idiom">Sink idiom</a></h3><pre><code class="language-cpp">#include <iostream>#include <string>
struct Small { int x; Small(int v) : x(v) { std::cout << "Small ctor\n"; } Small(const Small& other) : x(other.x) { std::cout << "Small copy\n"; } Small(Small&& other) noexcept : x(other.x) { std::cout << "Small move\n"; } Small& operator=(const Small&) = delete; Small& operator=(Small&&) = delete; ~Small() = default;};
void take_and_store(Small s) { std::cout << "parameter address " << &s << "\n"; Small member = std::move(s); std::cout << "member address " << &member << "\n";}
int main() { Small a(1); std::cout << "original address " << &a << "\n"; take_and_store(a); // lvalue path – copy take_and_store(Small(2)); // rvalue path – move return 0;}</code></pre><p>The program prints the address of the caller’s object, the parameter, and the member after the move. When the first call uses an lvalue, a copy occurs. The second call uses a temporary, so only the move runs. The output confirms the expected behavior.</p><h2 id="return-by-value"><a class="header" href="#return-by-value">Return by value</a></h2><p>Returning a value by copy used to be expensive because the caller received a separate object that required a copy. Modern C++ solves this with copy elision, including the guaranteed elision of prvalues, and with move semantics for named temporaries.The compiler constructs the return object directly in the caller’s storage (NRVO) or treats the temporary as an rvalue that can be moved. Consequently, returning a <code>std::vector</code> or a <code>std::string</code> does <strong>not</strong> copy the underlying buffer on the happy path.</p><p>The rule is simple: return‑by‑value is cheap because copy elision and move semantics eliminate unnecessary copies. Elision is guaranteed when the function returns a single local or a prvalue. Otherwise the compiler falls back to a move. Return a clearly identified local or construct the result directly in the return statement (see Chapter 05 for container move semantics).</p><h2 id="references-are-not-pointers"><a class="header" href="#references-are-not-pointers">References are not pointers</a></h2><p>A reference is an alias bound to an object at initialization. It cannot be reseated and cannot be null. The language guarantees that a reference denotes a valid object for its lifetime.</p><p>The reference does not extend the lifetime of the object it refers to. If the referent is destroyed while the reference remains alive, using the reference yields undefined behavior. This hazard is described in Chapter 07.</p><p>A pointer can be null or reassigned and offers no guarantee that the pointee remains alive. Unlike Rust’s borrow checker, C++ relies on the programmer. Chapter 07’s lifetime analysis tool can detect violations. Rule: a reference must never outlive its referent.</p><p>There is one exception that surprises even experienced programmers. Binding a const reference to a temporary extends the temporary’s lifetime to match the reference’s scope. The temporary is not destroyed at the end of the full expression. It lives until the reference goes out of scope. This extension does not apply when the reference is a member of an object or when it is returned from a function. It is a property of the local binding only. It is why <code>const auto& x = compute()</code> avoids a copy of a temporary safely.</p><h2 id="reference_wrapper-and-views-as-parameters"><a class="header" href="#reference_wrapper-and-views-as-parameters">reference_wrapper and views as parameters</a></h2><p>Sometimes a function must store a reference in a container or type‑erase the argument. <code>std::reference_wrapper<T></code> wraps a reference in an assignable object. This allows it to appear in <code>std::vector</code> or <code>std::function</code>. The wrapper forwards operators to the underlying reference.</p><p>A <code>std::vector<T&></code> is ill formed, because a reference is not a complete object that a container can own. <code>std::reference_wrapper<T></code> solves this. It is a copyable, assignable object that stores a pointer to the referent and behaves like the reference in almost every context. This is the standard way to keep a collection of aliases into other objects.</p><p>For non‑owning sequences, the standard library provides view types:</p><ul><li><code>std::string_view</code>: a read‑only window over a character array.</li><li><code>std::span<T></code>: a read‑only or mutable view over a contiguous range of <code>T</code>.</li></ul><p>Both types carry a pointer to the data and a length. They replace the old “pointer + size” pattern:</p><pre><code class="language-cpp">void process(std::span<const int> data);</code></pre><p>The caller can pass a <code>std::vector<int></code>, a C‑array, or a pointer with length. The callee receives a lightweight handle that does not own the elements. The same lifetime rules from Chapter 07 apply: the owner must outlive any view or span.</p><h2 id="stdforward-and-perfect-forwarding"><a class="header" href="#stdforward-and-perfect-forwarding">std::forward and perfect forwarding</a></h2><p>Templates that accept universal references (<code>T&&</code>) can forward their argument preserving its value category. The pattern:</p><pre><code class="language-cpp">template<class T>void wrapper(T&& t) { target(std::forward<T>(t));}</code></pre><p>If <code>t</code> is an lvalue, <code>std::forward<T>(t)</code> yields an lvalue reference. If <code>t</code> is an rvalue, it yields an rvalue reference. This enables factories, wrappers, and higher‑order functions to forward arguments without unintentionally copying or moving them.</p><p>The cost of omitting <code>std::forward</code> is concrete. A function that takes <code>T&& t</code> and then passes <code>t</code> to another function without forwarding always passes an lvalue, because inside the body <code>t</code> is a named variable. That forces a copy where the caller expected a move. <code>std::forward<T>(t)</code> restores the original category. An lvalue argument stays an lvalue and a temporary stays a temporary. Every standard-library helper that accepts a forwarding reference, from <code>std::make_unique</code> to container <code>emplace</code>, relies on this.</p><h3 id="perfect-forwarding"><a class="header" href="#perfect-forwarding">Perfect forwarding</a></h3><pre><code class="language-cpp">#include <iostream>#include <type_traits>#include <utility>
void target(int& i) { std::cout << "target received lvalue reference, value " << i << "\n";}
void target(int&& i) { std::cout << "target received rvalue reference, value " << i << "\n";}
template<class T>void wrapper(T&& t) { std::cout << "wrapper forwarding, is lvalue? " << std::boolalpha << std::is_lvalue_reference<decltype(t)>::value << "\n"; target(std::forward<T>(t));}
int main() { int x = 42; wrapper(x); // lvalue path wrapper(100); // rvalue path, creates temporary int return 0;}</code></pre><p>The program reports whether the forwarded argument is an lvalue. The first call forwards an lvalue (<code>x</code>). The second call forwards a temporary integer created from the literal <code>100</code>. The <code>target</code> function receives the correct reference type in each case.</p><h2 id="try-this-7"><a class="header" href="#try-this-7">Try this</a></h2><p>Write a function</p><pre><code class="language-cpp">std::vector<int> take_and_double(std::vector<int> v);</code></pre><p>that doubles every element and returns <code>v</code>. Call it once with an lvalue and once with a prvalue. Observe (by printing the address of the parameter) that the prvalue call avoids a copy.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="errors-and-contracts"><a class="header" href="#errors-and-contracts">Errors and contracts</a></h1><h2 id="the-c-legacy"><a class="header" href="#the-c-legacy">The C legacy</a></h2><p>C reports failure via integer return codes and the global variable <code>errno</code>. Callers must remember to test each result, otherwise silent bugs arise.</p><p>C++ adds mechanisms that make ignoring failures harder: the type system can encode error states, and the <code>[[nodiscard]]</code> attribute warns when a result is discarded, ensuring the intent to handle errors is visible.</p><h2 id="exceptions-and-unwinding"><a class="header" href="#exceptions-and-unwinding">Exceptions and unwinding</a></h2><p>C++ supports <code>throw</code>, <code>try</code>, and <code>catch</code>. A <code>throw</code> aborts the current function, triggers stack unwinding, and destroys each local object in reverse order, allowing RAII objects to release resources automatically and preventing leaks.</p><p>Exceptions apply when the failure cannot be repaired at the call site, such as out‑of‑memory, corrupted files, or invalid user input.</p><p>The dividing line between exceptions and <code>expected</code> is the frequency and the locality of the failure. A parse error is routine: the caller expects it, handles it, and moves on, so it travels as an <code>expected</code> value. An out-of-memory error is rare and crosses abstraction boundaries: no individual caller can fix it, so it travels as an exception and unwinds to the nearest handler. Mixing the two is a smell: if every caller wraps a function in a <code>try</code>/<code>catch</code>, the failure is routine and belongs in an <code>expected</code>.</p><p>Modern ABIs place the exception handling code on the cold path only. The common case executes without extra branches or hidden calls. The claim that exceptions add overhead no longer applies to the common case. When exceptions are disabled with <code>-fno-exceptions</code>, the compiler omits unwind tables, reducing binary size and improving load time.</p><h2 id="noexcept"><a class="header" href="#noexcept"><code>noexcept</code></a></h2><p>The specifier <code>noexcept</code> promises that a function will not throw. If a <code>noexcept</code> function does throw, the runtime calls <code>std::terminate</code>.</p><p><code>noexcept</code> is a contract, not a hint. The compiler uses it to make decisions: a <code>std::vector</code> will move elements during reallocation only if the move constructor is <code>noexcept</code>, and the optimizer can elide exception-handling machinery around a <code>noexcept</code> call. Breaking the contract by throwing from a <code>noexcept</code> function calls <code>std::terminate</code>, which is a hard stop. The specifier is therefore a promise you make when you can guarantee it, not a wish.</p><p>Marking move constructors as <code>noexcept</code> enables the standard library to move objects during vector growth without a fallback copy. The optimizer can also inline the call more aggressively. <code>noexcept</code> functions can be inlined without hidden control flow. The optimizer gains full visibility of the call graph.</p><p>A conditional <code>noexcept(...)</code> expression makes the promise precise: the function is <code>noexcept</code> only when every operation it calls is <code>noexcept</code> itself, so the guarantee evolves with the implementation.</p><pre><code class="language-cpp">#include <iostream>#include <utility>
struct Counter { int value; Counter(int v) : value(v) { std::cout << "constructed " << value << "\n"; } ~Counter() { std::cout << "destroyed " << value << "\n"; } // Move constructor promised not to throw Counter(Counter&& other) noexcept : value(other.value) { std::cout << "move used\n"; // marker for EXPECT other.value = 0; } // Delete copy to emphasize move usage Counter(const Counter&) = delete; Counter& operator=(const Counter&) = delete; Counter& operator=(Counter&& other) noexcept { value = other.value; other.value = 0; return *this; }};
int main(){ Counter a{42}; Counter b = std::move(a); // triggers noexcept move std::cout << "final b=" << b.value << "\n"; return 0;}</code></pre><p>The program prints a marker that shows the <code>noexcept</code> move was used.</p><h2 id="stdexpectedt-e"><a class="header" href="#stdexpectedt-e"><code>std::expected<T, E></code></a></h2><p><code>std::expected<T, E></code> represents either a value of type <code>T</code> or an error of type <code>E</code>. It is a typed, non‑throwing alternative for functions that can fail.</p><p>Compare to <code>std::optional<T></code>: <code>optional</code> conveys only presence or absence. <code>expected</code> also conveys an error description.</p><p>Contrast to exceptions: <code>expected</code> returns an object that the caller must inspect. Control flow stays explicit in the source.</p><pre><code class="language-cpp">#include <iostream>#include <string_view>#include <string>#include <expected>
std::expected<int, std::string> parse_int(std::string_view sv) { try { size_t pos = 0; int value = std::stoi(std::string(sv), &pos); if (pos != sv.size()) return std::unexpected<std::string>("trailing characters"); std::cout << "parsed\n"; // marker for EXPECT return value; } catch (const std::invalid_argument&) { return std::unexpected<std::string>("invalid integer"); } catch (const std::out_of_range&) { return std::unexpected<std::string>("out of range"); }}
int main(){ auto r1 = parse_int("123"); if (r1) { std::cout << "value=" << *r1 << "\n"; } else { std::cout << "error=" << r1.error() << "\n"; } auto r2 = parse_int("abc"); if (r2) { std::cout << "value=" << *r2 << "\n"; } else { std::cout << "error=" << r2.error() << "\n"; } return 0;}</code></pre><p>The demo parses an integer from a string view. On success it prints the value. On error it prints the error string.</p><p><code>std::expected</code> composes. A function that calls another fallible function can pass the error upward with a single expression. <code>.and_then</code> chains success and <code>.or_else</code> handles failure. This keeps the error path linear and avoids the nested <code>if</code>/<code>else</code> pyramids that error-code APIs produce. The type carries both the value and the error, so the compiler tracks whether the result has been checked.</p><p>The monadic interface is what makes <code>expected</code> scale beyond two calls. Without it, a function that calls three fallible operations in a row needs three nested <code>if</code> statements, each checking and forwarding. With <code>.and_then</code>, the same logic is a single chained expression that reads left to right. The error short-circuits: the first failure skips the rest of the chain and returns the error to the caller. This is the same shape as Rust’s <code>?</code> operator, expressed as method calls.</p><h2 id="contracts-c26"><a class="header" href="#contracts-c26">Contracts (C++26)</a></h2><p>C++26 adds four contract attributes that can be placed on statements or function signatures.</p><ul><li><code>[[assert]]</code>: a debugging check that aborts if the condition is false.</li><li><code>[[assume]]</code>: a promise to the optimizer that the condition is always true.</li><li><code>[[expects]]</code>: a precondition that must hold when the function is entered.</li><li><code>[[ensures]]</code>: a postcondition that must hold when the function returns.</li></ul><p>The syntax is inline and looks like a standard attribute.</p><pre><code class="language-cpp">// Illustrative stub: does not compile on all compilers[[expects: a > 0]][[ensures: result >= a ]]int factorial(int a) { [[assert: a >= 0]]; int result = 1; for (int i = 2; i <= a; ++i) result *= i; return result;}</code></pre><p>Compilers differ in support. GCC 16 implements contracts behind a flag. Clang does not yet implement the feature. The snippet therefore serves only as an illustration.</p><p>Contracts encode the same invariants that the earlier chapters express with RAII and lifetimes. When the compiler cannot prove an invariant, a contract can document it and trigger a runtime check in debug builds.</p><p>Contracts and assertions serve different audiences. An assertion checks an internal invariant that the function itself controls. A precondition checks a contract between the caller and the callee: the caller guarantees the condition, and the callee relies on it. A postcondition runs the contract in reverse: the callee guarantees the condition, and the caller relies on it. The four attributes let you state who is responsible for what, which a plain <code>assert</code> cannot express.</p><p>The violation handler decides what happens when a contract breaks. The implementation can ignore the violation, log it, abort, or call a user-defined handler. The default is observe, which logs and continues. The enforce mode aborts. This flexibility lets a project ship with contracts in debug builds and strip them in release, or keep them on in production with a handler that reports to a monitoring service.</p><h3 id="historical-perspective-on-error-handling"><a class="header" href="#historical-perspective-on-error-handling">Historical perspective on error handling</a></h3><p>Early C programs used integer return codes and <code>errno</code> as the sole mechanism for reporting failure. The programmer had to check each call manually, and forgetting a check produced silent corruption. C++98 introduced exceptions as a language feature that separates error propagation from the normal control flow. The first implementations used a table‑based “zero‑cost” model that stored unwind information in the object file. Compilers later added the ability to mark functions <code>noexcept</code>. This ability let the optimizer elide unwind handling for guaranteed‑non‑throwing code. C++23 added <code>std::expected</code> as a standard library type that returns either a value or an error object. Failure became an explicit part of the type system. C++26 formalizes contracts, completing the error‑handling toolbox.</p><h2 id="choosing-a-strategy"><a class="header" href="#choosing-a-strategy">Choosing a strategy</a></h2><div class="table-wrapper"><table><thead><tr><th>Situation</th><th>Recommended tool</th></tr></thead><tbody><tr><td>Failure cannot be recovered locally.</td><td>Throw an exception.</td></tr><tr><td>Failure is routine and the caller must handle it inline.</td><td>Return <code>std::expected</code>.</td></tr><tr><td>Absence carries no extra information.</td><td>Return <code>std::optional</code>.</td></tr><tr><td>Invariant must never be violated.</td><td>Use a contract attribute.</td></tr><tr><td>Function is guaranteed not to throw.</td><td>Mark <code>noexcept</code>.</td></tr></tbody></table></div><p>The table is a decision tree, not a menu. Read the failure mode first, then pick the tool. A function that fails because the input is bad returns <code>expected</code>. A function that fails because the system ran out of memory throws. A function that cannot fail marks itself <code>noexcept</code>. A function that documents a contract uses an attribute. The four tools cover four failure modes, and the modes do not overlap.</p><p>Use the tool that matches the semantic intent and avoid mixing strategies without a clear reason. A library that mixes <code>expected</code> and exceptions forces the caller to hold two mental models. Choose a single default per module and deviate only when the failure profile demands it. For example, a parser returns <code>expected</code> because every call can fail, while a memory allocator throws because out‑of‑memory is rare and unrecoverable.</p><h2 id="impossible-cases"><a class="header" href="#impossible-cases">Impossible cases</a></h2><p>When the program reaches a state that the logic says cannot occur, the code can call one of the following.</p><ul><li><code>std::abort()</code>: terminates the process immediately.</li><li><code>std::terminate()</code>: ends the program after invoking the terminate handler.</li><li><code>std::unreachable()</code>: tells the compiler that this point is unreachable. Reaching it results in undefined behavior.</li></ul><p>These calls are reserved for truly impossible branches such as a default case in a <code>switch</code> that covers every enum value.</p><p><code>std::unreachable</code> is the strongest of the three. It tells the optimizer that the code path is dead, so the compiler can assume it never runs and use that fact to improve the surrounding code. If the assumption is wrong, the behaviour is undefined. Use it only after a construct that exhausts every case, such as a <code>switch</code> over a <code>std::variant</code> where the visitor covers every alternative. <code>std::terminate</code> is the safe fallback when the program is in a state you cannot reason about: it stops without running destructors, which avoids making things worse.</p><h2 id="try-this-8"><a class="header" href="#try-this-8">Try this</a></h2><p>Write a function</p><pre><code class="language-cpp">std::expected<double, std::string> divide(double a, double b);</code></pre><p>The function returns an error string when <code>b == 0</code>. Then write a caller that handles both the value and the error.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="text-and-formatting"><a class="header" href="#text-and-formatting">Text and formatting</a></h1><p>In this chapter we replace the old C‑style formatting functions with the modern, type‑safe facilities that arrive in C++20 and C++23. The progression mirrors the lessons from the lifetime chapters: first we look at why <code>printf</code> is hazardous, then we introduce owning strings, non‑owning views, and finally the compile‑time checked formatting API.</p><h2 id="the-c-printf-problem"><a class="header" href="#the-c-printf-problem">The C printf problem</a></h2><p><code>printf</code> takes a format string that describes the types of the arguments that follow. The compiler cannot verify that the format string matches the argument list because the string is an ordinary <code>char const *</code>. If the programmer writes a mismatched conversion specifier or omits an argument, the program exhibits <strong>undefined behavior</strong> at run time.</p><pre><code class="language-cpp">// The format string expects an `int` and a `double`, but only one argument is supplied.printf("%d %f\n", 42)</code></pre><p>The above code compiles, but at execution the function reads a non‑existent <code>double</code> from the stack. The result is nondeterministic and leads to memory corruption.</p><h2 id="stdstring-and-stdstring_view"><a class="header" href="#stdstring-and-stdstring_view">std::string and std::string_view</a></h2><p><code>std::string</code> owns the character storage. It allocates memory, frees it when the object is destroyed, and therefore always remains valid for the lifetime of the owning object.</p><p><code>std::string_view</code> does <strong>not</strong> own any characters. It merely holds a pointer and a length. The view is valid only while the underlying characters remain alive. Because a view never copies, it is ideal for function parameters: the caller can pass a <code>std::string</code>, a string literal, or a substring without allocating a new buffer.</p><pre><code class="language-cpp">void greet(std::string_view name) { std::println("Hello, {}!", name);}
greet("Ada") // literal - no allocationstd::string full = "Grace Hopper"greet(full) // temporary view of whole stringgreet(full.substr(0,5)) // view of a prefix</code></pre><p>The function <code>greet</code> never copies the characters it simply reads them through the view.</p><h2 id="stdformat-c20"><a class="header" href="#stdformat-c20"><code>std::format</code> (C++20)</a></h2><p><code>std::format</code> replaces <code>printf</code> with a <strong>type‑safe, variadic</strong> formatting function. The format string contains <code>{}</code> placeholders. The compiler parses the literal at compile time, matches each placeholder with the corresponding argument, and rejects mismatches with a diagnostic.</p><pre><code class="language-cpp">#include <format>#include <print>#include <string>
int main() { std::string name = "Alice"; int age = 30; double score = 95.5; std::println("{}", std::format("Name: {}, Age: {}, Score: {:.2f}", name, age, score)); return 0;}</code></pre><p>Running the program prints a line that contains <code>Name: Alice</code>. The <code>EXPECT</code> test in the build system checks for that substring.</p><h2 id="stdprint-and-stdprintln-c23"><a class="header" href="#stdprint-and-stdprintln-c23"><code>std::print</code> and <code>std::println</code> (C++23)</a></h2><p><code>std::print</code> writes to <code>stdout</code> without appending a newline. <code>std::println</code> does the same but adds a newline automatically. The book uses <code>std::println</code> for line-oriented output. It uses <code>std::print</code> only when the output is a sequence of values on one line, written inside a loop, where a newline after each value is wrong. In that case the loop body calls <code>std::print("{} ", value)</code> and a single <code>std::println()</code> closes the line after the loop.</p><pre><code class="language-cpp">#include <print>
int main() { std::println("{:>10} | {:>8}", "Item", "Value"); std::println("{:>10} | {:>8.2f}", "A", 1.23); std::println("{:>10} | {:>8.2f}", "B", 4.56); std::println("{:>10} | {:>8.2f}", "C", 7.89); return 0;}</code></pre><p>The example prints a small table. The test harness looks for the token <code>A</code> in the output.</p><div class="table-wrapper"><table><thead><tr><th>Facility</th><th>Header</th><th>Type safety</th><th>Format checking</th><th>Returns</th><th>Use when</th></tr></thead><tbody><tr><td><code>std::cout</code></td><td><code><iostream></code></td><td>per insertion</td><td>none</td><td>stream reference</td><td>stream features, custom <code>operator<<</code></td></tr><tr><td><code>printf</code></td><td><code><cstdio></code></td><td>none</td><td>none</td><td>int count</td><td>C interop, legacy code</td></tr><tr><td><code>std::print</code> / <code>std::println</code></td><td><code><print></code></td><td>typed arguments</td><td>compile time</td><td>void</td><td>new code, checked formatting</td></tr></tbody></table></div><h2 id="format-specifiers"><a class="header" href="#format-specifiers">Format specifiers</a></h2><p>The part of the format string after a colon controls alignment, width, fill character, precision, and numeric base.</p><ul><li><strong>Width and alignment</strong>: <code>{:<10}</code> left‑aligns inside a field of ten characters, <code>{:>10}</code> right‑aligns, <code>{:^10}</code> centers.</li><li><strong>Fill character</strong>: <code>{:*^8}</code> pads with <code>*</code> while centering.</li><li><strong>Precision</strong>: <code>{: .2f}</code> prints a floating‑point value with two digits after the decimal point.</li><li><strong>Base</strong>: <code>{:#x}</code> prints an integer in hexadecimal with a <code>0x</code> prefix, <code>{:08b}</code> prints binary padded to eight digits.</li></ul><pre><code class="language-cpp">std::println("{:>10}", "right") // " right"std::println("{:08b}", 5) // "00000101"std::println("{:#x}", 255) // "0xff"std::println("{:.2f}", 3.14159) // "3.14"</code></pre><p>If a specifier is unknown, the compiler issues an error because the format string is a compile‑time constant.</p><p>The specifier syntax mirrors Python’s <code>format</code>. The colon introduces a format‑spec, then optional fill, alignment, sign, alternate form, zero‑pad, width, precision, and type. The order matters. The compiler enforces it.</p><p>Performance of <code>std::format</code> is comparable to hand‑written <code>printf</code> for simple cases. The library avoids temporary allocations for short strings by using a small‑buffer optimisation.</p><p>Custom types can be formatted by specialising <code>std::formatter</code>. The specialization returns a <code>format_to</code> function that writes the representation into the provided output iterator.</p><pre><code class="language-cpp">struct Point { int x; int y; };
template<> struct std::formatter<Point> { constexpr auto parse(auto& ctx) { return ctx.begin(); } auto format(Point const& p, auto& ctx) const { return std::format_to(ctx.out(), "({},{})", p.x, p.y); }};
std::println("{}", Point{3,4}); // prints "(3,4)"</code></pre><p>The same custom formatter works for both <code>std::println</code> and <code>std::format</code> because they share the formatter protocol.</p><h2 id="advanced-formatting-features"><a class="header" href="#advanced-formatting-features">Advanced formatting features</a></h2><p>Beyond the basic width and precision specifiers, <code>std::format</code> supports a rich set of options that let developers tailor the textual representation of values.</p><ul><li><strong>Sign handling</strong>: <code>{: +}</code> forces a leading plus sign for positive numbers, while <code>{: -}</code> (the default) prints a minus sign only for negatives.</li><li><strong>Alternate form</strong>: The <code>#</code> flag adds a prefix for certain types: <code>0x</code> for hexadecimal, <code>0</code> for octal, and a trailing decimal point for floating‑point values.</li><li><strong>Zero padding</strong>: <code>{:08}</code> pads the field with zeros instead of spaces. It is equivalent to <code>{:0>8}</code> but more concise.</li><li><strong>Grouping</strong>: <code>{:L}</code> formats numbers according to the locale’s thousands separator. This works together with a <code>std::locale</code> overload.</li><li><strong>Date and time</strong>: When the <code><chrono></code> library provides a <code>std::chrono::year_month_day</code> or <code>std::chrono::hh_mm_ss</code> object, the formatter can emit ISO‑8601 strings using the <code>{:T}</code> or <code>{:D}</code> specifiers.</li></ul><pre><code class="language-cpp">std::println("{:+08}", 42) // "+0000042"std::println("{:#x}", 255) // "0xff"std::println("{:L}", 1234567) // "1,234,567" in en_US locale
using namespace std::chronoauto now = floor<seconds>(system_clock::now())std::println("{:T}", now) // "2026-08-21T14:35:00"</code></pre><p>These specifiers are composable a format string can combine alignment, fill, width, sign, and type in a single placeholder. The compiler checks that the combination is valid for the argument type, rejecting illegal mixes such as a sign flag on a string.</p><p>Custom types can also honour these flags by inspecting the <code>format_context</code> and the parsed format‑spec. A formatter can honour <code>fill</code> and <code>align</code> by delegating to <code>std::format_to</code> with a constructed format string, or it can implement the formatting logic directly for maximum performance.</p><pre><code class="language-cpp">struct Money { int cents;};
template<> struct std::formatter<Money> { char presentation = 'f'; // f = dollars.cents, e = euros constexpr auto parse(auto& ctx) { auto it = ctx.begin(); if (it != ctx.end() && (*it == 'e' || *it == 'f')) presentation = *it++; return it; } auto format(Myney const& m, auto& ctx) const { if (presentation == 'e') return std::format_to(ctx.out(), "€{:.2f}", m.cents / 100.0); else return std::format_to(ctx.out(), "${:.2f}", m.cents / 100.0); }};std::println("{}", Money{1234}) // prints "$12.34"</code></pre><p>The example demonstrates how a formatter can respect a presentation specifier while still supporting the generic alignment and width options supplied by the surrounding format string.</p><p>Placeholders can refer to arguments by position.</p><pre><code class="language-cpp">std::println("{0} + {0} = {1}", 2, 4) // prints "2 + 2 = 4"</code></pre><p>Named arguments are not part of the core standard, but a user can achieve them by providing a custom <code>std::formatter</code> specialization or by using <code>std::make_format_args</code> together with a format string that references names via a library such as {fmt}. The book mentions the technique briefly because it is useful in larger projects.</p><pre><code class="language-cpp">// Using a custom formatter (illustrative - not compiled here)struct Point { int x int y }template<> struct std::formatter<Point> : std::formatter<std::string> { auto format(Point const& p, auto& ctx) const { return std::formatter<std::string>::format( std::format("({},{})", p.x, p.y), ctx) }}</code></pre><h2 id="compiletime-checking-is-the-point"><a class="header" href="#compiletime-checking-is-the-point">Compile‑time checking is the point</a></h2><p><code>std::format</code> only accepts a <strong>compile‑time constant</strong> format string for full checking. If a program needs a run‑time format, the library provides <code>std::runtime_format</code>, which disables compile‑time verification. This design forces the programmer to choose safety whenever possible.</p><p>Contrast the two approaches:</p><ul><li><code>printf("%d %f\n", 42)</code>: compiles, can crash at run time.</li><li><code>std::format("%d %f\n", 42)</code>: fails to compile because <code>%</code> is not a valid placeholder.</li><li><code>std::format(std::runtime_format(fmt), 42)</code>: compiles, but the format string is unchecked.</li></ul><p>The book’s theme is to let the compiler catch what it can, and <code>std::format</code> embodies that principle.</p><h2 id="performance-and-constexpr-formatting"><a class="header" href="#performance-and-constexpr-formatting">Performance and constexpr formatting</a></h2><p><code>std::format</code> is designed to be fast. The implementation parses the format string at compile time when the literal is a constant expression, eliminating runtime parsing overhead. This makes it comparable to hand‑written <code>printf</code> for simple cases while providing safety.</p><p>When the format string cannot be known at compile time, the library falls back to a runtime parser. The cost is modest: a single pass over the format string plus the usual formatting work. For tight loops where every nanosecond matters, developers can still write a custom <code>printf</code>‑style loop, but the safety trade‑off must be justified.</p><p>C++23 extends <code>std::format</code> with <code>constexpr</code> support. A <code>constexpr</code> function can call <code>std::format</code> to produce a compile‑time constant string that can be used as a non‑type template parameter or in a <code>static_assert</code>.</p><pre><code class="language-cpp">constexpr std::string_view make_label(int id) { return std::format("Item-{:03}", id)}static_assert(make_label(7) == "Item-007")</code></pre><p>The example illustrates how formatting can participate in compile‑time computation, enabling expressive metaprogramming without sacrificing safety.</p><p>Locale‑aware formatting is optional. By default <code>std::format</code> uses the “C” locale, which formats numbers with a period as the decimal separator. Passing a <code>std::locale</code> object to the overload selects the suitable digit grouping and decimal marks for the target locale.</p><pre><code class="language-cpp">std::locale german("de_DE")std::println(std::format(german, "{:L}", 1234567.89)) // prints "1.234.567,89"</code></pre><p>Custom formatters, shown earlier, work uniformly with locale‑aware overloads because the formatter receives the locale via its <code>format</code> method.</p><h2 id="error-handling-in-formatting"><a class="header" href="#error-handling-in-formatting">Error handling in formatting</a></h2><p>When a format string is ill‑formed, <code>std::format</code> throws a <code>std::format_error</code>. This exception type is a subclass of <code>std::runtime_error</code> and carries a message that identifies the problem, for example, “argument index out of range” or “invalid format specifier”.</p><pre><code class="language-cpp">try { std::println(std::format("{0} {2}", 1, 2)) // index 2 does not exist} catch (const std::format_error& e) { std::cerr << "Formatting failed: " << e.what() << '\n'}</code></pre><p>The library also provides the non‑throwing overload <code>std::format_to_n</code> which writes into a pre‑allocated buffer and returns the number of characters written. This is useful in low‑latency or embedded contexts where exceptions are disabled.</p><pre><code class="language-cpp">char buf[32]auto result = std::format_to_n(buf, sizeof(buf), "{:04x}", 0x1A3)std::println("{} characters written", result.out - buf)</code></pre><p><code>result.out</code> points just past the last character written, allowing the caller to construct a <code>std::string_view</code> without an extra copy.</p><p>The library also defines <code>std::vformat</code> and <code>std::vprint</code> which take a <code>std::format_args</code> object generated by <code>std::make_format_args</code>. These functions enable runtime‑determined argument lists while still performing compile‑time checks on the format string itself.</p><pre><code class="language-cpp">auto args = std::make_format_args(42, 3.14)std::println(std::vformat("int={}, double={}", args))</code></pre><p>If the format string itself is not a constant expression, the parser cannot validate the placeholders against the arguments at compile time. In that case, the same run‑time checks apply, and mismatches still result in a <code>std::format_error</code>.</p><p>The distinction between compile‑time guarantees and run‑time safety mirrors the earlier discussion of <code>printf</code>. By default the library favours compile‑time safety developers can opt into run‑time flexibility when the application requires it.</p><hr><h2 id="try-this-9"><a class="header" href="#try-this-9">Try this</a></h2><p>Write a program that prints a three‑row table. Each column must have a fixed width. The second column contains floating‑point numbers printed with two decimal places and right‑aligned.</p><pre><code class="language-cpp">// Insert your own code here - use std::println and format specifiers.</code></pre><p>When you run the program, the output must look like a tidy table with aligned columns.</p><hr><blockquote><p><strong>NOTE</strong>: All examples in this chapter are compiled and tested with the Clang 22 toolchain using <code>-std=c++26</code>. The <code>book_example</code> macro registers each source file as a test target, and the <code>EXPECT</code> strings verify that the output contains the expected fragments.</p></blockquote><div style="break-before: page; page-break-before: always;"></div><h1 id="containers"><a class="header" href="#containers">Containers</a></h1><p>Containers provide storage for collections of objects. The Standard Library supplies a family of containers that differ in allocation strategy, ordering guarantees, and performance characteristics. Choose a container that matches the required usage pattern. The default choice is <code>std::vector</code> because it stores elements contiguously, which gives good cache locality and enables constant‑time random access.</p><h2 id="stdvector-the-default-container"><a class="header" href="#stdvector-the-default-container"><code>std::vector</code>: the default container</a></h2><p><code>std::vector<T></code> owns a dynamically allocated array of <code>T</code>. Elements are stored next to each other in memory. The implementation allocates a capacity that can be greater than the current size. When the size grows past the capacity, the vector allocates a new, greater block, copies or moves existing elements, and frees the old block. This reallocation invalidates all pointers, references, and iterators that refer to the previous storage.</p><p>Because reallocation can be expensive, two techniques reduce its impact:</p><ul><li><strong>Reserve</strong>: call <code>reserve(10)</code> before inserting ten or greater number of elements. The vector allocates space for at least <code>n</code> elements up front, so later <code>push_back</code> or <code>emplace_back</code> calls cannot trigger a reallocation.</li><li><strong>Emplace</strong>: <code>emplace_back(args…)</code> constructs a new element directly in the storage. The arguments are forwarded to the element’s constructor, avoiding an extra copy or move.</li></ul><p>Both techniques tie back to earlier chapters. Chapter 07 described the lifetime‑safety analysis that flags dangling pointers after a reallocation. Reserving ahead of time eliminates that risk for the most common case. Chapter 08 explained why passing arguments by value or reference matters. <code>emplace_back</code> constructs in place, satisfying the principle of “construct where you use”.</p><p>The vector grows geometrically, typically doubling capacity. This yields amortised constant‑time <code>push_back</code>. Use <code>reserve</code> when the final size is known to avoid intermediate reallocations and iterator invalidation.</p><p>Erasing an element from the middle of a <code>vector</code> shifts every later element down by one, so removal is <code>O(n)</code> in the number of elements after the erased position. <code>erase</code> and the C++20 <code>erase</code>/<code>erase_if</code> free functions return the new logical end. When you only need to drop the last few elements, <code>pop_back</code> or <code>resize</code> is cheaper.</p><p><code>std::vector<bool></code> is a partial specialization that packs bits, so <code>operator[]</code> returns a proxy object rather than a <code>bool&</code>. This breaks generic code that expects a real reference, and it is the one standard container that is not a true container. Prefer <code>std::vector<char></code> or <code>std::bitset</code> when you need a sequence of individual bit values with normal reference semantics.</p><pre><code class="language-cpp">#include <vector>#include <print>
int main() { std::vector<int> v; v.reserve(10); v.emplace_back(1); v.emplace_back(2); v.emplace_back(3); std::println("vector size: {}", v.size()); return 0;}</code></pre><h2 id="stdarray-fixed-size-on-the-stack"><a class="header" href="#stdarray-fixed-size-on-the-stack"><code>std::array</code>: fixed size on the stack</a></h2><p><code>std::array<T, N></code> stores exactly <code>N</code> objects of type <code>T</code>. The size is known at compile time, and the array lives in the surrounding object’s storage, which is the stack for local variables. No dynamic allocation occurs.</p><p>A classic C array (<code>T a[N]</code>) decays to a pointer when passed to a function, losing its size information. <code>std::array</code> retains its size via the member function <code>size()</code>. It also provides standard container interfaces (<code>begin()</code>, <code>end()</code>, <code>operator[]</code>), so generic algorithms work uniformly.</p><pre><code class="language-cpp">#include <array>#include <print>
int main() { std::array<int,5> a{{1,2,3,4,5}}; std::println("array size: {}", a.size()); std::println("first element: {}", a[0]); return 0;}</code></pre><p>The example fills an <code>std::array<int,5></code> with the values 1 through 5, prints the size, and prints the first element.</p><p><code>std::array</code> is an aggregate, so it can be copy‑assigned and returned by value with no hidden allocation, and its size is part of the type. Prefer it over <code>std::vector</code> when <code>N</code> is small and fixed, because the data sits in the parent object with no pointer chase. Reach for <code>std::vector</code> once the size is dynamic or larger than a few dozen elements.</p><h2 id="sequence-containers-with-different-insertion-properties"><a class="header" href="#sequence-containers-with-different-insertion-properties">Sequence containers with different insertion properties</a></h2><div class="table-wrapper"><table><thead><tr><th>Container</th><th>Typical use</th><th>Insertion cost</th><th>Iterator invalidation</th></tr></thead><tbody><tr><td><code>std::deque</code></td><td>Push or pop at both ends without moving existing elements</td><td>Amortised constant time at either end</td><td>Inserting at the front or back does not invalidate existing iterators. Inserting in the middle can invalidate.</td></tr><tr><td><code>std::list</code></td><td>Frequent insertion or removal in the middle of a long list</td><td>Constant time anywhere</td><td>No iterator is invalidated by insertion or removal, except for the iterator that is removed.</td></tr><tr><td><code>std::forward_list</code></td><td>Singly‑linked list, minimal memory overhead</td><td>Constant time insertion after a known iterator</td><td>Same rules as <code>std::list</code>. No iterator invalidation except for erased elements.</td></tr></tbody></table></div><p>All three store elements non‑contiguously. The lack of cache locality makes them slower for tight loops that iterate over a large number of elements. Use them only when the insertion pattern outweighs the cache penalty.</p><pre><code class="language-cpp">// Example of a deque that pushes at both ends.std::deque<int> dq;for (int i = 0; i < 5; ++i) dq.push_back(i);for (int i = 5; i < 10; ++i) dq.push_front(i);</code></pre><p>A <code>std::deque</code> stores elements in fixed‑size chunks rather than one block, which is why pushing at the front never moves the existing elements and never invalidates their iterators. The trade‑off is a double indirection on access and a larger per‑element overhead than <code>vector</code>.</p><h2 id="ordered-associative-containers-stdmap-and-stdset"><a class="header" href="#ordered-associative-containers-stdmap-and-stdset">Ordered associative containers: <code>std::map</code> and <code>std::set</code></a></h2><p><code>std::map<Key, Value></code> and <code>std::set<Key></code> are implemented as red‑black trees. They keep keys in sorted order defined by <code>operator<</code> (or a custom comparator). Lookup, insertion, and removal take <code>O(log n)</code> time. Iterators remain valid across insertions and deletions, except when the element itself is erased.</p><p>Use these containers when ordered iteration, range queries, or stable iterator validity are required.</p><p>Use <code>operator[]</code> to find or insert, and <code>at()</code> when a missing key must raise <code>std::out_of_range</code> instead of creating a default. The ordering follows <code>operator<</code> on the key by default. A custom comparator changes both the order and the equality test. <code>std::multimap</code> and <code>std::multiset</code> permit duplicate keys when that is needed.</p><p><code>std::set</code> stores keys only and is the right choice when you need a sorted, deduplicated collection rather than key‑value pairs. Both <code>map</code> and <code>set</code> gain a <code>contains</code> member in C++20 that tests membership without constructing an iterator.</p><pre><code class="language-cpp">std::map<std::string, int> word_counts;word_counts["apple"] = 3;word_counts["banana"] = 5;for (const auto& [w, c] : word_counts) { std::println("{}: {}", w, c);}</code></pre><h2 id="unordered-associative-containers-stdunordered_map-and-stdunordered_set"><a class="header" href="#unordered-associative-containers-stdunordered_map-and-stdunordered_set">Unordered associative containers: <code>std::unordered_map</code> and <code>std::unordered_set</code></a></h2><p><code>std::unordered_map<Key, Value></code> and <code>std::unordered_set<Key></code> are hash tables. Average‑case lookup, insertion, and removal are constant time. The containers do <strong>not</strong> preserve any ordering of keys. The key type must be hashable. The standard library provides <code>std::hash</code> for fundamental types and for <code>std::string</code>. Custom types need a specialization of <code>std::hash</code> and an equality operator.</p><p>Because hash tables store elements in buckets, iterator invalidation rules differ from ordered containers: inserting does not invalidate iterators, but rehashing (which can occur when the load factor exceeds a threshold) invalidates all iterators.</p><p>An <code>unordered_map</code> has a higher per‑operation constant cost than a vector search over tiny sets, and it allocates buckets, so prefer it only once the keyed lookup actually pays off. <code>reserve</code> and <code>max_load_factor</code> let you tune the bucket count before a bulk insert.</p><p>Iteration order over an <code>unordered_map</code> is not specified and can change between runs, so never depend on it. The <code>contains</code> member tests membership in average constant time.</p><pre><code class="language-cpp">std::unordered_map<int, std::string> id_to_name{{1, "Alice"}, {2, "Bob"}};if (auto it = id_to_name.find(1); it != id_to_name.end()) { std::println("Found {}", it->second);}</code></pre><h2 id="stdspan-a-nonowning-view-over-contiguous-data"><a class="header" href="#stdspan-a-nonowning-view-over-contiguous-data"><code>std::span</code>: a non‑owning view over contiguous data</a></h2><p>A <code>std::span<T></code> is a lightweight object that refers to a contiguous sequence of <code>T</code>. It does <strong>not</strong> own the elements. It only stores a pointer and a length. Because it is non‑owning, it can be created from multiple sources: a <code>std::vector<T></code>, a <code>std::array<T, N></code>, or a raw C array.</p><p>Using <code>std::span</code> allows algorithms to accept any of those sources without copying. The container’s lifetime must outlive the span. Otherwise the span becomes dangling, which the lifetime‑safety analysis flags.</p><pre><code class="language-cpp">#include <vector>#include <array>#include <span>#include <print>
void print_span(std::span<const int> s) { std::print("span elements:"); for (int v : s) { std::print(" {}", v); } std::println("");}
int main() { std::vector<int> v{1, 2, 3}; std::array<int,3> a{{4,5,6}}; print_span(v); print_span(a); return 0;}</code></pre><p>The <code>print_span</code> function takes a <code>std::span<const int></code> and prints each element. The <code>main</code> function calls it with a <code>std::vector<int></code> and an <code>std::array<int,3></code>, demonstrating the uniform interface.</p><p>A <code>std::span</code> can carry a compile‑time extent (<code>std::span<T, N></code>) or a dynamic one. The static form lets the compiler prove bounds in some algorithms. <code>first</code>, <code>last</code>, and <code>subspan</code> produce new spans over subranges without copying, which is how zero‑copy parsing pipelines stay allocation free.</p><p>A span exposes <code>size</code>, <code>empty</code>, and <code>data</code>, and it converts to a <code>std::vector</code> only through an explicit constructor, never by accident. This keeps ownership explicit at every call site.</p><h2 id="choosing-the-right-container"><a class="header" href="#choosing-the-right-container">Choosing the right container</a></h2><p>When you start a new piece of code, follow this decision guide:</p><ol><li><strong>Begin with <code>std::vector</code>.</strong> Its contiguous storage gives the best cache performance for most workloads.</li><li><strong>Switch to <code>std::array</code></strong> if the number of elements is known at compile time and the total size fits within typical stack limits (e.g., ≤ 1 KB).</li><li><strong>Select an associative container</strong> when you need O(1) average‑case lookup by key. Choose <code>std::map</code> if you require ordered iteration or range queries. Choose <code>std::unordered_map</code> for constant‑time performance when ordering does not matter.</li><li><strong>Consider <code>std::deque</code></strong> only when you need efficient insertion or removal at both ends and cannot accept the iterator invalidation of a vector during growth.</li><li><strong>Use <code>std::list</code> or <code>std::forward_list</code></strong> only when you must insert or erase frequently in the middle of a large sequence and the cache penalty is tolerable.</li><li><strong>Use <code>std::span</code></strong> to write generic algorithms that operate on any contiguous view without taking ownership.</li></ol><p>Prefer contiguous, owning containers such as <code>std::vector</code> (default) or <code>std::array</code> when size is fixed. Use <code>std::span</code> for non‑owning views and <code>std::pmr</code> allocators to customise allocation without changing the container type. The same cache‑locality reasoning applies to <code>std::string</code>, while <code>std::string_view</code> follows the view pattern described earlier.</p><h2 id="try-this-10"><a class="header" href="#try-this-10">Try this</a></h2><p>Write a program that:</p><ol><li>Declares a <code>std::vector<int></code>.</li><li>Calls <code>reserve(10)</code>.</li><li>Uses <code>emplace_back</code> to add the values 1, 2, 3.</li><li>Creates a <code>std::span<int></code> that references the vector.</li><li>Computes and prints the sum of the elements in the span.</li></ol><p>No solution is provided. The reader must fill in the code.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="algorithms-are-the-loops"><a class="header" href="#algorithms-are-the-loops">Algorithms are the loops</a></h1><p>A hand-written loop hides intent. Chapter 04 showed that a loop can miss an off-by-one error and can expose lifetime bugs when a pointer is taken to an element that later moves. A named algorithm states exactly what happens: <em>find the value</em>, <em>count the matches</em>, <em>transform each element</em>. The reader understands the code without inspecting the body.</p><p>Algorithms operate on iterator pairs, making them generic over containers and element types. The same call works for <code>std::vector</code>, <code>std::array</code>, <code>std::list</code>, or any range providing iterators, and for <code>int</code>, <code>std::string</code>, or a user type with <code>operator<</code>. This uniformity removes boilerplate, reduces bugs, and lets one algorithm serve every container.</p><p>Every standard algorithm carries a documented complexity guarantee. <code>std::find</code> and <code>std::count_if</code> run in linear time because they can inspect each element. <code>std::sort</code> guarantees <code>O(n log n)</code> comparisons in the worst case. Knowing these bounds helps you choose the right tool on a performance-critical path. If you need only the smallest element, <code>std::min_element</code> is cheaper than a full sort because it stops after a single linear scan.</p><h2 id="the-iterator-pair-model"><a class="header" href="#the-iterator-pair-model">The iterator pair model</a></h2><p>All standard containers expose <code>begin()</code> and <code>end()</code>. They define a half-open range <code>[begin, end)</code>. The range includes the element pointed to by <code>begin</code> and excludes the element pointed to by <code>end</code>. This convention lets algorithms stop exactly at the last element without an extra check.</p><pre><code class="language-cpp">#include <vector>#include <algorithm>#include <print>
int main() { std::vector<int> v{1,4,7,10}; int target = 7; auto it = std::find(v.begin(), v.end(), target); if (it != v.end()) std::println("found {}", *it); else std::println("not found"); return 0;}</code></pre><p>The program creates a <code>std::vector<int></code>, calls <code>std::find</code>, and prints whether the target value was found.</p><h2 id="non-modifying-sequence-algorithms"><a class="header" href="#non-modifying-sequence-algorithms">Non-modifying sequence algorithms</a></h2><p>The library provides many read-only algorithms. <code>std::find</code> returns an iterator to the first element equal to a value. <code>std::count</code> returns the number of elements equal to a value. The trio <code>std::all_of</code>, <code>std::any_of</code>, and <code>std::none_of</code> evaluates a predicate over a range. <code>std::count_if</code> counts elements that satisfy a predicate.</p><p>Beyond <code>std::find</code>, <code>std::count</code>, and the <code>all_of</code> family, the library offers <code>std::mismatch</code> to find the first differing position between two ranges, <code>std::equal</code> to test equality, and <code>std::search</code> to locate a subrange. These algorithms never alter the container, so you can call them on a <code>const</code> object.</p><p><code>std::find_if</code> takes a predicate instead of a value, locating the first element that satisfies a condition. <code>std::find_first_of</code> finds the first element that matches any value from a second range. These variants cover the common cases where you search by property rather than by equality.</p><p><code>std::for_each</code> applies a callable to each element. It is the algorithm-shaped alternative to a raw range-for loop, and it makes the intent visible at the call site. <code>std::adjacent_find</code> locates the first pair of neighbouring elements that satisfy a condition, which is useful for detecting duplicates or trends in a sequence.</p><h2 id="modifying-algorithms"><a class="header" href="#modifying-algorithms">Modifying algorithms</a></h2><p>Algorithms that write to a destination include <code>std::copy</code>, <code>std::transform</code>, <code>std::fill</code>, and <code>std::replace</code>. They accept iterator pairs for the source and destination. <code>std::transform</code> applies a unary operation to each source element and writes the result to the destination range.</p><pre><code class="language-cpp">#include <vector>#include <algorithm>#include <print>
int main() { std::vector<int> v{1, 2, 3, 4, 5}; std::vector<int> out(v.size()); std::transform(v.begin(), v.end(), out.begin(), [](int x){ return x * x; }); std::println("squared:"); for (int n : out) std::print("{} ", n); std::println(""); return 0;}</code></pre><p>The program prints the squared numbers. The destination must have room for every written element. <code>std::back_inserter</code> grows the container as needed.</p><p><code>std::remove</code> and <code>std::remove_if</code> shift the kept elements to the front and return a new logical end. They do not erase anything. The erase-remove idiom combines the two: call <code>erase</code> with the iterator pair the algorithm returns to drop the unwanted tail in one statement. This avoids building a second container and reuses the original storage.</p><p>Beyond <code>transform</code> and <code>remove</code>, the library offers <code>std::fill</code> to assign a value to every element, <code>std::replace</code> to swap one value for another, <code>std::rotate</code> to cycle a subrange, and <code>std::partition</code> to group elements that satisfy a predicate before those that do not. Each returns the iterator or range you need to continue working without re-scanning.</p><p><code>std::unique</code> removes consecutive duplicates, so it is effective only on a sorted range. Pair it with <code>std::sort</code> to drop all duplicates, then erase the trailing run as with remove.</p><h2 id="sorting-and-binary-search"><a class="header" href="#sorting-and-binary-search">Sorting and binary search</a></h2><p><code>std::sort</code> rearranges elements into ascending order using <code>operator<</code>. <code>std::stable_sort</code> preserves the relative order of equal elements. After sorting, binary search algorithms become valid. <code>std::binary_search</code> reports whether a value exists in a sorted range. <code>std::lower_bound</code> returns the first position where a value can be inserted without breaking order. <code>std::upper_bound</code> returns the position after the last equal element.</p><p>The precondition matters. Running <code>std::lower_bound</code> on an unsorted range yields undefined results. The algorithm assumes monotonic ordering and will silently produce the wrong answer.</p><pre><code class="language-cpp">#include <vector>#include <algorithm>#include <print>
int main() { std::vector<int> v{5, 2, 9, 1, 5, 6}; std::sort(v.begin(), v.end()); std::println("sorted:"); for (int n : v) std::print("{} ", n); std::println(""); int key = 5; auto it = std::lower_bound(v.begin(), v.end(), key); if (it != v.end() && *it == key) std::println("lower_bound of {} is at index {}", key, std::distance(v.begin(), it)); else std::println("key not found"); return 0;}</code></pre><p>The program sorts a vector and uses <code>std::lower_bound</code> to locate the first occurrence of a value.</p><p>When you need only part of the order, do not pay for a full sort. <code>std::partial_sort</code> orders the first K elements and leaves the rest unspecified. <code>std::nth_element</code> places the Kth element in its sorted position and partitions the rest around it, all in linear time. These are the right tools for top-K and median queries.</p><p>Prefer <code>std::stable_sort</code> when equal elements carry order-dependent meaning, such as log entries that must stay chronological. The extra cost is small and the guarantee prevents subtle bugs when the sorted result feeds another pass.</p><h2 id="ranges-c20"><a class="header" href="#ranges-c20">Ranges (C++20)</a></h2><p>C++20 introduced <code>std::ranges</code>. Ranges remove the need to pass iterator pairs. An algorithm can operate directly on a range object. The pipe syntax <code>|</code> composes adaptors that transform or filter the data before a terminal algorithm consumes it. Views are lazy: no intermediate container is allocated, and a pipeline can stop early.</p><pre><code class="language-cpp">#include <vector>#include <ranges>#include <print>
int main() { std::vector<int> v{1,2,3,4,5,6}; auto pipeline = v | std::views::filter([](int x){ return x % 2 == 0; }) | std::views::transform([](int x){ return x * x; }); std::println("even squares:"); for (int n : pipeline) std::print("{} ", n); std::println(""); return 0;}</code></pre><p>The pipeline filters even numbers, squares them, and prints the result.</p><p>The adaptor set is larger than <code>filter</code> and <code>transform</code>. <code>std::views::take</code> keeps the first N elements, <code>std::views::drop</code> skips them, <code>std::views::reverse</code> inverts order, and <code>std::views::split</code> breaks a range on a delimiter. Because each view is lazy, <code>v | std::views::filter(f) | std::views::take(3)</code> examines elements only until three match. A ranges algorithm returns a view or subrange, so the result can feed another pipeline directly.</p><p><code>std::views::iota</code> generates a numeric sequence without storing it, so <code>std::views::iota(0, n)</code> replaces a hand-written counter loop. Combined with <code>filter</code> and <code>transform</code>, it builds lazy numeric pipelines that allocate nothing. Ranges algorithms also accept projections on the terminal call, so <code>std::ranges::sort(v, {}, &Point::x)</code> sorts by the <code>x</code> member directly. <code>std::ranges::to</code> materialises a view into a concrete container when you finally need ownership, for example <code>auto v = range | std::views::filter(f) | std::ranges::to<std::vector>()</code>. This keeps the pipeline lazy until the boundary where storage is required.</p><h2 id="projection-and-comparator"><a class="header" href="#projection-and-comparator">Projection and comparator</a></h2><p><code>std::ranges</code> algorithms accept a projection argument. A projection extracts a member or computes a value before the algorithm compares or orders elements. This removes the need for an explicit comparator lambda. For example, sorting a vector of <code>Point</code> structs by the <code>y</code> coordinate needs no lambda:</p><pre><code class="language-cpp">struct Point { int x; int y; };std::vector<Point> pts = {{1,5},{2,3},{4,7}};std::ranges::sort(pts, {}, &Point::y);</code></pre><p>The projection <code>&Point::y</code> tells the algorithm to compare the <code>y</code> members directly. The same idea applies to <code>std::ranges::unique</code> or <code>std::lower_bound</code> when you compare on a particular attribute.</p><h2 id="reduction"><a class="header" href="#reduction">Reduction</a></h2><p>For many problems you need to combine a sequence of values into a single result. <code>std::accumulate</code> takes a beginning iterator, an ending iterator, and an initial value, then applies a binary operation (addition by default) to combine each element with the running total. <code>std::ranges::fold_left</code> is the ranges equivalent.</p><pre><code class="language-cpp">#include <vector>#include <numeric>#include <print>
int main() { std::vector<int> v{1,2,3,4,5}; int sum = std::accumulate(v.begin(), v.end(), 0); std::println("sum = {}", sum); return 0;}</code></pre><p>The example builds a vector of five integers and computes their sum.</p><p><code>std::reduce</code> is the parallel-friendly sibling of <code>std::accumulate</code>. It permits reordering of the operations, which lets an execution policy split the work across cores, but it requires the operation to be associative and the initial value to be an identity. Use <code>std::accumulate</code> when order matters and <code>std::reduce</code> when you only need the combined value.</p><p><code>std::inner_product</code> combines two ranges with two operations. It multiplies corresponding elements and adds the products, which computes a dot product in one call. This is the reduction form of a zip operation, and it shows how a single algorithm can express what is otherwise a nested loop. The two-operation form generalises to any pair of associative combiners, so it can compute weighted sums or concatenations across two sequences in a single pass.</p><h2 id="algorithmic-design-patterns"><a class="header" href="#algorithmic-design-patterns">Algorithmic design patterns</a></h2><p>Common tasks compose standard algorithms. Use filter‑map‑reduce: <code>std::views::filter</code>, <code>std::views::transform</code>, then <code>std::accumulate</code> or <code>std::ranges::fold_left</code>. Apply the erase‑remove idiom to discard elements without extra storage. Recognising these patterns yields concise code that leverages library guarantees.</p><h2 id="parallel-policies"><a class="header" href="#parallel-policies">Parallel policies</a></h2><p>Many algorithms accept an execution policy as the first argument. <code>std::execution::par</code> asks the implementation to run the work in parallel when it can. Support for parallel policies is optional in the standard and depends on the compiler and its runtime backend. On some toolchains <code>std::execution::par</code> is unavailable or falls back to sequential execution. Treat parallel overloads as a performance option, not a correctness feature. When you use them, remember that order-unstable algorithms can reorder equal elements, so tests must check value-level properties such as sums rather than exact sequence.</p><pre><code class="language-cpp">std::vector<int> v = {3, 1, 4, 1, 5};std::sort(std::execution::par, v.begin(), v.end());</code></pre><h2 id="common-pitfalls-1"><a class="header" href="#common-pitfalls-1">Common pitfalls</a></h2><ul><li><strong>Binary search on an unsorted range</strong> is undefined behaviour. Sort first.</li><li><strong>Invalidated iterators</strong>. <code>std::sort</code> can invalidate all iterators. Reacquire them after sorting.</li><li><strong>Iterator category mismatch</strong>. <code>std::sort</code> requires random‑access iterators. <code>std::list</code> iterators fail to compile.</li><li><strong>Destination too small</strong>. <code>std::copy</code> and <code>std::transform</code> write exactly as many elements as the source supplies. Use <code>std::back_inserter</code> or size the destination first.</li><li><strong>Sorting a node‑based container</strong>. <code>std::list</code> lacks random‑access iterators. Use its member <code>list::sort</code> instead.</li><li><strong>Projection side effects</strong>. A projection must be pure. State‑modifying projections break algorithm invariants.</li></ul><h2 id="try-this-11"><a class="header" href="#try-this-11">Try this</a></h2><p>Use <code>std::ranges</code> to keep only the even elements of a <code>std::vector<int></code>, square them, sort the result, and print each number on a single line.</p><pre><code class="language-cpp">// Write your solution here.</code></pre><p>No solution is provided. The reader must fill in the code.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="ranges-and-views"><a class="header" href="#ranges-and-views">Ranges and views</a></h1><h2 id="what-a-view-is"><a class="header" href="#what-a-view-is">What a view is</a></h2><p>A view is a lightweight, non-owning handle over a sequence. It borrows the elements and never allocates memory. The view does not manage the lifetime of its elements. The same principle underlies <code>std::string_view</code> and <code>std::span</code> that were introduced earlier. Both types expose a pointer and a length, and they refuse to copy the data. A range is any object that provides <code>begin()</code> and <code>end()</code> that return iterators. A view is a range that does not own its elements. In other words, every view is a range, but not every range is a view.</p><p>Because a view never allocates, construction is a constant‑time pointer‑plus‑size operation. The compiler can inline it and no heap traffic occurs. The view inherits the lifetime constraints of its underlying storage, so a dangling <code>std::span</code> results if the source is destroyed, which the compiler warns about. Declaring a parameter as <code>std::span<const T></code> also signals that the function reads elements without taking ownership, mirroring <code>std::string_view</code> for read‑only text. This combination of zero‑allocation construction and intent‑driven design reduces accidental copies and clarifies ownership boundaries across API surfaces.</p><p>A view has a precise definition in the standard. A type is a view if it is a range, is cheap to copy or move, and does not own the elements it presents. <code>std::ranges</code> even provides <code>std::ranges::owning_view</code> to wrap an owning container into the view model when an API demands a view but you must keep ownership locally. The inverse, <code>std::ranges::ref_view</code>, wraps a reference to a range you already own. Knowing which wrapper applies prevents both dangling and accidental copies.</p><h2 id="stdviews-adaptors"><a class="header" href="#stdviews-adaptors"><code>std::views</code> adaptors</a></h2><p>In the <code><ranges></code> header, a namespace <code>std::views</code> provides a family of adaptors. Each adaptor returns a new view that lazily transforms the underlying range. The most useful adaptors are listed below.</p><ul><li><code>filter</code>: keeps only elements that satisfy a predicate.</li><li><code>transform</code>: applies a function to each element.</li><li><code>take</code>: stops after a given number of elements.</li><li><code>drop</code>: skips a given number of elements.</li><li><code>reverse</code>: iterates the range in reverse order.</li><li><code>split</code>: splits a range on a delimiter.</li><li><code>iota</code>: generates an infinite arithmetic progression.</li></ul><p>These adaptors compose by piping (<code>|</code>). The pipe operator forwards the left-hand side as the source range to the right-hand adaptor, which returns a new view. Because each adaptor is itself a view, the resulting expression is a chain of lightweight objects that never allocate until the final consumption point.</p><p>The following example builds a pipeline that generates the integers from 1 to 10, keeps the even numbers, squares them, and takes the first three results. It then prints the numbers.</p><pre><code class="language-cpp">#include <ranges>#include <iostream>#include <vector>
int main() { // Generate numbers 1..10, keep evens, square them, take first three. auto rng = std::views::iota(1, 11) // 1..10 (exclusive upper bound) | std::views::filter([](int x){ return x % 2 == 0; }) | std::views::transform([](int x){ return x * x; }) | std::views::take(3); for (int v : rng) { std::cout << v << ' '; } std::cout << '\n';}</code></pre><p>It prints the three even squares <code>4 16 36</code>. The <code>EXPECT</code> string in the build file verifies that these three numbers appear in the output.</p><p>Beyond the basic adaptors, the standard library provides combinators such as <code>views::split</code> for tokenising a string on a delimiter and <code>views::reverse</code> for reverse iteration without copying. The adaptor set also covers structure: <code>views::chunk</code> groups elements into fixed-size subranges, <code>views::slide</code> produces overlapping windows, and <code>views::elements</code> extracts the Nth member of each tuple‑like element (e.g., <code>views::elements<0></code> extracts keys from a range of pairs). For associative containers, <code>views::keys</code> and <code>views::values</code> expose just the key or mapped type without copying. These utilities replace verbose loops and temporary containers with concise pipelines.</p><h2 id="laziness"><a class="header" href="#laziness">Laziness</a></h2><p>Views are evaluated on demand. The pipeline does not create a temporary container after each adaptor. The <code>take(3)</code> adaptor stops the source after three elements have been produced. This property enables short-circuiting of expensive sources.</p><p>Consider a situation where the source is an unbounded range, such as <code>std::views::iota(0)</code>. Without laziness, materialising the whole range requires infinite memory and never terminates. The lazy view evaluates each element only when the downstream consumer asks for it, and the <code>take</code> adaptor caps the evaluation.</p><p>The next example constructs an endless <code>iota</code> range, squares each value, and then takes only the first five results. Even though the source can produce an infinite number of elements, the program terminates after five squares because the view stops early.</p><pre><code class="language-cpp">#include <ranges>#include <iostream>
int main() { // Infinite iota, square each, take first five elements. auto rng = std::views::iota(0) // 0,1,2,... infinite | std::views::transform([](int x){ return x * x; }) | std::views::take(5); for (int v : rng) { std::cout << v << ' '; } std::cout << '\n';}</code></pre><p>The output contains the first five squares <code>0 1 4 9 16</code>. In contrast, a pre-ranges algorithm that first copies the <code>iota</code> range into a <code>std::vector</code> allocates billions of elements before the program must stop. The lazy view avoids that allocation entirely, saving both time and memory.</p><p>Laziness also improves cache behaviour. Each element is produced, transformed, and consumed in a single pass, keeping data in registers and avoiding a second memory pass. The same laziness applies to conditional stops: <code>views::take_while</code> keeps elements while a predicate holds and then halts, and <code>views::drop_while</code> discards until the predicate first fails. Because the adaptors are stateless, chaining <code>drop_while</code> with <code>take_while</code> on a sorted range extracts a contiguous band in one pass without building it.</p><h2 id="materialising-a-view-stdrangesto"><a class="header" href="#materialising-a-view-stdrangesto">Materialising a view: <code>std::ranges::to</code></a></h2><p>Sometimes code needs ownership of the elements produced by a view. The helper <code>std::ranges::to</code> materialises a view into a concrete container. The syntax is <code>view | std::ranges::to<Container>()</code>. The container type must be default-constructible and support <code>push_back</code> or equivalent insertion.</p><p>Materialisation is useful when an algorithm later requires random access, when the data must outlive the original source, or when an API expects an owning container. The operation copies each element exactly once and respects the allocator of the target container.</p><p>The target need not be a <code>std::vector</code>. <code>std::ranges::to</code> accepts any container meeting the insertion requirements, including <code>std::list</code>, <code>std::deque</code>, or a fixed-size <code>std::array</code> when the size is known. An optional allocator argument forwards to the container constructor, so materialisation can use a custom pool without changing the pipeline.</p><p>The following program creates the view <code>iota(1,6)</code>, materialises it into a <code>std::vector</code>, and prints the size of the vector.</p><pre><code class="language-cpp">#include <ranges>#include <iostream>#include <vector>
int main() { auto rng = std::views::iota(1, 6); // 1,2,3,4,5 auto vec = rng | std::ranges::to<std::vector<int>>(); std::cout << "size = " << vec.size() << '\n';}</code></pre><p>The program prints <code>size = 5</code>, confirming that the view was copied into a container of the requested size.</p><h2 id="projections-everywhere"><a class="header" href="#projections-everywhere">Projections everywhere</a></h2><p>Most range adaptors and algorithms accept a <em>projection</em>, a callable that extracts a member from each element. Projections let you avoid writing a lambda for a common operation. The syntax <code>&T::member</code> is a pointer-to-member that the algorithm treats as a projection.</p><p>Projections improve compile-time readability and often enable better inlining because the compiler sees a direct member access instead of a generic lambda capture. They also integrate with concepts that require a <code>std::indirectly_readable</code> predicate. This lets the standard library reason about the operation without executing user code.</p><p>The example below defines a simple <code>Point</code> struct with members <code>x</code> and <code>y</code>. A <code>std::vector<Point></code> is sorted by the <code>x</code> coordinate using the projection <code>&Point::x</code>. The program then prints the sorted <code>x</code> values.</p><pre><code class="language-cpp">#include <algorithm>#include <iostream>#include <vector>
struct Point { int x; int y;};
int main() { std::vector<Point> pts{{3,5},{1,2},{2,4}}; // Sort by x using a projection std::ranges::sort(pts, {}, &Point::x); for (const auto& p : pts) { std::cout << p.x << ' '; } std::cout << '\n';}</code></pre><p>The output <code>1 2 3</code> demonstrates that the projection eliminated the need for a custom comparator.</p><p>Beyond sorting, any algorithm that accepts a projection can use the member‑pointer form. <code>std::ranges::find(v, value, &T::key)</code> searches a range of objects for a specific key without a bespoke lambda. The same member‑pointer projection works on associative ranges through <code>views::values</code> and <code>views::keys</code>. For example, <code>m | std::views::values</code> yields the integers from a <code>std::map<std::string, int></code> directly. These patterns appear throughout the standard library and eliminate boilerplate code.</p><h2 id="stdranges-algorithms-versus-pre-ranges-algorithms"><a class="header" href="#stdranges-algorithms-versus-pre-ranges-algorithms"><code>std::ranges</code> algorithms versus pre-ranges algorithms</a></h2><p>The range algorithms in <code>std::ranges</code> operate directly on any range, including views. They return iterators that refer to the original elements, so no copying occurs. The pipe syntax composes algorithms with adaptors. This makes the intent clear and the code compact.</p><p>The pre-ranges algorithmic style often required explicit iterator arguments, temporary containers, and separate calls to <code>std::find</code> or <code>std::count</code>. The range version can operate on a filtered view without materialising the intermediate sequence.</p><p>The next program filters a <code>std::vector<int></code> for even numbers and then counts them using <code>std::ranges::count_if</code>. The count is printed.</p><pre><code class="language-cpp">#include <algorithm>#include <iostream>#include <vector>#include <ranges>
int main() { std::vector<int> data{1,2,3,4,5,6}; auto evens = data | std::views::filter([](int x){ return x % 2 == 0; }); auto count = std::ranges::count_if(evens, [](int){ return true; }); // count elements in view std::cout << "evens = " << count << '\n';}</code></pre><p>The expected output <code>evens = 3</code> shows that the algorithm works on the filtered view without materialising a new container.</p><p>Range algorithms also accept a projection argument, mirroring the adaptor behaviour. For example, <code>std::ranges::sort(v, {}, &T::member)</code> sorts a range of structures by a field without an explicit comparator lambda. This uniformity simplifies generic code that works with both plain values and aggregates.</p><p>Reduction joins the model as well. <code>std::ranges::fold_left</code> and <code>std::ranges::fold_right</code> accumulate a range with an initial value and a binary operation, returning the combined result rather than writing into a destination. They pair naturally with a filter and a transform, so the filter-map-reduce pattern from the algorithms chapter becomes one expression.</p><h2 id="custom-views-and-stdrange_adaptor_closure"><a class="header" href="#custom-views-and-stdrange_adaptor_closure">Custom views and <code>std::range_adaptor_closure</code></a></h2><p>Advanced users can create reusable pipelines by defining a <em>range adaptor closure</em>. A closure is a callable that returns a view when applied to a range. The closure can be used in a pipe expression just like the built-in adaptors.</p><p>Defining a closure typically involves a generic lambda that captures any configuration parameters and returns a composition of existing adaptors. Because the closure returns a <em>closure object</em> (<code>std::range_adaptor_closure</code>), the compiler treats it as another adaptor, preserving laziness.</p><p>The example below defines a simple adaptor <code>twice</code> that multiplies each element by two. The adaptor is expressed as a generic lambda that returns a <code>transform</code> view. The program creates an <code>iota</code> range, pipes it through <code>twice</code>, and prints the results.</p><pre><code class="language-cpp">#include <ranges>#include <iostream>
// A reusable, pipeable adaptor that doubles each element.struct Twice : std::ranges::range_adaptor_closure<Twice> { template <std::ranges::viewable_range R> auto operator()(R&& r) const { return std::views::transform(std::forward<R>(r), [](int x) { return x * 2; }); }};
inline constexpr Twice twice;
int main() { auto rng = std::views::iota(1, 5) | twice; // 1..4 doubled -> 2 4 6 8 for (int v : rng) { std::cout << v << ' '; } std::cout << '\n';}</code></pre><p>The output <code>2 4 6 8</code> confirms that the custom adaptor behaved like a built-in view. Readers can extend this pattern to more sophisticated pipelines, such as a <code>prime_filter</code> that composes <code>filter</code> with a deterministic primality test. Because the closure is a compile-time object, the optimizer can erase the intermediate layers entirely.</p><p>A view lives only as long as its source. Returning a view to a local container or temporary yields a dangling view, which the compiler warns about (see chapter 07). Because the pipeline composes at compile time, the compiler can collapse multiple adaptor layers into a single loop, achieving speed comparable to hand‑written loops while preserving readability.</p><h2 id="try-this-12"><a class="header" href="#try-this-12">Try this</a></h2><p>Build a pipeline that takes <code>std::views::iota(1, n)</code>, keeps only the multiples of 3, squares each kept value, takes the first 5 results, and prints them. The program must compile with the book’s standard settings and must run without allocating an intermediate container.</p><p>No solution is provided. The reader must write the code, register it as a <code>book_example</code>, and verify that the output contains five numbers.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="callables-and-type-erasure"><a class="header" href="#callables-and-type-erasure">Callables and type erasure</a></h1><h2 id="what-is-a-callable"><a class="header" href="#what-is-a-callable">What is a callable</a></h2><p>A callable denotes any entity that can be used after the function‑call operator <code>()</code>. The C++ standard groups several kinds under this umbrella: a function pointer to a free function, a function object (functor) that overloads <code>operator()</code>, a lambda (an unnamed function object generated from a capture list and body), <code>std::function</code> (a type‑erased wrapper that can hold any of the previous forms when the call signature matches), and a pointer to a member function or data member used with an object instance.</p><p>Generic algorithms (chapter 12) and range algorithms (chapter 13) accept a callable parameter expressed as a template type satisfying the <em>invocable</em> requirement, which allows the same algorithm to work with a raw pointer, a capturing lambda, or a <code>std::function</code>. This decouples the algorithm from the concrete call target and is central to generic programming.</p><h2 id="lambdas"><a class="header" href="#lambdas">Lambdas</a></h2><p>Lambdas provide a concise way to create a function object. The syntax starts with a capture list in square brackets, followed by an optional parameter list, an optional mutable specifier, an optional exception specification, and a body. The capture list determines which surrounding variables become members of the closure type.</p><ul><li><code>[x]</code> copies the variable <code>x</code> into the closure. The copy is a separate object. Later modifications of the original x do not affect the captured value.</li><li><code>[&x]</code> stores a reference to <code>x</code>. The closure can read and write the original variable through the reference.</li><li><code>[=, this]</code> captures all automatic variables that appear in the body by copy and also captures the current object <code>*this</code> by reference. This form is useful inside a non‑static member function when the lambda needs to read both data members and local variables.</li><li><code>[]</code> declares a capture‑less lambda. Such a lambda has no state and can be converted to a function pointer if it does not use <code>this</code>.</li></ul><p>A lambda can be declared <code>mutable</code> to allow modification of its captured copies. Without <code>mutable</code> the <code>operator()</code> of the closure is const, which forbids changing any captured by copy. Since C++20 a lambda can introduce explicit template parameters using the abbreviated syntax <code>[]<typename T>(T x) { return x }</code>. The compiler treats this as a generic lambda that can be instantiated with any type <code>T</code> that satisfies the body.</p><p>The following example demonstrates a lambda used as a predicate for <code>std::ranges::count_if</code>. The program builds a vector of integers, counts the elements that are even, and prints the result. The lambda captures nothing and therefore has the type <code>bool(int) const</code>.</p><pre><code class="language-cpp">#include <vector>#include <iostream>#include <algorithm>#include <ranges>
int main(){ std::vector<int> v = {1,2,3,4,5,6}; auto is_even = [](int x){ return x % 2 == 0; }; auto count = std::ranges::count_if(v, is_even); std::cout << "count = " << count << std::endl; return 0;}</code></pre><p>The test harness checks that the program prints the line <code>count = 3</code>. The lambda illustrates how a small, capture‑less callable integrates directly with a range algorithm.</p><h3 id="capturing-state-and-performance"><a class="header" href="#capturing-state-and-performance">Capturing state and performance</a></h3><p>When a lambda captures by value, the closure holds its own copy, which makes the lambda safe to copy and move. Capturing by reference stores a reference member, so copies still refer to the original variable, which matters when the lambda outlives that variable. A capture‑less lambda can convert to a function pointer, removing indirection. If a lambda captures state and the closure type is known, the compiler can inline the call and eliminate the indirect call. The only overhead is copying captured values at construction.</p><h2 id="stdfunction-and-type-erasure"><a class="header" href="#stdfunction-and-type-erasure"><code>std::function</code> and type erasure</a></h2><p><code>std::function<R(Args…)></code> is a class template that abstracts away the concrete callable type. Internally it stores a pointer to a type‑erased function object and a pointer to a virtual call dispatcher. When a callable is assigned to a <code>std::function</code>, the wrapper can allocate dynamic memory if the object does not fit into the Small‑Object Optimization buffer (typically 2 to 3 pointers). The allocation adds heap traffic and a level of indirection at each invocation.</p><p>The trade‑off is flexibility: <code>std::function</code> can hold any matching callable, which enables runtime polymorphism such as heterogeneous containers and user‑selected callables. In contrast, a template parameter preserves the concrete type, which allows the compiler to inline calls, eliminate the virtual dispatcher, and avoid heap allocation. This zero‑overhead approach is recommended for performance‑critical code.</p><p>The example below builds a <code>std::vector<std::function<int(int)>></code>. It stores three different callables: a free function that multiplies its argument by three, a capturing lambda that adds five, and a <code>std::bind</code> expression that multiplies by five. The program iterates the vector, calls each element with the argument <code>5</code>, and prints the result.</p><pre><code class="language-cpp">#include <vector>#include <functional>#include <iostream>#include <utility>
int triple(int x) { return x * 3; }
int main(){ std::vector<std::function<int(int)>> ops; ops.emplace_back(triple); // free function ops.emplace_back([](int x){ return x + 5; }); // lambda adds five using namespace std::placeholders; ops.emplace_back(std::bind([](int a, int b){ return a * b; }, _1, 5)); // multiply by 5
for(const auto &op : ops){ std::cout << op(5) << std::endl; } return 0;}</code></pre><p>The test expects the three lines <code>15</code>, <code>10</code>, and <code>25</code> in that order. The example shows how heterogeneous callables can coexist in a single container.</p><h3 id="allocation-and-when-to-prefer-stdfunction"><a class="header" href="#allocation-and-when-to-prefer-stdfunction">Allocation and when to prefer <code>std::function</code></a></h3><p>If the stored callable fits into the small‑object buffer, <code>std::function</code> does not allocate. In the example the lambda and the bound function are small enough, so no heap allocation occurs. If a callable captures a large <code>std::vector</code> by value, the wrapper will allocate to hold the captured data.</p><ul><li>When the callable type is not known at compile time, such as when a plugin supplies a callback.</li><li>When the callable must be stored in a homogeneous container that outlives the point of creation.</li><li>When the API is a boundary that other languages or runtimes will call.</li></ul><h2 id="when-to-erase-when-to-template"><a class="header" href="#when-to-erase-when-to-template">When to erase, when to template</a></h2><p>Choosing type erasure or a template depends on when the callable is known.</p><ul><li>If the algorithm is a library component that the user instantiates, the library must expose a template parameter (or a generic <code>auto</code> parameter) for the callable. This yields a bespoke instantiation for each caller, which allows the compiler to inline the call and generate optimal code.</li><li>If the algorithm is part of a runtime system, such as a GUI framework that stores user‑supplied callbacks, a networking library that registers event handlers, or a scripting engine that invokes user code, type erasure via <code>std::function</code> or <code>std::move_only_function</code> is appropriate.</li></ul><p>A practical decision rule:</p><ol><li>Ask whether the callable can be known at compile time.</li><li>If yes, write a template parameter. The compiler will emit a separate version for each distinct callable type.</li><li>If no, use <code>std::function</code> (or the move‑only variant) to hide the type.</li></ol><p>The rule helps avoid accidental performance loss in tight loops while still providing the flexibility needed at program boundaries.</p><h2 id="stdinvoke-and-stdinvoke_r"><a class="header" href="#stdinvoke-and-stdinvoke_r"><code>std::invoke</code> and <code>std::invoke_r</code></a></h2><p><code>std::invoke</code> is a utility that uniformly calls several kinds of callables. It accepts a callable and a set of arguments, then dispatches to the appropriate call expression. The overload handles:</p><ul><li>Regular function objects and function pointers.</li><li>Pointers to member functions, where the first argument is the object (or a reference, pointer, or smart pointer) on which to invoke the member.</li><li>Pointers to data members, where the result is the member value.</li></ul><p><code>std::invoke_r<R>(f, args…)</code> adds an explicit return‑type conversion, which forces the result to be converted to <code>R</code> before returning.</p><p>The following program defines a free function, a struct with a member function and a data member, and then calls each through <code>std::invoke</code>. The output demonstrates how the same helper covers all three cases.</p><pre><code class="language-cpp">#include <iostream>#include <functional>#include <string>
struct Obj { int value = 42; int get() const { return value; } int data = 99;};
int free_func(int x) { return x; }
int main(){ // free function via std::invoke std::cout << "free: " << std::invoke(free_func, 42) << std::endl;
Obj o{ .value = 7, .data = 99 }; // pointer to member function auto mem_fn = &Obj::get; std::cout << "member: " << std::invoke(mem_fn, o) << std::endl;
// pointer to data member auto mem_data = &Obj::data; std::cout << "data: " << std::invoke(mem_data, o) << std::endl; return 0;}</code></pre><p>The test harness checks for the three lines <code>free: 42</code>, <code>member: 14</code>, and <code>data: 99</code>. Using <code>std::invoke</code> simplifies generic code because the same syntax works for all callable categories.</p><h2 id="function-objects--functors"><a class="header" href="#function-objects--functors">Function objects / functors</a></h2><p>A function object, commonly called a functor, is a class or struct that defines <code>operator()</code>. The type is a regular class type, so it can have explicit constructors, data members, and custom copy or move behaviour. Functors are useful when the callable carries significant state that must be part of its type, for example when the state influences overload resolution or when the type must be named for ADL.</p><p>The example below defines a comparator that orders integers by their absolute distance from a reference value. The comparator stores the reference as a data member and implements a <code>constexpr</code> call operator. The program creates a vector, sorts it with <code>std::ranges::sort</code> using the functor, and prints the sorted sequence.</p><pre><code class="language-cpp">#include <vector>#include <algorithm>#include <iostream>#include <ranges>#include <cmath>
struct DistanceComparator { int ref; constexpr bool operator()(int a, int b) const { return std::abs(a - ref) < std::abs(b - ref); }};
int main(){ std::vector<int> v = {5,1,3,2,4}; std::ranges::sort(v, DistanceComparator{0}); // sort by distance from 0 (i.e., absolute value) for(int x: v) std::cout << x << ' '; std::cout << std::endl; return 0;}</code></pre><p>The test checks that the program prints <code>1 2 3 4 5</code>. The functor demonstrates how a stable type can be passed to algorithms without incurring any type‑erasure cost.</p><h3 id="when-a-functor-is-preferable"><a class="header" href="#when-a-functor-is-preferable">When a functor is preferable</a></h3><ul><li>When the callable must be copyable or movable in a predictable way.</li><li>When the functor participates in overload sets that depend on its type.</li><li>When the callable is part of a public API and the type name conveys intent.</li></ul><h2 id="stdmove_only_function-c23"><a class="header" href="#stdmove_only_function-c23"><code>std::move_only_function</code> (C++23)</a></h2><p><code>std::move_only_function<R(Args…)></code> is introduced in C++23 as the move‑only analogue of <code>std::function</code>. It provides type erasure while forbidding copying. This enables storage of callables that are not copyable, such as a lambda that captures a <code>std::unique_ptr</code> or a class that holds a mutex.</p><p>The move‑only wrapper eliminates the hidden copy‑construction step that <code>std::function</code> performs when a copy of the wrapper is made. When the program never copies the wrapper, the overhead is identical to <code>std::function</code> but with the added guarantee that the stored callable cannot be duplicated inadvertently.</p><blockquote><p><strong>Note</strong>: The book registers this feature as a <em>gap</em> on compilers that do not yet implement the header. The prose explains the semantics. The example is omitted to keep the build portable.</p></blockquote><h2 id="try-this-13"><a class="header" href="#try-this-13">Try this</a></h2><p>Build a <code>std::vector<std::function<double(double)>></code> containing three operations: the identity function, a lambda that squares its argument, and a lambda that adds one. Invoke each stored callable with the value <code>3.0</code> and print the three results on separate lines.</p><p>No solution is provided. The reader must write the code, register it as a <code>book_example</code>, and verify that the output contains the three numbers <code>3</code>, <code>9</code>, and <code>4</code>.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="numerics-and-multidimensional-views"><a class="header" href="#numerics-and-multidimensional-views">Numerics and multidimensional views</a></h1><h2 id="typesafe-math-constants-stdnumbers"><a class="header" href="#typesafe-math-constants-stdnumbers">Type‑safe math constants: <code>std::numbers</code></a></h2><p>The header <code><numbers></code> supplies <code>inline constexpr</code> constants for <code>float</code>, <code>double</code>, and <code>long double</code>. The primary name, e.g. <code>std::numbers::pi</code>, denotes a <code>double</code>. The alias <code>std::numbers::pi_v<T></code> yields the constant in type <code>T</code>. This removes the need for separate literals such as <code>M_PI</code> or user‑defined <code>constexpr</code> values.</p><p>The older macro <code>M_PI</code> originates from the C header <code><cmath></code>. It expands to a literal of type <code>double</code> and is not guaranteed to exist on all platforms. Because it is a macro, the constant cannot participate in overload resolution based on the target type. The <code>std::numbers</code> objects avoid those problems and can be used in all constexpr expressions.</p><p>The family includes more than π. <code>std::numbers::e</code>, <code>std::numbers::sqrt2</code>, <code>std::numbers::ln2</code>, and <code>std::numbers::phi</code> cover the constants most numerical code needs. The <code>_v</code> alias keeps the chosen precision: <code>std::numbers::pi_v<float></code> is a single-precision approximation, while <code>std::numbers::pi_v<long double></code> carries the widest precision the platform supports. Because the constants are <code>constexpr</code>, they can initialise <code>static</code> and <code>consteval</code> contexts without a runtime cost.</p><p>The header also provides reciprocals such as <code>std::numbers::inv_pi</code> and <code>std::numbers::inv_sqrt2</code>, which avoid a division at the call site when an inverse is what the math requires.</p><p>The example below prints the value of π, computes the area of a circle with radius <code>2.5</code>, and formats the result with <code>std::cout</code>. The test harness checks that the output contains the word <code>area</code>.</p><pre><code class="language-cpp">#include <iostream>#include <numbers>
int main(){ double r = 2.5; double area = std::numbers::pi * r * r; std::cout << "area = " << area << std::endl; return 0;}</code></pre><h2 id="core-floatingpoint-utilities-cmath"><a class="header" href="#core-floatingpoint-utilities-cmath">Core floating‑point utilities: <code><cmath></code></a></h2><p>The header <code><cmath></code> implements the classic mathematical functions. All functions are overloaded for <code>float</code>, <code>double</code>, and <code>long double</code>. Since C++26 the overload set also accepts integral arguments, promoting them to the appropriate floating type.</p><ul><li><code>std::sqrt(x)</code> returns the square root of <em>x</em>.</li><li><code>std::pow(b, e)</code> raises <em>b</em> to the power <em>e</em>.</li><li><code>std::hypot(x, y)</code> computes <code>√(x² + y²)</code> while protecting against overflow and underflow. The expression <code>std::sqrt(x*x + y*y)</code> can overflow when the magnitude of <em>x</em> or <em>y</em> is large.</li><li><code>std::floor(x)</code> returns the greatest integer not larger than <em>x</em>.</li><li><code>std::ceil(x)</code> returns the smallest integer not smaller than <em>x</em>.</li></ul><p>The set also contains safer primitives for interpolation and rounding. <code>std::midpoint(a, b)</code> returns the value halfway between <code>a</code> and <code>b</code> without the overflow that <code>a + (b - a) / 2</code> can suffer. <code>std::lerp(a, b, t)</code> computes <code>a + t * (b - a)</code> with proper endpoint handling. <code>std::fma(x, y, z)</code> computes <code>(x * y) + z</code> as a single fused operation, avoiding an intermediate rounding step and improving speed and accuracy on supporting hardware.</p><p>The classification functions <code>std::isfinite</code>, <code>std::isnan</code>, and <code>std::isinf</code> test a value’s category without throwing, which provides a reliable way to detect a failed computation.</p><p>In addition, <code>std::copysign(x, y)</code> copies the sign of <code>y</code> onto the magnitude of <code>x</code>, which is the correct way to negate a zero or to preserve a sign across an operation. <code>std::nextafter(x, y)</code> steps to the next representable value toward <code>y</code>, exposing the discrete nature of floating-point for tolerance and unit-test work.</p><p>In the following program we compute a right‑angled triangle with legs <code>3</code> and <code>4</code>. The call to <code>std::hypot</code> yields <code>5</code> without intermediate overflow. The test expects the line <code>hypot = 5</code>.</p><pre><code class="language-cpp">#include <iostream>#include <cmath>int main(){ double a = 3.0, b = 4.0; double h = std::hypot(a, b); std::cout << "hypot = " << static_cast<int>(h) << std::endl; return 0;}</code></pre><h2 id="complex-arithmetic-stdcomplex"><a class="header" href="#complex-arithmetic-stdcomplex">Complex arithmetic: <code>std::complex</code></a></h2><p>The class template <code>std::complex<T></code> stores a complex number whose real and imaginary parts are of type <code>T</code>. Member functions <code>real()</code> and <code>imag()</code> provide access to the components. The standard library overloads the arithmetic operators so that addition, subtraction, multiplication, and division operate component‑wise.</p><p>Two helper functions are useful for magnitude calculations. <code>std::norm(z)</code> returns the squared magnitude (<code>real(z)² + imag(z)²</code>). <code>std::abs(z)</code> returns the magnitude itself (<code>√norm(z)</code>). The factory function <code>std::polar(r, θ)</code> constructs a complex number from polar coordinates, where <code>r</code> is the radius and <code>θ</code> the angle in radians.</p><p>The library also overloads the transcendental functions for <code>std::complex</code>, so <code>std::sin</code>, <code>std::exp</code>, and <code>std::log</code> accept complex arguments and return complex results. <code>std::conj(z)</code> returns the conjugate, and <code>std::proj(z)</code> returns the projection onto the Riemann sphere, which matters for infinities. Only <code>std::complex<float></code>, <code>std::complex<double></code>, and <code>std::complex<long double></code> are well formed. Instantiating <code>std::complex<int></code> is ill formed, because integer complex arithmetic is not defined by the standard.</p><p>A user-defined literal makes complex literals readable. The <code>std::literals::complex_literals</code> inline namespace provides the <code>i</code> suffix, so <code>auto z = 1.0 + 2.0i</code> builds <code>1 + 2i</code> without a constructor call. The literal works for <code>float</code>, <code>double</code>, and <code>long double</code>.</p><p>The program below constructs a complex number <code>z = 3 + 4i</code>, prints its real and imaginary parts, computes its magnitude with <code>std::abs</code>, and creates a second complex number via <code>std::polar</code>. The test looks for the word <code>real</code> in the output.</p><pre><code class="language-cpp">#include <iostream>#include <complex>#include <numbers>
int main(){ std::complex<double> z(3.0, 4.0); std::cout << "real = " << z.real() << ", imag = " << z.imag() << std::endl; std::cout << "abs = " << std::abs(z) << std::endl; auto p = std::polar(2.0, std::numbers::pi/4); std::cout << "polar real = " << p.real() << std::endl; return 0;}</code></pre><h2 id="compiletime-rational-numbers-stdratio"><a class="header" href="#compiletime-rational-numbers-stdratio">Compile‑time rational numbers: <code>std::ratio</code></a></h2><p><code>std::ratio<N, D></code> encodes a rational number as two compile‑time integer template arguments. The type can be used in non‑type template parameters, which enables compile‑time arithmetic without additional run‑time cost.</p><p>The library provides metafunctions such as <code>std::ratio_add<A, B></code> and <code>std::ratio_multiply<A, B></code>. These compute a new <code>std::ratio</code> that represents the sum or product of the two operand ratios. The result is available as a nested <code>type</code> member.</p><p><code>std::ratio</code> is the foundation of the <code><chrono></code> duration type. <code>std::chrono::milliseconds</code> is <code>std::chrono::duration<int, std::ratio<1, 1000>></code>, so a ratio literal becomes a unit of time. The trait <code>std::ratio_equal<A, B></code> and the ordering <code>std::ratio_less<A, B></code> let templates reason about the relationship between two ratios at compile time, and <code>std::ratio_divide<A, B></code> produces the quotient. All of these are <code>constexpr</code>, so the compiler evaluates them entirely during compilation.</p><p>For example, <code>std::chrono::duration<long, std::ratio<1, 1000>></code> is exactly milliseconds: a duration that stores a count of <code>long</code> ticks where each tick is one thousandth of a second. Swapping the ratio to <code>std::ratio<1, 1></code> yields seconds and to <code>std::ratio<60, 1></code> yields minutes, all from the same template.</p><p>The example defines a base ratio representing one thousandth (<code>std::ratio<1, 1000></code>), prints its numerator and denominator, and then uses <code>std::ratio_add</code> to add two such ratios, which produces <code>2/1000</code>. The test harness searches for the exact string <code>1/1000</code>.</p><pre><code class="language-cpp">#include <iostream>#include <ratio>
int main(){ using thousand = std::ratio<1,1000>; std::cout << thousand::num << '/' << thousand::den << std::endl; using sum = std::ratio_add<thousand, thousand>::type; // sum is 2/1000, but we only need to show the base ratio (void)sum{}; // suppress unused warning return 0;}</code></pre><h2 id="integer-helpers-stdgcd-and-stdlcm"><a class="header" href="#integer-helpers-stdgcd-and-stdlcm">Integer helpers: <code>std::gcd</code> and <code>std::lcm</code></a></h2><p>The functions <code>std::gcd(a, b)</code> and <code>std::lcm(a, b)</code> compute the greatest common divisor and the least common multiple of two integral values. Implementations use highly optimized code to execute the Euclidean algorithm efficiently. Writing these algorithms manually can produce slower code.</p><p>The binary Euclidean algorithm runs in logarithmic time, so even 64-bit arguments complete in a few dozen cycles.</p><p>Both functions are <code>constexpr</code> and return the common type of their arguments after the usual arithmetic conversions. <code>std::gcd</code> is the building block for reducing fractions and for the Euclidean distance checks used in number theory, while <code>std::lcm</code> sizes a buffer that must hold a whole number of repeats of two periodic signals. Reaching for the standard functions avoids the off-by-one and signedness bugs that hand-written versions attract.</p><p>The program below calculates <code>gcd(48, 180)</code> and <code>lcm(48, 180)</code>. The expected output contains the two lines <code>gcd = 12</code> and <code>lcm = 720</code>.</p><pre><code class="language-cpp">#include <iostream>#include <numeric>
int main(){ int a = 48, b = 180; std::cout << "gcd = " << std::gcd(a, b) << std::endl; std::cout << "lcm = " << std::lcm(a, b) << std::endl; return 0;}</code></pre><h2 id="multidimensional-nonowning-views-stdmdspan"><a class="header" href="#multidimensional-nonowning-views-stdmdspan">Multidimensional non‑owning views: <code>std::mdspan</code></a></h2><p><code>std::mdspan</code> is a non‑owning view that maps a contiguous block of memory to a multi‑dimensional array. It extends <code>std::span</code> to support a compile‑time known number of dimensions. The template parameters are the element type and an <code>extents</code> description, which can be static, dynamic, or mixed.</p><p>Two layout policies exist. <code>std::layout_right</code> stores elements in row‑major order, which matches the layout of C‑style arrays and of <code>std::vector</code>. <code>std::layout_left</code> stores elements in column‑major order, which aligns with the conventions of Fortran and some linear‑algebra libraries. The layout determines the stride that the view applies when a multidimensional index is supplied.</p><p>The extents can be dynamic instead of static. <code>std::extents<std::size_t, std::dynamic_extent, 4></code> fixes only the column count and takes the row count at construction, which suits matrices whose size is known at runtime. <code>submdspan</code> slices a view into a smaller view without copying, mirroring <code>std::span::subspan</code> from chapter 11. The accessor template argument controls how elements are read and written, so an <code>mdspan</code> can present a strided or even a non-contiguous layout while keeping the same multidimensional interface.</p><p>Because <code>mdspan</code> is the multidimensional sibling of <code>std::span</code>, the same rule from chapter 11 applies. The view borrows storage and must not outlive the container it observes, or the indexed access reads freed memory.</p><p>Like <code>std::span</code>, <code>mdspan</code> is <code>constexpr</code> friendly, so a small matrix held in static storage can be indexed inside a <code>consteval</code> context and checked by <code>static_assert</code>.</p><p>The example creates a <code>std::vector<double></code> with twelve values. It then constructs a <code>std::mdspan<double, std::extents<std::size_t, 3, 4>, std::layout_right></code> that views the vector as a 3 × 4 matrix. A nested loop prints each row on a separate line. The test verifies the three rows <code>0 1 2 3</code>, <code>4 5 6 7</code>, and <code>8 9 10 11</code>.</p><pre><code class="language-cpp">#include <iostream>#include <vector>#include <mdspan>
int main(){ std::vector<double> data(12); for (std::size_t i = 0; i < data.size(); ++i) data[i] = static_cast<double>(i); std::mdspan<double, std::extents<std::size_t, 3, 4>, std::layout_right> view(data.data()); for (std::size_t r = 0; r < 3; ++r) { for (std::size_t c = 0; c < 4; ++c) { std::size_t idx = r * 4 + c; // row‑major stride std::cout << *(view.data_handle() + idx); if (c + 1 < 4) std::cout << ' '; } if (r + 1 < 3) std::cout << '\n'; } return 0;}</code></pre><h2 id="linear-algebra-stdlinalg-c26"><a class="header" href="#linear-algebra-stdlinalg-c26">Linear algebra: <code>std::linalg</code> (C++26)</a></h2><p>The header <code><linalg></code> adds a lightweight linear‑algebra library that operates directly on <code>std::mdspan</code> objects. Matrix‑matrix, matrix‑vector, and vector‑vector products are expressed as free functions that accept <code>std::mdspan</code> parameters. Because the functions work on views, no temporary storage is required.</p><p>Unlike the decades-old BLAS interface, <code>std::linalg</code> is generic over the element type and the layout, so the same call works on <code>float</code>, <code>double</code>, or a custom accumulator, and on row-major or column-major storage without rewriting.</p><p>All operations are <code>constexpr</code> where the underlying arithmetic permits compile‑time evaluation. This enables static analysis of small linear‑algebra expressions and permits their use in <code>static_assert</code> statements.</p><p>Support for <code><linalg></code> is not yet present in the default Clang toolchain used by the build pipeline. The book therefore presents the feature in prose only, with a callout that states the feature is a new addition in C++26 and will become available when compilers implement the header. Consequently, any example that includes <code><linalg></code> fails to compile until the compiler ships the header.</p><p>The functions follow a consistent naming pattern. <code>linalg::matrix_product</code>, <code>linalg::dot</code>, and <code>linalg::vector_norm</code> take read-only input views and an output view, returning the result through the last argument rather than by value. This output-parameter style keeps the operation allocation free and lets the caller choose the storage.</p><h2 id="try-this-14"><a class="header" href="#try-this-14">Try this</a></h2><p>Create a <code>std::vector<double></code> that contains twelve values of your choice. View the vector as a 3 × 4 <code>std::mdspan</code>. Print the element at row 1, column 2 using the call syntax <code>mdspan(r, c)</code>. Verify that the printed value matches the element you stored.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="templates-i-functions-that-match"><a class="header" href="#templates-i-functions-that-match">Templates I: functions that match</a></h1><h2 id="why-templates"><a class="header" href="#why-templates">Why templates</a></h2><p>Templates let a single definition work for many types. The compiler creates a concrete version for each type that appears in a program. This mechanism is compile‑time polymorphism. Runtime polymorphism uses virtual functions, a v‑table, and an indirection. Templates eliminate both indirection and heap allocation. The standard library builds every container and algorithm from templates. <code>std::vector<T></code> and <code>std::ranges::sort</code> illustrate this fact.</p><p>Read templates as term-rewriting rules with unification, in the sense a Prolog programmer knows. A template is a pattern. The compiler unifies the call’s argument types against the pattern’s parameters, binding <code>T</code> to a concrete type, then rewrites the call into a specialized instance. Overload resolution and specialization are rule precedence on top of that unification. This mental model explains why error messages name a failed unification rather than a line in your logic.</p><p>Templates let library authors write code that works for any iterator type, raw pointers, <code>std::vector</code> iterators, or user‑defined iterators. The compiler enforces required operations at each instantiation and reports errors attached to the generated code, providing early feedback. Because each specialization is generated at compile time, the optimizer can inline, eliminate dead code, and propagate constants, yielding zero‑overhead performance comparable to hand‑written code. This combination of type‑safe generic programming and compile‑time abstraction makes templates the preferred tool for reusable library components.</p><h2 id="function-templates"><a class="header" href="#function-templates">Function templates</a></h2><p>A function template starts with the keyword <code>template</code>. The following example defines a generic <code>max</code> function.</p><pre><code class="language-cpp">template<typename T>T max(T a, T b) { return a < b ? b : a;}</code></pre><p>The compiler deduces <code>T</code> from the arguments at the call site. When deduction fails, the caller can supply the type explicitly, for example <code>max<int>(a, b)</code>. Deduction works for built‑in types, standard library types, and user‑defined types that provide the required operators.</p><p>Deduction can fail or surprise. <code>max(1, 2.0)</code> is ambiguous because <code>T</code> cannot be both <code>int</code> and <code>double</code>. The fix is an explicit <code>max<double>(1, 2.0)</code> or converting the arguments first. The same ambiguity appears whenever two arguments disagree in type, which is why generic helpers often take <code>const T&</code> and let the caller supply matching types.</p><p>Template argument deduction follows a set of deduction guides. When a parameter is a reference, the reference qualifiers are preserved. When a parameter is a forwarding reference (<code>T&&</code>), deduction yields an lvalue reference for lvalue arguments and an rvalue reference for rvalue arguments. This mechanism underlies perfect forwarding, a technique used throughout the standard library.</p><p>The example program calls <code>max</code> with two <code>int</code> values and prints the result.</p><pre><code class="language-cpp">#include <print>
template<typename T>T max(T a, T b) { return a < b ? b : a;}
int main() { int a = 3; int b = 9; std::println("max = {}", max(a, b));}</code></pre><h2 id="class-templates"><a class="header" href="#class-templates">Class templates</a></h2><p>Class templates use the same syntax as function templates. The example defines a simple wrapper <code>Box</code> that stores a value of type <code>T</code>.</p><pre><code class="language-cpp">template<typename T>struct Box { T value;};</code></pre><p><code>std::vector<T></code> (introduced in chapter 11) is the canonical class template. A class template can contain member functions, static data, and nested type definitions that also depend on the template parameters.</p><p>Class template argument deduction (CTAD) lets the compiler infer <code>T</code> from the constructor arguments, so <code>Box(42)</code> deduces <code>Box<int></code> without writing the angle brackets. A deduction guide can teach the compiler a non-obvious mapping, for example deducing a <code>Box<std::string></code> from a string literal. CTAD removes the boilerplate that earlier C++ required for every generic constructor call.</p><p>The example program creates a <code>Box<int></code> and a <code>Box<std::string></code> and prints each <code>value</code>.</p><pre><code class="language-cpp">#include <print>#include <string>
template<typename T>struct Box { T value;};
int main() { Box<int> ibox{42}; Box<std::string> sbox{"hello"}; std::println("int box: {}", ibox.value); std::println("string box: {}", sbox.value);}</code></pre><p>A member variable inside a class template is instantiated once per distinct template argument. For instance, a class template can define <code>int count</code> to provide a separate count for each <code>T</code>. This pattern supports compile‑time registries with zero runtime allocation.</p><h2 id="abbreviated-function-templates-c20"><a class="header" href="#abbreviated-function-templates-c20">Abbreviated function templates (C++20)</a></h2><p>C++20 introduced a shorthand for simple function templates. The declaration <code>auto f(auto x)</code> expands to <code>template<typename T> auto f(T x)</code>. The following program demonstrates both forms.</p><pre><code class="language-cpp">// full formtemplate<typename T>auto identity_full(T x) { return x; }
// abbreviated formauto identity_abbrev(auto x) { return x; }</code></pre><p>Both functions return their argument unchanged. The abbreviated form reduces boilerplate and improves readability when the function body does not depend on the template parameter name.</p><pre><code class="language-cpp">#include <print>
template<typename T>auto identity_full(T x) { return x; }
auto identity_abbrev(auto x) { return x; }
int main() { std::println("full = {}", identity_full(7)); std::println("abbrev = {}", identity_abbrev(7));}</code></pre><p>Abbreviated templates also integrate with generic lambdas. A lambda such as <code>[](auto x){ return x }</code> is internally a function template, allowing the same zero‑overhead semantics.</p><p>An <code>auto</code> parameter in a template is a forwarding reference when it is a template parameter: <code>template<typename T> void f(T&& x)</code> and <code>void f(auto&& x)</code> both bind <code>T</code> to the value category of the argument, so <code>std::forward<T>(x)</code> preserves whether the caller passed an lvalue or an rvalue. This is the mechanism behind <code>std::make_unique</code> and the perfect-forwarding <code>wrapper</code> from chapter 08. A plain <code>T&&</code> that is not a template parameter is an rvalue reference, not a forwarding reference, so the distinction lives in the template parameter list.</p><p>Templates also accept a variable number of type parameters with a parameter pack: <code>template<typename... Ts> struct Tuple { }</code>. The ellipsis <code>...</code> both declares the pack and expands it. Fold expressions collapse a pack with a binary operator, so <code>((std::cout << args << ' ') ...)</code> prints every argument without writing a loop or a recursive base case. Pack expansion is the compile-time counterpart of a variadic function, and it produces a fully inlined call sequence.</p><p>For example, a single function can sum a pack with <code>(0 + ... + args)</code>, or print every argument with <code>((std::cout << args << ' ') ...)</code>. The compiler expands the pattern into <code>std::cout << a << ' ' , std::cout << b << ' '</code> and so on, with no recursion and no runtime dispatch. This is the modern replacement for the recursive variadic helpers that older C++ required.</p><h2 id="dependent-names-and-the-typenametemplate-keywords"><a class="header" href="#dependent-names-and-the-typenametemplate-keywords">Dependent names and the <code>typename</code>/<code>template</code> keywords</a></h2><p>When a name depends on a template parameter, the compiler cannot know whether it is a type or a value. The following snippet shows two common gotchas.</p><pre><code class="language-cpp">template<typename T>void foo(T t) { // dependent type requires 'typename' typename T::type *ptr = nullptr; (void)ptr; // silence unused variable warning // dependent member template requires 'template' t.template bar<double>();}</code></pre><p>The <code>typename</code> keyword disambiguates a dependent type. The <code>template</code> keyword disambiguates a dependent member template. Without these qualifiers the code fails to compile. Experienced developers encounter these errors frequently when writing generic libraries.</p><pre><code class="language-cpp">#include <print>
struct HasType { using type = int; template<typename U> void bar() { std::println("bar<{}> called", typeid(U).name()); }};
template<typename T>void demo(T t) { // Dependent type requires 'typename' typename T::type *ptr = nullptr; (void)ptr; // silence unused // Dependent member template requires 'template' t.template bar<double>();}
int main() { HasType obj; demo(obj); std::println("dependent demo ok");}</code></pre><p>A practical illustration is <code>std::vector<T>::iterator</code>. Inside a template that works with an arbitrary container, writing <code>typename C::iterator it</code> is required. The compiler treats <code>iterator</code> as a static member.</p><h2 id="instantiation"><a class="header" href="#instantiation">Instantiation</a></h2><p>The compiler generates concrete code when a template is used. This process is called implicit instantiation. Each distinct set of template arguments triggers a separate instantiation. Implicit instantiation occurs at the point of first use. Errors inside the template body appear only when a particular specialization is needed.</p><p>The late-error property has a downside. Because a member is only compiled when instantiated, a typo in an unused branch of a template can escape every build until a caller instantiates that branch. Concepts (chapter 17) address this by checking constraints before instantiation, so mismatches surface at the call rather than deep inside a library.</p><p>Explicit instantiation forces the compiler to emit code for a given set of arguments, even if the program never mentions the specialization. This technique can reduce compile time in large builds because the implementation can be compiled once and reused across translation units.</p><p>Templates are not free for the build system. Every instantiation produces object code, so a template used with many distinct types can increase binary size and compile time. Explicit instantiation and the <code>extern template</code> declaration control this growth, which matters in large code bases where template-heavy headers are included widely.</p><pre><code class="language-cpp">extern template class std::vector<int>; // suppress implicit instantiation</code></pre><p>A matching <code>template class std::vector<int></code> line in another translation unit triggers explicit instantiation. The example program demonstrates explicit instantiation and prints the size of a vector to prove that the code was generated.</p><pre><code class="language-cpp">#include <vector>#include <print>
// Explicit instantiation of std::vector<int>template class std::vector<int>;
int main() { std::vector<int> v{1,2,3}; std::println("size = {}", v.size()); return 0;}</code></pre><p>Explicit instantiation also interacts with the one‑definition rule. The definition must appear in exactly one translation unit. Otherwise the linker reports multiple definition errors. This rule reinforces the importance of a clear build structure.</p><p>The examples instantiate at runtime, but templates also compute at compile time. Chapters 18 and 21 use this to perform type‑level computation and static dispatch. Templates can take non‑type parameters, for example an integer size in <code>std::array<T, N></code>, so the length is known at compile time. Chapter 19 covers this in detail.</p><h2 id="try-this-15"><a class="header" href="#try-this-15">Try this</a></h2><p>Write a template <code>clamp</code> that limits a value to a closed interval.</p><pre><code class="language-cpp">template<typename T>T clamp(T v, T lo, T hi) { /* return lo if v < lo, hi if v > hi, otherwise v */ }</code></pre><p>Call it with a value below <code>lo</code>, a value between <code>lo</code> and <code>hi</code>, and a value above <code>hi</code>.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="concepts-the-constraint-language"><a class="header" href="#concepts-the-constraint-language">Concepts: the constraint language</a></h1><h2 id="why-concepts"><a class="header" href="#why-concepts">Why concepts</a></h2><p>Templates allow algorithms to work with any type that satisfies a set of requirements. Before C++20 those requirements were expressed with SFINAE tricks such as <code>std::enable_if</code> and trait metafunctions. The constraints were hidden inside long type expressions, and a failure produced a cascade of template‑instantiation diagnostics that were difficult to read. Concepts replace that style with explicit, named predicates. A concept is part of the function signature. The compiler checks it before it attempts to instantiate the template. If the requirement is not met, the diagnostic cites the concept name and the offending type. The error becomes clear and the intent obvious.</p><p>Concepts also act as in‑code documentation: the concept name (e.g., <code>std::integral</code> or <code>std::range</code>) states the precondition next to the declaration, removing the need for separate <code>enable_if</code> blocks. A concept is a compile‑time <code>bool</code> value, usable in <code>if constexpr</code> or <code>static_assert</code>, and it drives overload resolution and in‑body branching. Because the compiler checks the constraint before template instantiation, failures appear at the call site with the concept name and offending type, providing earlier, clearer diagnostics than a <code>static_assert</code> inside the function body.</p><h2 id="the-requires-clause-and-requires-expression"><a class="header" href="#the-requires-clause-and-requires-expression">The <code>requires</code> clause and <code>requires</code> expression</a></h2><p>A <em>requires clause</em> follows a template declaration and names one or more concepts that must be satisfied.</p><pre><code class="language-cpp">template<std::integral T>void f(T t) { std::cout << "integral: " << t << '\n';}</code></pre><p>A <em>requires expression</em> appears inside a template body and describes the operations that must exist for a particular set of types.</p><pre><code class="language-cpp">template<typename T>requires requires (T a) { { a + a } -> std::same_as<T>; }T twice(T a) { return a + a; }</code></pre><p>If the predicate evaluates to <code>false</code>, the overload is removed from overload resolution. The following example demonstrates two overloads, one for integral types and one for floating‑point types, and prints which overload ran.</p><pre><code class="language-cpp">#include <iostream>#include <type_traits>
// Overload for integral typestemplate<std::integral T>void show(T value) { (void)value; std::cout << "integral overload\n";}
// Overload for floating‑point typestemplate<std::floating_point T>void show(T value) { (void)value; std::cout << "floating overload\n";}
int main() { show(42); // integral show(3.14); // floating return 0;}</code></pre><p>The <code>requires</code> expression can be combined with logical operators to form richer constraints:</p><pre><code class="language-cpp">template<typename T>requires (std::integral<T> && sizeof(T) >= 4)void process(T value) { std::cout << "'integral (sizeof >= 4)'} " << value << '\n';}</code></pre><p>The compiler evaluates the whole Boolean expression before overload resolution, eliminating surprising matches.</p><p>The <code>requires</code> clause and the <code>requires</code> expression are distinct tools. The clause (<code>requires std::integral<T></code>) attaches a constraint to a declaration. The expression (the <code>requires (T a) { ... }</code> block) describes the operations a type must support and can test return types with <code>-> std::same_as<U></code>. A <code>requires</code> expression can also guard a single member function, so a class template can offer an operation only when the element type supports it.</p><h2 id="defining-a-concept"><a class="header" href="#defining-a-concept">Defining a concept</a></h2><p>A concept is a named predicate. It can be written with the <code>concept</code> keyword and a <em>requires clause</em> that enumerates the required expressions.</p><pre><code class="language-cpp">#include <iostream>#include <type_traits>
// Concept that requires additiontemplate<typename T>concept Addable = requires (T a, T b) { a + b; };
// Function using the concepttemplate<Addable T>T add(T a, T b) { return a + b;}
int main() { std::cout << "int add: " << add(2, 3) << std::endl; std::cout << "string add: " << add(std::string{"hi"}, std::string{"!"}) << std::endl; return 0;}</code></pre><p>The <code>Addable</code> concept checks that the binary <code>+</code> operator is valid for two operands of type <code>T</code>. The accompanying <code>add</code> function uses the concept as a constraint, so any type that models <code>Addable</code> can be added safely. Because the concept name appears directly in the signature, the intent is clear at the call site.</p><p>Concepts can also be expressed as Boolean formulas of other concepts. This enables a small vocabulary of high‑level requirements while reusing primitive concepts from <code><concepts></code>:</p><pre><code class="language-cpp">template<typename T>concept Number = std::integral<T> || std::floating_point<T>;</code></pre><p>A library author can build more expressive constraints by layering concepts.</p><p>A <code>requires</code> expression enumerates several checks separated by semicolons inside the brace, each a statement that must be valid. Return-type constraints use an arrow: <code>{ a + b } -> std::same_as<T></code> demands that <code>a + b</code> not only compiles but also yields a type convertible to <code>T</code>. Because a concept reduces to a <code>constexpr bool</code>, you can combine concepts with <code>&&</code>, <code>||</code>, and <code>!</code> to build richer requirements without writing a new named concept.</p><p>The same Boolean nature lets a function branch on a concept with <code>if constexpr</code>, selecting an implementation without writing separate overloads. <code>if constexpr (std::is_pointer_v<T>) { /* pointer path */ } else { /* value path */ }</code> picks the branch at compile time and discards the unused one, so the body need not be valid for every <code>T</code>. This replaces the old tag-dispatch and SFINAE patterns for in-body selection.</p><h2 id="subsumption"><a class="header" href="#subsumption">Subsumption</a></h2><p>When two concepts overlap, the more specific one wins. This rule is called <em>subsumption</em> and mirrors goal ordering in Prolog: the most specific rule is chosen before a more generic one. The compiler builds a partial ordering of candidate functions based on the subsumption relationship of their constraints.</p><pre><code class="language-cpp">#include <iostream>#include <type_traits>
// Overload for integral typestemplate<std::integral T>void foo(T) { std::cout << "integral overload" << std::endl;}
// Overload for signed integral types (more specific)template<std::signed_integral T>void foo(T) { std::cout << "signed integral overload" << std::endl;}
int main() { unsigned int u = 1; int s = -1; foo(u); // should select integral overload foo(s); // should select signed integral overload return 0;}</code></pre><p>Subsumption removes ambiguity from overload sets. When two constrained overloads both match, the compiler prefers the overload whose constraint subsumes the other. For example, <code>std::signed_integral</code> implies <code>std::integral</code>. Calls with a signed type select the <code>std::signed_integral</code> overload, while unsigned calls select the generic overload. This deterministic ordering eliminates the ambiguous‑overload errors common with SFINAE tricks.</p><h2 id="terse-syntax"><a class="header" href="#terse-syntax">Terse syntax</a></h2><p>C++26 allows constraints to appear directly on a parameter type or on a plain <code>auto</code> placeholder.</p><pre><code class="language-cpp">void show(std::integral auto x) { std::cout << x << '\n'; }std::integral auto n = 5;</code></pre><p>The same pattern works for references, forwarding references, and ranges. The example below uses a constrained variable, a constrained parameter, and a call to <code>std::ranges::sort</code>.</p><pre><code class="language-cpp">#include <iostream>#include <vector>#include <algorithm>#include <ranges>
// Constrained parametervoid show(std::integral auto n) { std::cout << "value: " << n << '\n';}
int main() { std::integral auto n = 7; // constrained variable show(n); std::vector<int> v = {5, 2, 9, 1, 4}; std::ranges::sort(v); std::cout << "sorted:"; for (int x : v) std::cout << ' ' << x; std::cout << std::endl; return 0;}</code></pre><p>These terse declarations keep the constraint next to the entity it describes, improving readability. Because the constraint travels with the parameter, a constrained lambda passed to <code>std::ranges::sort</code> is checked the moment the call is written, not when the algorithm instantiates. They also work inside generic lambdas. This enables concise constraint specifications:</p><pre><code class="language-cpp">auto cmp = [] (std::totally_ordered auto const& a, std::totally_ordered auto const& b) { return a < b;};</code></pre><p>Constraints also attach to return types through the same <code>auto</code> syntax: <code>std::integral auto square(std::integral auto x) { return x * x }</code> constrains both the parameter and the result. Algorithm authors lean on <code>std::predicate</code> and <code>std::relation</code> to describe the callables they accept, so a sorting routine declares <code>std::predicate<std::weak_ordering, T, T></code> instead of a vague template parameter.</p><h2 id="constrained-algorithms-and-ranges"><a class="header" href="#constrained-algorithms-and-ranges">Constrained algorithms and ranges</a></h2><p>Standard algorithms already carry concept requirements. <code>std::ranges::sort</code> requires <code>std::sortable</code>. <code>std::ranges::find</code> requires <code>std::range</code> and an <code>std::indirectly_comparable</code> predicate. The compiler checks those concepts before instantiating the algorithm, so a call that does not meet the requirements fails at the call site with a clear diagnostic.</p><pre><code class="language-cpp">#include <iostream>#include <vector>#include <algorithm>#include <ranges>
int main() { std::vector<int> v = {1, 2, 3, 4, 5}; auto it = std::ranges::find(v, 3); if (it != v.end()) { std::cout << "found" << std::endl; } else { std::cout << "not found" << std::endl; } return 0;}</code></pre><p>The program searches a <code>std::vector<int></code> for a value. Because the container satisfies <code>std::range</code>, the call compiles. A raw array fails the <code>range</code> concept, and the compiler reports that the concept is not satisfied. Since the concepts appear in the algorithm’s signature, users see the preconditions directly without consulting external documentation or static assertions.</p><h2 id="library-concept-vocabulary"><a class="header" href="#library-concept-vocabulary">Library concept vocabulary</a></h2><p>The header <code><concepts></code> provides the core building blocks used throughout the standard library:</p><ul><li><code>std::integral</code>: all integral types.</li><li><code>std::floating_point</code>: all floating‑point types.</li><li><code>std::same_as<T, U></code>: two types are identical.</li><li><code>std::convertible_to<From, To></code>: implicit conversion is possible.</li><li><code>std::invocable<F, Args...></code>: a callable can be invoked with the given arguments.</li><li><code>std::range<R></code>: a type provides <code>begin</code> and <code>end</code> iterators.</li></ul><p>The iterator library adds concepts such as <code>std::input_iterator</code>, <code>std::forward_iterator</code>, <code>std::random_access_iterator</code>, and <code>std::contiguous_iterator</code>. The ranges library refines those with concepts like <code>std::sized_range</code> and <code>std::view</code>. These names become part of the public interface. Reading an algorithm signature tells you exactly which properties the arguments must provide.</p><p>Several concepts describe callables rather than types. <code>std::predicate<P, Args...></code> is true when <code>P</code> can be called on <code>Args...</code> and the result is convertible to <code>bool</code>, which is exactly what <code>std::ranges::find_if</code> requires of its comparator. <code>std::relation</code> and <code>std::strict_weak_order</code> capture the ordering contracts that sorting and set operations depend on. Using them in your own signatures lets the compiler reject a comparator that returns the wrong category before any element is compared.</p><p>The <code>std::predicate</code> and <code>std::relation</code> concepts are exactly the constraints behind the algorithms from chapters 12 and 13, so the range pipelines written there were concept-checked whether or not the signatures made it explicit.</p><p>Concepts are the modern replacement for the SFINAE machinery that chapter 21 teaches as reading fluency. Where an old signature used <code>std::enable_if</code> in a return type, a new signature writes the concept in the parameter list. The behaviour is equivalent, but the new form is checkable before instantiation and readable at a glance.</p><h2 id="reading-concept-diagnostics"><a class="header" href="#reading-concept-diagnostics">Reading concept diagnostics</a></h2><p>When a constraint fails, the compiler prints the name of the concept and the type that caused the failure. For example, compiling the following code:</p><pre><code class="language-cpp">template<std::integral T>void g(T) {}
g("text");</code></pre><p>produces a diagnostic similar to:</p><blockquote><p>error: static assertion failed: constraints not satisfiedrequired from ‘std::integral<char const*>’note: substitution failed for ‘T = char const*’The message shows that <code>std::integral</code> was the offending concept and that <code>char const*</code> does not satisfy it. This is far clearer than the cascades of template‑instantiation errors that appeared before concepts.</p></blockquote><p>When user‑defined concepts are involved, the diagnostic includes the failed sub‑requirement. This makes it possible to pinpoint exactly which operation is missing. For instance, if <code>Addable</code> were violated because <code>+</code> returned the wrong type, the compiler points to the <code>{ a + b } -> std::same_as<T></code> clause.</p><h2 id="try-this-16"><a class="header" href="#try-this-16">Try this</a></h2><p>Define a concept <code>Comparable</code> that requires both <code>a < b</code> and <code>a == b</code> to be valid. Then write a <code>min</code> function constrained by <code>Comparable</code>. Call the function with two <code>int</code> values and with two <code>std::string</code> values.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="compile-time-c"><a class="header" href="#compile-time-c">Compile-time C++</a></h1><h2 id="constexpr-deeply"><a class="header" href="#constexpr-deeply">constexpr deeply</a></h2><p><code>constexpr</code> marks a function, variable, or constructor as eligible for constant evaluation. The standard defines a constant expression as an expression that can be evaluated during translation when all operands are themselves constant. When the compiler encounters a call to a <code>constexpr</code> function in such a context, it substitutes the computed value directly into the program.</p><p>Since C++14 a <code>constexpr</code> function can contain loops, local variables, and <code>if</code> statements, so its body resembles ordinary code. The constant‑evaluation engine still enforces a sandbox: no I/O, no <code>asm</code>, and no use of the address of a non‑constant object. If a call cannot be evaluated, the compiler either falls back to a runtime call where legal or issues a hard error in a context that requires a constant expression.</p><p>Since C++14 a <code>constexpr</code> function can contain loops, local variables, and <code>if</code> statements, so the body looks like ordinary code. The compiler still enforces a constant-evaluation sandbox: no I/O, no <code>asm</code>, and no use of the address of a non-constant object. When a call cannot be evaluated, the compiler either falls back to a runtime call where that is legal, or reports a hard error in a context that demands a constant expression.</p><p>The most common pattern is a recursive algorithm that terminates at compile time. The classic example is factorial:</p><pre><code class="language-cpp">#include <iostream>
constexpr long long factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1);}
static_assert(factorial(5) == 120, "factorial compile‑time test");
int main() { std::cout << "factorial ok\n"; return 0;}</code></pre><p>The static‑assert in the file forces the compiler to evaluate <code>factorial(5)</code> at compile time. The result of <code>120</code> becomes part of the program’s constant pool. The <code>main</code> function prints a short marker so the book’s test harness can verify that the binary linked and executed. This example also demonstrates that <code>constexpr</code> functions can be called from other <code>constexpr</code> contexts, such as template non‑type parameters, <code>std::array</code> sizes, or <code>static_assert</code> conditions.</p><h2 id="consteval-immediate-functions"><a class="header" href="#consteval-immediate-functions">consteval (immediate functions)</a></h2><p><code>consteval</code> is a stronger guarantee introduced in C++20. An immediate function <strong>must</strong> be evaluated at translation time. Any attempt to call it where a constant expression is not required is ill‑formed. The compiler therefore rejects the program outright, which produces a diagnostic that points to the offending call site. Immediate functions are ideal for compile‑time utilities that must never appear in the generated binary, such as compile‑time string hashing, type‑level identifiers, or compile‑time parsing of literals.</p><p>The following example computes a simple additive hash of a string literal. Because the function is declared <code>consteval</code>, the call <code>hash("abc")</code> is forced into the constant‑evaluation engine. The resulting value is verified with a <code>static_assert</code>. The <code>main</code> function prints a marker that confirms the program compiled successfully.</p><pre><code class="language-cpp">#include <iostream>#include <cstddef>
// Very simple compile‑time hash: sum of character codes.consteval std::size_t hash(const char* str) { std::size_t h = 0; for (std::size_t i = 0; str[i] != '\0'; ++i) { h += static_cast<std::size_t>(str[i]); } return h;}
static_assert(hash("abc") == ('a' + 'b' + 'c'), "hash compile‑time test");
int main() { std::cout << "hash ok\n"; return 0;}</code></pre><p>If a programmer later tries to invoke <code>hash</code> with a run‑time string, the compilation fails with a clear message: <em>call to a consteval function is not a constant expression</em>.</p><p>Prefer <code>consteval</code> when the function exists only to compute compile-time values, such as a hash or a table generator, because it removes the runtime path entirely and lets the optimizer assume the result is a constant. Prefer <code>constexpr</code> when the same logic also serves runtime inputs, as a parser or a math helper does.</p><h2 id="constinit"><a class="header" href="#constinit">constinit</a></h2><p>Static or thread‑local objects with static storage duration are normally zero‑initialized first and then later given their dynamic initializer. This two‑step process can lead to the infamous <em>static‑initialization‑order fiasco</em> when one translation unit accesses a global defined in another before its dynamic initializer runs. The <code>constinit</code> specifier forces the initializer to be a constant expression, guaranteeing that the object is fully initialized before any dynamic initialization begins.</p><p>The example below defines a global counter whose value is computed in a <code>constinit</code> variable. The lambda runs at compile time, sums the numbers <code>0</code> through <code>4</code>, and the result <code>10</code> becomes the object’s constant initial value. A <code>static_assert</code> validates the result, and the program prints a marker.</p><pre><code class="language-cpp">#include <iostream>
// Compute a compile‑time sum using a constexpr lambda.constinit const int global_counter = []constexpr noexcept{ int x = 0; for (int i = 0; i < 5; ++i) x += i; return x;}();
static_assert(global_counter == 10, "constinit compile‑time init");
int main() { std::cout << "constinit ok\n"; return 0;}</code></pre><p>Because <code>global_counter</code> is <code>constinit</code>, the compiler must emit an error if its initializer cannot be evaluated at compile time. This eliminates a whole class of runtime bugs without any additional runtime checks.</p><p><code>constinit</code> differs from a <code>constexpr</code> variable. A <code>constexpr</code> variable is itself constant and can be used in constant expressions. A <code>constinit</code> variable can be non-constant, so it can be mutated later, but its initializer must be constant. Use <code>constinit</code> for mutable globals that must avoid the initialization-order fiasco.</p><h2 id="stdis_constant_evaluated"><a class="header" href="#stdis_constant_evaluated">std::is_constant_evaluated</a></h2><p>Inside a <code>constexpr</code> function the standard library provides <code>std::is_constant_evaluated()</code>. It returns <code>true</code> when the current evaluation is performed by the constant‑evaluation engine, and <code>false</code> when the function runs at run time. This enables a single implementation to take two distinct paths: a highly optimized compile‑time algorithm and a more flexible run‑time fallback.</p><p>The <code>branch</code> example below illustrates this technique. When called inside a <code>static_assert</code>, the compile‑time branch returns <code>0</code>. When called from <code>main</code>, the run‑time branch prints the word <code>runtime</code> and returns the argument unchanged. The static‑assert confirms the compile‑time result, and the program output shows the run‑time branch execution.</p><pre><code class="language-cpp">#include <iostream>#include <type_traits>
constexpr int branch(int x) { if (std::is_constant_evaluated()) { return 0; // compile‑time path } else { std::cout << "runtime\n"; return x; }}
static_assert(branch(42) == 0, "branch compile‑time result");
int main() { constexpr int result = branch(7); std::cout << "branch result: " << result << "\n"; return 0;}</code></pre><p>Using <code>std::is_constant_evaluated</code> is a common idiom for providing cheap compile‑time shortcuts without sacrificing generic run‑time behavior. It also avoids the need for separate overloads guarded by <code>if constexpr</code> in user code.</p><p>The same test appears inside the standard library. <code>std::vector</code> and <code>std::string</code> check <code>is_constant_evaluated</code> internally to choose an allocation-free path during constant evaluation. Recognizing this pattern helps you understand why a container can be <code>constexpr</code> when a raw allocation cannot be.</p><h2 id="transient-constexpr-allocation"><a class="header" href="#transient-constexpr-allocation">Transient constexpr allocation</a></h2><p>C++20 lifted the restriction that dynamic allocation cannot appear in a constant expression. The rule states that any allocation performed during constant evaluation is <em>transient</em>: the allocated storage exists only for the duration of the evaluation and is reclaimed automatically when the evaluation finishes. This makes it possible to build containers such as <code>std::vector</code> or <code>std::string</code> inside a <code>constexpr</code> function, as long as the container does not escape the evaluation.</p><p>The example builds a <code>std::array</code> via a <code>constexpr</code> helper that fills the elements with squared indices. Although <code>std::array</code> does not allocate dynamically, the pattern demonstrates how a compile‑time algorithm can populate a fixed‑size aggregate. The <code>static_assert</code> checks the final element, and the program prints a marker.</p><pre><code class="language-cpp">#include <iostream>#include <array>
constexpr std::array<int,5> make_array() { std::array<int,5> a{}; for (std::size_t i = 0; i < a.size(); ++i) a[i] = static_cast<int>(i * i); return a;}
constexpr auto arr = make_array();static_assert(arr[4] == 16, "constexpr array element");
int main() { std::cout << "array ok\n"; return 0;}</code></pre><p>On compilers that fully support transient allocation, the same pattern can be written with <code>std::vector</code>:</p><pre><code class="language-cpp">constexpr std::vector<int> make_vec() { std::vector<int> v; for (int i = 0; i < 5; ++i) v.push_back(i * i); return v; // OK in C++20 and later}</code></pre><p>If the toolchain does not yet implement this feature, the fallback to a fixed‑size container still provides a compile‑time constant data structure.</p><p>A practical consequence is that compile-time data tables can be built with ordinary loops and containers, then baked into the binary as constants. This removes both the runtime construction cost and the need to hand-write static initializer lists.</p><h2 id="compiletime-as-pure-evaluation-lisp-framing"><a class="header" href="#compiletime-as-pure-evaluation-lisp-framing">Compile‑time as pure evaluation (Lisp framing)</a></h2><p>A <code>constexpr</code> function behaves like a pure term‑rewriter: given inputs it produces an output without observable side effects. The compiler treats it as a mathematical function, can memoize results for identical constant arguments, and folds the value into the program. This mirrors the term‑rewriting semantics of templates introduced in Chapter 16, where the compile‑time engine performs substitution, checks constraints, and yields a result.</p><p>When invoked repeatedly with the same constant arguments, the compiler can emit the value once and reuse it, reducing code size and eliminating redundant work. This is analogous to a Lisp interpreter evaluating a pure function at compile time and storing the result for later calls.</p><h2 id="the-convention-flip-staticassert-test-suites"><a class="header" href="#the-convention-flip-staticassert-test-suites">The convention flip: static‑assert test suites</a></h2><p>Historically, examples printed values and asked the reader to run the program and verify the output manually. With <code>constexpr</code>, the result is known at compile time, so the testing strategy flips to compile‑time verification: a <code>static_assert</code> encodes the expectation directly in the source file. The compiler checks it during translation. A failure stops the build with a clear diagnostic reported by the CI pipeline. The chapter still includes a short <code>std::cout</code> marker so the CTest harness can confirm that the binary linked and executed.</p><h2 id="c26-constexpr-frontiers"><a class="header" href="#c26-constexpr-frontiers">C++26 constexpr frontiers</a></h2><p>C++26 expands the constexpr toolbox with several ambitious features that are <strong>not yet supported</strong> by the Clang 22.1.8 toolchain used for verification.</p><blockquote><p><strong>Not yet deployable.</strong> <code>constexpr</code> placement‑<code>new</code> permits constructing an object in a pre‑allocated buffer during constant evaluation. Example syntax:</p></blockquote><pre><code class="language-cpp">constexpr char buffer[sizeof(MyType)];constexpr MyType* p = new (buffer) MyType{ /* args */ };</code></pre><blockquote><p><strong>Not yet deployable.</strong> <code>constexpr</code> exception handling makes it possible to <code>throw</code> and <code>catch</code> inside a constant expression. Example syntax:</p></blockquote><pre><code class="language-cpp">constexpr int safe_div(int a, int b) { if (b == 0) throw std::runtime_error("divide by zero"); return a / b;}static_assert(safe_div(4,2) == 2);</code></pre><blockquote><p><strong>Not yet deployable.</strong> User‑generated <code>static_assert</code> messages can incorporate constexpr data, which allows a compile‑time error to report a computed value. Example syntax:</p></blockquote><pre><code class="language-cpp">template<int N>struct prime_checker { static constexpr bool value = /* primality test */; static_assert(value, "Value " #N " is not prime");};</code></pre><p>These proposals (P2002R2 for placement‑new, P2003R1 for constexpr exceptions, and P2004R0 for message templating) are documented in the C++26 draft but remain unavailable in the current verification environment. The book marks them with a callout so readers understand the future direction without being blocked by the existing toolchain.</p><h2 id="try-this-17"><a class="header" href="#try-this-17">Try this</a></h2><p>Write a <code>constexpr</code> function <code>is_prime</code> that returns <code>true</code> if its argument is a prime number and <code>false</code> otherwise. Add <code>static_assert</code> checks for a few known values and print a marker from <code>main</code>.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="values-as-template-arguments-and-user-defined-literals"><a class="header" href="#values-as-template-arguments-and-user-defined-literals">Values as template arguments and user-defined literals</a></h1><h2 id="nontype-template-parameters-nttps"><a class="header" href="#nontype-template-parameters-nttps">Non‑type template parameters (NTTPs)</a></h2><p>Non‑type template parameters turn compile‑time values into part of the type system. By embedding a constant in a template argument the compiler generates a distinct specialization for each value. This enables zero‑overhead dispatch: the generated code contains only the paths required for the specific constant, and any branches that depend on the value disappear after constant folding. Standard library containers such as <code>std::array<T,N></code> and <code>std::span</code> rely on this technique to expose size information without runtime storage. Compile‑time bounds checking, static‑asserted preconditions, and compile‑time hash tables also become possible when the size or key is an NTTP.</p><p>The <code>auto</code> NTTP form, <code>template<auto V></code>, deduces the argument type. It accepts an integral, pointer, reference, or structural class object. This pattern forwards a value without naming its type, reducing boilerplate. The compiler encodes the value in the mangled name, giving each distinct argument a unique symbol. Using many distinct values can increase binary size, but the gain is eliminating runtime conditionals. It accepts an integral, a pointer, a reference, or a structural class object. Generic utilities frequently use this pattern to forward a value to another template without naming its type, reducing boilerplate and improving readability. The compiler records the value in the mangled name of the instantiation, so each distinct argument yields a unique symbol. This can increase binary size if many values are used, but the trade‑off is often worthwhile for the performance gain of eliminating runtime conditionals.</p><p>When an NTTP participates in overload resolution, the compiler prefers a more specialized non‑type argument. This mirrors concept overload resolution and enables tag‑dispatch based on constexpr values. For example, a function template can provide a fast path for a power‑of‑two size by matching <code>template<std::size_t N> requires (N & (N-1)) == 0</code>.</p><p>C++26 extends NTTPs to floating‑point values and to class types with non‑trivial constructors, simplifying patterns that rely on encoding a floating value in an integral representation.</p><p>Before C++26, a floating‑point NTTP required a workaround: encode the value as a fraction of two integers, or store it in a <code>constexpr</code> static variable and pass a reference. The extension makes the value a direct template argument, so a template can specialise on a literal <code>1.5</code> the same way it specialises on an integer. The class-type extension lifts the structural-type restriction that required aggregate initialisation, so a type with a <code>constexpr</code> constructor can now appear as an NTTP even if it has a non-trivial one.</p><pre><code class="language-cpp">#include <iostream>
// Basic non‑type template parameter: size of an array.// The size N is a compile‑time constant supplied as a template argument.
template<int N>struct Arr { int data[N]{}; // array of N ints, default‑initialized to zero};
int main() { Arr<5> a; // N = 5 std::cout << "Arr size: " << sizeof(a) << " bytes" << std::endl; return 0;}</code></pre><p>Compile‑time hashing of string literals also relies on NTTPs. A hash function defined as <code>constexpr</code> can accept a <code>fixed_string</code> NTTP and produce a constant hash value. This value can be used as a case label in a <code>switch</code> statement. The result is a zero‑overhead dispatch table for command strings.</p><p>An NTTP argument must come from one of a fixed set of categories:</p><ul><li>Integral and enumeration constants (<code>int</code>, <code>char</code>, <code>enum</code>).</li><li>Pointers or references to objects with static storage duration, including function pointers.</li><li>Class‑type values that satisfy the <em>structural type</em> requirements.</li><li><code>auto</code> can deduce the type. This allows <code>template<auto V></code> to accept any of the above categories.</li></ul><h2 id="classtype-nttps-c20"><a class="header" href="#classtype-nttps-c20">Class‑type NTTPs (C++20)</a></h2><p>C++20 extends NTTPs to accept <em>structural types</em>. A structural type is a class or struct whose members are all public, have non‑mutable types, and themselves are structural. The class must not declare any user‑provided constructor, so it can be aggregate‑initialized.</p><pre><code class="language-cpp">#include <iostream>
// Structural non‑type template parameter.// The type must be a structural type: all members are public, no mutable, no user‑declared constructor.
struct Config { int a; double b;};
// The template takes a Config as a compile‑time value.
template<Config C>struct UseConfig { static constexpr int val_a = C.a; static constexpr double val_b = C.b;};
int main() { // Instantiate with a concrete Config value. using My = UseConfig<Config{3, 4.5}>; static_assert(My::val_a == 3); static_assert(My::val_b == 4.5); std::cout << "config a=" << My::val_a << " b=" << My::val_b << std::endl; return 0;}</code></pre><p><code>Config</code> satisfies the structural rules: it contains two data members, both of which are fundamental types, and the struct has no constructors. The template <code>UseConfig</code> receives a concrete <code>Config{3,4.5}</code> as a compile‑time constant. Inside the specialization the values appear as <code>constexpr</code> static data members. The <code>static_assert</code>s verify the values during translation, while the program prints a short marker confirming successful runtime execution.</p><p>Structural NTTPs enable compile‑time configuration tables, policy objects, and domain‑specific languages without runtime overhead. The structural‑type rule requires all members to be public and non‑mutable, so the compiler can compare two NTTPs for equality during overload resolution and template deduplication. Two aggregates are equal when their members are equal, which keeps hashing and name mangling well defined.</p><p>The structural-type rule exists so the compiler can compare two NTTPs for equality during overload resolution and template deduplication. Because every member is public and the class has no user constructor, two aggregate values are equal exactly when their members are equal, which keeps hashing and name mangling well defined. This is what lets a <code>Config{3, 4.5}</code> name one unique specialization.</p><h2 id="the-fixed_string-recipe"><a class="header" href="#the-fixed_string-recipe">The <code>fixed_string</code> recipe</a></h2><p>Passing a literal string as an NTTP is a common requirement, but the language does not provide a built‑in string NTTP. A small helper class called <code>fixed_string</code> fills this gap. It stores the characters of a literal in a <code>constexpr</code> array and supplies a deduction guide so that a plain string literal can be used directly as a template argument.</p><pre><code class="language-cpp">#include <iostream>#include <cstddef>
// Fixed‑string non‑type template parameter.// Stores the characters of a literal at compile time.
template<std::size_t N>struct fixed_string { char buf[N]{}; // constexpr constructor copies characters. constexpr fixed_string(const char(&s)[N]) { for (std::size_t i = 0; i < N; ++i) buf[i] = s[i]; } // constexpr size query. constexpr std::size_t size() const { return N; }};
// Deduction guide from a string literal.template<std::size_t N>fixed_string(const char(&)[N]) -> fixed_string<N>;
// Example function taking a fixed_string NTTP.template<fixed_string S>void print_fixed() { std::cout << "fixed_string: " << S.buf << " (size=" << S.size() << ")" << std::endl;}
int main() { print_fixed<"hello">(); return 0;}</code></pre><p><code>fixed_string</code> is a class template parameterised by the literal length <code>N</code>. Its <code>constexpr</code> constructor copies each character from the literal into the internal buffer <code>buf</code>. The deduction guide <code>fixed_string(const char(&)[N]) -> fixed_string<N></code> allows the user to write <code>print_fixed<"hello">()</code> without explicitly naming the size.</p><p>The function <code>print_fixed</code> receives the <code>fixed_string</code> as an NTTP and can access the characters at compile time. The <code>size()</code> member reports the length, and the runtime <code>std::cout</code> prints the stored characters. This technique underlies many compile‑time parsers, compile‑time reflection utilities, and the constexpr‑SQL capstone in Chapter 22.</p><p>Because the characters live in the buffer, a <code>consteval</code> parser can walk <code>S.buf</code> at compile time to validate or transform the literal before any runtime code exists. This is the foundation of the compile-time parsing that chapter 22 exploits for its SQL engine: the query string is checked, tokenized, and turned into row types while the program is still being translated.</p><p>The structural-type rule is what makes <code>fixed_string</code> legal as an NTTP. Every member is public, the class has no user-declared constructors beyond the <code>constexpr</code> one, and the array of <code>char</code> is a structural type. Two <code>fixed_string</code> values with the same characters produce the same template argument, so the compiler can deduplicate the specialization. This is the same rule that lets a <code>Config{3, 4.5}</code> name one unique specialization, applied to text instead of numbers.</p><h2 id="userdefined-literals-udls"><a class="header" href="#userdefined-literals-udls">User‑defined literals (UDLs)</a></h2><p>A <em>user‑defined literal</em> extends the set of literal suffixes that the language recognises. The compiler looks for a function named <code>operator""_suffix</code> in the same namespace as the call site (or via argument‑dependent lookup). The function can accept an integral, floating‑point, character, or string literal form.</p><pre><code class="language-cpp">#include <iostream>
struct Distance { double value; const char* unit;};
// User‑defined literal for kilometres.constexpr Distance operator""_km(long double v) { return Distance{static_cast<double>(v), "km"};}
int main() { constexpr Distance d = 5.0_km; std::cout << "Distance: " << d.value << " " << d.unit << std::endl; return 0;}</code></pre><p>The example defines a literal suffix <code>_km</code> that converts a floating‑point literal into a <code>Distance</code> object. The <code>operator""_km</code> is <code>constexpr</code>, so the expression <code>5.0_km</code> is computed at compile time. The program prints <code>Distance: 5 km</code>, which the test harness verifies with the regular expression <code>Distance: 5 km</code>.</p><p>User‑defined literals give a natural, readable syntax for domain‑specific units, bit masks, or compile‑time identifiers. Because they are ordinary functions they can be overloaded, templated, and placed in header files for reuse across translation units.</p><p>A UDL has a form for each literal category. Integer literals dispatch to <code>operator""_suffix(unsigned long long)</code>, floating-point literals to <code>operator""_suffix(long double)</code>, character literals to <code>operator""_suffix(char)</code>, and string literals to <code>operator""_suffix(const char*, std::size_t)</code>. The <code>_suffix</code> identifier must begin with an underscore. A suffix without a leading underscore is reserved for the implementation. Placing the operator in a namespace and bringing it in with <code>using</code> keeps the literal vocabulary explicit.</p><h2 id="udls-for-parsed-literals-constexpr"><a class="header" href="#udls-for-parsed-literals-constexpr">UDLs for parsed literals (constexpr)</a></h2><p>A more advanced use of UDLs parses the literal characters at compile time. It turns a string literal into a value without any runtime work. The function must be <code>consteval</code> (or <code>constexpr</code> in C++20) and can perform arbitrary compile‑time computation.</p><pre><code class="language-cpp">#include <iostream>#include <cstddef>
// consteval user‑defined literal that parses a decimal integer from a string.// The literal receives the characters and length of the literal.
consteval unsigned int parse_number(const char* str, std::size_t len) { unsigned int value = 0; for (std::size_t i = 0; i < len; ++i) { char c = str[i]; value = value * 10 + static_cast<unsigned int>(c - '0'); } return value;}
// UDL suffix _num parses a literal string like "1234"_num into an unsigned int.consteval unsigned int operator""_num(const char* str, std::size_t len) { return parse_number(str, len);}
int main() { constexpr unsigned int v = "1234"_num; static_assert(v == 1234); std::cout << "Parsed number: " << v << std::endl; return 0;}</code></pre><p><code>operator""_num</code> receives the raw character data and length of the literal. It iterates over the characters, computes the numeric value, and returns it. The parse runs entirely during translation because the literal is a compile-time constant, and the compiler folds the result into the constant <code>v</code>.</p><p>In <code>main</code> the literal <code>"1234"_num</code> is parsed into the constant <code>v</code>. A <code>static_assert</code> verifies the result, and the program prints <code>Parsed number: 1234</code>. This pattern is useful for compile‑time parsing of IP addresses, colour codes, or UUID strings.</p><h2 id="nttp-callables-c26"><a class="header" href="#nttp-callables-c26">NTTP callables (C++26)</a></h2><p>C++26 adds the ability to pass a <em>callable</em> (a function pointer, lambda, or function object) as a non‑type template argument. The placeholder syntax <code>template<auto F></code> captures the callable, and the template can invoke it at compile time. The standard library also introduces <code>std::bind_front</code> and <code>std::bind_back</code>, which adapt a callable by pre‑binding or post‑binding arguments, and the resulting binder can be used as an NTTP.</p><blockquote><p><strong>Not yet deployable.</strong> NTTP callables and <code>std::bind_front</code>/<code>bind_back</code> are part of the C++26 draft. Current Clang versions lack support, so no compiled example is provided. Readers can experiment with GCC 16 or later once the feature lands.</p></blockquote><h2 id="units-example-nttp-ratios"><a class="header" href="#units-example-nttp-ratios">Units example (NTTP ratios)</a></h2><p>Compile‑time unit arithmetic can be expressed using <code>std::ratio</code>, a compile‑time fraction type introduced in Chapter 15. By making a ratio an NTTP a template can encode a conversion factor that the compiler evaluates without runtime cost.</p><pre><code class="language-cpp">#include <iostream>#include <ratio>
// Compile‑time ratio representing a speed unit (length / time).
template<int Num, int Den>struct SpeedRatio { static constexpr int num = Num; static constexpr int den = Den; static void print() { std::cout << "speed = " << num << " m/s" << std::endl; }};
int main() { SpeedRatio<10,1>::print(); // 10 metres per second return 0;}</code></pre><p><code>SpeedRatio</code> stores a numerator and denominator as template arguments. The <code>print</code> member writes a human‑readable representation. The values are known at compile time, so any arithmetic involving the ratio is folded by the optimizer. The program prints <code>speed = 10 m/s</code>, matching the test harness expectation.</p><p>Because the conversion factors are ratios baked into the type, a <code>m/s</code> result and a <code>km/h</code> result cannot be mixed silently. The compiler rejects arithmetic that requires an unknown conversion and accepts only what a dimension analysis allows. This turns a class of unit errors into compile-time diagnostics, the same payoff as the lifetime analysis of chapter 07 but for dimensional correctness. Each <code>std::ratio</code> pair is a distinct type, so <code>meter</code> and <code>second</code> are unrelated unless a template combines them into <code>meter_per_second</code>.</p><h2 id="advanced-nttp-patterns"><a class="header" href="#advanced-nttp-patterns">Advanced NTTP patterns</a></h2><p>Beyond basic usage, NTTPs can drive compile‑time state machines, generate lookup tables, and enable perfect‑hash dispatch. By encoding a map of string keys to function pointers as a <code>constexpr</code> array of <code>fixed_string</code>/function pointer pairs, a <code>switch</code> on the NTTP can select the handler at compile time, eliminating any runtime map lookup. This approach is useful for command‑line parsers, protocol dispatch, or embedded DSLs where the set of commands is fixed.</p><p>The cost is that each distinct NTTP emits a separate specialization, so a table of one hundred string keys produces one hundred instantiations. The technique pays off when the set of commands is fixed and dispatch is hot. For a set that changes, a runtime <code>std::map</code> is simpler. Perfect hashing over <code>fixed_string</code> keys turns a linear scan of command names into a single arithmetic jump, so a hot dispatch loop stays branch-predictable and never performs a runtime string comparison.</p><h2 id="try-this-18"><a class="header" href="#try-this-18">Try this</a></h2><p>Write a user‑defined literal <code>_hex</code> that parses a hexadecimal string literal into an unsigned integer at compile time and verify the result with a <code>static_assert</code>.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="specialization-overloading-and-customization-points"><a class="header" href="#specialization-overloading-and-customization-points">Specialization, overloading, and customization points</a></h1><p>This chapter presents the decision rule for choosing concepts versus specialization, then shows the related mechanisms.</p><h2 id="class-template-specialization"><a class="header" href="#class-template-specialization">Class template specialization</a></h2><p>A class template defines a family of types parameterised by one or more template arguments. A <strong>full specialization</strong> provides a concrete definition for a <em>specific</em> set of arguments. A <strong>partial specialization</strong> fixes <em>some</em> arguments while leaving others as parameters.</p><pre><code class="language-cpp">// Primary template: works for any type Ttemplate <typename T>struct Printer { static void print() { std::cout << "primary" << '\n'; }};
// Partial specialization for pointer typestemplate <typename T>struct Printer<T*> { static void print() { std::cout << "pointer" << '\n'; }};</code></pre><p>The primary template is selected when the argument list does not match any partial or full specialization. The partial specialization above matches any pointer type <code>T*</code>. The compiler chooses the <em>most specialised</em> viable definition.</p><pre><code class="language-cpp">#include <iostream>
// Primary templatetemplate <typename T>struct Printer { static void print() { std::cout << "primary\n"; }};
// Partial specialization for pointer typestemplate <typename T>struct Printer<T*> { static void print() { std::cout << "pointer\n"; }};
int main() { Printer<int>::print(); // prints "primary" Printer<int*>::print(); // prints "pointer" return 0;}</code></pre><h3 id="how-the-compiler-decides"><a class="header" href="#how-the-compiler-decides">How the compiler decides</a></h3><ul><li><strong>Matching</strong>: the argument list is compared against each specialization’s pattern.</li><li><strong>Partial ordering</strong>: if more than one specialization matches, the compiler ranks them by how many arguments are fixed. The one with the <em>greater</em> number of fixed arguments is preferred.</li><li><strong>Full specialization</strong>: a specialization that fixes <em>all</em> arguments is the ultimate match and overrides any partial specialization.</li></ul><p>Because specializations are an <em>out‑of‑band</em> mechanism, they do not participate in overload resolution. They merely replace the primary definition before the compiler instantiates the class template.</p><p>A full specialization is written with an empty template parameter list and a concrete argument: <code>template<> struct Printer<int> { ... };</code>. It is the only form that can introduce a definition with a completely different set of members, because it no longer depends on any template parameter. Partial specializations must still match the primary template’s parameter list in shape, so they can vary the pattern but not the member set arbitrarily. In practice most code needs at most one partial specialization and a handful of full ones.</p><p>One caution: the primary template must remain well formed even if only specializations are used, because the compiler instantiates the primary in some contexts before consulting specializations. Keep a sensible default body in the primary and treat specializations as refinements.</p><h2 id="the-decision-rule"><a class="header" href="#the-decision-rule">The decision rule</a></h2><p>When you need different behaviour, ask two questions:</p><ol><li><strong>Do the types share the same logical interface?</strong> If the answer is <em>yes</em> but the implementation varies based on a property (e.g., pointer vs non‑pointer), prefer <strong>concepts</strong> or <strong><code>if constexpr</code></strong> inside a generic implementation.</li><li><strong>Is the representation fundamentally different?</strong> If the answer is <em>no</em> but the underlying storage or layout differs (e.g., a raw array versus a <code>std::span</code>), use <strong>specialization</strong>.</li></ol><p>In short, <em>choice is semantic → use concepts</em>. <em>Representation changes → specialise</em>.</p><p>Applying the decision rule consistently avoids scattered overloads and specializations. When a concept describes a semantic property, the requirement appears directly in the function signature and the implementation stays in a single generic body. Use specialization only when the type’s layout or representation differs fundamentally, such as a raw array versus a <code>std::span</code>. This discipline simplifies refactoring and provides clearer compile‑time diagnostics.</p><h2 id="variable-templates"><a class="header" href="#variable-templates">Variable templates</a></h2><p>Variable templates let you define <strong>compile‑time constants</strong> that depend on a template parameter. They are the value‑side analogue of function templates.</p><pre><code class="language-cpp">template <typename T>constexpr T pi = static_cast<T>(3.1415926535897932385L);
static_assert(pi<double> == 3.1415926535897932385);</code></pre><p>The standard library uses this pattern extensively, e.g. the <code>_v</code> suffix for trait variables such as <code>std::is_same_v<T, U></code>.</p><pre><code class="language-cpp">#include <type_traits>#include <iostream>
template <typename T>constexpr T pi = static_cast<T>(3.1415926535897932385L);
int main() { static_assert(pi<double> == 3.1415926535897932385); std::cout << "pi<double> = " << pi<double> << '\n'; return 0;}</code></pre><p>Variable templates are instantiated only when ODR‑used, so they impose no runtime cost. They also serve as a natural place for configuration constants such as <code>std::numeric_limits<T>::max()</code>. They compose, e.g., <code>template<typename T> constexpr T half_pi = pi<T> / 2;</code>, allowing the compiler to fold arithmetic at translation time. Because they are <code>constexpr</code>, they can feed <code>static_assert</code>, <code>if constexpr</code> conditions, or non‑type template arguments, bridging value and type worlds. Variable templates also act as value‑side customization points. A library can declare <code>template<typename T> struct traits;</code> and specialize <code>traits<T>::value</code>, while a variable template like <code>template<typename T> inline constexpr bool is_trivially_copyable_v</code> provides the standard‑preferred concise spelling.</p><h2 id="if-constexpr-as-inbody-dispatch"><a class="header" href="#if-constexpr-as-inbody-dispatch"><code>if constexpr</code> as in‑body dispatch</a></h2><p>Sometimes a single function needs two completely different implementations, but you do not want to write separate overloads or specializations. <code>if constexpr</code> lets you branch at compile time based on a constant expression.</p><pre><code class="language-cpp">template <typename T>void show() { if constexpr (std::is_pointer_v<T>) { std::cout << "pointer" << '\n'; } else { std::cout << "primary" << '\n'; }}
int main() { show<int*>(); show<int>(); }</code></pre><p>The compiler discards the unreachable branch, so no ill‑formed code can appear in the omitted path. This technique is especially useful when the two paths require different headers or heavy SFINAE tricks.</p><pre><code class="language-cpp">#include <type_traits>#include <iostream>
template <typename T>void show() { if constexpr (std::is_pointer_v<T>) { std::cout << "pointer" << '\n'; } else { std::cout << "primary" << '\n'; }}
int main() { show<int*>(); show<int>(); return 0;}</code></pre><p><code>if constexpr</code> replaces tag dispatch. The compiler discards the unreachable branch, so the same <code>show</code> function works for both pointer and non‑pointer arguments without any overload set.</p><h2 id="specialising-stdformatter"><a class="header" href="#specialising-stdformatter">Specialising <code>std::formatter</code></a></h2><p><code>std::format</code> formats arbitrary types using the <strong>formatter</strong> customization point. To make a user‑defined type printable, you specialise <code>std::formatter<T></code> for your type <code>T</code>.</p><pre><code class="language-cpp">struct Vec2 { int x, y; };
template <> struct std::formatter<Vec2> : std::formatter<std::string> { // parse format spec - we ignore it for simplicity constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } // format the value auto format(const Vec2& v, format_context& ctx) const { return std::format_to(ctx.out(), "({},{})", v.x, v.y); }};
int main() { Vec2 v{3,4}; std::cout << std::format("Vec2: {}", v) << '\n';}</code></pre><p>The specialization lives in the same namespace as <code>std</code> (the only exception allowed by the standard). It enables the type to be used with <code>std::format</code>, <code>std::print</code>, and any other formatting facility that forwards to the <code>std::formatter</code> trait.</p><pre><code class="language-cpp">#include <format>#include <iostream>
struct Vec2 { int x, y; };
template <> struct std::formatter<Vec2> : std::formatter<std::string> { // No format specifiers – ignore the parse context constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } auto format(const Vec2& v, std::format_context& ctx) const { return std::format_to(ctx.out(), "({},{})", v.x, v.y); }};
int main() { Vec2 v{3,4}; std::cout << std::format("Vec2: {}", v) << '\n'; return 0;}</code></pre><h3 id="why-specialise-instead-of-overloading"><a class="header" href="#why-specialise-instead-of-overloading">Why specialise instead of overloading?</a></h3><p>Overloading <code>operator<<</code> works only for stream‑based APIs. <code>std::format</code> follows a <em>type‑centric</em> design: the formatter is a <strong>customisation point object (CPO)</strong> that the library calls. By providing a specialization, you integrate with the whole formatting ecosystem without pulling in iostreams.</p><p>The <code>parse</code> member decodes the part of the format string that follows the colon, and <code>format</code> writes the value. Inheriting from an existing formatter such as <code>std::formatter<std::string></code> is a shortcut when you want default spec handling. For a type with no natural spec, <code>parse</code> just returns the begin iterator. The specialization must be visible in the namespace of the type or of <code>std</code>, which is why the standard permits specialising <code>std::formatter</code> for user types even though it otherwise forbids adding to <code>std</code>.</p><h2 id="specialising-stdhash"><a class="header" href="#specialising-stdhash">Specialising <code>std::hash</code></a></h2><p><code>std::unordered_map</code> and <code>std::unordered_set</code> hash their keys through <code>std::hash<Key></code>. A user type has no default, so to use one as a key you specialise <code>std::hash</code>:</p><pre><code class="language-cpp">struct Key { int id; std::string name; };
template <>struct std::hash<Key> { std::size_t operator()(const Key& k) const noexcept { std::size_t h1 = std::hash<int>{}(k.id); std::size_t h2 = std::hash<std::string>{}(k.name); return h1 ^ (h2 << 1); }};</code></pre><p>The specialization must be a complete type with an <code>operator()</code> returning <code>std::size_t</code>. Combine the hashes of the members with a shift and XOR so the result depends on both fields. Equality must stay consistent with the hash: two keys that compare equal must hash equal, or the container misbehaves. The same pattern powers the customization that <code>std::unordered_map</code> relies on for every key type.</p><p>Because the hash and the equality operator must agree, define them together. If you change the equality later, update the hash in the same change or lookups return wrong results silently. The standard library also lets you supply a custom hasher or comparator as template arguments to <code>unordered_map</code>, so a bespoke type can avoid touching <code>std::hash</code> entirely when its needs are unusual.</p><h2 id="adl-and-hidden-friends"><a class="header" href="#adl-and-hidden-friends">ADL and hidden friends</a></h2><p>Argument-dependent lookup (ADL) finds functions in the namespaces of their arguments. When <code>a == b</code> is written, the compiler looks for <code>operator==</code> not only in the enclosing scope but also in the namespaces of <code>a</code> and <code>b</code>. A <em>hidden friend</em> is an <code>operator==</code> defined inside the class body. It is reachable only through ADL, so it never pollutes the global namespace and cannot be called without the right argument types.</p><pre><code class="language-cpp">struct Point { int x, y; friend bool operator==(const Point& a, const Point& b) = default;};</code></pre><p>Because the friend is defined inline, it is a hidden friend and ADL is the only mechanism that finds it. This keeps the operator close to the type, prevents accidental calls on unrelated types, and gives the best diagnostics: a call with a mismatched operand names <code>Point</code> rather than surfacing an unrelated overload. The same reasoning applies to any operator used only with its own operand types.</p><p>To summarize, the library can combine concepts, specializations, variable templates, and customization point objects to provide a clear, layered customization strategy that keeps generic code simple and concrete overrides focused.</p><h2 id="try-this-19"><a class="header" href="#try-this-19">Try this</a></h2><p>Create a small <code>struct Color { uint8_t r,g,b }</code> and write a <code>std::formatter<Color></code> that formats the colour as a hex string <code>#RRGGBB</code>. Verify the output with <code>std::format</code>.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="reading-the-old-magic-sfinae-type_traits-and-friends"><a class="header" href="#reading-the-old-magic-sfinae-type_traits-and-friends">Reading the old magic: SFINAE, type_traits, and friends</a></h1><h2 id="why-you-must-read-old-tmp"><a class="header" href="#why-you-must-read-old-tmp">Why you must read old TMP</a></h2><p>The C++ ecosystem did not jump from C++98 straight to concepts. Decades of libraries, such as the Standard Library, Boost, Eigen, and countless in-house frameworks, were written using SFINAE, <code>std::enable_if</code>, <code><type_traits></code> utilities, tag dispatch, and the Curiously Recurring Template Pattern (CRTP). When you encounter a templated overload that appears cryptic, you must not rewrite it immediately. First, decode the intent:</p><ol><li>Identify the <em>constraint</em> expressed by the enable-if or trait.</li><li>Translate that constraint into a <code>requires</code> clause or a named concept.</li><li>Verify that the modern form selects the same overload.</li></ol><p>This disciplined approach preserves the original algorithmic intent while allowing you to modernise gradually. Moreover, many code reviewers still expect you to read SFINAE-heavy code, because refactoring large libraries without breaking ABI is risky. By mastering the legacy patterns you become a bridge between the old and the new.</p><p>SFINAE and <code>enable_if</code> are not a separate language. They are template deduction rules pushed to their limits. Reading them as such, rather than as magic, makes the modern forms obvious. When you see <code>std::enable_if_t<...> = 0</code> in a signature, translate it in your head to the pre-concepts spelling of a <code>requires</code> clause, and the intent snaps into focus.</p><hr><h2 id="stdenable_if-positions"><a class="header" href="#stdenable_if-positions"><code>std::enable_if</code> positions</a></h2><p><code>std::enable_if</code> can appear in three classic locations:</p><ul><li><strong>Return type</strong>: the function’s result type is conditionally enabled.</li><li><strong>Default template parameter</strong>: the template parameter list carries a hidden <code>enable_if</code> that activates the overload.</li><li><strong>Parameter type</strong>: the function parameter itself is wrapped in <code>enable_if</code>, often for <code>std::string</code> arguments.</li></ul><p>Immediate-context rule: a substitution failure in the part of a declaration that participates in overload resolution removes the candidate without a diagnostic. This is the basis of SFINAE (Substitution Failure Is Not An Error). It lets <code>enable_if</code> expressions in return types, default template parameters, or parameter types silently disable overloads.</p><p>Below is a single file that demonstrates all three signatures. The program prints a message that identifies the selected overload. The modern rewrite, shown as comments, uses concepts to achieve the same overload resolution.</p><pre><code class="language-cpp">// ch21_enable_if_decode.cpp#include <iostream>#include <type_traits>#include <string>
// Overload using enable_if in return type (integral types)template <typename T>std::enable_if_t<std::is_integral_v<T>, std::string> foo(T) { std::cout << "int overload" << std::endl; return "int";}
// Overload using enable_if as a default template parameter (floating point types)template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>std::string foo(T) { std::cout << "float overload" << std::endl; return "float";}
// Overload using enable_if in a parameter type (std::string)template <typename T>std::enable_if_t<std::is_same_v<T, std::string>, std::string> foo(T const& value) { std::cout << "string overload" << std::endl; return value;}
int main() { foo(42); foo(3.14); foo(std::string{"hello"}); return 0;}</code></pre><pre><code class="language-cpp">// Modern rewrite (concept-based)template <typename T>requires std::integral<T>std::string foo(T) { std::cout << "int overload" << std::endl; return "int"; }
template <typename T>requires std::floating_point<T>std::string foo(T) { std::cout << "float overload" << std::endl; return "float"; }
template <typename T>requires std::same_as<T, std::string>std::string foo(T const& v) { std::cout << "string overload" << std::endl; return v; }</code></pre><p>Running the legacy example yields:</p><pre><code>int overloadfloat overloadstring overload</code></pre><p>Each overload is selected exactly as the concept-based version is, demonstrating a mechanical one-to-one mapping.</p><pre><code class="language-cpp">template <typename T, typename = std::void_t<decltype(std::declval<T>().size())>>void f(T const&) { std::cout << "has size" << std::endl; }
template <typename T>void f(T const&) { std::cout << "no size" << std::endl; }</code></pre><p>If <code>T</code> has a static member <code>size()</code>, the first overload is viable. Otherwise the substitution fails, the candidate is discarded, and the second overload wins. This behaviour underlies the <code>enable_if</code> patterns shown earlier. The <code>enable_if</code> expression lives in the immediate context, so a non-matching type eliminates the overload.</p><hr><h2 id="the-type_traits-vocabulary"><a class="header" href="#the-type_traits-vocabulary">The <code><type_traits></code> vocabulary</a></h2><p>The <code><type_traits></code> header provides a small declarative language that predates concepts. The most common utilities are:</p><ul><li><code>std::is_same_v<T,U></code>: true if <code>T</code> and <code>U</code> denote the same type.</li><li><code>std::is_integral_v<T></code>: true for integral types.</li><li><code>std::is_floating_point_v<T></code>: true for floating-point types.</li><li><code>std::conditional_t<C,T,F></code>: selects <code>T</code> if <code>C</code> is true, otherwise <code>F</code>.</li><li><code>std::void_t<...></code>: used to build detection idioms.</li></ul><p>The classic detection idiom uses <code>void_t</code> to test whether a particular expression is well-formed. The example below shows both the legacy and the modern approach.</p><pre><code class="language-cpp">// ch21_void_t_has_size.cpp#include <iostream>#include <type_traits>
// --- Classic detection using std::void_t ---struct WithSize { static constexpr std::size_t size() { return 5; }};struct WithoutSize {};
template <typename, typename = void>struct has_size : std::false_type {};
template <typename T>struct has_size<T, std::void_t<decltype(T::size())>> : std::true_type {};
// --- Modern detection using a requires expression (concept) ---template <typename T>concept HasSize = requires { T::size(); };
int main() { std::cout << "has_size<WithSize>::value = " << has_size<WithSize>::value << "\n"; std::cout << "has_size<WithoutSize>::value = " << has_size<WithoutSize>::value << "\n"; static_assert(HasSize<WithSize>, "WithSize satisfies HasSize"); static_assert(!HasSize<WithoutSize>, "WithoutSize does not satisfy HasSize"); return 0;}</code></pre><pre><code class="language-cpp">// Modern rewrite using a requires expression (concept)template <typename T>concept HasSize = requires { T::size(); };</code></pre><p>Both versions print the same truth values and compile-time <code>static_assert</code>s, confirming that the concept captures the exact same property as the <code>void_t</code> detector.</p><hr><h2 id="tag-dispatch"><a class="header" href="#tag-dispatch">Tag dispatch</a></h2><p>Tag dispatch separates overloads by passing an artificial <em>tag</em> type as an extra argument. The tag is selected by a <code>constexpr</code> function that examines type traits. The pattern is useful when you need a fast, binary-size friendly dispatch that does not rely on SFINAE.</p><pre><code class="language-cpp">// ch21_tag_dispatch.cpp#include <iostream>#include <type_traits>
// Tag typesstruct IntegralTag {};struct FloatingTag {};
// Primary overload – catches any type via tag dispatchtemplate <typename T>void print_type(T, IntegralTag) { std::cout << "integral" << std::endl;}
template <typename T>void print_type(T, FloatingTag) { std::cout << "floating" << std::endl;}
// Helper to select tag based on type traitstemplate <typename T>constexpr auto select_tag() { if constexpr (std::is_integral_v<T>) { return IntegralTag{}; } else if constexpr (std::is_floating_point_v<T>) { return FloatingTag{}; } else { static_assert(!std::is_same_v<T, T>, "Unsupported type"); }}
int main() { print_type(42, select_tag<int>()); print_type(3.14, select_tag<double>()); return 0;}</code></pre><p>The modern replacement uses <code>if constexpr</code> directly inside the function body, eliminating the need for separate tag types:</p><pre><code class="language-cpp">template <typename T>void print_type(T) { if constexpr (std::is_integral_v<T>) { std::cout << "integral" << std::endl; } else if constexpr (std::is_floating_point_v<T>) { std::cout << "floating" << std::endl; } else { static_assert(!std::is_same_v<T,T>, "Unsupported type"): }}</code></pre><p>Both implementations produce identical runtime output for the test cases. The tag types are cheap, because an empty struct carries no data and the compiler folds the dispatch away entirely, which is why the pattern remains common in performance-sensitive code.</p><hr><h2 id="crtp-curiously-recurring-template-pattern"><a class="header" href="#crtp-curiously-recurring-template-pattern">CRTP (Curiously Recurring Template Pattern)</a></h2><p>CRTP is a form of static polymorphism where a base class template takes the derived class as a parameter. The base can call functions that the derived implements. It uses <code>static_cast</code> to do so. This pattern appears in many older libraries (e.g., Boost.Fusion, Eigen) and remains useful for compile-time interfaces.</p><pre><code class="language-cpp">// ch21_crtp_shape.cpp#include <iostream>#include <type_traits>
// CRTP base that provides an interface for area()template <typename Derived>struct ShapeBase { // Calls the derived implementation via static_cast double area() const { return static_cast<const Derived*>(this)->area_impl(); }};
// Circle implementation using CRTPstruct Circle : ShapeBase<Circle> { double radius; explicit Circle(double r) : radius(r) {} double area_impl() const { return 3.1415926535 * radius * radius; }};
// Rectangle implementation using CRTPstruct Rectangle : ShapeBase<Rectangle> { double width, height; Rectangle(double w, double h) : width(w), height(h) {} double area_impl() const { return width * height; }};
int main() { Circle c(2.0); Rectangle r(3.0, 4.0); std::cout << "Circle area: " << c.area() << "\n"; std::cout << "Rectangle area: " << r.area() << "\n"; return 0;}</code></pre><p>A concept-based modern alternative expresses the same requirement with a <em>requires clause</em> on a free function or a generic algorithm, avoiding inheritance entirely:</p><pre><code class="language-cpp">template <typename Shape>concept ShapeLike = requires(const Shape& s) { s.area_impl(): }:
template <ShapeLike S>double area(const S& s) { return s.area_impl(): }</code></pre><p>The concept-based version provides the same static guarantee (<code>area_impl</code> exists) without the boilerplate of a CRTP base class.</p><p>The CRTP also lets a base provide default implementations that call into the derived type, which is how many mixin-style helpers share code without virtual dispatch. Because the call is resolved at compile time, it is inlined. This gives the performance of hand-written code with the reuse of a shared base.</p><hr><h2 id="translation-table"><a class="header" href="#translation-table">Translation table</a></h2><p>The following table summarises the mechanical rewrite from legacy constructs to their modern equivalents. Each row represents a direct replacement that preserves behaviour while simplifying the code.</p><div class="table-wrapper"><table><thead><tr><th>Legacy pattern</th><th>Modern equivalent</th></tr></thead><tbody><tr><td><code>std::enable_if</code> in return type, default template parameter, or parameter type</td><td><code>requires</code> clause or named concept</td></tr><tr><td>SFINAE (substitution failure)</td><td>Concept <em>subsumption</em> during overload resolution</td></tr><tr><td>Detection idiom with <code>std::void_t</code></td><td><code>requires { expr: }</code> (requires expression)</td></tr><tr><td>Tag dispatch (tag type + overload)</td><td><code>if constexpr</code> inside a single overload</td></tr><tr><td>CRTP static polymorphism</td><td>Concept-constrained free function or algorithm</td></tr></tbody></table></div><hr><h2 id="practical-migration-workflow"><a class="header" href="#practical-migration-workflow">Practical migration workflow</a></h2><p>When converting legacy TMP code, follow a systematic process. First, locate the overload set that uses <code>enable_if</code> or a detection idiom. Next, write an equivalent <code>requires</code> clause that captures the same logical condition. Then replace the multiple overloads with a single constrained function if possible. After editing, compile the program and verify that the output matches the original. Finally, run the book’s test harness to confirm that the <code>EXPECT</code> strings still pass.</p><p>Apply the same methodology to tag dispatch. Identify the tag types and the helper <code>select_tag</code> function. Replace them with an <code>if constexpr</code> chain that performs the same trait checks. Remove the tag definitions and the extra overloads. Compile and test to ensure identical behavior.</p><p>For CRTP bases, extract the required interface into a concept. Write free functions that operate on any type satisfying the concept. Remove the CRTP inheritance and replace it with calls to the free functions. Verify that the program still prints the expected results.</p><p>By iterating through each pattern in the translation table, you gradually modernize the codebase while maintaining functional parity. This approach reduces technical debt and improves readability for future maintainers.</p><h2 id="try-this-20"><a class="header" href="#try-this-20">Try this</a></h2><p>Translate the following three-overload <code>enable_if</code> set into a single concept-ordered function family. The original overloads handle (a) integral types, (b) floating-point types, and (c) all other types. After you rewrite, add a <code>static_assert</code> that verifies the same overload is selected for <code>int</code>, <code>double</code>, and <code>std::string</code>.</p><pre><code class="language-cpp">// Original legacy code (do not modify)template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>void process(T) { std::cout << "int" << std::endl; }
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, long> = 0>void process(T) { std::cout << "float" << std::endl; }
template <typename T, std::enable_if_t<!std::is_arithmetic_v<T>, void*> = nullptr>void process(T) { std::cout << "other" << std::endl; }</code></pre><p><em>Write the concept-based version and the <code>static_assert</code>s. No solution is provided.</em></p><hr><p>The translation of legacy patterns into concepts is not merely a stylistic upgrade: it has practical impacts on compilation speed, error diagnostics, and maintainability. Modern compilers can evaluate <code>requires</code> expressions early, pruning invalid overloads before template instantiation proceeds, which often reduces template recursion depth and improves compile-time feedback. Error messages tied to a concept name point directly at the violated constraint, a stark contrast to the often-cryptic cascade of errors produced by deep SFINAE failures.</p><p>Furthermore, concepts become part of the public interface. When a library author publishes a concept, downstream users can read the requirement in the function signature without hunting through implementation details. This self-documenting quality aligns with the book’s overarching philosophy: code must convey intent as clearly as possible.</p><p>When converting existing code, adopt a staged approach:</p><ol><li>Identify a SFINAE-enabled overload.</li><li>Write an equivalent <code>requires</code> clause, preserving the logical condition.</li><li>Replace the overload set with a single constrained function if possible.</li><li>Run the test suite to confirm behaviour remains identical.</li><li>Refactor additional legacy patterns (type-trait checks, tag dispatch, CRTP) following the same mechanical mapping.</li></ol><p>By iterating through the table above, you systematically reduce technical debt, modernise the codebase, and equip future contributors with clearer abstractions. The chapter’s examples demonstrate each step. They give you concrete, compile-tested references for every transformation.</p><p><em>The examples in this chapter are compiled and tested with the <code>book_example</code> macro. The <code>EXPECT</code> strings in the CMake registration verify that each program prints the expected identifiers.</em></p><div style="break-before: page; page-break-before: always;"></div><h1 id="capstone-a-constexpr-sql-in-c26"><a class="header" href="#capstone-a-constexpr-sql-in-c26">Capstone: a constexpr SQL in C++26</a></h1><h2 id="why-a-constexpr-edsl"><a class="header" href="#why-a-constexpr-edsl">Why a constexpr EDSL</a></h2><p>A domain‑specific language (DSL) that runs at compile time gives the same safety as a Lisp macro system but without textual substitution. The query is parsed, type‑checked, and turned into data while the compiler translates the translation unit, so a malformed query produces a compilation error instead of a runtime failure. This idea follows the term‑rewriting view of templates (chapter 16) and the compile‑time execution model (chapter 18).</p><p>The lineage is clear. Deane and Turner demonstrated a constexpr JSON parser that turned a string literal into a <code>constexpr</code> data structure. CTParser and the CTRE library later showed how regular‑expression‑based parsers can live in <code>consteval</code> functions. More recently, mkitzan’s constexpr‑sql project combined those ideas to build a tiny SQL‑like EDSL. The capstone brings those ingredients together in a single, self‑contained example.</p><p>A constexpr EDSL is not a new parser library bolted onto the build. It uses the same term‑rewriting machinery as the template system applied to a string literal. The query becomes compiler data, not a runtime command, so the compiler checks every branch as it evaluates. A compile‑time failure is the cheapest failure because it occurs before the binary exists and names the exact erroneous query text. Consequently, the compiler acts as the test runner and the query literal serves as the fixture, eliminating the need for a separate runtime test harness.</p><p>A compile-time failure is the cheapest kind of failure, because it happens before the binary exists and names the exact query text that is wrong. A runtime test needs a runner, an assertion library, and a suite to maintain. Here the compiler is the runner and the query literal is the fixture.</p><h2 id="the-building-blocks"><a class="header" href="#the-building-blocks">The building blocks</a></h2><p>The capstone re‑uses exactly the mechanisms introduced earlier:</p><ul><li><strong><code>fixed_string</code> NTTP</strong> (chapter 19) carries the query literal as a non‑type template parameter.</li><li><strong><code>consteval</code></strong> (chapter 18) forces the parser to run during translation.</li><li><strong><code>constexpr</code> containers</strong>: <code>std::array</code> and <code>std::vector</code> with transient allocation hold tables and query results entirely at compile time.</li><li><strong><code>if constexpr</code></strong> selects between the supported comparison operators without generating unreachable code.</li></ul><p>No other library is required. The whole program lives in a single source file, and each piece (<code>fixed_string</code>, <code>consteval</code>, and <code>constexpr</code> containers) is a plain, non‑exotic component whose combination yields a real, self‑checking domain language without any runtime component.</p><h2 id="the-schema-as-aggregates"><a class="header" href="#the-schema-as-aggregates">The schema as aggregates</a></h2><p>A table row is a plain aggregate struct, exactly as in chapter 3:</p><pre><code class="language-cpp">struct Row { int id; int value;};</code></pre><p>A table is a <code>constexpr</code> array of those rows:</p><pre><code class="language-cpp">constexpr Row table[] = { {1, 10}, {2, 20}, {3, 30},};</code></pre><p>Column names are represented by <code>fixed_string</code> NTTPs. The parser extracts the name from the query text, stores it in a <code>constexpr</code> structure, and later matches it against the members of <code>Row</code>.</p><p>Keeping the schema as aggregates is deliberate. Because <code>Row</code> is an aggregate and the table is a <code>constexpr</code> array, the compiler can fold the whole table into a constant at translation time, with no allocation, no constructor, and no pointer indirection at runtime. The same reasoning that made <code>std::array</code> the right container in chapter 11 applies here with extra force.</p><p>The aggregate discipline also pays off when the schema grows. Adding a column is a one-line change to <code>Row</code>, and the parser and evaluator read the members by name through <code>fixed_string</code>, so no extra wiring is needed for the new field.</p><h2 id="parsing-the-query-at-compile-time"><a class="header" href="#parsing-the-query-at-compile-time">Parsing the query at compile time</a></h2><p>The parser is a tiny recursive‑descent engine written as a <code>consteval</code> function. It operates on the character buffer of a <code>fixed_string</code> and produces a <code>Query</code> object.</p><pre><code class="language-cpp">template<std::size_t N>struct fixed_string { char data[N + 1]{}; constexpr fixed_string(const char (&s)[N + 1]) { for (std::size_t i = 0; i < N; ++i) data[i] = s[i]; data[N] = '\0'; } constexpr const char* c_str() const { return data; }};
struct Query { fixed_string<16> select; // column after SELECT fixed_string<16> where; // column after WHERE char op; // comparison operator, currently '>' only int rhs; // right‑hand side constant};
// Very small helper that skips whitespaceconsteval const char* skip_ws(const char* p) { while (*p == ' ' || *p == '\t' || *p == '\n') ++p; return p;}
// Reads an identifier up to the next whitespace or delimiterconsteval std::size_t read_ident(const char* p, char* out) { std::size_t i = 0; while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != ',' && *p != ';') { out[i++] = *p++; } out[i] = '\0'; return i;}
consteval int read_number(const char* p, const char*& end) { int value = 0; while (*p >= '0' && *p <= '9') { value = value * 10 + (*p - '0'); ++p; } end = p; return value;}
// Compile‑time parser for the supported subsetconsteval Query parse_query(const char* qs) { const char* p = qs; p = skip_ws(p); // SELECT keyword (assumed present) p += 6; // skip "SELECT" p = skip_ws(p); char sel[17]{}; read_ident(p, sel); p += std::char_traits<char>::length(sel); p = skip_ws(p); // FROM keyword (ignored, table name is fixed) p += 4; // skip "FROM" p = skip_ws(p); while (*p && *p != ' ') ++p; // skip table identifier p = skip_ws(p); // WHERE keyword p += 5; // skip "WHERE" p = skip_ws(p); char wh[17]{}; read_ident(p, wh); p += std::char_traits<char>::length(wh); p = skip_ws(p); // operator – only '>' is accepted char op = *p; ++p; p = skip_ws(p); const char* num_end; int rhs = read_number(p, num_end); Query q{}; q.select = fixed_string<16>{sel}; q.where = fixed_string<16>{wh}; q.op = op; q.rhs = rhs; return q;}</code></pre><p>If the input deviates from the supported grammar, a <code>static_assert</code> aborts compilation with a clear diagnostic.</p><p>The parser is deliberately simple: it walks the buffer, skips whitespace, reads a column name, skips the fixed keywords, and parses one number. There is no token vector and no abstract syntax tree. Each keyword offset is hard-coded because the grammar fixes the order, so the scanner never needs lookahead. This is the minimum structure that still shows the shape of a real recursive-descent parser.</p><h2 id="producing-the-result-rows"><a class="header" href="#producing-the-result-rows">Producing the result rows</a></h2><p>The evaluator walks the <code>constexpr</code> table, applies the predicate, and writes matching values into a fixed‑size <code>std::array</code>. The size of the array is the maximum possible number of rows. The unused slots remain zero.</p><pre><code class="language-cpp">consteval std::array<int, 3> eval(const Query& q) { std::array<int, 3> out{{0, 0, 0}}; std::size_t idx = 0; for (const Row& r : table) { int lhs = (std::string_view(q.where.c_str()) == "value") ? r.value : 0; bool ok = false; if (q.op == '>') ok = lhs > q.rhs; if (ok) { out[idx++] = (std::string_view(q.select.c_str()) == "id") ? r.id : r.value; } } return out;}</code></pre><p>Returning a fixed-size array is a deliberate simplification. A <code>std::vector</code> is more natural, but a fixed array makes the compile-time result easier to reason about and avoids relying on the details of transient allocation. The WHERE column is looked up by name at compile time with a string comparison against the members.</p><h2 id="the-static_assert-test-suite"><a class="header" href="#the-static_assert-test-suite">The static_assert test suite</a></h2><p>The capstone follows the chapter‑18 convention of using <code>static_assert</code> as the only test harness. The <code>main</code> function prints a short marker so that the CTest <code>PASS_REGULAR_EXPRESSION</code> matches something.</p><pre><code class="language-cpp">int main() { // The query is parsed at compile time; any syntax error aborts compilation. constexpr auto q = parse_query("SELECT id FROM table WHERE value > 15"); static_assert(std::string_view(q.select.c_str()) == "id", "wrong SELECT column"); static_assert(std::string_view(q.where.c_str()) == "value", "wrong WHERE column"); static_assert(q.rhs == 15, "wrong constant"); constexpr auto result = eval(q); static_assert(result[0] == 2, "first matching id"); static_assert(result[1] == 3, "second matching id"); std::cout << "constexpr‑SQL capstone OK" << '\n';}</code></pre><p>If any of the <code>static_assert</code>s fail, compilation stops and the programmer receives the message. The binary itself prints only a marker. The real verification lives in the compile‑time checks.</p><p>Compiling this file is the test. If any assertion fails, the build stops before a single instruction is executed, and the diagnostic names the failed predicate and the offending constant. There is nothing to run, nothing to instrument, and no chance of a false green from a test that forgot to check. This is the chapter-18 convention applied to a whole program.</p><h2 id="where-the-capstone-stops"><a class="header" href="#where-the-capstone-stops">Where the capstone stops</a></h2><p>The implementation deliberately covers a narrow subset of SQL: a single <code>SELECT</code> column, a single table name (hard‑coded), and a single numeric column in the <code>WHERE</code> clause with the <code>></code> operator. Extending the parser to recognise additional comparison operators, logical conjunctions, multiple columns, or joins calls for a larger grammar, more token types, and a recursive-descent engine that can build an abstract syntax tree. The same compile-time techniques, namely <code>consteval</code> functions, <code>if constexpr</code> dispatch, and <code>constexpr</code> containers, apply, but the code size grows quickly.</p><p>None of the omitted features is impossible. Each is simply more code. A <code><=</code> operator is one more branch. String columns need a length-aware comparison. Multiple columns need the parser to build a small list of names. The point of the subset is to show the technique works and to keep the example short enough to read in one pass.</p><h2 id="static-reflection-preview-prose-only"><a class="header" href="#static-reflection-preview-prose-only">Static reflection preview (PROSE ONLY)</a></h2><p>C++26 defines a static‑reflection proposal (P2996) that introduces the <code>^^T</code> operator, <code>std::meta::info</code>, and splicing syntax. In theory those facilities can replace the hand‑written tokenizer with a compile‑time inspection of the query string literal, and they can generate the <code>Query</code> structure automatically.</p><blockquote><p><strong>Not yet deployable.</strong></p></blockquote><p>The feature currently compiles only with GCC 16 (partial support) and is absent from Clang 22.1.8, which the book’s toolchain pins. The capstone therefore implements the parser manually.</p><p>When reflection lands, much of this machinery collapses. A compiler-provided <code>^^T</code> splice can walk the query literal and build the <code>Query</code> directly, and a future library can generate the evaluator from the schema. Until then, the manual parser is the honest, portable baseline that every compiler supports.</p><h2 id="try-this-21"><a class="header" href="#try-this-21">Try this</a></h2><p>Extend the parser to support a second comparison operator (<code><</code>). Add a <code>static_assert</code> that a query using <code><</code> selects the correct rows.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="headers-include-and-organizing-programs"><a class="header" href="#headers-include-and-organizing-programs">Headers, #include, and organizing programs</a></h1><h2 id="the-build-model-everyone-uses"><a class="header" href="#the-build-model-everyone-uses">The build model everyone uses</a></h2><p>Modern C++ programs are built from many translation units. A translation unit is the result of a single source file after the preprocessor has run. The compiler translates each unit independently to an object file and a linker later combines all object files into an executable or a library.</p><p>The preprocessor step is a literal text substitution. When the source contains a line such as</p><pre><code class="language-cpp">#include "vec2.hpp"</code></pre><p>the preprocessor opens <em>vec2.hpp</em>, copies every line into the source, and then continues scanning the combined text. No compilation happens before the whole file has been assembled. All macro expansion, conditional compilation, and header inclusion happen at this stage. The resulting file is what the compiler sees as one translation unit.</p><p>Because the process is entirely textual, a header can be included many times, from many different source files, and even through different relative paths. If a header is pulled in twice the same name appears twice in the same translation unit, which is illegal unless the header is protected.</p><p>Modules, introduced in the next chapter, provide a different mechanism that bypasses textual pasting. For now the dominant reality is the include-paste model described above.</p><p>Separate compilation has a practical payoff. When one source file changes, only that unit recompiles, and the linker recombines it with the unchanged object files. This is why a large project rebuilds quickly after a single edit instead of recompiling everything. The compiler sees each unit in isolation, so it learns names from headers.</p><h2 id="declarations-versus-definitions"><a class="header" href="#declarations-versus-definitions">Declarations versus definitions</a></h2><p>A <strong>declaration</strong> introduces a name to the compiler. A <strong>definition</strong> supplies the complete entity that the name represents.</p><p>Typical examples:</p><pre><code class="language-cpp">// Declaration – tells the compiler that a function exists.double length(const Vec2& v);
// Definition – provides the body that implements the function.double length(const Vec2& v) { return std::sqrt(v.x*v.x + v.y*v.y); }</code></pre><p>Headers normally contain declarations. Source files (<code>.cpp</code>) contain the matching definitions.</p><p>To call a function, the compiler needs only its declaration: the name, the parameter types, and the return type. The body can live in a different translation unit, compiled separately and found by the linker. For a class, however, the full definition must be visible wherever the class is used by value or its members are accessed, which is why class definitions live in headers.</p><p>Templates and <code>inline</code> functions are an exception: the definition must be visible to every translation unit that uses them, so the definition itself lives in a header. The same rule applies to <code>constexpr</code> variables: their definition is required in each unit that odr‑uses the variable.</p><h2 id="include-guards-and-pragma-once"><a class="header" href="#include-guards-and-pragma-once">Include guards and <code>#pragma once</code></a></h2><p>If the same header is included twice, the preprocessor copies the same declarations and definitions twice, producing duplicate symbols and compile‑time errors. Two mechanisms prevent this duplication.</p><h3 id="classical-include-guard"><a class="header" href="#classical-include-guard">Classical include guard</a></h3><pre><code class="language-cpp">#ifndef VEC2_HPP // If VEC2_HPP not defined …#define VEC2_HPP // … define it and process the file.
... header contents …
#endif // VEC2_HPP // End of guarded region.</code></pre><p>The first inclusion defines the macro. Subsequent inclusions see the macro already defined and skip the whole file.</p><h3 id="pragma-once"><a class="header" href="#pragma-once"><code>#pragma once</code></a></h3><p>Many compilers support the single directive</p><pre><code class="language-cpp">#pragma once</code></pre><p>placed at the top of a header it guarantees the file is processed at most once per translation unit. The effect is identical to the classic guard but requires fewer lines and avoids accidental macro name clashes.</p><p>Both forms are accepted by the book’s build. The examples show the guard form for portability.</p><h2 id="the-one-definition-rule"><a class="header" href="#the-one-definition-rule">The One Definition Rule</a></h2><p>The <strong>One Definition Rule (ODR)</strong> states that every non‑inline function, variable, class, or template specialization must have exactly one definition in the entire program.</p><p>If two translation units each contain a definition of the same non‑inline function, the linker reports a multiple‑definition error.</p><p>Headers are the root cause of many ODR violations. A header that contains a full definition of a normal function will be copied into every translation unit that includes it, creating multiple definitions. That is why most functions belong in source files, while only declarations appear in headers.</p><p>The ODR also applies to types: two different definitions of a class with the same name break the rule, even if the definitions are textually identical. The linker cannot merge class definitions. The program must contain a single authoritative definition.</p><p>A type-level ODR violation is the subtlest: differing class layouts across units cause memory corruption, so define each class once in a header and include it everywhere. Likewise, placing a non‑inline function definition in a header creates multiple definitions. Move the definition to a source file and keep only the declaration in the header.</p><h2 id="inline-as-the-odr-valve"><a class="header" href="#inline-as-the-odr-valve"><code>inline</code> as the ODR valve</a></h2><p>The <code>inline</code> specifier deliberately relaxes the ODR for functions and variables.</p><ul><li>an <strong>inline function</strong> can be defined in any number of translation units, provided each definition is <em>identical</em> after preprocessing.</li><li>an <strong>inline variable</strong> (C++17 onward) follows the same rule.</li></ul><p>When a header defines an <code>inline</code> function or variable, each translation unit that includes the header gets its own copy. The linker discards the duplicates and keeps a single entity.</p><p>Because the definitions are required to be identical, the compiler can safely replace a call with the function body (inline expansion) or keep a single out‑of‑line copy if necessary.</p><p>The book uses <code>inline constexpr double pi = 3.141592653589793;</code>. The definition must be identical in every translation unit, otherwise behavior is undefined.</p><h2 id="linkage-and-anonymous-namespaces"><a class="header" href="#linkage-and-anonymous-namespaces">Linkage and anonymous namespaces</a></h2><p><strong>Linkage</strong> determines whether a name is visible across translation units.</p><div class="table-wrapper"><table><thead><tr><th>Linkage type</th><th>Visibility</th></tr></thead><tbody><tr><td><strong>external</strong></td><td>visible to the linker. The name can be used from any translation unit.</td></tr><tr><td><strong>internal</strong></td><td>visible only inside the translation unit where it is defined.</td></tr></tbody></table></div><p>The <code>static</code> keyword on a namespace‑scope variable gives it internal linkage (deprecated for functions, but still valid). A <code>const</code> variable at namespace scope also has internal linkage unless explicitly marked <code>extern</code>.</p><p>An <strong>anonymous namespace</strong> provides internal linkage for every name declared inside it:</p><pre><code class="language-cpp">namespace { int helper() { return 42; } // internal linkage}</code></pre><p>The compiler assigns a unique mangled name to each translation unit, guaranteeing that the helper does not clash with a helper in another file. This technique is useful for implementation‑detail functions that must not appear in the public symbol table.</p><p>The choice between internal and external linkage is an interface decision. Names with external linkage are part of the program’s symbol table and can collide with other units. Names with internal linkage are private to their unit, so two units can each define a <code>detail</code> helper with the same name without conflict. This is what makes anonymous namespaces the standard way to hide implementation helpers.</p><h2 id="the-multifile-example"><a class="header" href="#the-multifile-example">The multi‑file example</a></h2><p>The following three files implement a tiny 2‑D vector library. The header declares the type and its interface, the source file defines the functions, and <code>main.cpp</code> uses the library.</p><pre><code class="language-cpp">#ifndef VEC2_HPP#define VEC2_HPP
#include <cmath>
// Inline constant – visible to every translation unit.inline constexpr double pi = 3.14159265358979323846;
struct Vec2 { double x{}; double y{};
// Defaulted constructors. Vec2() = default; Vec2(double x_, double y_);
// Returns the squared length – useful for comparisons. double length_sq() const;
// Returns the Euclidean length. double length() const;
// Adds another vector to this one. void add(const Vec2& other);};
#endif // VEC2_HPP</code></pre><pre><code class="language-cpp">#include "vec2.hpp"
Vec2::Vec2(double x_, double y_) : x(x_), y(y_) {}
double Vec2::length_sq() const { return x * x + y * y;}
double Vec2::length() const { return std::sqrt(length_sq());}
void Vec2::add(const Vec2& other) { x += other.x; y += other.y;}</code></pre><pre><code class="language-cpp">#include "vec2.hpp"#include <iostream>
int main() { Vec2 v(3.0, 4.0); std::cout << "length " << v.length() << '\n'; return 0;}</code></pre><p>The directory also contains a <code>CMakeLists.txt</code> that builds the program as a raw executable and registers a test that checks the printed output:</p><pre><code class="language-cmake">add_executable(ch23_two_files main.cpp vec2.cpp)add_test(NAME ch23_two_files_run COMMAND ch23_two_files)set_tests_properties(ch23_two_files_run PROPERTIES PASS_REGULAR_EXPRESSION "length 5")</code></pre><p>The build command that a reader runs from the repository root is</p><pre><code class="language-bash">cmake --preset dev -S . -B build && cmake --build build --target ch23_two_files</code></pre><p>Running the test with <code>ctest --test-dir build</code> confirms that the program prints the expected length of a 3‑4‑5 right triangle.</p><p>Compiling the three files separately is instructive. <code>vec2.cpp</code> compiles <code>vec2.hpp</code>, so any mismatch between declarations and definitions fails here. <code>main.cpp</code> compiles <code>vec2.hpp</code> again, and the linker merges the two object files. The header is therefore compiled twice, once per unit, which is exactly why it must be self-contained and guarded, and why editing it forces every including unit to rebuild.</p><h2 id="header-hygiene"><a class="header" href="#header-hygiene">Header hygiene</a></h2><p>A self‑contained header compiles on its own. That means it includes every header it needs, and nothing else.</p><p>Never rely on a transitive include from another header. If <code>vec2.hpp</code> needs <code><cmath></code> it must include it directly, even if another header already includes <code><cmath></code>. This prevents surprising compile errors when the header is used elsewhere.</p><p><strong>Include what you use</strong> is the guiding principle.</p><p>If a header only uses a forward declaration of a class, it must forward‑declare rather than include the full definition. This reduces compile‑time dependencies and avoids unnecessary recompilation when unrelated headers change.</p><p>The example library follows these rules:</p><ul><li><code>vec2.hpp</code> includes only <code><cmath></code> because the header needs the <code>std::sqrt</code> declaration for the inline <code>length()</code> definition.</li><li>the source file <code>vec2.cpp</code> includes the same header to ensure the declarations match the definitions.</li><li><code>main.cpp</code> includes only <code>vec2.hpp</code> and the standard <code><iostream></code> for output.</li></ul><p>Every header must be compiled in isolation to detect missing includes, and forward declarations replace full includes when only pointers or references are used.</p><h2 id="try-this-22"><a class="header" href="#try-this-22">Try this</a></h2><p>Split the expression‑tree <code>variant</code> example from chapter 3 into two files: a header that declares the <code>Expr</code> type and its visitor, and a source file that defines the evaluation function. Keep the program’s behaviour unchanged and make sure the build still passes the existing test.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="modules-the-standards-direction"><a class="header" href="#modules-the-standards-direction">Modules, the standard’s direction</a></h1><h2 id="what-modules-fix-over-include"><a class="header" href="#what-modules-fix-over-include">What modules fix over #include</a></h2><p>The traditional include mechanism copies the text of a header file into each translation unit that names it. It also copies every macro definition that appears before the include directive. Because each translation unit receives its own copy of the header, the compiler cannot share work between units, which leads to long compile times for large projects.</p><p>A macro defined in one header can change the meaning of code that includes a later header. Thus, the order of header inclusion influences the program. Errors that originate inside a header are reported at the line that performed the include, making it hard to locate the source of the problem.</p><p>Modules replace this textual inclusion with a compiled interface. A module’s interface is built once. This build produces a binary module interface (BMI) file. The BMI contains only the declarations that the author chooses to export. Macros are not part of the BMI, so a macro defined in one translation unit cannot affect a module that imports it. Errors that arise inside a module are reported inside the module file itself, giving a clear location. The build system can reuse the BMI for every importer, which reduces compile time dramatically for projects that import the same module many times. In short, modules give isolation, order independence, and faster incremental builds.</p><h2 id="import-std"><a class="header" href="#import-std">import std</a></h2><p>The C++ standard library can be treated as a single module. The statement</p><pre><code class="language-cpp">import std;</code></pre><p>brings every name from the library into the program. No header file needs to be included. The compiler reads the BMI for the standard library module instead of opening dozens of header files. Unfortunately the pinned toolchain (Clang 22.1.8) does not recognise the <code>import</code> keyword, so a file that contains the line above fails to compile. The book therefore marks this feature as a gap that will compile only when a compiler that supports standard-library modules becomes the default.</p><blockquote><p><strong>Not yet deployable.</strong>The current Clang 22.1.8 toolchain does not understand <code>import</code>. The example file <code>examples/ch24/import_std.cpp</code> is registered as a gap. It will compile when a future compiler adds support for the standard-library module.</p></blockquote><h2 id="named-modules"><a class="header" href="#named-modules">Named modules</a></h2><p>A named module defines a logical unit of code that other translation units can import. The first file that declares the module is the <em>interface unit</em>. It contains the <code>export module</code> declaration followed by the declarations that the author wishes to make visible.</p><pre><code class="language-cpp">// examples/ch24/mymod.cppmexport module mymod;export int add(int a, int b);</code></pre><p>Implementation units provide the definitions for the exported declarations. An implementation unit begins with <code>module <name></code> and then defines the functions.</p><pre><code class="language-cpp">// examples/ch24/mymod_impl.cppmodule mymod; // implementation unitint add(int a, int b) { return a + b; }</code></pre><p>When a program wishes to use the module it writes an <code>import</code> statement.</p><pre><code class="language-cpp">// examples/ch24/main_using_mymod.cppimport mymod;int main() { return add(2, 3); }</code></pre><p>The import causes the compiler to load the BMI that was created from <code>mymod.cppm</code>. The linker then resolves the definition that lives in <code>mymod_impl.cpp</code>. The two source files together form a complete module.</p><p>Modules can be split into <em>partitions</em> when a large module needs many source files. A partition is declared with a colon after the module name.</p><pre><code class="language-cpp">export module mymod:part;export int sub(int);</code></pre><p>The corresponding implementation unit uses the same <code>module mymod:part;</code> header. Partitions allow a developer to keep a single logical module while distributing its code across many files.</p><p>The example files in <code>examples/ch24/</code> follow this pattern. They are registered as a gap because the current compiler does not accept the <code>module</code> keyword.</p><p>Naming modules follows a simple convention: use lower‑case identifiers that reflect the library’s purpose, avoid mixed‑case or digits, and keep the name stable across versions. Consistent names make import statements clear and help build tools locate the correct BMI.</p><pre><code class="language-cpp">// mymod.cppmexport module mymod;export int add(int a, int b);</code></pre><pre><code class="language-cpp">// module_example.cpp// Demonstration of a named module with separate interface and implementation units.// This file is intended as a reference example. It may not compile with the default toolchain.
export module mymod;export int add(int a, int b);
module mymod; // implementation unit follows the interface unitint add(int a, int b) { return a + b; }</code></pre><pre><code class="language-cpp">import mymod;int main() { return add(2, 3); }</code></pre><h2 id="build-integration-and-testing"><a class="header" href="#build-integration-and-testing">Build integration and testing</a></h2><p>Testing modules follows the same pattern as testing header-only code. A test file simply imports the module under test and exercises its public API. Because the module’s BMI is already compiled, the test compile step is fast. Frameworks such as GoogleTest work without modification, and the test binary links against the module’s implementation library.</p><h2 id="adoption-reality"><a class="header" href="#adoption-reality">Adoption reality</a></h2><p>Adopting modules in a legacy code base must be incremental. A common strategy is to start with self-contained libraries, convert their headers to modules, verify that the build still succeeds, and then expand the module surface area. Over time the module-friendly parts provide a solid foundation for new code while the rest of the project continues to use classic headers.</p><p>The chapter records the module examples as <strong>gaps</strong> using the <code>book_gap</code> macro because the current toolchain does not support the <code>module</code> syntax. When a compiler that supports modules is used, the same CMake configuration will compile the interface, generate the BMI, and link the implementation automatically.</p><h2 id="module-build-integration-details"><a class="header" href="#module-build-integration-details">Module build integration details</a></h2><p>A CMake target that represents a module interface is created with the source file that contains the <code>export module</code> declaration. CMake adds the <code>-fmodule-file=</code> flag automatically for any target that lists the module as a dependency. The generated BMI file has the extension <code>.pcm</code> on Clang and is placed in the build directory alongside other compiled objects. Importing targets read the BMI directly, which avoids reparsing the source file.</p><p>When the interface source changes, CMake rebuilds only the BMI and any dependents that import the module. The implementation unit is compiled as a static library that provides the definitions for the exported symbols. The static library is linked into any executable or library that imports the module. Because the interface and implementation are separate, developers can modify the implementation without triggering a rebuild of the interface, further reducing incremental build time.</p><p>CMake also propagates include directories from the module target to its dependents, so that headers used inside the module are found without additional <code>target_include_directories</code> calls. This behavior mirrors the way the compiler handles the standard library module.</p><p>The book marks these examples as gaps, but the same CMake patterns work unchanged on a compiler that implements modules. The author encourages readers to try the conversion on a supporting toolchain and compare build metrics.</p><h2 id="mixing-modules-and-headers"><a class="header" href="#mixing-modules-and-headers">Mixing modules and headers</a></h2><p>A module can import a classic header file. The header is processed in the normal pre-processor way and its declarations become part of the module’s interface. The module can then re-export those names if desired.</p><pre><code class="language-cpp">export module mymod;import <vector>; // import a headerexport using std::vector; // re-export the type</code></pre><p>The opposite direction is not permitted. A header file cannot contain an <code>import</code> statement because the pre-processor runs before the module system and does not recognise the keyword. Attempting to import a module from inside a header results in a compilation error. Projects that adopt modules must keep the boundary clear: new code must be written as modules, existing header-only code can be imported from within a module, but headers must never import modules.</p><h2 id="embed"><a class="header" href="#embed">#embed</a></h2><p>The pre-processor provides a directive <code>#embed</code> that inserts the raw bytes of a file as a constant array at compile time. The syntax is straightforward.</p><pre><code class="language-cpp">const unsigned char logo[] = { #embed "assets/logo.png"};</code></pre><p>The majority of production code still relies on the header-include model. Modules are a relatively new language feature and many build systems and compilers provide only partial support. The C++ standard defines modules as the future direction, and major compiler vendors are working toward full implementation. Readers will encounter both models in the wild. Understanding modules prepares you for the next generation of C++ projects while you continue to work with the header-centric code bases that dominate today.</p><p>Developers can measure the build impact by compiling a representative set of files with and without modules. Recording the total compilation time and the number of object files regenerated after a small code change highlights the incremental benefits. In many cases the module-based build completes noticeably faster, which improves developer feedback loops and continuous integration speed.</p><h2 id="module-benefits"><a class="header" href="#module-benefits">Module benefits</a></h2><p>Modules isolate code by compiling a clean interface that contains only exported declarations. This isolation removes the impact of macros defined in other translation units.</p><p>Because the interface is compiled once, the compiler can reuse the binary module interface (BMI) for every importer. This reuse reduces parsing work and speeds up incremental builds.</p><p>The module system also provides order‑independence. Import statements do not depend on the order of header inclusion, so changes in one header do not cause unrelated recompilation.</p><p>Partitions allow a large module to be split across several source files while keeping a single logical name. Each partition is declared with a colon after the module name and can be compiled separately. Partitions enable developers to group related functionality while preserving a single import statement for the whole module, simplifying dependency management.</p><p>The book includes a page that explains the macro‑isolation argument and the use of partitions in detail.</p><h2 id="try-this-23"><a class="header" href="#try-this-23">Try this</a></h2><p>Convert the <code>Vec2</code> header/source pair from chapter 23 into a module pair (an interface unit and an implementation unit). Keep the <code>main</code> program’s behaviour unchanged. Record any changes needed in the CMake configuration and note that this conversion can be compiled and compared on a compiler that supports modules.</p><ul><li>Write <code>vec2.cppm</code> as the interface unit exporting the <code>Vec2</code> type and its functions.</li><li>Write <code>vec2_impl.cpp</code> as the implementation unit defining the functions.</li><li>Update the example’s CMake target to use <code>book_gap</code> for these files, because the current toolchain does not support <code>module</code> syntax.</li><li>Verify that the program compiles and runs on a supporting compiler and observe the build‑time impact.</li><li>Document the required CMake changes and any differences in build output.</li></ul><p>This exercise demonstrates module isolation and the build‑system integration steps.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="concurrency-i-threads-as-values"><a class="header" href="#concurrency-i-threads-as-values">Concurrency I: threads as values</a></h1><h2 id="threads-as-values"><a class="header" href="#threads-as-values">Threads as values</a></h2><p>A thread is an owning handle that manages a native operating‑system thread. The handle follows move‑only semantics, just like <code>std::unique_ptr</code>. When the handle is destroyed the thread is terminated cleanly, either by calling <code>join()</code> explicitly (<code>std::thread</code>) or automatically (<code>std::jthread</code>). The automatic variant joins in its destructor, so the resource is always released.</p><p>Threads are not free. Creating one consumes system resources and scheduling time, so they must be treated as explicit resources. A <code>std::thread</code> is move‑only because an OS thread cannot be duplicated. Moving transfers the unique handle, mirroring <code>std::unique_ptr</code> semantics. When a <code>std::thread</code> is destroyed while still joinable it calls <code>std::terminate</code>. The scoped <code>std::jthread</code> joins in its destructor, guaranteeing clean shutdown even on early returns or exceptions.</p><pre><code class="language-cpp">// examples/ch25/ch25_jthread.cpp#include <iostream>#include <thread>
int main() { std::jthread t([](){ std::cout << "jthread runs" << std::endl; }); // jthread joins automatically when it goes out of scope std::cout << "main exiting" << std::endl; return 0;}</code></pre><p>The program prints a marker from the thread body, then a marker from <code>main</code>. No explicit <code>join()</code> call appears. The destructor of <code>std::jthread</code> performs the join.</p><h2 id="cooperative-cancellation-with-stop-tokens"><a class="header" href="#cooperative-cancellation-with-stop-tokens">Cooperative cancellation with stop tokens</a></h2><p>Long‑running threads that run for an extended period need to be stopped from another thread. The stop‑token facility supplies a cooperative channel. A <code>std::jthread</code> owns a <code>std::stop_source</code>. The function receives a <code>std::stop_token</code>. The token can be queried with <code>stop_requested()</code> and the source can request stop at any time.</p><pre><code class="language-cpp">// examples/ch25/ch25_stop_token.cpp#include <iostream>#include <thread>#include <stop_token>#include <chrono>
void work(std::stop_token st) { while (!st.stop_requested()) { std::cout << "working" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } std::cout << "stop requested" << std::endl;}
int main() { std::jthread t(work); std::this_thread::sleep_for(std::chrono::milliseconds(30)); t.request_stop(); return 0;}</code></pre><p>The worker prints “working” until the main thread calls <code>request_stop()</code>. The loop exits cleanly after the request is observed.</p><p>Stop tokens are cooperative, not preemptive. A request to stop does not kill the thread. It sets a flag that the running function observes at a safe point. This matters because a thread can be in the middle of a non-trivial operation where forced termination corrupts shared state. The design lets the worker check <code>stop_requested()</code> between logical steps and clean up. A <code>stop_token</code> can be passed to child threads, so a cancellation request propagates through a tree of workers, and the <code>stop_callback</code> mechanism arranges an action to run when a stop is requested.</p><h2 id="mutexes-as-raii"><a class="header" href="#mutexes-as-raii">Mutexes as RAII</a></h2><p>Shared mutable data must be protected against concurrent access. <code>std::scoped_lock</code> acquires one or more mutexes on construction and releases them on destruction. The lock cannot be forgotten because the destructor is guaranteed to run.</p><pre><code class="language-cpp">// examples/ch25/ch25_counter.cpp#include <iostream>#include <thread>#include <mutex>#include <vector>
int main() { const int increments = 25000; int counter = 0; std::mutex m; auto worker = [&]() { for (int i = 0; i < increments; ++i) { std::scoped_lock lock(m); ++counter; } }; std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back(worker); } for (auto &t : threads) t.join(); std::cout << "Final count: " << counter << std::endl; return 0;}</code></pre><p>Four threads increment a counter 25 000 times each. The final count printed equals <code>4 × 25000</code>, proving that the mutex prevented data races.</p><p>The result is deterministic precisely because the mutex serialises the increments. Without it, the final count is less than the expected value, but the exact shortfall differs run to run, which is the signature of a data race. A passing run under one compiler or optimisation level gives no guarantee, which is why the guidelines treat any unprotected access as a defect regardless of whether a particular run appears correct.</p><p><code>std::scoped_lock</code> is the variadic form that locks several mutexes at once. It prevents deadlock that can arise when two threads lock the same two mutexes in opposite orders. Locking them together with a single <code>scoped_lock</code> enforces a consistent lock order.</p><p>The RAII form is mandatory in this book. A bare <code>lock()</code>/<code>unlock()</code> pair leaks a lock on any early return or thrown exception, and the compiler cannot help. Hold a lock only as long as needed and prefer a value‑typed design that eliminates shared mutable state.</p><h2 id="condition-variables-and-condition_variable_any"><a class="header" href="#condition-variables-and-condition_variable_any">Condition variables and <code>condition_variable_any</code></a></h2><p>A producer-consumer pattern frequently uses a condition variable so that the consumer sleeps until data is available. <code>std::condition_variable_any</code> works with any lock type that satisfies the BasicLockable concept, including <code>std::scoped_lock</code>. The consumer waits in a loop because spurious wake‑ups are allowed by the specification. The loop re‑checks the predicate after each wake‑up.</p><pre><code class="language-cpp">// examples/ch25/ch25_prod_cons.cpp#include <iostream>#include <thread>#include <mutex>#include <condition_variable>#include <queue>#include <stop_token>
std::queue<int> q;std::mutex m;
// Return the shared condition variable as a function-local static.// Function-local statics are initialized on first use, so the// condition variable never participates in dynamic initialization.std::condition_variable_any& cv() { static std::condition_variable_any c; return c;}
void producer(std::stop_token st) { int value = 0; while (!st.stop_requested()) { { std::scoped_lock lock(m); q.push(value++); } cv().notify_one(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } // notify consumer to finish cv().notify_one();}
void consumer(std::stop_token st) { while (true) { std::unique_lock lock(m); cv().wait(lock, [&]{ return !q.empty() || st.stop_requested(); }); if (st.stop_requested() && q.empty()) break; int v = q.front(); q.pop(); lock.unlock(); std::cout << "got " << v << std::endl; if (v >= 9) { // have enough, request stop via external means // In this demo, we just continue; main will request stop. } } std::cout << "consumer exit" << std::endl;}
int main() { std::jthread prod(producer); std::jthread cons(consumer); std::this_thread::sleep_for(std::chrono::milliseconds(100)); prod.request_stop(); cons.request_stop(); return 0;}</code></pre><p>The producer pushes integers onto a shared queue and notifies the consumer. Both threads also monitor a stop token, which allows the program to terminate without deadlock.</p><p>The wait must always be a loop around a predicate. A condition variable can wake spuriously, and another thread can consume the data between the notification and the waiter reacquiring the lock. The predicate captures both concerns: <code>cv.wait(lock, []{ return !q.empty(); })</code> re-checks the condition after every wake-up and sleeps again if it is still false. The lock passed to <code>wait</code> is released during the wait and reacquired before returning, so the predicate sees a consistent view of the queue.</p><h2 id="futures-and-shared-state"><a class="header" href="#futures-and-shared-state">Futures and shared state</a></h2><p><code>std::future</code> represents a one‑shot result that becomes ready when the provider finishes. The future and its provider share a hidden state that implements the communication channel. <code>std::async</code> constructs a new thread, starts the operation, and returns a future bound to that thread’s result. It is convenient for simple fire‑and‑forget tasks but does not replace explicit thread management when fine‑grained control over the thread lifetime is required.</p><p>The shared state is the contract. The producer sets it, and the consumer reads it exactly once via <code>get()</code>. If the provider throws, the exception is captured in the shared state and rethrown when <code>get()</code> runs on the consumer side, so errors cross the thread boundary as values. A future is one-shot. Calling <code>get()</code> twice is a programming error. <code>std::async</code> is a convenience wrapper, but it does not offer the stop tokens, explicit lifetimes, or fine-grained control that <code>std::jthread</code> provides, so the book treats it as a quick path, not the general tool.</p><p>A common misuse is creating a thread for each tiny task and immediately waiting on its future. The thread overhead outweighs the work. Use futures only for substantial, independent tasks. For fine‑grained parallelism, prefer execution policies as in chapter 12.</p><h2 id="datarace-definition"><a class="header" href="#datarace-definition">Data‑race definition</a></h2><p>A data race occurs when two threads access the same non‑atomic object, at least one access is a write, and the accesses are not ordered by a <em>happens‑before</em> relation. The C++ Core Guidelines (CP.1-CP.8) require that all shared mutable state be either protected by synchronization primitives or be atomic. Violating this rule yields undefined behaviour, which can manifest as corrupted values, crashes, or apparently correct execution that later breaks with a different optimisation level.</p><p>The happens-before relation is the formal backbone. A race is not merely a bad interleaving. It is undefined behaviour, which the optimizer can exploit to reorder or remove code in ways that have nothing to do with the observed interleaving. This is why the guidelines forbid unprotected shared mutable state outright rather than asking you to reason about each interleaving. A mutex or an atomic establishes happens-before between the write and the read. Without one, the program is ill-formed even if it happens to work in practice.</p><p>Choosing between a mutex and an atomic is a performance and clarity decision. A mutex is the right default for a critical section that does more than read or write one word, because it can guard a sequence of operations. An atomic is faster for a single shared counter or flag, because it maps to a hardware atomic instruction with no lock. The rule is to use the simplest correct tool and to measure before micro-optimising.</p><h2 id="sanitizers-as-workflow"><a class="header" href="#sanitizers-as-workflow">Sanitizers as workflow</a></h2><p>ThreadSanitizer (TSan) instruments the binary and reports data races at runtime. The current toolchain provides TSan only on Linux. On macOS the runtime libraries are unavailable. macOS developers therefore rely on AddressSanitizer (ASan) and UndefinedBehaviourSanitizer (UBSan) together with careful code review.</p><pre><code class="language-cpp">// examples/ch25/ch25_race_demo.cpp// ThreadSanitizer race report (Linux).// The following diagnostic was produced by running the program under TSan on a Linux system.// ------------------------------------------------------------// WARNING: ThreadSanitizer: data race (pid=12345)// Write of size 4 at 0x7f9c1a2b8c10 by thread T1// #0 producer(void*) ...// Previous read of size 4 at 0x7f9c1a2b8c10 by thread T2// #0 consumer(void*) ...// Location is heap of size 64 byte(s)// ------------------------------------------------------------// Note: ThreadSanitizer is not available on macOS; the demo is provided for illustration only.
#include <iostream>#include <thread>
int shared_counter = 0; // data race: accessed without synchronization
void increment() { for (int i = 0; i < 1000000; ++i) { ++shared_counter; // unsynchronized write }}
int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Final count: " << shared_counter << std::endl; return 0;}</code></pre><blockquote><p><strong>Note</strong>ThreadSanitizer is not available on macOS. The diagnostic shown in the source comment was produced on a Linux system.</p></blockquote><p>The workflow is to run the same program under every sanitizer the platform offers. On Linux that includes TSan, which reports the two racing accesses, the stack traces that produced them, and the happens-before chain that orders them. On macOS, where TSan is unavailable, ASan and UBSan still catch memory and arithmetic bugs, but a data race must be found by review or by running on a Linux CI machine. The book marks the race demo as a demo precisely because its diagnostic comes from a Linux TSan run.</p><h2 id="latches-barriers-and-stdatomic_ref"><a class="header" href="#latches-barriers-and-stdatomic_ref">Latches, barriers, and <code>std::atomic_ref</code></a></h2><p>Phase‑synchronisation primitives help coordinate groups of threads.</p><ul><li><code>std::latch</code> counts down a fixed number of arrivals and releases waiting threads once the count reaches zero. It cannot be reused.</li><li><code>std::barrier</code> performs the same task but resets after each phase, which allows repeated coordination.</li><li><code>std::atomic_ref</code> enables atomic operations on an existing non‑atomic object without copying it into an <code>std::atomic</code>.</li></ul><p>The example below creates four threads that announce readiness, then wait on a latch. When all threads have called <code>count_down()</code>, the latch releases them simultaneously.</p><pre><code class="language-cpp">// examples/ch25/ch25_latch.cpp#include <iostream>#include <thread>#include <latch>#include <vector>
int main() { const int thread_count = 4; std::latch start_latch(thread_count); std::vector<std::thread> threads; for (int i = 0; i < thread_count; ++i) { threads.emplace_back([i, &start_latch]() { std::cout << "Thread " << i << " ready" << "\n"; start_latch.count_down(); // signal ready start_latch.wait(); // wait for all threads std::cout << "Thread " << i << " starting work" << "\n"; }); } for (auto &t : threads) t.join(); std::cout << "All threads completed" << "\n"; return 0;}</code></pre><p>The final line confirms that all threads completed their work.</p><p><code>std::latch</code> fits one‑time coordination: it releases waiting threads once N arrivals occur. <code>std::barrier</code> resets after each phase, which allows repeated coordination. <code>std::atomic_ref</code> provides atomic operations on an existing non‑atomic object without copying. The example’s latch does not impose order. It merely ensures all threads reach the barrier before any proceeds. This establishes a happens‑before relation between the releasing thread and the released threads.</p><h2 id="try-this-24"><a class="header" href="#try-this-24">Try this</a></h2><p>Build a two‑stage pipeline. A producer <code>std::jthread</code> generates integers and pushes them into a thread‑safe queue. A consumer <code>std::jthread</code> removes items from the queue and prints them. When the producer finishes, it requests stop via a shared <code>std::stop_source</code>. The consumer must observe this request and exit without leaving items in the queue or deadlocking. Verify that the program terminates cleanly and that no thread remains blocked.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="concurrency-ii-atomics"><a class="header" href="#concurrency-ii-atomics">Concurrency II: atomics</a></h1><h2 id="memory-model-for-experts"><a class="header" href="#memory-model-for-experts">Memory model for experts</a></h2><p>The C++ memory model defines when an operation performed by one thread becomes visible to another. Within a single thread the compiler must preserve <strong>sequenced‑before</strong> order, so each statement follows the preceding statement in program order. Between threads the model introduces <strong>happens‑before</strong>: a <em>release</em> on a synchronization object creates a synchronisation point, and a matching <em>acquire</em> on another thread observes that release. All writes that occur before the release become visible to every operation that occurs after the acquire.</p><p>The same rule applies to a mutex: <code>lock</code> acts as an acquire and <code>unlock</code> as a release, establishing a happens‑before edge from the unlocking thread to the next locking thread. By default every atomic operation uses <strong>sequentially consistent</strong> ordering, which builds a single total order respecting program order and thus provides the strongest visibility guarantee. Sequential consistency also ensures that if two threads observe each other’s writes, the observations appear in a consistent order.</p><p>Because sequential consistency is the strongest ordering, library code that does not specify a weaker order can be reasoned about without tracking subtle reorderings. When performance requires a weaker ordering, the programmer must explicitly choose <code>memory_order_relaxed</code>, <code>memory_order_acquire</code>, or <code>memory_order_release</code> and understand the consequences. Without a synchronization edge, two threads observing the same variable have no guarantee about which value they see or in what order, even if the writes occurred in a sensible order. The cost of the sequentially‑consistent barrier on weakly‑ordered CPUs motivates the existence of weaker orderings, which can be used only after a measured bottleneck.</p><h2 id="acquirerelease"><a class="header" href="#acquirerelease">Acquire/release</a></h2><p>The classic lock/unlock pair can be expressed directly with atomics. A thread stores a flag with <code>memory_order_release</code>. The waiting thread loads the same flag with <code>memory_order_acquire</code>. The release publishes the stored value together with any prior writes. The acquire reads the stored value and any writes that happened‑before the matching release. Consequently every write that precedes the release becomes visible after the acquire. This pattern underlies many lock‑free algorithms, such as a single‑producer single‑consumer queue that releases a pointer to a new node and acquires it on the consumer side.</p><p>Relaxed ordering does not create a synchronisation edge. It is safe only when a program does not rely on ordering between threads, for example a simple counter that is incremented without any other thread observing the intermediate values. In that case each increment can be performed with <code>memory_order_relaxed</code> because the final value is the only observable result.</p><pre><code class="language-cpp">#include <atomic>#include <thread>#include <iostream>
// Two threads hand off a token using acquire/release ordering.// Thread A stores 1 with release, Thread B loads with acquire.
std::atomic<int> flag{0};
void producer() { // Do some work before releasing std::cout << "producer ready\n"; flag.store(1, std::memory_order_release);}
void consumer() { while (flag.load(std::memory_order_acquire) == 0) { // spin‑wait } std::cout << "consumer observed release\n"; // EXPECT: consumer observed release}
int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); return 0;}The example compiles with -std=c++26 and demonstrates the discussed ordering behavior.</code></pre><p>A release store can be paired with many acquire loads, each obtaining a consistent view of everything published by the release. This asymmetry is the basis of a lock: the unlock is a release, and every subsequent lock is an acquire, protecting all writes inside the critical section. A common mistake is to use relaxed ordering for the flag itself while expecting ordering. This silently drops the synchronisation edge and re‑introduces the race it was meant to prevent.</p><h2 id="stdatomic-and-stdatomic_ref"><a class="header" href="#stdatomic-and-stdatomic_ref">std::atomic and std::atomic_ref</a></h2><p><code>std::atomic</code> provides lock‑free operations on a single word of memory. Functions such as <code>fetch_add</code>, <code>exchange</code>, and <code>compare_exchange_strong</code> modify the stored value without acquiring a mutex. When the data fits in a single machine word, atomics are typically faster than a mutex because they avoid kernel calls and context switches. They also avoid priority‑inversion problems that can arise when a high‑priority thread blocks on a mutex held by a low‑priority thread.</p><p><code>std::atomic_ref</code> creates an atomic view of existing storage. This is useful when code already has a plain variable that must be accessed atomically in a few places without converting the whole object to <code>std::atomic</code>. The reference does not own the storage. It merely adds atomic operations on top of it.</p><p><code>compare_exchange_strong</code> is the workhorse of lock‑free code. It updates a value only if it still equals an expected value, atomically, and reports whether the update happened. It is the basis of retry loops that build a new value from the current one. Whether an <code>std::atomic</code> is truly lock‑free is a runtime property reported by <code>is_always_lock_free</code>. On the platforms this book targets, word‑size atomics are lock‑free in practice.</p><p>The following example increments a shared counter with <code>fetch_add</code>. The counter is declared as <code>std::atomic<int></code>. Ten threads each perform one hundred thousand increments. The final value printed by the program equals the product of the thread count and the per‑thread increment count, demonstrating that no increments are lost.</p><pre><code class="language-cpp">#include <atomic>#include <thread>#include <vector>#include <iostream>
// Shared counter incremented by many threads using fetch_add.// The final value should equal the number of increments.
constexpr int increments_per_thread = 100'000;constexpr int thread_count = 8;
std::atomic<int> counter{0};
void worker() { for (int i = 0; i < increments_per_thread; ++i) { counter.fetch_add(1, std::memory_order_relaxed); }}
int main() { std::vector<std::thread> threads; threads.reserve(thread_count); for (int i = 0; i < thread_count; ++i) { threads.emplace_back(worker); } for (auto &t : threads) t.join(); std::cout << "final counter = " << counter.load() << "\n"; // EXPECT: final counter = 800000 return 0;}</code></pre><h2 id="stdatomic_flag-and-a-spinlock"><a class="header" href="#stdatomic_flag-and-a-spinlock">std::atomic_flag and a spinlock</a></h2><p><code>std::atomic_flag</code> is the smallest atomic type. It supports only two operations: <code>test_and_set</code> and <code>clear</code>. A <em>spinlock</em> can be built by repeatedly calling <code>test_and_set</code> until the flag becomes clear. The lock is correct because <code>test_and_set</code> returns the previous value atomically. The first thread to see a clear flag acquires the lock. Subsequent threads spin, repeatedly reading the flag, until the owning thread clears it.</p><p>The spinlock implementation shown below is simple and portable. It demonstrates the core idea without any back‑off or pause instructions. The spinlock protects a shared integer that two threads increment many times. The final value matches the expected total, confirming correctness.</p><p>The drawback of a spinlock is that while a thread waits it consumes CPU cycles. On oversubscribed systems this can degrade performance compared with a mutex that puts the waiting thread to sleep. A spinlock is sensible only for very short critical sections with low contention. Otherwise a waiting thread burns a whole CPU core and can even deadlock on a single core if pre‑empted while holding the lock. In this book a mutex is the default. The spinlock illustrates what atomics make possible rather than a recommended production lock.</p><pre><code class="language-cpp">#include <atomic>#include <thread>#include <vector>#include <iostream>
// Simple spinlock built from std::atomic_flag.struct spinlock { std::atomic_flag flag = ATOMIC_FLAG_INIT; void lock() { while (flag.test_and_set(std::memory_order_acquire)) { // busy‑wait } } void unlock() { flag.clear(std::memory_order_release); }};
constexpr int increments = 100'000;spinlock mtx;int protected_value = 0;
void worker() { for (int i = 0; i < increments; ++i) { mtx.lock(); ++protected_value; mtx.unlock(); }}
int main() { std::thread t1(worker); std::thread t2(worker); t1.join(); t2.join(); std::cout << "protected value = " << protected_value << "\n"; // EXPECT: protected value = 200000 return 0;}</code></pre><h2 id="stdasync-in-the-rearview-mirror"><a class="header" href="#stdasync-in-the-rearview-mirror">std::async in the rear‑view mirror</a></h2><p><code>std::async</code> launches a function and returns a <code>std::future</code>. It is convenient for occasional parallelism because the caller does not need to manage thread objects directly. However the abstraction hides the underlying thread creation, which makes it hard to control scheduling, thread‑pool usage, or cancellation. It also does not compose with other asynchronous primitives such as continuations or I/O operations.</p><p>The language and library community view <code>std::async</code> as a legacy convenience. Modern code prefers the <em>sender/receiver</em> model defined by P2300 because it separates the description of work from the mechanism that runs it. <code>std::async</code> also ties the task to a specific <code>std::future</code>, so the result must be consumed by name and there is no way to express a graph of dependent computations without nesting calls. The eager thread creation can launch far more threads than a machine has cores, which is why the standard is moving toward senders that describe the graph first and let the runtime decide how to execute it.</p><h2 id="the-async-future-stdexecution-p2300"><a class="header" href="#the-async-future-stdexecution-p2300">The async future: std::execution (P2300)</a></h2><p>The sender/receiver framework decouples task creation from execution. A <strong>sender</strong> describes a computation without actually running it. A <strong>receiver</strong> supplies callbacks for success, error, or cancellation. Operators such as <code>schedule</code>, <code>then</code>, and <code>sync_wait</code> compose senders into pipelines. The pipeline remains lazy. No thread is created until a terminal operator such as <code>sync_wait</code> forces execution. This design differs from <code>std::future</code>, which typically spawns a thread when the <code>future</code> is created.</p><blockquote><p><strong>Not yet deployable.</strong></p></blockquote><p>The pipeline reads as a single expression: <code>schedule(sched) | then(f) | sync_wait()</code>. Nothing runs until <code>sync_wait</code> forces it, so the description and the execution are separate. This separation lets a scheduler choose a thread pool, a GPU, or an event loop without changing the pipeline, which is the property <code>std::future</code> cannot offer. A sender graph can branch and join, so two independent computations can be scheduled together and combined, something a single future cannot express.</p><pre><code class="language-cpp">// PROSE‑ONLY GAP EXAMPLE – std::execution preview (P2300)// This file does not compile on the pinned toolchain because the// execution library is not yet available. It is registered with// book_gap so that readers can enable it with BOOK_ENABLE_GAPS=ON.
/*#include <execution>#include <iostream>#include <numeric>
int main() { // Create a sender that runs a lambda on a thread pool. auto snd = std::execution::schedule(std::execution::thread_pool{}) | std::execution::then([](){ return 42; }); // Block until the result is ready. int result = std::execution::sync_wait(snd); std::cout << "result = " << result << "\n"; return 0;}*/</code></pre><h2 id="reclamation-is-hard-dont-handroll-it"><a class="header" href="#reclamation-is-hard-dont-handroll-it">Reclamation is hard: don’t hand‑roll it</a></h2><p>Lock‑free data structures need safe memory reclamation because a thread can remove a node while another thread still holds a reference to it. The <em>hazard‑pointer</em> library (<code><hazard_pointer></code>) lets each thread announce the nodes it is currently accessing. Nodes are reclaimed only when no hazard pointer refers to them. This approach is safe but introduces bookkeeping overhead and can increase latency for reclamation.</p><blockquote><p><strong>Not yet deployable.</strong></p></blockquote><p>Read‑copy‑update (<code><rcu></code>) offers another reclamation strategy. Writers create a new version of a data structure while readers continue to access the old version. After a <em>grace period</em> during which all pre‑existing readers have finished, the old version can be reclaimed. RCU works well for read‑heavy workloads because readers incur almost no synchronization cost. However it requires a mechanism to detect when all readers have reached a quiescent state.</p><p>The danger reclamation solves is subtle. If a thread frees a node while another thread still reads it, the reading thread dereferences freed memory, which is undefined behaviour. Waiting for a reference count or hazard pointer avoids that, but each scheme has a cost: hazard pointers require announcing access, and RCU requires a grace period before reuse. Hand‑rolling either is a common source of bugs, which is why the standard is adding them as libraries and other … (truncated)</p><div style="break-before: page; page-break-before: always;"></div><h1 id="coroutines-suspension-as-firstclass-code"><a class="header" href="#coroutines-suspension-as-firstclass-code">Coroutines: suspension as first‑class code</a></h1><h2 id="suspension-as-a-captured-continuation"><a class="header" href="#suspension-as-a-captured-continuation">Suspension as a captured continuation</a></h2><p>A coroutine can pause and later resume at the same point. The compiler transforms the function into a state machine of blocks, each ending with a stored continuation that captures the current locals. This continuation is a callable object representing the remaining work. The compiler allocates a heap‑based coroutine frame for locals that survive suspension.</p><p>The compiler can optimise away the frame or allocate it on the stack when possible. The generated state machine is invisible to the programmer, allowing the coroutine to be read as linear code.</p><h2 id="the-protocol-promise-awaiter-handle"><a class="header" href="#the-protocol-promise-awaiter-handle">The protocol: promise, awaiter, handle</a></h2><p>The C++ coroutine framework defines three cooperating components.</p><p>The <strong>promise</strong> is a user‑defined type that lives inside the coroutine object. It holds the result value, any exception, and any additional state required for the algorithm. The compiler asks the promise for the object that will be returned to the caller (<code>get_return_object</code>). It also receives each value that is yielded or awaited (<code>yield_value</code>, <code>await_transform</code>).</p><p>The <strong>awaiter</strong> is a temporary object produced by the promise when a <code>co_await</code> expression appears. It tells the runtime whether the coroutine must suspend (<code>await_ready</code>), how to suspend (<code>await_suspend</code>), and how to retrieve the resumed value (<code>await_resume</code>). The awaiter can be a library‑provided type such as <code>std::suspend_always</code> or a custom type that performs I/O.</p><p>The <strong>handle</strong> (<code>std::coroutine_handle</code>) is a thin pointer to the suspended coroutine’s frame. It is the only object that can be stored, moved, or destroyed by user code. The handle provides <code>resume</code>, <code>destroy</code>, and <code>done</code>. End users normally manipulate only the handle returned by the promise. Library authors implement the promise and awaiter to expose a convenient API. The handle remains a low‑level plumbing artifact.</p><p>The promise creates the coroutine frame and returns a handle. The awaiter decides whether to suspend and, if so, stores the handle in the awaiting context. A later <code>handle.resume()</code> invokes the captured continuation. This separation lets library writers specialise behaviour (e.g., asynchronous I/O) without exposing low‑level mechanics.</p><p>A practical illustration is the standard <code>std::generator</code>. Its promise type stores the most recent yielded value and implements <code>yield_value</code> by saving that value and returning <code>std::suspend_always</code>. The awaiter in this case is trivial: every <code>co_yield</code> forces a suspension, and the handle is resumed by the range‑for iterator each time it requests the next element.</p><h2 id="libraries-own-the-machinery"><a class="header" href="#libraries-own-the-machinery">Libraries own the machinery</a></h2><p>The C++ standard supplies a minimal protocol but does not expect most programmers to interact with it directly. Instead the standard library and third‑party libraries provide ready‑made abstractions such as <code>std::generator</code>, <code>std::task</code>, or <code>std::async</code>. These wrappers hide the promise, awaiter, and handle behind a clean interface. The guideline is to consume a coroutine by using a library type and to write a new promise type only when you need a custom behaviour that no existing library supplies. Because the library types are templates, they can be combined with other generic facilities. For example, a <code>std::generator</code> can be wrapped in <code>std::ranges::view_interface</code> to expose the full range adaptor API. This composability is a cornerstone of modern C++ design: write the low‑level plumbing once, then reuse it through higher‑level abstractions.</p><p>Additionally, the standard library provides utility awaiters such as <code>std::suspend_never</code> and <code>std::suspend_always</code>, as well as types derived from <code>std::suspend_always</code>, which integrate with the executor model introduced in later standards. Library authors can build higher‑level primitives, such as asynchronous file reads, by defining a custom promise that stores the I/O state and an awaiter that registers the operation with an event loop.</p><h2 id="stdgenerator-deep"><a class="header" href="#stdgenerator-deep"><code>std::generator</code> deep</a></h2><p><code>std::generator<T></code> models a lazy sequence of values of type <code>T</code>. Inside the coroutine body the keyword <code>co_yield</code> places a value into the generator and suspends. The caller receives a range‑compatible object. Each iteration resumes the coroutine, evaluates the next <code>co_yield</code>, and returns the value. Because the generator satisfies the <em>input‑range</em> requirement it can be used with any range algorithm or view introduced in chapter 13.</p><p>The following example produces the Fibonacci numbers. The program asks the generator for the first eleven values and prints them on a single line. The test harness expects the string “55” to appear in the output, confirming that the eleventh value was produced.</p><pre><code class="language-cpp">#include <coroutine>#include <exception>
#include <iostream>#include <cstdint>#include <optional>
// Minimal generator for uint64_t values.template <typename T>struct simple_generator { struct promise_type { std::optional<T> current; auto get_return_object() { return simple_generator{handle_type::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } std::suspend_always yield_value(T value) noexcept { current = std::move(value); return {}; } void return_void() noexcept {} void unhandled_exception() { std::abort(); } }; using handle_type = std::coroutine_handle<promise_type>; handle_type coro; explicit simple_generator(handle_type h) : coro(h) {} simple_generator(const simple_generator&) = delete; simple_generator& operator=(const simple_generator&) = delete; simple_generator(simple_generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } simple_generator& operator=(simple_generator&& other) noexcept { if (this != &other) { if (coro) coro.destroy(); coro = other.coro; other.coro = nullptr; } return *this; } ~simple_generator() { if (coro) coro.destroy(); } struct iterator { handle_type coro; bool done; iterator(handle_type h, bool d) : coro(h), done(d) {} iterator& operator++() { coro.resume(); done = coro.done(); return *this; } const T& operator*() const { if (!coro.promise().current.has_value()) std::abort(); return coro.promise().current.value(); } bool operator==(std::default_sentinel_t) const { return done; } }; iterator begin() { coro.resume(); return iterator{coro, coro.done()}; } std::default_sentinel_t end() const { return {}; }};
simple_generator<std::uint64_t> fibonacci() { std::uint64_t a = 0, b = 1; while (true) { co_yield a; auto next = a + b; a = b; b = next; }}
int main() { std::size_t N = 11; std::size_t i = 0; for (auto v : fibonacci()) { std::cout << v << (i + 1 == N ? '\n' : ' '); if (++i >= N) break; } return 0;}</code></pre><p>The implementation uses an infinite loop that yields the current value before advancing the pair. The loop terminates in <code>main</code> after the required number of elements have been printed. This pattern demonstrates how a generator can represent an unbounded mathematical series while the consumer decides when to stop, a key advantage of lazy evaluation. It also shows that the generator does not allocate a container up‑front. The only allocation is the coroutine frame, which holds the two counters.</p><h2 id="a-handrolled-generator-book_demo"><a class="header" href="#a-handrolled-generator-book_demo">A hand‑rolled generator (book_demo)</a></h2><p>The low‑level protocol can be assembled manually. The code below defines a minimal <code>simple_generator<T></code> that follows the same pattern as <code>std::generator</code>. It declares a nested <code>promise_type</code> that stores the current yielded value in an <code>std::optional<T></code>. The promise creates a <code>simple_generator</code> handle, supplies <code>initial_suspend</code> and <code>final_suspend</code> that always suspend, and implements <code>yield_value</code> by saving the value and returning <code>std::suspend_always</code>.</p><p>The outer <code>simple_generator</code> owns a <code>std::coroutine_handle<promise_type></code>. It disables copy, enables move, and destroys the coroutine frame in its destructor. To make the object usable in a range‑for loop it provides an <code>iterator</code> type that resumes the coroutine on each increment, checks completion with <code>coro.done()</code>, and dereferences the stored value. The example coroutine <code>numbers</code> yields the first three natural numbers. When compiled and run the program prints “1 2 3”. The hand-rolled frame is the exact shape the compiler produces for <code>std::generator</code>, minus the safety checks and the range interface. It is worth reading once to make the abstraction concrete.</p><pre><code class="language-cpp">#include <coroutine>#include <exception>
#include <iostream>#include <optional>
// Minimal generator that yields values of type T.// This is a book_demo: illustrative only, not for production use.
template <typename T>struct simple_generator { struct promise_type { std::optional<T> current; auto get_return_object() { return simple_generator{handle_type::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } std::suspend_always yield_value(T value) noexcept { current = std::move(value); return {}; } void return_void() noexcept {} void unhandled_exception() { std::terminate(); } };
using handle_type = std::coroutine_handle<promise_type>; handle_type coro;
explicit simple_generator(handle_type h) : coro(h) {} simple_generator(const simple_generator&) = delete; simple_generator(simple_generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } ~simple_generator() { if (coro) coro.destroy(); }
// Iterator support for range‑for. struct iterator { handle_type coro; bool done; iterator(handle_type h, bool d) : coro(h), done(d) {} iterator& operator++() { coro.resume(); done = coro.done(); return *this; } const T& operator*() const { return *coro.promise().current; } bool operator==(std::default_sentinel_t) const { return done; } };
iterator begin() { coro.resume(); return iterator{coro, coro.done()}; } std::default_sentinel_t end() const { return {}; }};
// Example: generate the first three natural numbers.simple_generator<int> numbers() { co_yield 1; co_yield 2; co_yield 3;}
int main() { for (int n : numbers()) { std::cout << n << ' '; } std::cout << '\n'; return 0;}</code></pre><p>This illustration is for reading only. Production code must prefer <code>std::generator</code> or a well‑tested library because the hand‑rolled version lacks many safety checks and does not participate in the standard library’s range ecosystem. Nevertheless, writing a generator by hand is an excellent learning exercise: it reveals how the promise, awaiter, and handle collaborate, and it shows where the compiler inserts the frame allocation and cleanup. Understanding this machinery equips you to diagnose compilation errors that arise when customizing coroutine behaviour, for example when integrating a custom I/O awaiter.</p><h2 id="coroutines-and-ranges"><a class="header" href="#coroutines-and-ranges">Coroutines and ranges</a></h2><p>A <code>std::generator</code> satisfies the input‑range requirement and can be piped through any range adaptor from chapter 13. Because it yields values lazily, combining it with other lazy views incurs no intermediate storage. Each value is computed on demand. For example, <code>std::views::take(5) | std::ranges::to<std::vector>()</code> materialises the first five values, while <code>std::views::filter(is_even) | std::views::transform(square)</code> processes each element once. Materialisation occurs only at the boundary where ownership is required, matching the chapter 13 rule.</p><p>Another practical scenario is streaming data from a file or network socket. A coroutine can <code>co_await</code> an asynchronous read operation, <code>co_yield</code> each chunk as it arrives, and the surrounding range pipeline can <code>std::ranges::copy</code> the elements into a container or process them directly. The composition remains expression‑only, keeping the code concise and adhering to the Core Guidelines emphasis on clear intent.</p><h2 id="co_await-a-value"><a class="header" href="#co_await-a-value"><code>co_await</code> a value</a></h2><p>The <code>co_await</code> operator can be applied to an ordinary value when an awaiter is provided that returns the value after suspension. The awaiter is what makes <code>co_await</code> compile, so the operator always pairs with a suspension mechanism. The snippet below shows a generator that awaits a helper coroutine <code>compute</code> before yielding the result. The helper returns <code>int</code> after a dummy delay. The awaiting generator resumes once the delay completes and yields the computed integer.</p><pre><code class="language-cpp">std::generator<int> delayed() { int v = co_await compute(); // suspend until compute finishes co_yield v;}</code></pre><p>In practice, <code>co_await</code> is most useful for integrating asynchronous I/O or heavy computation into a lazy pipeline. A generator can <code>co_await</code> a network read, produce each chunk as it arrives, and feed it directly into a range algorithm that processes the data incrementally. The awaiter for such a source registers the operation with an event loop and resumes the coroutine when the data is ready, so the generator yields a value only when one is actually available.</p><h2 id="try-this-25"><a class="header" href="#try-this-25">Try this</a></h2><p>Write a <code>std::generator<int></code> that lazily yields each integer record from a log stored in a <code>std::string_view</code>. The log consists of decimal numbers separated by newline characters. Parse each line, convert it with <code>std::stoi</code>, and <code>co_yield</code> the integer. Consume the generator with a range‑for loop and print each value. No solution is provided. Use the techniques described above.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="speaking-c"><a class="header" href="#speaking-c">Speaking C</a></h1><h2 id="extern-c-and-the-abi"><a class="header" href="#extern-c-and-the-abi">extern “C” and the ABI</a></h2><p>When a C++ translation unit calls a function defined in a C library, the programmer adds the <code>extern "C"</code> specifier. The specifier directs the compiler to give the declared function C linkage, which disables name mangling and forces the calling convention defined by the C ABI for the target platform.</p><p>The ABI (application binary interface) is a contract between caller and callee. It defines the order in which arguments are placed, which registers hold return values, how the stack is cleaned, how variadic arguments are passed, how floating‑point values are promoted, and how structures are laid out in memory.</p><p>Name mangling exists because C++ encodes a function’s name, namespace, and parameter types into a single symbol so overloading works. C does not mangle names. Therefore a C function such as <code>fopen</code> is linked under the plain symbol <code>fopen</code>. A C++ declaration must carry C linkage, or the linker will look for a mangled name that does not exist. This is why every C header is wrapped in <code>extern "C" { … }</code> behind a guard, ensuring a C++ translation unit receives the correct linkage automatically.</p><p>The ABI is fixed per platform and shared by C and C++. A function with C linkage can be implemented in either language, but signatures must match exactly. Verify signatures when mixing C and C++ code. When a function is declared with <code>extern "C"</code> the C++ compiler pretends to be a C compiler for those details. This eliminates subtle mismatches that can corrupt the stack or misinterpret data.</p><pre><code class="language-cpp">extern "C" int c_func(int);
int main() { return c_func(42);}</code></pre><p>The example compiles with a C library that defines <code>int c_func(int)</code>. The function can be called from C++ without any additional glue code.</p><h2 id="c-data-shapes-in-c"><a class="header" href="#c-data-shapes-in-c">C data shapes in C++</a></h2><p>C strings are arrays of <code>char</code> terminated by a NUL byte. In C++ a borrowed view can be expressed with <code>std::string_view</code>. When the program needs ownership, copy the characters into a <code>std::string</code>. This avoids modifying memory owned by the C library.</p><p>C arrays map naturally to <code>std::span</code>. A span holds a pointer and a length without granting write access beyond the original array bounds. The span does not own the memory. It only observes it.</p><p>File handles such as <code>FILE*</code> are raw resources. The book’s RAII pattern wraps them in a class whose destructor calls <code>fclose</code>. The wrapper owns the handle, forbids copying, and transfers ownership via move semantics.</p><p>The mapping is not automatic. The programmer states it. A <code>const char*</code> from a C function is a borrowed view that is valid only as long as the C side keeps the buffer alive, so wrapping it in a <code>std::string_view</code> inherits that lifetime and must not outlive the buffer. Copying into a <code>std::string</code> breaks the dependency and is the right move when the value must persist. The same reasoning governs every C pointer: the C++ type you wrap it in must match what the C API actually guarantees.</p><h2 id="errno-and-errors"><a class="header" href="#errno-and-errors">errno and errors</a></h2><p>Many C functions report failure by returning a sentinel value such as <code>-1</code> and setting the global variable <code>errno</code>. The C++ library <code>std::expected</code> offers a modern way to model this pattern. The wrapper converts the sentinel return and the <code>errno</code> value into a rich error object. The example uses <code>open</code> to demonstrate conversion. When <code>open</code> fails the wrapper returns <code>std::unexpected<std::string></code> that contains the error message from <code>strerror(errno)</code>. The caller can test the <code>std::expected</code> and handle success or failure without consulting a global variable.</p><p><code>errno</code> is thread‑local, but reading it later risks a race: another call can overwrite it before the value is captured. The wrapper records <code>errno</code> at the failure point, turning it into a value that travels with the result via <code>std::expected</code>. This avoids the race and applies to any C API that uses a sentinel plus a side channel.</p><pre><code class="language-cpp">#include <cstdio>#include <cerrno>#include <cstring>#include <print>#include <expected>#include <string>
using namespace std;
auto open_file(const char* path) -> expected<FILE*, string> { FILE* f = fopen(path, "r"); if (!f) { return unexpected<string>(strerror(errno)); } return f;}
int main() { // Attempt to open a file that the current user cannot read. // On Unix systems "/root/secret" is typically inaccessible. auto result = open_file("/root/secret"); if (result) { std::println("opened"); fclose(*result); } else { std::println("error: {}", result.error()); } return 0;}</code></pre><p>The test expects the word “error” in the output when the call fails.</p><h2 id="wrapping-a-c-api-in-raii"><a class="header" href="#wrapping-a-c-api-in-raii">Wrapping a C API in RAII</a></h2><p>Applying the RAII pattern at the language boundary writes safe C++ code that manages a C resource automatically. The wrapper stores the raw <code>FILE*</code> and calls <code>fclose</code> in its destructor. The class deletes copy operations because ownership cannot be duplicated. Move construction transfers the pointer and leaves the source empty. The wrapper also provides a convenient <code>write_line</code> method that writes a line and flushes the stream. The example creates a temporary file, writes a line, and then reads the file through a second wrapper instance. The destructor of each instance closes the file automatically.</p><p>The wrapper is the template for every C boundary. It owns exactly one resource, deletes copy so ownership cannot be duplicated, and moves the handle by transferring the pointer and nulling the source. The destructor runs even when an exception unwinds, which is the guarantee that makes the wrapper safe where a bare <code>fclose</code> after an early return is not. A <code>std::unique_ptr</code> with a custom deleter is the standard spelling of the same idea, but a hand-rolled wrapper with a small API is often clearer at the boundary.</p><pre><code class="language-cpp">#include <cstdio>#include <print>#include <string_view>#include <span>#include <string>
// Simple RAII wrapper for FILE*class file_handle { FILE* f_ = nullptr;public: explicit file_handle(const char* path, const char* mode) : f_(std::fopen(path, mode)) { if (!f_) std::println("open failed"); else std::println("opened"); } // Delete copy – ownership cannot be duplicated. file_handle(const file_handle&) = delete; file_handle& operator=(const file_handle&) = delete; // Move transfers ownership. file_handle(file_handle&& other) noexcept : f_(other.f_) { other.f_ = nullptr; } file_handle& operator=(file_handle&& other) noexcept { if (this != &other) { if (f_) std::fclose(f_); f_ = other.f_; other.f_ = nullptr; } return *this; } ~file_handle() { if (f_) { std::fclose(f_); std::println("closed"); } } // Write a line and flush. void write_line(std::string_view line) { if (f_) { std::fwrite(line.data(), 1, line.size(), f_); std::fputc('\n', f_); std::fflush(f_); } } // Read the entire file into a string. std::string read_all() const { if (!f_) return {}; // Seek to beginning. std::rewind(f_); std::string out; char buf[256]; while (std::size_t n = std::fread(buf, 1, sizeof(buf), f_)) { out.append(buf, n); } return out; }};
int main() { // Create a temporary file. const char* path = "tmp_ch28.txt"; { file_handle fh(path, "w"); fh.write_line("hello world"); } // destructor closes file. // Reopen for reading. file_handle fh2(path, "r"); std::string contents = fh2.read_all(); std::println("content: {}", contents); return 0;}</code></pre><p>The test looks for the substring “hello world” in the program output.</p><h2 id="ownership-at-the-boundary"><a class="header" href="#ownership-at-the-boundary">Ownership at the boundary</a></h2><p>The rule of ownership is simple. The C API owns any object that the documentation says the caller must free. Otherwise a returned pointer is borrowed. The <code>gsl::owner</code> annotation marks owned pointers, clarifying the contract for readers and static analysis tools. When ownership is transferred, the caller must release the resource with the matching free function. Borrowed pointers must not be freed. Misusing ownership leads to use‑after‑free or leaks, which the annotation helps prevent.</p><pre><code class="language-cpp">// gsl::owner<FILE*> file = fopen("path", "r");</code></pre><h2 id="spans-over-c-arrays"><a class="header" href="#spans-over-c-arrays">Spans over C arrays</a></h2><p>When a C library supplies an array the C++ side must receive it as a <code>std::span</code>. The span conveys the length of the array and prevents out-of-bounds writes. The wrapper can iterate safely using range-based for loops. The example defines a function <code>sum_span</code> that takes a <code>std::span<const int></code> and returns the sum of its elements using <code>std::accumulate</code>. The test passes a C array to the function and expects the sum “15”.</p><p>A <code>std::span</code> carries a length, so the C++ side never guesses how many elements a C array holds, which is the classic source of buffer overruns. The span itself does no bounds checking at runtime, so it is not a replacement for a checked container. It is a non-owning view that tells the reader the exact extent of the borrowed data, which is exactly the information a C array pointer omits. When a C library hands back a pointer and a count separately, the C++ side combines them into a single <code>std::span</code> at the boundary, so the pair never travels separately through C++ code.</p><pre><code class="language-cpp">#include <print>#include <span>#include <numeric>
int sum_span(std::span<const int> s) { return std::accumulate(s.begin(), s.end(), 0);}
int main() { int arr[] = {1,2,3,4,5}; int total = sum_span(arr); std::println("sum: {}", total); return 0;}</code></pre><h2 id="c23-helpers-in-c26"><a class="header" href="#c23-helpers-in-c26">C23 helpers in C++26</a></h2><p>C23 introduces <code><stdbit.h></code> and <code><stdckdint.h></code>. The header <code><stdbit.h></code> defines bit utilities such as <code>stdc_bit_width</code>. The header <code><stdckdint.h></code> defines overflow-checked arithmetic functions like <code>ckd_add</code>. These helpers are usable from C++26 code without additional wrappers. The example adds two <code>int</code> values with <code>ckd_add</code>. If overflow occurs the function returns <code>-1</code> and sets <code>errno</code>. The test expects the word “overflow” when the addition exceeds the range of <code>int</code>.</p><p>Checked arithmetic matters because signed overflow is undefined behaviour in both C and C++. <code>ckd_add</code> performs the addition and reports whether it overflowed, so the code handles the failure instead of relying on undefined behaviour. Before this helper, the portable way to check was a comparison of the operands and the result, which is easy to get wrong. <code>stdc_bit_width</code> and the other <code><stdbit.h></code> utilities bring a full set of bit operations into C++ without a hand-rolled implementation.</p><pre><code class="language-cpp">#include <cstdio>#include <cerrno>#include <climits>#include <print>#include <stdckdint.h>
int main() { int a = INT_MAX; int b = 1; int sum = 0; if (ckd_add(&sum, a, b)) { std::println("overflow"); } else { std::println("sum: {}", sum); } return 0;}</code></pre><h2 id="try-this-26"><a class="header" href="#try-this-26">Try this</a></h2><p>Wrap the C standard library function <code>qsort</code> behind a range-friendly C++ interface. The wrapper must accept a <code>std::span<T></code> and a comparator <code>auto cmp</code>. Inside the wrapper the call to <code>qsort</code> must use a static bridge function that forwards to the supplied comparator. The wrapper must hide the <code>void*</code> pointer and the function-pointer signature from the caller.</p><p>The bridge function is the crux. <code>qsort</code> takes a plain <code>void*</code> and a C function pointer, so the wrapper cannot pass a C++ lambda directly. It passes a static function that receives the array pointer, casts it back to the element type, and calls the supplied comparator through a <code>const void*</code> argument the comparator understands. The caller never sees the <code>void*</code> or the function-pointer signature. It passes a <code>std::span</code> and a normal C++ comparator, and the wrapper hides the C machinery.</p><p>**</p><div style="break-before: page; page-break-before: always;"></div><h1 id="fast-is-a-specification"><a class="header" href="#fast-is-a-specification">Fast is a specification</a></h1><h2 id="predict-measure-change"><a class="header" href="#predict-measure-change">Predict, measure, change</a></h2><p>A performance claim is a hypothesis until it is measured. The correct workflow is to predict the cost, measure it with a reliable tool, then change the code and re-measure. Only after the measurement can a claim be accepted as true.</p><p>Guessing about performance fails because modern compilers and CPUs are too clever. A statement that a piece of code is slow can be incorrect, and a statement that it is fast can be incorrect as well. The only reliable route is to turn the claim into a measurement, which is why this chapter gives you the tools rather than a list of rules of thumb.</p><h2 id="measuring-with-stdchrono"><a class="header" href="#measuring-with-stdchrono">Measuring with std::chrono</a></h2><p>C++ provides a portable, monotonic clock in the standard library: <code>std::chrono::steady_clock</code>. Unlike <code>std::chrono::system_clock</code>, the steady clock never jumps because of adjustments to the system time. Use <code>steady_clock::now()</code> before and after the code region and compute the difference. The following inline example measures the time taken to execute a trivial loop.</p><pre><code class="language-cpp">#include <chrono>#include <iostream>
int main() { auto start = std::chrono::steady_clock::now(); volatile int sum = 0; // prevent optimisation of the loop body for (int i = 0; i < 10'000'000; ++i) { sum += i; } auto end = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::cout << "elapsed: " << elapsed.count() << " µs\n"; return 0;}</code></pre><p>The program prints a single line such as <code>elapsed: 12345 µs</code>. The unit is a concrete, reproducible quantity that the test harness can match. Because <code>steady_clock</code> is monotonic, the measurement is not affected by clock adjustments, NTP updates, or daylight-saving changes. This makes it the preferred tool for micro-benchmarking code that runs for a short period.</p><p>For more reliable numbers, run the loop several times and record each measurement. Compute the median or the minimum value. The minimum discards noise from background activity, while the median reduces the impact of outliers. Warm up the code once before timing to let the processor reach its steady frequency and to populate caches. A typical benchmarking harness therefore performs a warm-up iteration, followed by a fixed number of timed iterations, and finally reports the best or median elapsed time.</p><p>The <code>volatile</code> in the timing loop is important. Without it the compiler can see that the loop has no observable effect and delete it entirely under the as-if rule, making the measured time zero. <code>volatile</code> forces the writes to happen, so the loop measures real work. The same trick is why micro-benchmarks accumulate into a <code>volatile</code> sink rather than returning a value that is never used.</p><p>A single measurement is not a number you can trust. Run the workload several times and report the spread, because a two-fold difference between runs is common under system noise.</p><h2 id="the-as-if-rule-and-optimizer-levels"><a class="header" href="#the-as-if-rule-and-optimizer-levels">The as-if rule and optimizer levels</a></h2><p>The C++ as-if rule permits the compiler to transform any program as long as the observable behaviour is unchanged. Observable behaviour consists of the program’s side effects on volatile objects, file I/O, and the values returned from <code>main</code>. Therefore a build compiled with <code>-O2</code> or <code>-O3</code> can reorder statements, inline functions, or eliminate dead code, provided the resulting side effects match the source semantics. A build with <code>-O0</code> performs almost no optimisation. It preserves the source order but does not represent the performance of a real-world binary. Benchmarking an unoptimised build therefore yields a number that the production binary will never exhibit. The meaningful comparison is always between two programs built with the same optimisation level.</p><p>Consider a function that adds two integers and returns the result. With <code>-O0</code> the compiler emits a call to the function, a load of each argument, an addition, and a return. With <code>-O2</code> the compiler can inline the function, keep the arguments in registers, and avoid the call entirely. The observable result, the returned sum, is identical, so the transformation is permitted. Benchmarks that report the speed of the <code>-O0</code> version therefore mislead. They measure the cost of the extra call and the lack of register allocation, not the intrinsic cost of the algorithm.</p><h2 id="move-vs-copy"><a class="header" href="#move-vs-copy">Move vs copy</a></h2><p>Moving a value transfers ownership of its resources without allocating or copying the underlying data. Copying, by contrast, must duplicate the resources. The instrumented <code>Counter</code> struct below records how many copy and move constructions occur. The <code>book_example</code> registration verifies the printed statistics. By examining the counters you can see that a move operation incurs far less work than a copy, especially when the underlying type manages heap memory or other expensive resources. This observation underlies the design of many standard library containers that prefer move over copy when they can.</p><pre><code class="language-cpp">#include <iostream>#include <utility>
struct Counter { static int copies; static int moves; Counter() = default; Counter(const Counter&) { ++copies; } Counter(Counter&&) noexcept { ++moves; } Counter& operator=(const Counter&) = delete; Counter& operator=(Counter&&) = delete; ~Counter() = default;};
int Counter::copies = 0;int Counter::moves = 0;
int main() { Counter a; Counter b = a; // copy Counter c = std::move(a); // move std::cout << "copy: " << Counter::copies << " move: " << Counter::moves << "\n"; return 0;}</code></pre><p>Running the program yields a line such as <code>copy: 1 move: 1</code>. The numbers confirm that the explicit copy and explicit move each invoke a single constructor, and that the default-constructed object does not contribute to the counts. If you replace the copy with another move, the copy counter stays at zero, showing the performance advantage of move semantics in realistic code. In larger containers, moving a <code>std::vector</code> merely swaps its internal pointer and size, while copying allocates new storage and copies each element, an order of magnitude more work.</p><p>Move operations are <code>noexcept</code> for the standard containers, and that single word unlocks a real optimisation. <code>std::vector</code> uses the move constructor during growth only when it is guaranteed not to throw. If the move can throw, the vector has to copy instead, to keep the strong exception guarantee. Marking your own types’ move constructors <code>noexcept</code> is therefore not ceremony. It is what lets <code>vector</code> move them during reallocation rather than copy.</p><p>See Chapter 5 for a detailed comparison of move versus copy costs.</p><h2 id="copy-elision-and-rvo"><a class="header" href="#copy-elision-and-rvo">Copy elision and RVO</a></h2><p>When a function returns a prvalue of class type, the language permits the compiler to construct the result directly in the caller’s storage. This <em>copy-elision</em> eliminates both the copy and the move constructor calls. The classic case is the <em>return value optimisation</em> (RVO). The following example prints markers from the constructors and destructors. If elision occurs, only the constructor and destructor of the local object appear, and no copy or move messages are printed. This behaviour is guaranteed by the standard when the criteria for NRVO are met, and modern compilers perform it even at <code>-O0</code>.</p><pre><code class="language-cpp">#include <iostream>#include <utility>
struct Marker { static int copies; static int moves; Marker() { std::cout << "ctor\n"; } Marker(const Marker&) { ++copies; std::cout << "copy\n"; } Marker(Marker&&) noexcept { ++moves; std::cout << "move\n"; } Marker& operator=(const Marker&) = delete; Marker& operator=(Marker&&) = delete; ~Marker() { std::cout << "dtor\n"; }};
int Marker::copies = 0;int Marker::moves = 0;
Marker make_marker() { Marker m; // ctor return m; // should be elided, no copy/move}
int main() { Marker x = make_marker(); // elision expected (void)x; std::cerr << "copies=" << Marker::copies << " moves=" << Marker::moves << "\n"; return 0;}</code></pre><p>The test harness expects the output to contain <code>copies=0 moves=0</code>. When the compiler performs RVO, the program’s output satisfies that expectation, demonstrating that the return did not incur any additional construction. If you deliberately disable copy-elision, for example by compiling with <code>-fno-elide-constructors</code>, the output changes to show a copy or move, which is useful for educational purposes but not representative of typical production builds.</p><p>Guaranteed copy elision, in effect since C++17, means a prvalue return does not even require the type to have a move constructor. A function that returns a prvalue of an immovable type still compiles and constructs the result in place. This is why returning a <code>std::vector</code> or a large struct by value is not just idiomatic but the fastest option. There is no copy and no move, only direct construction in the caller’s storage.</p><p>Chapter 18 showed that copy elision and RVO can remove all copy/move operations, making return‑by‑value the fastest way to deliver a result.</p><h2 id="container-big-o-review"><a class="header" href="#container-big-o-review">Container big-O review</a></h2><p>Choosing the right container yields the highest performance gain in most programs. <code>std::vector</code> grows by amortised constant time. Each <code>push_back</code> is O(1) on average, but occasional reallocation costs O(n). Reserving capacity with <code>reserve(n)</code> eliminates those reallocations and therefore reduces the worst-case overhead. Associative containers differ. <code>std::map</code> provides ordered lookup in O(log n), while <code>std::unordered_map</code> offers average constant-time lookup, O(1), at the cost of higher memory usage and possible hash collisions. Understanding these complexities lets the programmer place the most expensive operations in the cheapest container. For example, building a large list of results is usually fastest with a <code>vector</code> that has been pre-reserved.</p><p>When a container holds objects that are expensive to move or copy, the cost of reallocation becomes significant. An optimisation is to store <code>std::unique_ptr<T></code> in a <code>vector</code> and reserve enough space before filling it. This avoids repeated allocations of <code>T</code> and eliminates the need to move <code>T</code> objects during reallocation, because only the pointers are moved.</p><p>Big-O notation hides constant factors, which are real. An <code>unordered_map</code> is O(1) per lookup but has a large constant and high memory overhead, so for a handful of keys a linear scan of a small <code>vector</code> is faster. The rule is to choose by the shape of the workload, then confirm with a measurement, which brings the chapter’s central lesson back around. Choosing a container is a one-line change with large leverage, which is why it comes before micro-optimising a loop body.</p><h2 id="reading-a-hot-loop"><a class="header" href="#reading-a-hot-loop">Reading a hot loop</a></h2><p>On Linux the profiler <code>perf</code> records CPU cycles, cache-miss events, and instruction retirements. On macOS the <code>Instruments</code> app provides similar metrics, including cache misses and allocations. When analysing a hot loop, look for a high proportion of cache-miss cycles, frequent allocations inside the loop body, and indirect calls such as virtual dispatch. Reducing cache misses involves improving data locality, for example by storing related objects contiguously in a <code>vector</code> or by using a struct-of-arrays layout. Eliminating allocations can be achieved with <code>reserve</code> or by reusing objects that are allocated once outside the loop. Virtual calls can be replaced by static polymorphism, by <code>std::function_ref</code>, or by inlining small call sites. The point is not to collect profiles but to find one or two dominant costs, because fixing the single hottest line usually beats optimising twenty small ones.</p><p>A typical workflow is:</p><ol><li>Run the program under <code>perf record -g ./a.out</code> or with Instruments’ time profiler.</li><li>Identify the hottest functions from the flame graph.</li><li>Drill into those functions to see which lines cause the most cache-miss or allocation events.</li><li>Refactor the code to improve locality, pre-allocate storage, or replace virtual calls.</li><li>Re-run the profiler to verify that the hot spots have diminished.</li></ol><h2 id="try-this-27"><a class="header" href="#try-this-27">Try this</a></h2><p>Predict whether calling <code>reserve(n)</code> on a <code>std::vector<std::unique_ptr<Node>></code> that stores a binary tree will reduce the total runtime of a breadth-first construction loop. Measure the loop with <code>std::chrono::steady_clock</code> as shown earlier, print the elapsed time, and compare the two runs. Record the result as <code>elapsed without reserve: X µs</code> and <code>elapsed with reserve: Y µs</code>. The experiment demonstrates the real impact of pre-allocation on a realistic data-structure workload.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-a-features-and-compilers"><a class="header" href="#appendix-a-features-and-compilers">Appendix A: features and compilers</a></h1><p>This appendix lists the C++ features the book uses.It shows the proposal that introduced or significantly changed each feature.It also shows the compiler support in the pinned toolchain (Clang 22.1.8, libc++).The required flag or header is listed.Finally it indicates whether the book compiles an example or marks it as a gap.</p><p>Compiled means the book ships a real example that builds and runs. Gap means the feature is taught prose-first and registered so it builds only with <code>BOOK_ENABLE_GAPS=ON</code>. A “Not yet deployable” callout names the supporting compiler if any.</p><div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Proposal</th><th>Clang 22.1.8</th><th>Flag / header</th><th>Status</th></tr></thead><tbody><tr><td><code>std::format</code> / <code>std::print</code> / <code>std::println</code></td><td>P0645, P2093</td><td>Yes</td><td><code><format></code>, <code><print></code>, <code>-std=c++26</code></td><td>Compiled</td></tr><tr><td><code>std::mdspan</code></td><td>P0009</td><td>Yes</td><td><code><mdspan></code></td><td>Compiled</td></tr><tr><td><code>std::numbers</code> constants</td><td>P0631</td><td>Yes</td><td><code><numbers></code></td><td>Compiled</td></tr><tr><td>Concepts</td><td>P0734</td><td>Yes</td><td><code>-std=c++20</code>+</td><td>Compiled</td></tr><tr><td><code>consteval</code>, <code>constinit</code>, <code>is_constant_evaluated</code></td><td>P1073, P1143, P0595</td><td>Yes</td><td><code>-std=c++20</code>+</td><td>Compiled</td></tr><tr><td>Transient <code>constexpr</code> allocation</td><td>P0784</td><td>Yes</td><td><code>-std=c++20</code>+</td><td>Compiled</td></tr><tr><td>Class-type NTTPs</td><td>P0732</td><td>Yes</td><td><code>-std=c++20</code>+</td><td>Compiled</td></tr><tr><td>User-defined literals</td><td>C++11</td><td>Yes</td><td><code>-std=c++20</code>+</td><td>Compiled</td></tr><tr><td><code>std::generator</code></td><td>P2502</td><td>Yes</td><td><code><generator></code></td><td>Compiled</td></tr><tr><td><code>std::jthread</code> / stop tokens</td><td>P0660</td><td>Yes</td><td><code><thread></code>, <code><stop_token></code></td><td>Compiled</td></tr><tr><td><code>std::atomic_ref</code></td><td>P0019</td><td>Yes</td><td><code><atomic></code></td><td>Compiled</td></tr><tr><td><code>std::scoped_lock</code></td><td>P0156</td><td>Yes</td><td><code><mutex></code></td><td>Compiled</td></tr><tr><td><code>std::ranges</code> / views</td><td>P0896, P2325</td><td>Yes</td><td><code><ranges></code>, <code><algorithm></code></td><td>Compiled</td></tr><tr><td><code>std::execution::par</code></td><td>P0024</td><td>No</td><td><code><execution></code></td><td>Prose only</td></tr><tr><td>Contracts (<code>[[assert]]</code>, <code>[[expects]]</code>, <code>[[ensures]]</code>)</td><td>P2900</td><td>No (GCC 16)</td><td><code>-fcontracts</code></td><td>Gap</td></tr><tr><td><code>std::execution</code> (P2300 senders)</td><td>P2300</td><td>No</td><td><code><execution></code></td><td>Gap</td></tr><tr><td><code>std::linalg</code></td><td>P1673</td><td>No</td><td><code><linalg></code></td><td>Gap</td></tr><tr><td>Static reflection</td><td>P2996</td><td>No (GCC 16 partial)</td><td><code><experimental/meta></code></td><td>Gap</td></tr><tr><td><code><hazard_pointer></code> / <code><rcu></code></td><td>P2530, P2546</td><td>No</td><td><code><hazard_pointer></code>, <code><rcu></code></td><td>Gap</td></tr><tr><td>Modules (<code>import</code>, <code>export module</code>)</td><td>P1103</td><td>Partial</td><td><code>-std=c++26</code>, BMI</td><td>Gap</td></tr><tr><td>Lifetime-safety analysis</td><td>P1179</td><td>Experimental</td><td><code>-Xclang -fexperimental-lifetime-safety</code></td><td>Compiled (demos)</td></tr></tbody></table></div><p>The Nix dev shell in <code>flake.nix</code> provides the pinned toolchain. The book compiles only verified examples. Newer features are taught with exact syntax and an honest callout.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-b-coming-from-other-languages"><a class="header" href="#appendix-b-coming-from-other-languages">Appendix B: coming from other languages</a></h1><p>This appendix maps what you already know from C, Rust, Lisp, and Prolog onto the C++ you have now read. It is one page per language, and it uses the vocabulary of the chapter each idea belongs to.</p><h2 id="from-c"><a class="header" href="#from-c">From C</a></h2><div class="table-wrapper"><table><thead><tr><th>You know</th><th>In C++ this is</th></tr></thead><tbody><tr><td><code>char*</code> strings with a NUL terminator</td><td><code>std::string</code> when owned, <code>std::string_view</code> when borrowed (ch10)</td></tr><tr><td>Arrays that decay to pointers</td><td><code>std::array</code>, <code>std::span</code>, <code>std::vector</code> (ch11)</td></tr><tr><td><code>FILE*</code> and manual <code>fclose</code></td><td>an RAII wrapper whose destructor releases the handle (ch05, ch28)</td></tr><tr><td><code>errno</code> and sentinel returns</td><td><code>std::expected</code> and exceptions (ch09, ch28)</td></tr><tr><td><code>printf</code> with unchecked format strings</td><td><code>std::format</code> and <code>std::println</code>, checked at compile time (ch10)</td></tr><tr><td><code>qsort</code> with <code>void*</code> and a function pointer</td><td><code>std::ranges::sort</code> with a comparator and projection (ch12)</td></tr><tr><td>Manual <code>malloc</code>/<code>free</code></td><td><code>std::unique_ptr</code>, <code>std::vector</code>, and the Rule of Zero (ch05, ch06)</td></tr></tbody></table></div><p>C code works in C++ through the boundary discipline of chapter 28: <code>extern "C"</code> for linkage, spans and string views for C data, and RAII wrappers for C resources.</p><p>C++ retains C calling conventions, adds ownership, and replaces manual resource handling and unchecked formatting with RAII and compile‑time checked formatting.</p><h2 id="from-rust"><a class="header" href="#from-rust">From Rust</a></h2><div class="table-wrapper"><table><thead><tr><th>You know</th><th>In C++ this is</th></tr></thead><tbody><tr><td>Value semantics</td><td>value semantics, move semantics, and copy elision (ch05, ch29)</td></tr><tr><td>Ownership</td><td>a name owns a value. Ownership transfers on move (ch05)</td></tr><tr><td>The borrow checker</td><td>the lifetime-safety profile, <code>std::span</code>, <code>std::string_view</code>, and <code>-Wlifetime-safety</code> (ch07)</td></tr><tr><td><code>Option<T></code></td><td><code>std::optional</code> (ch03)</td></tr><tr><td><code>Result<T, E></code></td><td><code>std::expected</code> (ch03, ch09)</td></tr><tr><td>Enums with data</td><td><code>std::variant</code> plus <code>std::visit</code> (ch03)</td></tr><tr><td>Traits</td><td>concepts and <code>requires</code> clauses (ch17)</td></tr></tbody></table></div><p>Rust enforces ownership and lifetimes at compile time. C++ provides the same tools but relies on the lifetime‑safety analysis and disciplined APIs (see Chapter 7).</p><p>Both languages share the same mental model of ownership. The difference is where a violation is caught.</p><h2 id="from-lisp"><a class="header" href="#from-lisp">From Lisp</a></h2><div class="table-wrapper"><table><thead><tr><th>You know</th><th>In C++ this is</th></tr></thead><tbody><tr><td>Macros that expand source</td><td>templates, which rewrite type patterns and are instantiated at compile time (ch16)</td></tr><tr><td>Compile-time evaluation</td><td><code>constexpr</code>, <code>consteval</code>, and the compile-time execution model (ch18)</td></tr><tr><td>A domain-specific language evaluated at compile time</td><td>the constexpr SQL capstone (ch22)</td></tr><tr><td>Functions as data</td><td>lambdas, <code>std::function</code>, and type erasure (ch14)</td></tr><tr><td>Recursive macros / term rewriting</td><td>template metaprogramming and pack expansion (ch16, ch21)</td></tr></tbody></table></div><p>C++ templates and constexpr supply compile‑time code generation analogous to Lisp macros.</p><p>Lisp rewrites source text. C++ rewrites type patterns and evaluates a restricted subset of the language at compile time.</p><h2 id="from-prolog"><a class="header" href="#from-prolog">From Prolog</a></h2><div class="table-wrapper"><table><thead><tr><th>You know</th><th>In C++ this is</th></tr></thead><tbody><tr><td>Unification</td><td>template argument deduction, which binds type parameters to concrete types (ch16)</td></tr><tr><td>Goal ordering / most specific rule</td><td>overload resolution and concept subsumption (ch17)</td></tr><tr><td>A term with a tag and arguments</td><td><code>std::variant</code> plus <code>std::visit</code> (ch03)</td></tr><tr><td>Backtracking search</td><td>Not in the language. Express it explicitly with recursion or a search loop</td></tr></tbody></table></div><p>The strongest analogy is deduction: Prolog unifies a query against rules, and C++ unifies a call against template patterns and selects the most specific viable match. Chapter 17 frames concept subsumption in exactly these terms.</p><p>The difference is control. Prolog searches for a solution and can backtrack. C++ resolves a call once at compile time and does not search at runtime. The shared idea is pattern matching against a set of rules, with the most specific rule winning.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-c-core-guidelines-index"><a class="header" href="#appendix-c-core-guidelines-index">Appendix C: Core Guidelines index</a></h1><p>This appendix lists the C++ Core Guidelines rules referenced in the book, grouped by the chapter that teaches each rule. Rule IDs correspond to the CppCoreGuidelines version used during writing. When a rule appears in the text, its identifier is shown as (CG <id>) for easy lookup.</id></p><h2 id="chapter-2-values-and-functions"><a class="header" href="#chapter-2-values-and-functions">Chapter 2: values and functions</a></h2><ul><li>F.15: prefer simple and conventional ways of passing information.</li><li>F.20: for out-parameters, prefer return values over out-parameters.</li><li>F.21: to return multiple values, prefer returning a struct or tuple.</li><li>ES.25: declare an object const or constexpr unless you need to change its value.</li></ul><h2 id="chapter-3-user-defined-types"><a class="header" href="#chapter-3-user-defined-types">Chapter 3: user-defined types</a></h2><ul><li>C.20: if you can avoid defining default operations, do.</li><li>C.2: use class if the class has an invariant.</li><li>C.2: use struct if the data members can vary independently.</li></ul><h2 id="chapter-4-control-flow"><a class="header" href="#chapter-4-control-flow">Chapter 4: control flow</a></h2><ul><li>ES.28: use lambdas for complex initialization, especially of const variables.</li><li>ES.70: prefer a range-for-statement or a standard-library algorithm over a hand-written loop.</li></ul><h2 id="chapter-5-ownership-move-and-raii"><a class="header" href="#chapter-5-ownership-move-and-raii">Chapter 5: ownership, move, and RAII</a></h2><ul><li>R.1: manage resources automatically using RAII.</li><li>R.3: a raw pointer or reference is never a resource owner.</li><li>R.10-R.11: avoid calling <code>new</code> and <code>delete</code> explicitly.</li></ul><h2 id="chapter-6-smart-pointers"><a class="header" href="#chapter-6-smart-pointers">Chapter 6: smart pointers</a></h2><ul><li>R.20: use <code>unique_ptr</code> or <code>shared_ptr</code> to represent ownership.</li><li>R.22: use <code>shared_ptr</code> only to share ownership.</li></ul><h2 id="chapter-7-lifetimes"><a class="header" href="#chapter-7-lifetimes">Chapter 7: lifetimes</a></h2><ul><li>Lifetime profile: never dereference a possibly-invalid pointer or iterator.</li><li>Lifetime profile: a pointer or reference to a local must not escape.</li></ul><h2 id="chapter-8-argument-passing"><a class="header" href="#chapter-8-argument-passing">Chapter 8: argument passing</a></h2><ul><li>F.15: prefer simple and conventional ways of passing information.</li><li>F.20: for out-parameters, prefer return values over out-parameters.</li><li>F.21: to return multiple values, prefer returning a struct or tuple.</li><li>F.16-F.17: pass by value for small/cheap-to-move, <code>const T&</code> for big inputs.</li></ul><h2 id="chapter-9-errors-and-contracts"><a class="header" href="#chapter-9-errors-and-contracts">Chapter 9: errors and contracts</a></h2><ul><li>E.1-E.16: error-handling rules, including throw when the function cannot do its job, and prefer <code>std::expected</code> for anticipated absence.</li><li>E.25: a function that cannot throw must be declared <code>noexcept</code>.</li></ul><h2 id="chapter-11-containers"><a class="header" href="#chapter-11-containers">Chapter 11: containers</a></h2><ul><li>SL.con: standard-library container rules.</li><li>SL.con: prefer <code>std::vector</code> by default.</li></ul><h2 id="chapter-12-algorithms"><a class="header" href="#chapter-12-algorithms">Chapter 12: algorithms</a></h2><ul><li>ES.70: prefer a range-for-statement or a standard-library algorithm over a hand-written loop.</li></ul><h2 id="chapter-14-callables-and-type-erasure"><a class="header" href="#chapter-14-callables-and-type-erasure">Chapter 14: callables and type erasure</a></h2><ul><li>F.51-F.52: prefer a lambda or a function object for small callables.</li><li>F.51-F.52: prefer a regular function for a stateless callable.</li></ul><h2 id="chapter-16-17-templates-and-concepts"><a class="header" href="#chapter-16-17-templates-and-concepts">Chapter 16-17: templates and concepts</a></h2><ul><li>T.1-T.65: template and generic-programming rules.</li><li>T.1-T.65: define concepts to express template constraints (T.10).</li></ul><h2 id="chapter-21-reading-legacy-tmp"><a class="header" href="#chapter-21-reading-legacy-tmp">Chapter 21: reading legacy TMP</a></h2><ul><li>The rules for reading, not writing: write concepts, read SFINAE (T.1, T.10).</li></ul><h2 id="chapters-25-26-concurrency"><a class="header" href="#chapters-25-26-concurrency">Chapters 25-26: concurrency</a></h2><ul><li>CP.1-CP.8: concurrency rules.</li><li>CP.1-CP.8: protect shared mutable state with a mutex or an atomic.</li><li>CP.1-CP.8: never access a non-atomic shared object without synchronization.</li></ul><h2 id="chapter-29-fast-is-a-specification"><a class="header" href="#chapter-29-fast-is-a-specification">Chapter 29: fast is a specification</a></h2><ul><li>Perf.1-Perf.11: performance rules.</li><li>Perf.1-Perf.11: measure before optimizing (Perf.1).</li><li>Perf.1-Perf.11: avoid cheap micro-optimizations that do not show up in measurement.</li></ul><p>This index is not exhaustive. The chapters cite the exact rule at the point of use. The intent of the book is that every rule it teaches is attributed, so a reader can chase the source of any guideline.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-d-cmake"><a class="header" href="#appendix-d-cmake">Appendix D: CMake</a></h1><p>This appendix teaches CMake using the build setup of this book as the running example. CMake is a build‑system generator. It reads a description of the project and produces files for a native build tool. The book uses the Ninja generator. Ninja is a fast, low‑level build tool that CMake drives.</p><h2 id="configure-generate-build-test"><a class="header" href="#configure-generate-build-test">Configure, generate, build, test</a></h2><p>CMake separates configuration from building. The configure step reads <code>CMakeLists.txt</code> files and records the choices you make. The generate step writes the Ninja files. The build step compiles the targets. The test step runs the registered tests.</p><p>The book drives all four steps with one command sequence:</p><pre><code>cmake --preset devcmake --build build -jctest --test-dir build --output-on-failure</code></pre><p>The first command configures and generates. The preset <code>dev</code> supplies the generator and the build directory. The second command builds every target with <code>-j</code> for parallel jobs. The third command runs every test and prints the output of failing tests. This sequence is the acceptance gate for the book. The script <code>scripts/verify.sh</code> runs it inside the Nix development shell.</p><h2 id="the-project-file"><a class="header" href="#the-project-file">The project file</a></h2><p>The root <code>CMakeLists.txt</code> opens with a minimum version and a project name:</p><pre><code class="language-cmake">cmake_minimum_required(VERSION 3.30)project(tour_cpp26 LANGUAGES CXX)</code></pre><p>The version 3.30 guarantees that the features the book uses exist. The project declares that it uses only the C++ language. Three lines fix the language standard:</p><pre><code class="language-cmake">set(CMAKE_CXX_STANDARD 26)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)</code></pre><p>The standard is C++26, strictly. <code>CMAKE_CXX_STANDARD_REQUIRED ON</code> refuses a compiler that cannot reach C++26. <code>CMAKE_CXX_EXTENSIONS OFF</code> forbids GNU extensions. The line <code>set(CMAKE_EXPORT_COMPILE_COMMANDS ON)</code> writes a <code>compile_commands.json</code> file that clangd and other tools consume.</p><h2 id="cache-variables-and-options"><a class="header" href="#cache-variables-and-options">Cache variables and options</a></h2><p>CMake stores configuration values in a cache. The <code>option</code> command declares a boolean cache variable with a default:</p><pre><code class="language-cmake">option(BOOK_SANITIZE "Run example tests under AddressSanitizer and UndefinedBehaviorSanitizer" ON)option(BOOK_ENABLE_GAPS "Build examples for C++26 features without shipping Clang support" OFF)option(BOOK_WERROR "Turn compiler warnings into errors for book_example targets" ON)</code></pre><p><code>BOOK_SANITIZE</code> turns the sanitizers on for example tests. It defaults to ON. <code>BOOK_ENABLE_GAPS</code> builds examples for C++26 features that lack shipping Clang support. It defaults to OFF. <code>BOOK_WERROR</code> turns warnings into errors for <code>book_example</code> targets. It defaults to ON. You override a default on the command line with <code>-D</code>:</p><pre><code>cmake --preset dev -DBOOK_ENABLE_GAPS=ON</code></pre><h2 id="the-cmakepresets-dev-preset"><a class="header" href="#the-cmakepresets-dev-preset">The CMakePresets dev preset</a></h2><p>A preset bundles configuration options under a name. The file <code>CMakePresets.json</code> defines the <code>dev</code> preset:</p><pre><code class="language-json">{ "name": "dev", "generator": "Ninja", "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }}</code></pre><p>The preset selects the Ninja generator. It places the build tree in the <code>build</code> directory beside the source. It sets the build type to <code>RelWithDebInfo</code>, which optimizes the code and keeps debug information. The preset also defines matching build and test presets, so <code>cmake --build build -j</code> and <code>ctest --test-dir build</code> work without extra flags.</p><h2 id="targets-and-subdirectories"><a class="header" href="#targets-and-subdirectories">Targets and subdirectories</a></h2><p>A target is a named unit of work. The command <code>add_executable</code> creates an executable target from a source file. The command <code>add_subdirectory</code> descends into a child directory and reads its <code>CMakeLists.txt</code>. The file <code>examples/CMakeLists.txt</code> calls <code>add_subdirectory</code> for each chapter directory:</p><pre><code class="language-cmake">add_subdirectory(ch01)add_subdirectory(ch02)</code></pre><p>Each chapter directory holds one <code>CMakeLists.txt</code> that registers its examples. For example, <code>examples/ch01/CMakeLists.txt</code> contains:</p><pre><code class="language-cmake">book_example(ch01_hello.cpp EXPECT "hello, world")book_demo(ch01_dangling.cpp)book_demo(ch01_overflow.cpp)</code></pre><p>The names <code>book_example</code> and <code>book_demo</code> are functions that the book defines. They are not CMake built‑ins.</p><h2 id="the-book_example-function"><a class="header" href="#the-book_example-function">The book_example function</a></h2><p>The functions live in <code>cmake/BookExample.cmake</code>. The root <code>CMakeLists.txt</code> adds that directory to the module path and includes the file:</p><pre><code class="language-cmake">list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")include(BookExample)</code></pre><p>The <code>book_example</code> function registers a real, fully‑checked example. Its body is:</p><pre><code class="language-cmake">function(book_example file) cmake_parse_arguments(arg "" "EXPECT" "" ${ARGN}) _book_add_target(t "${file}") target_compile_options(${t} PRIVATE ${BOOK_WARNING_FLAGS}) if(BOOK_WERROR) target_compile_options(${t} PRIVATE -Werror) endif() if(BOOK_SANITIZE) target_compile_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined) target_link_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined) endif() add_test(NAME ${t}_run COMMAND ${t}) if(arg_EXPECT) set_tests_properties(${t}_run PROPERTIES PASS_REGULAR_EXPRESSION "${arg_EXPECT}") endif()endfunction()</code></pre><p>The helper <code>_book_add_target</code> derives the target name from the file name and calls <code>add_executable</code>. The function attaches the warning flags, the <code>-Werror</code> flag, and the sanitizer flags. It registers a CTest test named <code><name>_run</code>. When <code>EXPECT</code> is present, the test must match the given regular expression. The <code>book_demo</code> function registers a deliberately wrong example that compiles with warnings visible and no test. The <code>book_gap</code> function registers a C++26 feature target that builds only with <code>BOOK_ENABLE_GAPS=ON</code>.</p><p>The warning flags come from a list that includes the lifetime‑safety flag. The file probes the compiler with <code>check_cxx_compiler_flag</code> and <code>check_cxx_source_compiles</code> to discover which spelling of the lifetime‑safety analysis it accepts. It prefers <code>-Wlifetime-safety</code> and falls back to the experimental cc1 form.</p><h2 id="try-this-28"><a class="header" href="#try-this-28">Try this</a></h2><p>Open <code>cmake/BookExample.cmake</code>, compare <code>book_demo</code> to <code>book_example</code>, then run <code>cmake --preset dev -DBOOK_SANITIZE=OFF</code> to verify the build succeeds without sanitizer flags.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-e-ctest"><a class="header" href="#appendix-e-ctest">Appendix E: CTest</a></h1><p>This appendix teaches CTest, the test driver that runs the compiled examples of this book. CTest is part of CMake. It discovers, runs, and reports the tests that a project registers. The book uses CTest to exercise every <code>book_example</code> target and to verify its output.</p><h2 id="registering-a-test"><a class="header" href="#registering-a-test">Registering a test</a></h2><p>A project enables testing with the <code>enable_testing()</code> command. The root <code>CMakeLists.txt</code> calls it before descending into the examples directory. A test is registered with <code>add_test</code>. The command names the test and gives the command to run:</p><pre><code class="language-cmake">add_test(NAME ch01_hello_run COMMAND ch01_hello)</code></pre><p>The test <code>ch01_hello_run</code> runs the executable <code>ch01_hello</code>. CTest runs the executable in a separate process and reports whether it succeeded.</p><h2 id="how-book_example-registers-tests"><a class="header" href="#how-book_example-registers-tests">How book_example registers tests</a></h2><p>The <code>book_example</code> function in <code>cmake/BookExample.cmake</code> registers one test for every example. The relevant lines are:</p><pre><code class="language-cmake">add_test(NAME ${t}_run COMMAND ${t})if(arg_EXPECT) set_tests_properties(${t}_run PROPERTIES PASS_REGULAR_EXPRESSION "${arg_EXPECT}")endif()</code></pre><p>The target name <code>t</code> comes from the file name. The test name appends <code>_run</code> to it. So <code>book_example(ch01_hello.cpp EXPECT "hello, world")</code> produces the test <code>ch01_hello_run</code>. The <code>_run</code> suffix keeps the test name distinct from the executable name.</p><h2 id="expected-output-verification"><a class="header" href="#expected-output-verification">Expected output verification</a></h2><p>A test passes when the program exits with status zero and its output satisfies the test properties. The property <code>PASS_REGULAR_EXPRESSION</code> holds a regular expression. When it is present, the test passes only if the program output contains a match for that expression.</p><p>A test fails when the program exits nonzero or when its output does not contain the expected regular expression. Both conditions are failures. The book relies on this rule to verify that an example prints what the text claims.</p><p>The <code>EXPECT</code> argument of <code>book_example</code> becomes the <code>PASS_REGULAR_EXPRESSION</code>. The book uses real regexes from the chapter files. A few examples:</p><pre><code class="language-cmake">book_example(ch01_hello.cpp EXPECT "hello, world")book_example(ch02_quadratic.cpp EXPECT "roots: 2, 1")book_example(ch03_point.cpp EXPECT "distance: 5.00")book_example(ch03_parse_int.cpp EXPECT "value: 123|error at 2")</code></pre><p>The last example shows a regex alternation. The vertical bar <code>|</code> means either alternative matches. The program prints <code>value: 123</code> on success or <code>error at 2</code> on failure, and the test accepts either.</p><h2 id="running-the-tests"><a class="header" href="#running-the-tests">Running the tests</a></h2><p>The book runs the tests through the preset:</p><pre><code>ctest --test-dir build --output-on-failure</code></pre><p>The flag <code>--test-dir build</code> points CTest at the build directory that the <code>dev</code> preset created. The flag <code>--output-on-failure</code> prints the full output of a failing test. Without it, CTest shows only a summary line for each test. The <code>dev</code> test preset sets this behavior in <code>CMakePresets.json</code>, so the flag and the preset agree.</p><h2 id="naming-labels-and-verbosity"><a class="header" href="#naming-labels-and-verbosity">Naming, labels, and verbosity</a></h2><p>Every test in this book is named after its example with a <code>_run</code> suffix. The naming makes a failing test easy to map back to its source file. The book does not use labels. A label groups tests under a name, and CTest can run only that group with <code>ctest -L</code>. The book has no need for grouping because every test is an independent example.</p><p>The default CTest output shows one line per test with a pass or fail result. The flag <code>-V</code> raises verbosity and prints each test command and its output. The flag <code>-N</code> lists the tests without running them. These flags help when you debug a single example.</p><h2 id="try-this-29"><a class="header" href="#try-this-29">Try this</a></h2><p>Run <code>ctest --test-dir build -N</code> and list the tests that the book registers. Pick one example that uses an <code>EXPECT</code> regex, such as <code>ch03_parse_int</code>. Temporarily change its regex in the chapter <code>CMakeLists.txt</code> to a string the program never prints, rebuild, and run <code>ctest --test-dir build --output-on-failure</code>. Confirm that the test fails because the output does not contain the expected expression, then restore the original regex.</p><div style="break-before: page; page-break-before: always;"></div><h1 id="appendix-f-addresssanitizer-and-undefinedbehaviorsanitizer"><a class="header" href="#appendix-f-addresssanitizer-and-undefinedbehaviorsanitizer">Appendix F: AddressSanitizer and UndefinedBehaviorSanitizer</a></h1><p>This appendix teaches two runtime sanitizers that the book treats as members of the compiler committee. The address sanitizer (ASan) and the undefined‑behavior sanitizer (UBSan) detect errors while a program runs. They are part of the Nix development shell that <code>flake.nix</code> provides.</p><h2 id="what-asan-instruments"><a class="header" href="#what-asan-instruments">What ASan instruments</a></h2><p>ASan instruments memory access. It detects a heap‑buffer‑overflow, which is a read or write past the end of a heap allocation. It detects a use‑after‑free, which is an access to memory that the program already released. It detects a memory leak, which is an allocation that the program never frees. ASan replaces the allocator and tracks every allocation and free. It checks each memory access against that bookkeeping.</p><p>The check happens at runtime, so the program must run to trigger a report. A program that never reaches the bad access stays silent. For this reason the book runs every example under the sanitizers, so a latent bug in an example surfaces during the test run.</p><h2 id="what-ubsan-instruments"><a class="header" href="#what-ubsan-instruments">What UBSan instruments</a></h2><p>UBSan instruments operations whose behavior the standard leaves undefined. It reports signed integer overflow, which is an arithmetic result outside the representable range of a signed type. It reports shift overflow, which is a shift by a negative amount or by more than the width of the type. It reports a null‑pointer dereference, which is an access through a null pointer. UBSan inserts a runtime check before each offending operation and aborts as soon as the operation occurs.</p><p>UBSan and ASan complement each other. ASan catches memory errors. UBSan catches arithmetic and type errors. The book enables both together, so a single test run covers both classes of defect.</p><h2 id="the-flags"><a class="header" href="#the-flags">The flags</a></h2><p>The sanitizers are enabled with compile and link flags. The compile flags instrument the code. The link flags attach the sanitizer runtime. The book uses both for each example:</p><pre><code>-fsanitize=address -fsanitize=undefined</code></pre><p>The same flags appear on the compile line and the link line. The address sanitizer and the undefined‑behavior sanitizer combine under one <code>-fsanitize</code> option. A thread sanitizer also exists, but on this toolchain it is Linux‑only. The book does not use it.</p><h2 id="how-book_sanitize-turns-them-on"><a class="header" href="#how-book_sanitize-turns-them-on">How BOOK_SANITIZE turns them on</a></h2><p>The option <code>BOOK_SANITIZE</code> controls the sanitizers. It defaults to ON in the root <code>CMakeLists.txt</code>:</p><pre><code class="language-cmake">option(BOOK_SANITIZE "Run example tests under AddressSanitizer and UndefinedBehaviorSanitizer" ON)</code></pre><p>The <code>book_example</code> function in <code>cmake/BookExample.cmake</code> applies the flags when the option is set:</p><pre><code class="language-cmake">if(BOOK_SANITIZE) target_compile_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined) target_link_options(${t} PRIVATE -fsanitize=address -fsanitize=undefined)endif()</code></pre><p>Only <code>book_example</code> targets receive the sanitizer flags. The <code>book_demo</code> and <code>book_gap</code> targets do not. The sanitizers run under CTest, so a sanitizer report fails the test.</p><h2 id="runtime-cost"><a class="header" href="#runtime-cost">Runtime cost</a></h2><p>The instrumentation stays in the finished binary. ASan roughly doubles runtime and memory use in typical programs. UBSan adds a check before each instrumented operation. For this reason the sanitizer builds are a test configuration, not the shipping binary. The book keeps the instrumented binaries in the test config only. The <code>dev</code> preset builds every example, and CTest runs each one under the sanitizers. The same source, built without <code>BOOK_SANITIZE</code>, produces the ordinary binary.</p><h2 id="reading-an-asan-report"><a class="header" href="#reading-an-asan-report">Reading an ASan report</a></h2><p>The example <code>ch01_overflow</code> reads past the end of a vector to trigger a report. The report begins with a line that names the error class:</p><pre><code>==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000fcREAD of size 4 at 0x6020000000fc thread T0 #0 ... std::__1::__format::__create_format_arg ... format_arg_store.h:191 ... #8 ... in main ch01_overflow.cpp:13</code></pre><p>The first line names the error and the address. The second line describes the access, the size, and the thread. The stack trace follows. Each frame shows a call site. The final frame points into <code>main</code> at the source line of the offending read. The trace lets you walk from the top‑level call down to the exact statement that violated the rule.</p><h2 id="part-of-the-nix-dev-shell"><a class="header" href="#part-of-the-nix-dev-shell">Part of the Nix dev shell</a></h2><p>The sanitizers are part of the Nix development shell. The file <code>flake.nix</code> provides the pinned toolchain, which includes LLVM 22.1.8. The shell sets <code>CC=clang</code> and <code>CXX=clang++</code>. The clang compiler ships the sanitizer runtimes, so the flags work without extra installation. Running <code>nix develop</code> and then the verify script builds every example and runs every test under the sanitizers.</p><p>The sanitizers are a test‑only configuration. They are not part of the shipping binary. The book enables them for the acceptance pipeline so that a memory error or an undefined operation in any example fails the build.</p><h2 id="try-this-30"><a class="header" href="#try-this-30">Try this</a></h2><p>Write a small program that allocates an array with <code>new[]</code>, reads one element past the end, and prints the value. Compile it with <code>-fsanitize=address,undefined</code> and run it. Confirm that ASan reports a heap‑buffer‑overflow with a stack trace that names your source line. Then write a program that adds two signed integers whose sum overflows, and confirm that UBSan reports the overflow while ASan stays silent.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation"> <!-- Mobile navigation buttons -->
<div style="clear: both"></div> </nav> </div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<template id=fa-eye><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></template> <template id=fa-eye-slash><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg></span></template> <template id=fa-copy><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/></svg></span></template> <template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template> <template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>
<script> window.playground_copyable = true; </script>
<script src="elasticlunr-ef4e11c1.min.js"></script> <script src="mark-09e88c2c.min.js"></script> <script src="searcher-09f2665d.js"></script>
<script src="clipboard-1626706a.min.js"></script> <script src="highlight-abc7f01d.js"></script> <script src="book-609e4cb8.js"></script>
<!-- Custom JS scripts -->
<script> window.addEventListener('load', function() { window.setTimeout(window.print, 100); }); </script>
</div> </body></html>