1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204 |
- // Copyright Louis Dionne 2013-2017
- // Distributed under the Boost Software License, Version 1.0.
- // (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
- /*!
- @mainpage User Manual
- @tableofcontents
- @section tutorial-description Description
- ------------------------------------------------------------------------------
- Hana is a header-only library for C++ metaprogramming suited for computations
- on both types and values. The functionality it provides is a superset of what
- is provided by the well established [Boost.MPL][] and [Boost.Fusion][]
- libraries. By leveraging C++11/14 implementation techniques and idioms,
- Hana boasts faster compilation times and runtime performance on par or better
- than previous metaprogramming libraries, while noticeably increasing the level
- of expressiveness in the process. Hana is easy to extend in a ad-hoc manner
- and it provides out-of-the-box inter-operation with Boost.Fusion, Boost.MPL
- and the standard library.
- @section tutorial-installation Prerequisites and installation
- ------------------------------------------------------------------------------
- Hana is a header-only library without external dependencies (not even the rest
- of Boost). Hence, using Hana in your own project is very easy. Basically, just
- download the project and add the `include/` directory to your compiler's header
- search path and you are done. However, if you want to cleanly install Hana, you
- have a couple of options:
- 1. __Install Boost__\n
- Hana is included in the [Boost][] distribution starting from Boost 1.61.0, so
- installing that will give you access to Hana.
- 2. __Use Homebrew__\n
- On Mac OS, Hana can be installed with [Homebrew][]:
- @code{.sh}
- brew install hana
- @endcode
- 3. __Install manually__\n
- You can download the code from the official GitHub [repository][Hana.repository]
- and install the library manually by issuing the following commands from the root
- of the project (requires [CMake][]):
- @code{.sh}
- mkdir build && cd build
- cmake ..
- cmake --build . --target install
- @endcode
- This will install Hana to the default install-directory for your platform
- (`/usr/local` for Unix, `C:/Program Files` for Windows). If you want to
- install Hana in a custom location, you can use
- @code{.sh}
- cmake .. -DCMAKE_INSTALL_PREFIX=/custom/install/prefix
- @endcode
- If you just want to contribute to Hana, you can see how to best setup your
- environment for development in the [README][Hana.hacking].
- @note
- - Both the manual installation and the Homebrew installation will also install
- a `HanaConfig.cmake` file for use with CMake and a `hana.pc` file for use with
- [pkg-config][].
- - Do not mix a standalone installation of Hana (i.e. Hana not installed through
- Boost) with a full installation of Boost. The Hana provided within Boost and
- the standalone one may clash, and you won't know which version is used where.
- This is asking for trouble.
- @subsection tutorial-installation-cmake Note for CMake users
- If you use [CMake][], depending on Hana has never been so easy. When installed,
- Hana creates a `HanaConfig.cmake` file that exports the `hana` interface library
- target with all the required settings. All you need is to install Hana (through
- Homebrew or manually), use `find_package(Hana)`, and then link your own targets
- against the `hana` target. Here is a minimal example of doing this:
- @snippet example/cmake_integration/CMakeLists.txt snip
- If you have installed Hana in a non-standard place, you might need to play with
- `CMAKE_PREFIX_PATH`. For example, this can happen if you "manually" install
- Hana locally to another project. In this case, you'll need to tell CMake where
- to find the `HanaConfig.cmake` file by using
- @code{cmake}
- list(APPEND CMAKE_PREFIX_PATH "${INSTALLATION_PREFIX_FOR_HANA}")
- or
- cmake ... -DCMAKE_PREFIX_PATH=${INSTALLATION_PREFIX_FOR_HANA}
- @endcode
- where `INSTALLATION_PREFIX_FOR_HANA` is the path to the place where Hana was
- installed.
- @subsection tutorial-installation-requirements Compiler requirements
- The library relies on a C++14 compiler and standard library, but nothing else
- is required. However, we only guarantee support for the compilers listed
- below, which are tested on an ongoing basis:
- Compiler/Toolchain | Status
- ------------------ | ------
- Clang >= 3.9.1 | Fully working; tested on each push to GitHub
- Xcode >= 9.1 | Fully working; tested on each push to GitHub
- GCC >= 6.0.0 | Fully working; tested on each push to GitHub
- VS2017 >= Update 7 | Fully working; tested on each push to GitHub
- More specifically, Hana requires a compiler/standard library supporting the
- following C++14 features (non-exhaustively):
- - Generic lambdas
- - Generalized `constexpr`
- - Variable templates
- - Automatically deduced return type
- - All the C++14 type traits from the `<type_traits>` header
- Using a compiler not listed above may work, but support for such compilers is
- not guaranteed. More information for specific platforms is available on
- [the wiki][Hana.wiki].
- @section tutorial-support Support
- ------------------------------------------------------------------------------
- If you have a problem, please review the [FAQ](@ref tutorial-rationales) and
- [the wiki][Hana.wiki]. Searching [the issues][Hana.issues] for your problem is
- also a good idea. If that doesn't help, feel free to chat with us in [Gitter][Hana.chat],
- or open a new issue. [StackOverflow][] with the [boost-hana][Hana.StackOverflow]
- tag is the preferred place to ask questions on usage. If you are encountering
- what you think is a bug, please open an issue.
- @section tutorial-introduction Introduction
- ------------------------------------------------------------------------------
- When Boost.MPL first appeared, it provided C++ programmers with a huge relief
- by abstracting tons of template hackery behind a workable interface. This
- breakthrough greatly contributed to making C++ template metaprogramming more
- mainstream, and today the discipline is deeply rooted in many serious projects.
- Recently, C++11 and C++14 brought many major changes to the language, some of
- which make metaprogramming much easier, while others drastically widen the
- design space for libraries. A natural question then arises: is it still
- desirable to have abstractions for metaprogramming, and if so, which ones?
- After investigating different options like the [MPL11][], the answer eventually
- came by itself in the form of a library; Hana. The key insight to Hana is that
- the manipulation of types and values are nothing but two sides of the same
- coin. By unifying both concepts, metaprogramming becomes easier and new
- exciting possibilities open before us.
- @subsection tutorial-introduction-quadrants C++ computational quadrants
- But to really understand what is Hana all about, it is essential to understand
- the different types of computations in C++. We will focus our attention on
- four different kinds of computations, even though a finer grained separation
- would be possible. First, we have runtime computations, which are the usual
- computations we use in C++. In that world, we have runtime containers,
- runtime functions and runtime algorithms:
- @snippet example/tutorial/introduction.cpp runtime
- The usual toolbox for programming within this quadrant is the C++ standard
- library, which provides reusable algorithms and containers operating at
- runtime. Since C++11, a second kind of computation is possible: `constexpr`
- computations. There, we have `constexpr` containers, `constexpr` functions
- and `constexpr` algorithms:
- @code{cpp}
- constexpr int factorial(int n) {
- return n == 0 ? 1 : n * factorial(n - 1);
- }
- template <typename T, std::size_t N, typename F>
- constexpr std::array<std::result_of_t<F(T)>, N>
- transform(std::array<T, N> array, F f) {
- // ...
- }
- constexpr std::array<int, 4> ints{{1, 2, 3, 4}};
- constexpr std::array<int, 4> facts = transform(ints, factorial);
- static_assert(facts == std::array<int, 4>{{1, 2, 6, 24}}, "");
- @endcode
- @note
- For the above code to actually work, `std::array`'s `operator==` would have to
- be marked `constexpr`, which is not the case (even in C++14).
- Basically, a `constexpr` computation is different from a runtime computation
- in that it is simple enough to be evaluated (interpreted, really) by the
- compiler. In general, any function that does not perform anything too
- _unfriendly_ to the compiler's evaluator (like throwing or allocating memory)
- can be marked `constexpr` without any further change. This makes `constexpr`
- computations very similar to runtime computations, except `constexpr`
- computations are more restricted and they gain the ability to be evaluated
- at compile-time. Unfortunately, there is no commonly used toolbox for
- `constexpr`-programming, i.e. there is no widely adopted "standard library"
- for `constexpr` programming. However, the [Sprout][] library may be worth
- checking out for those with some interest in `constexpr` computations.
- The third kind of computations are heterogeneous computations. Heterogeneous
- computations differ from normal computations in that instead of having
- containers holding homogeneous objects (all objects having the same type),
- the containers may hold objects with different types. Furthermore, functions
- in this quadrant of computation are _heterogeneous_ functions, which is a
- complicated way of talking about template functions. Similarly, we have
- heterogeneous algorithms that manipulate heterogeneous containers and
- functions:
- @snippet example/tutorial/introduction.cpp heterogeneous
- If manipulating heterogeneous containers seems overly weird to you, just think
- of it as glorified `std::tuple` manipulation. In a C++03 world, the go-to
- library for doing this kind of computation is [Boost.Fusion][], which provides
- several data structures and algorithms to manipulate heterogeneous collections
- of data. The fourth and last quadrant of computation that we'll be considering
- here is the quadrant of type-level computations. In this quadrant, we have
- type-level containers, type-level functions (usually called metafunctions)
- and type-level algorithms. Here, everything operates on types: containers hold
- types and metafunctions take types as arguments and return types as results.
- @snippet example/tutorial/introduction.cpp type-level
- The realm of type-level computations has been explored quite extensively, and
- the de-facto solution for type-level computations in C++03 is a library named
- [Boost.MPL][], which provides type-level containers and algorithms. For
- low-level type transformations, the metafunctions provided by the
- `<type_traits>` standard header can also be used since C++11.
- @subsection tutorial-quadrants-about What is this library about?
- So all is good, but what is this library actually about? Now that we have set
- the table by clarifying the kinds of computations available to us in C++, the
- answer might strike you as very simple. __The purpose of Hana is to merge the
- 3rd and the 4th quadrants of computation__. More specifically, Hana is a
- (long-winded) constructive proof that heterogeneous computations are strictly
- more powerful than type-level computations, and that we can therefore express
- any type-level computation by an equivalent heterogeneous computation. This
- construction is done in two steps. First, Hana is a fully featured library of
- heterogeneous algorithms and containers, a bit like a modernized Boost.Fusion.
- Secondly, Hana provides a way of translating any type-level computation into its
- equivalent heterogeneous computation and back, which allows the full machinery
- of heterogeneous computations to be reused for type-level computations without
- any code duplication. Of course, the biggest advantage of this unification is
- seen by the user, as you will witness by yourself.
- @section tutorial-quickstart Quick start
- ------------------------------------------------------------------------------
- The goal of this section is to introduce the main concepts of the library
- from a very high level and at a fairly rapid pace; don't worry if you don't
- understand everything that's about to be thrown at you. However, this tutorial
- assumes the reader is already at least _familiar_ with basic metaprogramming
- and the [C++14 standard][C++14]. First, let's include the library:
- @snippet example/tutorial/quickstart.cpp includes
- Unless specified otherwise, the documentation assumes the above lines to be
- present before examples and code snippets. Also note that finer grained
- headers are provided and will be explained in the [Header organization]
- (@ref tutorial-header_organization) section. For the purpose of the
- quickstart, let's now include some additional headers and define some
- lovely animal types that we'll need below:
- @snippet example/tutorial/quickstart.cpp additional_setup
- If you are reading this documentation, chances are you already know
- `std::tuple` and `std::make_tuple`. Hana provides its own tuple and
- `make_tuple`:
- @snippet example/tutorial/quickstart.cpp animals
- This creates a tuple, which is like an array, except that it can hold elements
- with different types. Containers that can hold elements with different types
- such as this are called heterogeneous containers. While the standard library
- provides very few operations to manipulate `std::tuple`s, Hana provides several
- operations and algorithms to manipulate its own tuples:
- @snippet example/tutorial/quickstart.cpp algorithms
- @note
- `1_c` is a [C++14 user-defined literal][C++14.udl] creating a
- [compile-time number](@ref tutorial-integral). These user-defined
- literals are contained in the `boost::hana::literals` namespace,
- hence the `using` directive.
- Notice how we pass a [C++14 generic lambda][C++14.glambda] to `transform`;
- this is required because the lambda will first be called with a `Fish`, then
- a `Cat`, and finally a `Dog`, which all have different types. Hana provides
- most of the algorithms provided by the C++ standard library, except they work
- on tuples and related heterogeneous containers instead of `std::vector` &
- friends. In addition to working with heterogeneous values, Hana makes it
- possible to perform type-level computations with a natural syntax, all at
- compile-time and with no overhead whatsoever. This compiles and does just
- what you would expect:
- @snippet example/tutorial/quickstart.cpp type-level
- @note
- `type_c<...>` is not a type! It is a [C++14 variable template][C++14.vtemplate]
- yielding an object representing a type for Hana. This is explained in the
- section on [type computations](@ref tutorial-type).
- In addition to heterogeneous and compile-time sequences, Hana provides several
- features to make your metaprogramming nightmares a thing of the past. For
- example, one can check for the existence of a struct member with one easy
- line instead of relying on [clunky SFINAE hacks][SO.sfinae]:
- @snippet example/tutorial/quickstart.cpp has_name
- Writing a serialization library? Stop crying, we've got you covered.
- Reflection can be added to user-defined types very easily. This allows
- iterating over the members of a user-defined type, querying members with
- a programmatic interface and much more, without any runtime overhead:
- @snippet example/tutorial/quickstart.cpp serialization
- That's cool, but I can already hear you complaining about incomprehensible
- error messages. However, it turns out Hana was built for humans, not
- professional template metaprogrammers, and this shows. Let's intentionally
- screw up and see what kind of mess is thrown at us. First, the mistake:
- @snippet example/tutorial/quickstart.cpp screw_up
- Now, the punishment:
- @code{cpp}
- error: static_assert failed "hana::for_each(xs, f) requires 'xs' to be Foldable"
- static_assert(Foldable<S>::value,
- ^ ~~~~~~~~~~~~~~~~~~
- note: in instantiation of function template specialization
- 'boost::hana::for_each_t::operator()<
- std::__1::basic_ostream<char> &, (lambda at [snip])>' requested here
- hana::for_each(os, [&](auto member) {
- ^
- note: in instantiation of function template specialization
- 'main()::(anonymous class)::operator()<Person>' requested here
- serialize(std::cout, john);
- ^
- @endcode
- Not that bad, right? However, since small examples are very good to show off
- without actually doing something useful, let's examine a real world example.
- @subsection tutorial-quickstart-any A real world example
- In this section our goal will be to implement a kind of `switch` statement
- able to process `boost::any`s. Given a `boost::any`, the goal is to dispatch
- to the function associated to the dynamic type of the `any`:
- @snippet example/tutorial/quickstart.switchAny.cpp usage
- @note
- In the documentation, we will often use the `s` suffix on string literals to
- create `std::string`s without syntactic overhead. This is a standard-defined
- [C++14 user-defined literal][C++14.udl].
- Since the any holds a `char`, the second function is called with the `char`
- inside it. If the `any` had held an `int` instead, the first function would
- have been called with the `int` inside it. When the dynamic type of the `any`
- does not match any of the covered cases, the `%default_` function is called
- instead. Finally, the result of the `switch` is the result of calling the
- function associated to the `any`'s dynamic type. The type of that result is
- inferred to be the common type of the result of all the provided functions:
- @snippet example/tutorial/quickstart.switchAny.cpp result_inference
- We'll now look at how this utility can be implemented using Hana. The first
- step is to associate each type to a function. To do so, we represent each
- `case_` as a `hana::pair` whose first element is a type and whose second
- element is a function. Furthermore, we (arbitrarily) decide to represent the
- `%default_` case as a `hana::pair` mapping a dummy type to a function:
- @snippet example/tutorial/quickstart.switchAny.cpp cases
- To provide the interface we showed above, `switch_` will have to return a
- function taking the cases. In other words, `switch_(a)` must be a function
- taking any number of cases (which are `hana::pair`s), and performing the logic
- to dispatch `a` to the right function. This can easily be achieved by having
- `switch_` return a C++14 generic lambda:
- @code{cpp}
- template <typename Any>
- auto switch_(Any& a) {
- return [&a](auto ...cases_) {
- // ...
- };
- }
- @endcode
- However, since parameter packs are not very flexible, we'll put the cases
- into a tuple so we can manipulate them:
- @code{cpp}
- template <typename Any>
- auto switch_(Any& a) {
- return [&a](auto ...cases_) {
- auto cases = hana::make_tuple(cases_...);
- // ...
- };
- }
- @endcode
- Notice how the `auto` keyword is used when defining `cases`; it is often
- easier to let the compiler deduce the type of the tuple and use `make_tuple`
- instead of working out the types manually. The next step is to separate the
- default case from the rest of the cases. This is where things start to get
- interesting. To do so, we use Hana's `find_if` algorithm, which works a bit
- like `std::find_if`:
- @code{cpp}
- template <typename Any>
- auto switch_(Any& a) {
- return [&a](auto ...cases_) {
- auto cases = hana::make_tuple(cases_...);
- auto default_ = hana::find_if(cases, [](auto const& c) {
- return hana::first(c) == hana::type_c<default_t>;
- });
- // ...
- };
- }
- @endcode
- `find_if` takes a `tuple` and a predicate, and returns the first element of
- the tuple which satisfies the predicate. The result is returned as a
- `hana::optional`, which is very similar to a `std::optional`, except
- whether that optional value is empty or not is known at compile-time. If the
- predicate is not satisfied for any element of the `tuple`, `find_if` returns
- `nothing` (an empty value). Otherwise, it returns `just(x)` (a non-empty value),
- where `x` is the first element satisfying the predicate. Unlike predicates
- used in STL algorithms, the predicate used here must be generic because the
- tuple's elements are heterogeneous. Furthermore, that predicate must return
- what Hana calls an `IntegralConstant`, which means that the predicate's result
- must be known at compile-time. These details are explained in the section on
- [cross-phase algorithms](@ref tutorial-algorithms-cross_phase). Inside the
- predicate, we simply compare the type of the case's first element to
- `type_c<default_t>`. If you recall that we were using `hana::pair`s to encode
- cases, this simply means that we're finding the default case among all of the
- provided cases. But what if no default case was provided? We should fail at
- compile-time, of course!
- @code{cpp}
- template <typename Any>
- auto switch_(Any& a) {
- return [&a](auto ...cases_) {
- auto cases = hana::make_tuple(cases_...);
- auto default_ = hana::find_if(cases, [](auto const& c) {
- return hana::first(c) == hana::type_c<default_t>;
- });
- static_assert(default_ != hana::nothing,
- "switch is missing a default_ case");
- // ...
- };
- }
- @endcode
- Notice how we can use `static_assert` on the result of the comparison with
- `nothing`, even though `%default_` is a non-`constexpr` object? Boldly, Hana
- makes sure that no information that's known at compile-time is lost to the
- runtime, which is clearly the case of the presence of a `%default_` case.
- The next step is to gather the set of non-default cases. To achieve this, we
- use the `filter` algorithm, which keeps only the elements of the sequence
- satisfying the predicate:
- @code{cpp}
- template <typename Any>
- auto switch_(Any& a) {
- return [&a](auto ...cases_) {
- auto cases = hana::make_tuple(cases_...);
- auto default_ = hana::find_if(cases, [](auto const& c) {
- return hana::first(c) == hana::type_c<default_t>;
- });
- static_assert(default_ != hana::nothing,
- "switch is missing a default_ case");
- auto rest = hana::filter(cases, [](auto const& c) {
- return hana::first(c) != hana::type_c<default_t>;
- });
- // ...
- };
- }
- @endcode
- The next step is to find the first case matching the dynamic type of the `any`,
- and then call the function associated to that case. The simplest way to do this
- is to use classic recursion with variadic parameter packs. Of course, we could
- probably intertwine Hana algorithms in a convoluted way to achieve this, but
- sometimes the best way to do something is to write it from scratch using basic
- techniques. To do so, we'll call an implementation function with the contents
- of the `rest` tuple by using the `unpack` function:
- @snippet example/tutorial/quickstart.switchAny.cpp switch_
- `unpack` takes a `tuple` and a function, and calls the function with the content
- of the `tuple` as arguments. The result of `unpack` is the result of calling that
- function. In our case, the function is a generic lambda which in turn calls the
- `process` function. Our reason for using `unpack` here was to turn the `rest`
- tuple into a parameter pack of arguments, which are easier to process recursively
- than tuples. Before we move on to the `process` function, it is worthwhile to
- explain what `second(*%default_)` is all about. As we explained earlier,
- `%default_` is an optional value. Like `std::optional`, this optional value
- overloads the dereference operator (and the arrow operator) to allow accessing
- the value inside the `optional`. If the optional is empty (`nothing`), a
- compile-time error is triggered. Since we know `%default_` is not empty (we
- checked that just above), what we're doing is simply pass the function
- associated to the default case to the `process` function. We're now ready
- for the final step, which is the implementation of the `process` function:
- @snippet example/tutorial/quickstart.switchAny.cpp process
- There are two overloads of this function: an overload for when there is at least
- one case to process, and the base case overload for when there's only the default
- case. As we would expect, the base case simply calls the default function and
- returns that result. The other overload is slightly more interesting. First,
- we retrieve the type associated to that case and store it in `T`. This
- `decltype(...)::%type` dance might seem convoluted, but it is actually quite
- simple. Roughly speaking, this takes a type represented as an object (a `type<T>`)
- and pulls it back down to the type level (a `T`). The details are explained in
- the section on [type-level computations](@ref tutorial-type). Then, we compare
- whether the dynamic type of the `any` matches this case, and if so we call the
- function associated to this case with the `any` casted to the proper type.
- Otherwise, we simply call `process` recursively with the rest of the cases.
- Pretty simple, wasn't it? Here's the final solution:
- @snippet example/tutorial/quickstart.switchAny.cpp full
- That's it for the quick start! This example only introduced a couple of useful
- algorithms (`find_if`, `filter`, `unpack`) and heterogeneous containers
- (`tuple`, `optional`), but rest assured that there is much more. The next
- sections of the tutorial gradually introduce general concepts pertaining to
- Hana in a friendly way, but you may use the following cheatsheet for quick
- reference if you want to start coding right away. This cheatsheet contains
- the most frequently used algorithms and containers, along with a short
- description of what each of them does.
- @section tutorial-cheatsheet Cheatsheet
- ### Remarks
- - Most algorithms work on both types and values (see the section on
- [type computations](@ref tutorial-type))
- - Algorithms always return their result as a new container; no in-place
- mutation is ever performed (see the section on [algorithms]
- (@ref tutorial-algorithms))
- - All algorithms are `constexpr` function objects
- container | description
- :----------------- | :----------
- <code>[tuple](@ref boost::hana::tuple)</code> | General purpose index-based heterogeneous sequence with a fixed length. Use this as a `std::vector` for heterogeneous objects.
- <code>[optional](@ref boost::hana::optional)</code> | Represents an optional value, i.e. a value that can be empty. This is a bit like `std::optional`, except that the emptiness is known at compile-time.
- <code>[map](@ref boost::hana::map)</code> | Unordered associative array mapping (unique) compile-time entities to arbitrary objects. This is like `std::unordered_map` for heterogeneous objects.
- <code>[set](@ref boost::hana::set)</code> | Unordered container holding unique keys that must be compile-time entities. This is like `std::unordered_set` for heterogeneous objects.
- <code>[range](@ref boost::hana::range)</code> | Represents an interval of compile-time numbers. This is like `std::integer_sequence`, but better.
- <code>[pair](@ref boost::hana::pair)</code> | Container holding two heterogeneous objects. Like `std::pair`, but compresses the storage of empty types.
- <code>[string](@ref boost::hana::string)</code> | Compile-time string.
- <code>[type](@ref boost::hana::type)</code> | Container representing a C++ type. This is the root of the unification between types and values, and is of interest for MPL-style computations (type-level computations).
- <code>[integral_constant](@ref boost::hana::integral_constant)</code> | Represents a compile-time number. This is very similar to `std::integral_constant`, except that `hana::integral_constant` also defines operators and more syntactic sugar.
- <code>[lazy](@ref boost::hana::lazy)</code> | Encapsulates a lazy value or computation.
- <code>[basic_tuple](@ref boost::hana::basic_tuple)</code> | Stripped-down version of `hana::tuple`. Not standards conforming, but more compile-time efficient.
- function | description
- :------------------------------------------ | :----------
- <code>[adjust](@ref ::boost::hana::adjust)(sequence, value, f)</code> | Apply a function to each element of a sequence that compares equal to some value and return the result.
- <code>[adjust_if](@ref ::boost::hana::adjust_if)(sequence, predicate, f)</code> | Apply a function to each element of a sequence satisfying some predicate and return the result.
- <code>{[all](@ref ::boost::hana::all),[any](@ref ::boost::hana::any),[none](@ref ::boost::hana::none)}(sequence)</code> | Returns whether all/any/none of the elements of a sequence are true-valued.
- <code>{[all](@ref ::boost::hana::all_of),[any](@ref ::boost::hana::any_of),[none](@ref ::boost::hana::none_of)}_of(sequence, predicate)</code> | Returns whether all/any/none of the elements of the sequence satisfy some predicate.
- <code>[append](@ref ::boost::hana::append)(sequence, value)</code> | Append an element to a sequence.
- <code>[at](@ref ::boost::hana::at)(sequence, index)</code> | Returns the n-th element of a sequence. The index must be an `IntegralConstant`.
- <code>[back](@ref ::boost::hana::back)(sequence)</code> | Returns the last element of a non-empty sequence.
- <code>[concat](@ref ::boost::hana::concat)(sequence1, sequence2)</code> | Concatenate two sequences.
- <code>[contains](@ref ::boost::hana::contains)(sequence, value)</code> | Returns whether a sequence contains the given object.
- <code>[count](@ref ::boost::hana::count)(sequence, value)</code> | Returns the number of elements that compare equal to the given value.
- <code>[count_if](@ref ::boost::hana::count_if)(sequence, predicate)</code> | Returns the number of elements that satisfy the predicate.
- <code>[drop_front](@ref ::boost::hana::drop_front)(sequence[, n])</code> | Drop the first `n` elements from a sequence, or the whole sequence if `length(sequence) <= n`. `n` must be an `IntegralConstant`. When not provided, `n` defaults to 1.
- <code>[drop_front_exactly](@ref ::boost::hana::drop_front_exactly)(sequence[, n])</code> | Drop the first `n` elements from a sequence. `n` must be an `IntegralConstant` and the sequence must have at least `n` elements. When not provided, `n` defaults to 1.
- <code>[drop_back](@ref ::boost::hana::drop_back)(sequence[, n])</code> | Drop the last `n` elements from a sequence, or the whole sequence if `length(sequence) <= n`. `n` must be an `IntegralConstant`. When not provided, `n` defaults to 1.
- <code>[drop_while](@ref ::boost::hana::drop_while)(sequence, predicate)</code> | Drops elements from a sequence while a predicate is satisfied. The predicate must return an `IntegralConstant`.
- <code>[fill](@ref ::boost::hana::fill)(sequence, value)</code> | Replace all the elements of a sequence with some value.
- <code>[filter](@ref ::boost::hana::filter)(sequence, predicate)</code> | Remove all the elements that do not satisfy a predicate. The predicate must return an `IntegralConstant`.
- <code>[find](@ref ::boost::hana::find)(sequence, value)</code> | Find the first element of a sequence which compares equal to some value and return `just` it, or return `nothing`. See `hana::optional`.
- <code>[find_if](@ref ::boost::hana::find_if)(sequence, predicate)</code> | Find the first element of a sequence satisfying the predicate and return `just` it, or return `nothing`. See `hana::optional`.
- <code>[flatten](@ref ::boost::hana::flatten)(sequence)</code> | Flatten a sequence of sequences, a bit like `std::tuple_cat`.
- <code>[fold_left](@ref ::boost::hana::fold_left)(sequence[, state], f)</code> | Accumulates the elements of a sequence from the left, optionally with a provided initial state.
- <code>[fold_right](@ref ::boost::hana::fold_right)(sequence[, state], f)</code> | Accumulates the elements of a sequence from the right, optionally with a provided initial state.
- <code>[fold](@ref ::boost::hana::fold)(sequence[, state], f)</code> | Equivalent to `fold_left`; provided for consistency with Boost.MPL and Boost.Fusion.
- <code>[for_each](@ref ::boost::hana::for_each)(sequence, f)</code> | Call a function on each element of a sequence. Returns `void`.
- <code>[front](@ref ::boost::hana::front)(sequence)</code> | Returns the first element of a non-empty sequence.
- <code>[group](@ref ::boost::hana::group)(sequence[, predicate])</code> | %Group adjacent elements of a sequence which all satisfy (or all do not satisfy) some predicate. The predicate defaults to equality, in which case the elements must be `Comparable`.
- <code>[index_if](@ref ::boost::hana::index_if)(sequence, predicate)</code> | Find the index of the first element in a sequence satisfying the predicate and return `just` it, or return `nothing`. See `hana::optional`.
- <code>[insert](@ref ::boost::hana::insert)(sequence, index, element)</code> | Insert an element at a given index. The index must be an `IntegralConstant`.
- <code>[insert_range](@ref ::boost::hana::insert_range)(sequence, index, elements)</code> | Insert a sequence of elements at a given index. The index must be an `IntegralConstant`.
- <code>[is_empty](@ref ::boost::hana::is_empty)(sequence)</code> | Returns whether a sequence is empty as an `IntegralConstant`.
- <code>[length](@ref ::boost::hana::length)(sequence)</code> | Returns the length of a sequence as an `IntegralConstant`.
- <code>[lexicographical_compare](@ref ::boost::hana::lexicographical_compare)(sequence1, sequence2[, predicate])</code> | Performs a lexicographical comparison of two sequences, optionally with a custom predicate, by default with `hana::less`.
- <code>[maximum](@ref ::boost::hana::maximum)(sequence[, predicate])</code> | Returns the greatest element of a sequence, optionally according to a predicate. The elements must be `Orderable` if no predicate is provided.
- <code>[minimum](@ref ::boost::hana::minimum)(sequence[, predicate])</code> | Returns the smallest element of a sequence, optionally according to a predicate. The elements must be `Orderable` if no predicate is provided.
- <code>[partition](@ref ::boost::hana::partition)(sequence, predicate)</code> | Partition a sequence into a pair of elements that satisfy some predicate, and elements that do not satisfy it.
- <code>[prepend](@ref ::boost::hana::prepend)(sequence, value)</code> | Prepend an element to a sequence.
- <code>[remove](@ref ::boost::hana::remove)(sequence, value)</code> | Remove all the elements that are equal to a given value.
- <code>[remove_at](@ref ::boost::hana::remove_at)(sequence, index)</code> | Remove the element at the given index. The index must be an `IntegralConstant`.
- <code>[remove_if](@ref ::boost::hana::remove_if)(sequence, predicate)</code> | Remove all the elements that satisfy a predicate. The predicate must return an `IntegralConstant`.
- <code>[remove_range](@ref ::boost::hana::remove_range)(sequence, from, to)</code> | Remove the elements at indices in the given `[from, to)` half-open interval. The indices must be `IntegralConstant`s.
- <code>[replace](@ref ::boost::hana::replace)(sequence, oldval, newval)</code> | Replace the elements of a sequence that compare equal to some value by some other value.
- <code>[replace_if](@ref ::boost::hana::replace_if)(sequence, predicate, newval)</code> | Replace the elements of a sequence that satisfy some predicate by some value.
- <code>[reverse](@ref ::boost::hana::reverse)(sequence)</code> | Reverse the order of the elements in a sequence.
- <code>[reverse_fold](@ref ::boost::hana::reverse_fold)(sequence[, state], f)</code> | Equivalent to `fold_right`; provided for consistency with Boost.MPL and Boost.Fusion.
- <code>[size](@ref ::boost::hana::size)(sequence)</code> | Equivalent to `length`; provided for consistency with the C++ standard library.
- <code>[slice](@ref ::boost::hana::slice)(sequence, indices)</code> | Returns a new sequence containing the elements at the given indices of the original sequence.
- <code>[slice_c](@ref ::boost::hana::slice_c)<from, to>(sequence)</code> | Returns a new sequence containing the elements at indices contained in `[from, to)` of the original sequence.
- <code>[sort](@ref ::boost::hana::sort)(sequence[, predicate])</code> | Sort (stably) the elements of a sequence, optionally according to a predicate. The elements must be `Orderable` if no predicate is provided.
- <code>[take_back](@ref ::boost::hana::take_back)(sequence, number)</code> | Take the last n elements of a sequence, or the whole sequence if `length(sequence) <= n`. n must be an `IntegralConstant`.
- <code>[take_front](@ref ::boost::hana::take_front)(sequence, number)</code> | Take the first n elements of a sequence, or the whole sequence if `length(sequence) <= n`. n must be an `IntegralConstant`.
- <code>[take_while](@ref ::boost::hana::take_while)(sequence, predicate)</code> | Take elements of a sequence while some predicate is satisfied, and return that.
- <code>[transform](@ref ::boost::hana::transform)(sequence, f)</code> | Apply a function to each element of a sequence and return the result.
- <code>[unique](@ref ::boost::hana::unique)(sequence[, predicate])</code> | Removes all consecutive duplicates from a sequence. The predicate defaults to equality, in which case the elements must be `Comparable`.
- <code>[unpack](@ref ::boost::hana::unpack)(sequence, f)</code> | Calls a function with the contents of a sequence. Equivalent to `f(x1, ..., xN)`.
- <code>[zip](@ref ::boost::hana::zip)(s1, ..., sN)</code> | Zip `N` sequences into a sequence of tuples. All the sequences must have the same length.
- <code>[zip_shortest](@ref ::boost::hana::zip_shortest)(s1, ..., sN)</code> | Zip `N` sequences into a sequence of tuples. The resulting sequence has the length of the shortest input sequence.
- <code>[zip_with](@ref ::boost::hana::zip_with)(f, s1, ..., sN)</code> | Zip `N` sequences with a `N`-ary function. All the sequences must have the same length.
- <code>[zip_shortest_with](@ref ::boost::hana::zip_shortest_with)(f, s1, ..., sN)</code> | Zip `N` sequences with a `N`-ary function. The resulting sequence has the length of the shortest input sequence.
- @section tutorial-assert Assertions
- ------------------------------------------------------------------------------
- In the rest of this tutorial, you will come across code snippets where different
- kinds of assertions like `BOOST_HANA_RUNTIME_CHECK` and `BOOST_HANA_CONSTANT_CHECK`
- are used. Like any sensible `assert` macro, they basically check that the
- condition they are given is satisfied. However, in the context of heterogeneous
- programming, some informations are known at compile-time, while others are known
- only at runtime. The exact type of assertion that's used in a context tells you
- whether the condition that's asserted upon can be known at compile-time or if it
- must be computed at runtime, which is a very precious piece of information. Here
- are the different kinds of assertions used in the tutorial, with a small
- description of their particularities. For more details, you should check
- the [reference on assertions](@ref group-assertions).
- assertion | description
- :--------------------------- | :----------
- `BOOST_HANA_RUNTIME_CHECK` | Assertion on a condition that is not known until runtime. This assertion provides the weakest form of guarantee.
- `BOOST_HANA_CONSTEXPR_CHECK` | Assertion on a condition that would be `constexpr` if lambdas were allowed inside constant expressions. In other words, the only reason for it not being a `static_assert` is the language limitation that lambdas can't appear in constant expressions, which [might be lifted][N4487] in C++17.
- `static_assert` | Assertion on a `constexpr` condition. This is stronger than `BOOST_HANA_CONSTEXPR_CHECK` in that it requires the condition to be a constant expression, and it hence assures that the algorithms used in the expression are `constexpr`-friendly.
- `BOOST_HANA_CONSTANT_CHECK` | Assertion on a boolean `IntegralConstant`. This assertion provides the strongest form of guarantee, because an `IntegralConstant` can be converted to a `constexpr` value even if it is not `constexpr` itself.
- @section tutorial-integral Compile-time numbers
- ------------------------------------------------------------------------------
- This section introduces the important notion of `IntegralConstant` and the
- philosophy behind Hana's metaprogramming paradigm. Let's start with a rather
- odd question. What is an `integral_constant`?
- @code{cpp}
- template<class T, T v>
- struct integral_constant {
- static constexpr T value = v;
- typedef T value_type;
- typedef integral_constant type;
- constexpr operator value_type() const noexcept { return value; }
- constexpr value_type operator()() const noexcept { return value; }
- };
- @endcode
- @note
- If this is totally new to you, you might want to take a look at the
- [documentation][C++14.ice] for `std::integral_constant`.
- One valid answer is that `integral_constant` represents a type-level
- encoding of a number, or more generally any object of an integral type.
- For illustration, we could define a successor function on numbers in that
- representation very easily by using a template alias:
- @code{cpp}
- template <typename N>
- using succ = integral_constant<int, N::value + 1>;
- using one = integral_constant<int, 1>;
- using two = succ<one>;
- using three = succ<two>;
- // ...
- @endcode
- This is the way `integral_constant`s are usually thought of; as _type-level_
- entities that can be used for template metaprogramming. Another way to see
- an `integral_constant` is as a runtime object representing a `constexpr` value
- of an integral type:
- @code{cpp}
- auto one = integral_constant<int, 1>{};
- @endcode
- Here, while `one` is not marked as `constexpr`, the abstract value it holds
- (a `constexpr 1`) is still available at compile-time, because that value is
- encoded in the type of `one`. Indeed, even if `one` is not `constexpr`, we
- can use `decltype` to retrieve the compile-time value it represents:
- @code{cpp}
- auto one = integral_constant<int, 1>{};
- constexpr int one_constexpr = decltype(one)::value;
- @endcode
- But why on earth would we want to consider `integral_constant`s as objects
- instead of type-level entities? To see why, consider how we could now
- implement the same successor function as before:
- @code{cpp}
- template <typename N>
- auto succ(N) {
- return integral_constant<int, N::value + 1>{};
- }
- auto one = integral_constant<int, 1>{};
- auto two = succ(one);
- auto three = succ(two);
- // ...
- @endcode
- Did you notice anything new? The difference is that instead of implementing
- `succ` at the type-level with a template alias, we're now implementing it at
- the value-level with a template function. Furthermore, we can now perform
- compile-time arithmetic using the same syntax as that of normal C++. This
- way of seeing compile-time entities as objects instead of types is the key
- to Hana's expressive power.
- @subsection tutorial-integral-arithmetic Compile-time arithmetic
- The MPL defines [arithmetic operators][MPL.arithmetic] that can be used to do
- compile-time computations with `integral_constant`s. A typical example of such
- an operation is `plus`, which is implemented roughly as:
- @code{cpp}
- template <typename X, typename Y>
- struct plus {
- using type = integral_constant<
- decltype(X::value + Y::value),
- X::value + Y::value
- >;
- };
- using three = plus<integral_constant<int, 1>,
- integral_constant<int, 2>>::type;
- @endcode
- By viewing `integral_constant`s as objects instead of types, the translation
- from a metafunction to a function is very straightforward:
- @code{cpp}
- template <typename V, V v, typename U, U u>
- constexpr auto
- operator+(integral_constant<V, v>, integral_constant<U, u>)
- { return integral_constant<decltype(v + u), v + u>{}; }
- auto three = integral_constant<int, 1>{} + integral_constant<int, 2>{};
- @endcode
- It is very important to emphasize the fact that this operator does not return
- a normal integer. Instead, it returns a value-initialized object whose type
- contains the result of the addition. The only useful information contained in
- that object is actually in its type, and we're creating an object because it
- allows us to use this nice value-level syntax. It turns out that we can make
- this syntax even better by using a [C++14 variable template][C++14.vtemplate]
- to simplify the creation of an `integral_constant`:
- @code{cpp}
- template <int i>
- constexpr integral_constant<int, i> int_c{};
- auto three = int_c<1> + int_c<2>;
- @endcode
- Now we're talking about a visible gain in expressiveness over the initial
- type-level approach, aren't we? But there's more; we can also use
- [C++14 user defined literals][C++14.udl] to make this process even simpler:
- @code{cpp}
- template <char ...digits>
- constexpr auto operator"" _c() {
- // parse the digits and return an integral_constant
- }
- auto three = 1_c + 2_c;
- @endcode
- Hana provides its own `integral_constant`s, which define arithmetic operators
- just like we showed above. Hana also provides variable templates to easily
- create different kinds of `integral_constant`s: `int_c`, `long_c`, `bool_c`,
- etc... This allows you to omit the trailing `{}` braces otherwise required to
- value-initialize these objects. Of course, the `_c` suffix is also provided;
- it is part of the `hana::literals` namespace, and you must import it into
- your namespace before using it:
- @code{cpp}
- using namespace hana::literals;
- auto three = 1_c + 2_c;
- @endcode
- This way, you may do compile-time arithmetic without having to struggle with
- awkward type-level idiosyncrasies, and your coworkers will now be able to
- understand what's going on.
- @subsection tutorial-integral-distance Example: Euclidean distance
- To illustrate how good it gets, let's implement a function computing a 2-D
- euclidean distance at compile-time. As a reminder, the euclidean distance of
- two points in the 2-D plane is given by
- @f[
- \mathrm{distance}\left((x_1, y_1), (x_2, y_2)\right)
- := \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}
- @f]
- First, here's how it looks like with a type-level approach (using the MPL):
- @snippet example/tutorial/integral.cpp distance-mpl
- Yeah... Now, let's implement it with the value-level approach presented above:
- @snippet example/tutorial/integral.cpp distance-hana
- This version looks arguably cleaner. However, this is not all. Notice how the
- `distance` function looks exactly as the one you would have written for
- computing the euclidean distance on dynamic values? Indeed, because we're
- using the same syntax for dynamic and compile-time arithmetic, generic
- functions written for one will work for both!
- @snippet example/tutorial/integral.cpp distance-dynamic
- __Without changing any code__, we can use our `distance` function on runtime
- values and everything just works. Now that's DRY.
- @subsection tutorial-integral-branching Compile-time branching
- Once we have compile-time arithmetic, the next thing that might come to mind
- is compile-time branching. When metaprogramming, it is often useful to have
- one piece of code be compiled if some condition is true, and a different one
- otherwise. If you have heard of [static_if][N4461], this should sound very
- familiar, and indeed it is exactly what we are talking about. Otherwise, if
- you don't know why we might want to branch at compile-time, consider the
- following code (adapted from [N4461][]):
- @code{cpp}
- template <typename T, typename ...Args>
- std::enable_if_t<std::is_constructible<T, Args...>::value,
- std::unique_ptr<T>> make_unique(Args&&... args) {
- return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
- }
- template <typename T, typename ...Args>
- std::enable_if_t<!std::is_constructible<T, Args...>::value,
- std::unique_ptr<T>> make_unique(Args&&... args) {
- return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
- }
- @endcode
- This code creates a `std::unique_ptr` using the correct form of syntax for the
- constructor. To achieve this, it uses [SFINAE][] and requires two different
- overloads. Now, anyone sane seeing this for the first time would ask why it
- is not possible to simply write:
- @code{cpp}
- template <typename T, typename ...Args>
- std::unique_ptr<T> make_unique(Args&&... args) {
- if (std::is_constructible<T, Args...>::value)
- return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
- else
- return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
- }
- @endcode
- The reason is that the compiler is required to compile both branches of the
- `if` statement, regardless of the condition (even though it is known at
- compile-time). But when `T` is _not_ constructible from `Args...`, the second
- branch will fail to compile, which will cause a hard compilation error. What
- we need really is a way to tell the compiler __not to compile__ the second
- branch when the condition is true, and the first branch when the condition is
- false.
- To emulate this, Hana provides an `if_` function that works a bit like a
- normal `if` statement, except except it takes a condition that can be an
- `IntegralConstant` and returns the one of two values (which may have
- different types) chosen by the condition. If the condition is true, the
- first value is returned, and otherwise the second value is returned. A
- somewhat vain example is the following:
- @code{cpp}
- auto one_two_three = hana::if_(hana::true_c, 123, "hello");
- auto hello = hana::if_(hana::false_c, 123, "hello");
- @endcode
- @note
- `hana::true_c` and `hana::false_c` are just boolean `IntegralConstant`s
- representing a compile-time true value and a compile-time false value,
- respectively.
- Here, `one_two_three` is equal to `123`, and `hello` is equal to `"hello"`.
- In other words, `if_` is a little bit like the ternary conditional operator
- `? :`, except that both sides of the `:` can have different types:
- @code{cpp}
- // fails in both cases because both branches have incompatible types
- auto one_two_three = hana::true_c ? 123 : "hello";
- auto hello = hana::false_c ? 123 : "hello";
- @endcode
- Ok, so this is neat, but how can it actually help us write complete branches
- that are lazily instantiated by the compiler? The answer is to represent both
- branches of the `if` statement we'd like to write as generic lambdas, and to
- use `hana::if_` to return the branch that we'd like to execute. Here's how we
- could rewrite `make_unique`:
- @snippet example/tutorial/integral-branching.cpp make_unique.if_
- Here, the first value given to `hana::if_` is a generic lambda representing
- the branch we want to execute if the condition is true, and the second value
- is the branch we want to execute otherwise. `hana::if_` simply returns the
- branch chosen by the condition, and we call that branch (which is a generic
- lambda) immediately with `std::forward<Args>(args)...`. Hence, the proper
- generic lambda ends up being called, with `x...` being `args...`, and we
- return the result of that call.
- The reason why this approach works is because the body of each branch can only
- be instantiated when the types of all `x...` are known. Indeed, since the
- branch is a generic lambda, the type of the argument is not known until the
- lambda is called, and the compiler must wait for the types of `x...` to be
- known before type-checking the lambda's body. Since the erroneous lambda is
- never called when the condition is not satisfied (`hana::if_` takes care of
- that), the body of the lambda that would fail is never type-checked and no
- compilation error happens.
- @note
- The branches inside the `if_` are lambdas. As such, they really are different
- functions from the `make_unique` function. The variables appearing inside
- those branches have to be either captured by the lambdas or passed to them as
- arguments, and so they are affected by the way they are captured or passed
- (by value, by reference, etc..).
- Since this pattern of expressing branches as lambdas and then calling them
- is very common, Hana provides a `eval_if` function whose purpose is to make
- compile-time branching easier. `eval_if` comes from the fact that in a lambda,
- one can either receive input data as arguments or capture it from the context.
- However, for the purpose of emulating a language level `if` statement,
- implicitly capturing variables from the enclosing scope is usually more
- natural. Hence, what we would prefer writing is
- @code{cpp}
- template <typename T, typename ...Args>
- std::unique_ptr<T> make_unique(Args&&... args) {
- return hana::if_(std::is_constructible<T, Args...>{},
- [&] { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); },
- [&] { return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); }
- );
- }
- @endcode
- Here, we're capturing the `args...` variables from the enclosing scope, which
- prevents us from having to introduce the new `x...` variables and passing them
- as arguments to the branches. However, this has two problems. First, this will
- not achieve the right result, since `hana::if_` will end up returning a lambda
- instead of returning the result of calling that lambda. To fix this, we can use
- `hana::eval_if` instead of `hana::if_`:
- @code{cpp}
- template <typename T, typename ...Args>
- std::unique_ptr<T> make_unique(Args&&... args) {
- return hana::eval_if(std::is_constructible<T, Args...>{},
- [&] { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); },
- [&] { return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); }
- );
- }
- @endcode
- Here, we capture the enclosing `args...` by reference using `[&]`, and we do
- not need to receive any arguments. Also, `hana::eval_if` assumes that its
- arguments are branches that can be called, and it will take care of calling
- the branch that is selected by the condition. However, this will still cause
- a compilation failure, because the bodies of the lambdas are not dependent
- anymore, and semantic analysis will be done for both branches even though
- only one would end up being used. The solution to this problem is to make
- the bodies of the lambdas artificially dependent on something, to prevent the
- compiler from being able to perform semantic analysis before the lambda is
- actually used. To make this possible, `hana::eval_if` will call the selected
- branch with an identity function (a function that returns its argument
- unchanged), if the branch accepts such an argument:
- @snippet example/tutorial/integral-branching.cpp make_unique.eval_if
- Here, the bodies of the branches take an additional argument called `_` by
- convention. This argument will be provided by `hana::eval_if` to the branch
- that was selected. Then, we use `_` as a function on the variables that we
- want to make dependent within the body of each branch. What happens is that
- `_` will always be a function that returns its argument unchanged. However,
- the compiler can't possibly know it before the lambda has actually been called,
- so it can't know the type of `_(args)`. This prevents the compiler from being
- able to perform semantic analysis, and no compilation error happens. Plus,
- since `_(x)` is guaranteed to be equivalent to `x`, we know that we're not
- actually changing the semantics of the branches by using this trick.
- While using this trick may seem cumbersome, it can be very useful when dealing
- with many variables inside a branch. Furthermore, it is not required to wrap
- all variables with `_`; only variables that are involved in an expression whose
- type-checking has to be delayed must be wrapped, but the other ones are not
- required. There are still a few things to know about compile-time branching
- in Hana, but you can dig deeper by looking at the reference for `hana::eval_if`,
- `hana::if_` and `hana::lazy`.
- @subsection tutorial-integral-more Why stop here?
- Why should we limit ourselves to arithmetic operations and branching? When you
- start considering `IntegralConstant`s as objects, it becomes sensible to augment
- their interface with more functions that are generally useful. For example,
- Hana's `IntegralConstant`s define a `times` member function that can be used
- to invoke a function a certain number of times, which is especially useful
- for loop unrolling:
- @code{cpp}
- __attribute__((noinline)) void f() { }
- int main() {
- hana::int_c<10>.times(f);
- }
- @endcode
- In the above code, the 10 calls to `f` are expanded at compile-time. In other
- words, this is equivalent to writing
- @code{cpp}
- f(); f(); ... f(); // 10 times
- @endcode
- @note
- Always [be careful][Chandler.MeetingC++] about manually unrolling loops or
- doing other such optimizations manually. In most cases, your compiler is
- probably better than you at optimizing. When in doubt, benchmark.
- Another nice use of `IntegralConstant`s is to define good-looking operators
- for indexing heterogeneous sequences. Whereas `std::tuple` must be accessed
- with `std::get`, `hana::tuple` can be accessed using the familiar `operator[]`
- used for standard library containers:
- @code{cpp}
- auto values = hana::make_tuple(1, 'x', 3.4f);
- char x = values[1_c];
- @endcode
- How this works is very simple. Basically, `hana::tuple` defines an `operator[]`
- taking an `IntegralConstant` instead of a normal integer, in a way similar to
- @code{cpp}
- template <typename N>
- constexpr decltype(auto) operator[](N const&) {
- return std::get<N::value>(*this);
- }
- @endcode
- This is the end of the section on `IntegralConstant`s. This section introduced
- the feel behind Hana's new way of metaprogramming; if you liked what you've
- seen so far, the rest of this tutorial should feel just like home.
- @section tutorial-type Type computations
- ------------------------------------------------------------------------------
- At this point, if you are interested in doing type-level computations as with
- the MPL, you might be wondering how is Hana going to help you. Do not despair.
- Hana provides a way to perform type-level computations with a great deal of
- expressiveness by representing types as values, just like we represented
- compile-time numbers as values. This is a completely new way of approaching
- metaprogramming, and you should try to set your old MPL habits aside for a bit
- if you want to become proficient with Hana.
- However, please be aware that modern C++ features like [auto-deduced return type]
- [C++14.auto_rt] remove the need for type computations in many cases. Hence,
- before even considering to do a type computation, you should ask yourself
- whether there's a simpler way to achieve what you're trying to achieve. In
- most cases, the answer will be yes. However, when the answer is no, Hana will
- provide you with nuclear-strength facilities to do what needs to be done.
- @subsection tutorial-type-objects Types as objects
- The key behind Hana's approach to type-level computations is essentially the
- same as the approach to compile-time arithmetic. Basically, the idea is to
- represent compile-time entities as objects by wrapping them into some kind of
- container. For `IntegralConstant`s, the compile-time entities were constant
- expressions of an integral type and the wrapper we used was `integral_constant`.
- In this section, the compile-time entities will be types and the wrapper we'll
- be using is called `type`. Just like we did for `IntegralConstant`s, let's
- start by defining a dummy template that could be used to represent a type:
- @code{cpp}
- template <typename T>
- struct basic_type {
- // empty (for now)
- };
- basic_type<int> Int{};
- basic_type<char> Char{};
- // ...
- @endcode
- @note
- We're using the name `basic_type` here because we're only building a naive
- version of the actual functionality provided by Hana.
- While this may seem completely useless, it is actually enough to start writing
- metafunctions that look like functions. Indeed, consider the following
- alternate implementations of `std::add_pointer` and `std::is_pointer`:
- @code{cpp}
- template <typename T>
- constexpr basic_type<T*> add_pointer(basic_type<T> const&)
- { return {}; }
- template <typename T>
- constexpr auto is_pointer(basic_type<T> const&)
- { return hana::bool_c<false>; }
- template <typename T>
- constexpr auto is_pointer(basic_type<T*> const&)
- { return hana::bool_c<true>; }
- @endcode
- We've just written metafunctions that look like functions, just like we wrote
- compile-time arithmetic metafunctions as heterogeneous C++ operators in the
- previous section. Here's how we can use them:
- @code{cpp}
- basic_type<int> t{};
- auto p = add_pointer(t);
- BOOST_HANA_CONSTANT_CHECK(is_pointer(p));
- @endcode
- Notice how we can now use a normal function call syntax to perform type-level
- computations? This is analogous to how using values for compile-time numbers
- allowed us to use normal C++ operators to perform compile-time computations.
- Like we did for `integral_constant`, we can also go one step further and use
- C++14 variable templates to provide syntactic sugar for creating types:
- @code{cpp}
- template <typename T>
- constexpr basic_type<T> type_c{};
- auto t = type_c<int>;
- auto p = add_pointer(t);
- BOOST_HANA_CONSTANT_CHECK(is_pointer(p));
- @endcode
- @note
- This is not exactly how the `hana::type_c` variable template is implemented
- because of some subtleties; things were dumbed down here for the sake of the
- explanation. Please check the reference for `hana::type` to know exactly
- what you can expect from a `hana::type_c<...>`.
- @subsection tutorial-type-benefits Benefits of this representation
- But what does that buy us? Well, since a `type_c<...>` is just an object, we
- can store it in a heterogeneous sequence like a tuple, we can move it around
- and pass it to (or return it from) functions, and we can do basically anything
- else that requires an object:
- @snippet example/tutorial/type.cpp tuple
- @note
- Writing `make_tuple(type_c<T>...)` can be annoying when there are several types.
- For this reason, Hana provides the `tuple_t<T...>` variable template, which is
- syntactic sugar for `make_tuple(type_c<T>...)`.
- Also, notice that since the above tuple is really just a normal heterogeneous
- sequence, we can apply heterogeneous algorithms on that sequence just like we
- could on a tuple of `int`s, for example. Furthermore, since we're just
- manipulating objects, we can now use the full language instead of just the
- small subset available at the type-level. For example, consider the task of
- removing all the types that are not a reference or a pointer from a sequence
- of types. With the MPL, we would have to use a placeholder expression to
- express the predicate, which is clunky:
- @snippet example/tutorial/type.cpp filter.MPL
- Now, since we're manipulating objects, we can use the full language and use a
- generic lambda instead, which leads to much more readable code:
- @snippet example/tutorial/type.cpp filter.Hana
- Since Hana handles all heterogeneous containers uniformly, this approach of
- representing types as values also has the benefit that a single library is
- now needed for both heterogeneous computations and type level computations.
- Indeed, whereas we would normally need two different libraries to perform
- almost identical tasks, we now need a single library. Again, consider the
- task of filtering a sequence with a predicate. With MPL and Fusion, this
- is what we must do:
- @snippet example/tutorial/type.cpp single_library.then
- With Hana, a single library is required. Notice how we use the same `filter`
- algorithm and the same container, and only tweak the predicate so it can
- operate on values:
- @snippet example/tutorial/type.cpp single_library.Hana
- But that is not all. Indeed, having a unified syntax for type-level and
- value-level computations allows us to achieve greater consistency in the
- interface of heterogeneous containers. For example, consider the simple
- task of creating a heterogeneous map associating types to values, and then
- accessing an element of it. With Fusion, what's happening is far from obvious
- to the untrained eye:
- @snippet example/tutorial/type.cpp make_map.Fusion
- However, with a unified syntax for types and values, the same thing becomes
- much clearer:
- @snippet example/tutorial/type.cpp make_map.Hana
- While Hana's way takes more lines of codes, it is also arguably more readable
- and closer to how someone would expect to initialize a map.
- @subsection tutorial-type-working Working with this representation
- So far, we can represent types as values and perform type-level computations
- on those objects using the usual C++ syntax. This is nice, but it is not very
- useful because we have no way to get back a normal C++ type from an object
- representation. For example, how could we declare a variable whose type is the
- result of a type computation?
- @code{cpp}
- auto t = add_pointer(hana::type_c<int>); // could be a complex type computation
- using T = the-type-represented-by-t;
- T var = ...;
- @endcode
- Right now, there is no easy way to do it. To make this easier to achieve, we
- enrich the interface of the `basic_type` container that we defined above.
- Instead of being an empty `struct`, we now define it as
- @code{cpp}
- template <typename T>
- struct basic_type {
- using type = T;
- };
- @endcode
- @note
- This is equivalent to making `basic_type` a metafunction in the MPL sense.
- This way, we can use `decltype` to easily access the actual C++ type
- represented by a `type_c<...>` object:
- @code{cpp}
- auto t = add_pointer(hana::type_c<int>);
- using T = decltype(t)::type; // fetches basic_type<T>::type
- T var = ...;
- @endcode
- In general, doing type-level metaprogramming with Hana is a three step process:
- 1. Represent types as objects by wrapping them with `hana::type_c<...>`
- 2. Perform type transformations with value syntax
- 3. Unwrap the result with `decltype(...)::%type`
- Now, you must be thinking that this is incredibly cumbersome. In reality, it
- is very manageable for several reasons. First, this wrapping and unwrapping
- only needs to happen at some very thin boundaries.
- @code{cpp}
- auto t = hana::type_c<T>;
- auto result = huge_type_computation(t);
- using Result = decltype(result)::type;
- @endcode
- Furthermore, since you get the advantage of working with objects (without
- having to wrap/unwrap) inside the computation, the cost of wrapping and
- unwrapping is amortized on the whole computation. Hence, for complex type
- computations, the syntactic noise of this three step process quickly becomes
- negligible in light of the expressiveness gain of working with values inside
- that computation. Also, using values instead of types means that we can avoid
- typing `typename` and `template` all around the place, which accounted for a
- lot of syntactic noise in classic metaprogramming.
- Another point is that the three full steps are not always required. Indeed,
- sometimes one just needs to do a type-level computation and query something
- about the result, without necessarily fetching the result as a normal C++ type:
- @code{cpp}
- auto t = hana::type_c<T>;
- auto result = type_computation(t);
- BOOST_HANA_CONSTANT_CHECK(is_pointer(result)); // third step skipped
- @endcode
- In this case, we were able to skip the third step because we did not need to
- access the actual type represented by `result`. In other cases, the first step
- can be avoided, like when using `tuple_t`, which has no more syntactic noise
- than any other pure type-level approach:
- @snippet example/tutorial/type.cpp skip_first_step
- For skeptical readers, let's consider the task of finding the smallest type
- in a sequence of types. This is a very good example of a short type-only
- computation, which is where we would expect the new paradigm to suffer the
- most. As you will see, things stay manageable even for small computations.
- First, let's implement it with the MPL:
- @snippet example/tutorial/type.cpp smallest.MPL
- The result is quite readable (for anyone familiar with the MPL). Let's now
- implement the same thing using Hana:
- @snippet example/tutorial/type.cpp smallest.Hana
- As you can witness, the syntactic noise of the 3-step process is almost
- completely hidden by the rest of the computation.
- @subsection tutorial-type-lifting The generic lifting process
- The first type-level computation that we introduced in the form of a function
- looked like:
- @code{cpp}
- template <typename T>
- constexpr auto add_pointer(hana::basic_type<T> const&) {
- return hana::type_c<T*>;
- }
- @endcode
- While it looks more complicated, we could also write it as:
- @code{cpp}
- template <typename T>
- constexpr auto add_pointer(hana::basic_type<T> const&) {
- return hana::type_c<typename std::add_pointer<T>::type>;
- }
- @endcode
- However, this implementation emphasizes the fact that we're really emulating
- an existing metafunction and simply representing it as a function. In other
- words, we're _lifting_ a metafunction (`std::add_pointer`) to the world of
- values by creating our own `add_pointer` function. It turns out that this
- lifting process is a generic one. Indeed, given any metafunction, we could
- write almost the same thing:
- @code{cpp}
- template <typename T>
- constexpr auto add_const(hana::basic_type<T> const&)
- { return hana::type_c<typename std::add_const<T>::type>; }
- template <typename T>
- constexpr auto add_volatile(hana::basic_type<T> const&)
- { return hana::type_c<typename std::add_volatile<T>::type>; }
- template <typename T>
- constexpr auto add_lvalue_reference(hana::basic_type<T> const&)
- { return hana::type_c<typename std::add_lvalue_reference<T>::type>; }
- // etc...
- @endcode
- This mechanical transformation is easy to abstract into a generic lifter
- that can handle any [MPL Metafunction][MPL.metafunction] as follows:
- @snippet example/tutorial/type.cpp metafunction1
- More generally, we'll want to allow metafunctions with any number of
- arguments, which brings us to the following less naive implementation:
- @snippet example/tutorial/type.cpp metafunction2
- Hana provides a similar generic metafunction lifter called `hana::metafunction`.
- One small improvement is that `hana::metafunction<F>` is a function object
- instead of an overloaded function, so one can pass it to higher-order
- algorithms. It is also a model of the slightly more powerful concept of
- `Metafunction`, but this can safely be ignored for now. The process we
- explored in this section does not only apply to metafunctions; it also
- applies to templates. Indeed, we could define:
- @snippet example/tutorial/type.cpp template_
- Hana provides a generic lifter for templates named `hana::template_`, and it
- also provides a generic lifter for [MPL MetafunctionClasses][MPL.mfc] named
- `hana::metafunction_class`. This gives us a way to uniformly represent "legacy"
- type-level computations as functions, so that any code written using a classic
- type-level metaprogramming library can almost trivially be used with Hana. For
- example, say you have a large chunk of MPL-based code and you'd like to
- interface with Hana. The process of doing so is no harder than wrapping your
- metafunctions with the lifter provided by Hana:
- @code{cpp}
- template <typename T>
- struct legacy {
- using type = ...; // something you really don't want to mess with
- };
- auto types = hana::make_tuple(...);
- auto use = hana::transform(types, hana::metafunction<legacy>);
- @endcode
- However, note that not all type-level computations can be lifted as-is with
- the tools provided by Hana. For example, `std::extent` can't be lifted because
- it requires non-type template parameters. Since there is no way to deal with
- non-type template parameters uniformly in C++, one must resort to using a
- hand-written function object specific to that type-level computation:
- @snippet example/tutorial/type.cpp extent
- @note
- Do not forget to include the bridge header for `std::integral_constant`s
- (`<boost/hana/ext/std/integral_constant.hpp>`) when using type traits from
- `<type_traits>` directly.
- In practice, however, this should not be a problem since the vast majority
- of type-level computations can be lifted easily. Finally, since metafunctions
- provided by the `<type_traits>` header are used so frequently, Hana provides
- a lifted version for every one of them. Those lifted traits are in the
- `hana::traits` namespace, and they live in the `<boost/hana/traits.hpp>` header:
- @snippet example/tutorial/type.cpp traits
- This is the end of the section on type computations. While this new paradigm
- for type level programming might be difficult to grok at first, it will make
- more sense as you use it more and more. You will also come to appreciate how
- it blurs the line between types and values, opening new exciting possibilities
- and simplifying many tasks.
- @note
- Curious or skeptical readers should consider checking the minimal
- reimplementation of the MPL presented in the appendices.
- @section tutorial-introspection Introspection
- ------------------------------------------------------------------------------
- Static introspection, as we will discuss it here, is the ability of a program
- to examine the type of an object at compile-time. In other words, it is a
- programmatic interface to interact with types at compile-time. For example,
- have you ever wanted to check whether some unknown type has a member named
- `foo`? Or perhaps at some point you have needed to iterate on the members
- of a `struct`?
- @code{cpp}
- struct Person {
- std::string name;
- int age;
- };
- Person john{"John", 30};
- for (auto& member : john)
- std::cout << member.name << ": " << member.value << std::endl;
- // name: John
- // age: 30
- @endcode
- If you have written a bit of templates in your life, chances are very high
- that you came across the first problem of checking for a member. Also, anyone
- having tried to implement object serialization or even just pretty printing
- has come across the second problem. In most dynamic languages like Python,
- Ruby or JavaScript, these problems are completely solved and introspection is
- used every day by programmers to make a lot of tasks simpler. However, as a
- C++ programmer, we do not have language support for those things, which makes
- several tasks much harder than they should be. While language support would
- likely be needed to properly tackle this problem, Hana makes some common
- introspection patterns much more accessible.
- @subsection tutorial-introspection-is_valid Checking expression validity
- Given an object of an unknown type, it is sometimes desirable to check
- whether this object has a member (or member function) with some name.
- This can be used to perform sophisticated flavors of overloading. For
- example, consider the problem of calling a `toString` method on objects
- that support it, but providing another default implementation for objects
- that do not support it:
- @code{cpp}
- template <typename T>
- std::string optionalToString(T const& obj) {
- if (obj.toString() is a valid expression)
- return obj.toString();
- else
- return "toString not defined";
- }
- @endcode
- @note
- While most use cases for this technique will be addressed by [concepts lite]
- [C++17.clite] in future revisions of the standard, there will still be cases
- where a quick and dirty check is more convenient than creating a full blown
- concept.
- How could we implement a check for the validity of `obj.toString()` as above
- in a generic fashion (so it can be reused in other functions, for example)?
- Normally, we would be stuck writing some kind of SFINAE-based detection:
- @snippet example/tutorial/introspection.cpp has_toString.then
- This works, but the intent is not very clear and most people without a deep
- knowledge of template metaprogramming would think this is black magic. Then,
- we could implement `optionalToString` as
- @code{cpp}
- template <typename T>
- std::string optionalToString(T const& obj) {
- if (has_toString<T>::value)
- return obj.toString();
- else
- return "toString not defined";
- }
- @endcode
- @note
- Of course, this implementation won't actually work because both branches of
- the `if` statement will be compiled. If `obj` does not have a `toString`
- method, the compilation of the `if` branch will fail. We will address
- this issue in a moment.
- Instead of the above SFINAE trick, Hana provides a `is_valid` function that
- can be combined with [C++14 generic lambdas][C++14.glambda] to obtain a much
- cleaner implementation of the same thing:
- @snippet example/tutorial/introspection.cpp has_toString.now
- This leaves us with a function object `has_toString` which returns whether the
- given expression is valid on the argument we pass to it. The result is returned
- as an `IntegralConstant`, so `constexpr`-ness is not an issue here because the
- result of the function is represented as a type anyway. Now, in addition to
- being less verbose (that's a one liner!), the intent is much clearer. Other
- benefits are the fact that `has_toString` can be passed to higher order
- algorithms and it can also be defined at function scope, so there is no need
- to pollute the namespace scope with implementation details. Here is how we
- would now write `optionalToString`:
- @code{cpp}
- template <typename T>
- std::string optionalToString(T const& obj) {
- if (has_toString(obj))
- return obj.toString();
- else
- return "toString not defined";
- }
- @endcode
- Much cleaner, right? However, as we said earlier, this implementation won't
- actually work because both branches of the `if` always have to be compiled,
- regardless of whether `obj` has a `toString` method. There are several
- possible options, but the most classical one is to use `std::enable_if`:
- @snippet example/tutorial/introspection.cpp optionalToString.then
- @note
- We're using the fact that `has_toString` returns an `IntegralConstant` to
- write `decltype(...)::%value`, which is a constant expression. For some
- reason, `has_toString(obj)` is not considered a constant expression, even
- though I think it should be one because we never read from `obj` (see the
- section on [advanced constexpr](@ref tutorial-appendix-constexpr)).
- While this implementation is perfectly valid, it is still pretty cumbersome
- because it requires writing two different functions and going through the
- hoops of SFINAE explicitly by using `std::enable_if`. However, as you might
- remember from the section on [compile-time branching](@ref tutorial-integral-branching),
- Hana provides an `if_` function that can be used to emulate the functionality
- of [static_if][N4461]. Here is how we could write `optionalToString` with
- `hana::if_`:
- @snippet example/tutorial/introspection.cpp optionalToString
- Now, the previous example covered only the specific case of checking for the
- presence of a non-static member function. However, `is_valid` can be used to
- detect the validity of almost any kind of expression. For completeness, we
- now present a list of common use cases for validity checking along with how
- to use `is_valid` to implement them.
- @subsubsection tutorial-introspection-is_valid-non_static Non-static members
- The first idiom we'll look at is checking for the presence of a non-static
- member. We can do it in a similar way as we did for the previous example:
- @snippet example/tutorial/introspection.cpp non_static_member_from_object
- Notice how we cast the result of `x.member` to `void`? This is to make sure
- that our detection also works for types that can't be returned from functions,
- like array types. Also, it is important to use a reference as the parameter to
- our generic lambda, because that would otherwise require `x` to be
- [CopyConstructible][], which is not what we're trying to check. This approach
- is simple and the most convenient when an object is available. However, when
- the checker is intended to be used with no object around, the following
- alternate implementation can be better suited:
- @snippet example/tutorial/introspection.cpp non_static_member_from_type
- This validity checker is different from what we saw earlier because the
- generic lambda is not expecting an usual object anymore; it is now expecting
- a `type` (which is an object, but still represents a type). We then use the
- `hana::traits::declval` _lifted metafunction_ from the `<boost/hana/traits.hpp>`
- header to create an rvalue of the type represented by `t`, which we can then
- use to check for a non-static member. Finally, instead of passing an actual
- object to `has_member` (like `Foo{}` or `Bar{}`), we now pass a `type_c<...>`.
- This implementation is ideal for when no object is lying around.
- @subsubsection tutorial-introspection-is_valid-static Static members
- Checking for a static member is easy, and it is provided for completeness:
- @snippet example/tutorial/introspection.cpp static_member
- Again, we expect a `type` to be passed to the checker. Inside the generic
- lambda, we use `decltype(t)::%type` to fetch the actual C++ type represented
- by the `t` object, as explained in the section on [type computations]
- (@ref tutorial-type-working). Then, we fetch the static member inside
- that type and cast it to `void`, for the same reason as we did for non-static
- members.
- @subsubsection tutorial-introspection-is_valid-nested-typename Nested type names
- Checking for a nested type name is not hard, but it is slightly more
- convoluted than the previous cases:
- @snippet example/tutorial/introspection.cpp nested_type_name
- One might wonder why we use `-> hana::type<typename-expression>` instead
- of simply `-> typename-expression`. Again, the reason is that we want to
- support types that can't be returned from functions, like array types or
- incomplete types.
- @subsubsection tutorial-introspection-is_valid-nested-template Nested templates
- Checking for a nested template name is similar to checking for a nested type
- name, except we use the `template_<...>` variable template instead of
- `type<...>` in the generic lambda:
- @snippet example/tutorial/introspection.cpp nested_template
- @subsubsection tutorial-introspection-is_valid-template Template specializations
- Checking whether a template specialization is valid can be done too, but we
- now pass a `template_<...>` to `is_valid` instead of a `type<...>`, because
- that's what we want to make the check on:
- @snippet example/tutorial/introspection.cpp template_specialization
- @note
- Doing this will not cause the template to be instantiated. Hence, it will only
- check whether the given template can be mentioned with the provided template
- arguments, not whether the instantiation of the template with those arguments
- is valid. Generally speaking, there is no way to check that programmatically.
- @subsection tutorial-introspection-sfinae Taking control of SFINAE
- Doing something only if an expression is well-formed is a very common pattern
- in C++. Indeed, the `optionalToString` function is just one instance of the
- following pattern, which is very general:
- @code{cpp}
- template <typename T>
- auto f(T x) {
- if (some expression involving x is well-formed)
- return something involving x;
- else
- return something else;
- }
- @endcode
- To encapsulate this pattern, Hana provides the `sfinae` function, which allows
- executing an expression, but only if it is well-formed:
- @snippet example/tutorial/introspection.sfinae.cpp maybe_add
- Here, we create a `maybe_add` function, which is simply a generic lambda
- wrapped with Hana's `sfinae` function. `maybe_add` is a function which takes
- two inputs and returns `just` the result of the generic lambda if that call
- is well-formed, and `nothing` otherwise. `just(...)` and `nothing` both belong
- to a type of container called `hana::optional`, which is essentially a
- compile-time `std::optional`. All in all, `maybe_add` is morally equivalent
- to the following function returning a `std::optional`, except that the check
- is done at compile-time:
- @code{cpp}
- auto maybe_add = [](auto x, auto y) {
- if (x + y is well formed)
- return std::optional<decltype(x + y)>{x + y};
- else
- return std::optional<???>{};
- };
- @endcode
- It turns out that we can take advantage of `sfinae` and `optional` to
- implement the `optionalToString` function as follows:
- @snippet example/tutorial/introspection.sfinae.cpp optionalToString.sfinae
- First, we wrap `toString` with the `sfinae` function. Hence, `maybe_toString`
- is a function which either returns `just(x.toString())` if that is well-formed,
- or `nothing` otherwise. Secondly, we use the `.value_or()` function to extract
- the optional value from the container. If the optional value is `nothing`,
- `.value_or()` returns the default value given to it; otherwise, it returns the
- value inside the `just` (here `x.toString()`). This way of seeing SFINAE as a
- special case of computations that might fail is very clean and powerful,
- especially since `sfinae`'d functions can be combined through the
- `hana::optional` `Monad`, which is left to the reference documentation.
- @subsection tutorial-introspection-adapting Introspecting user-defined types
- Have you ever wanted to iterate over the members of a user-defined type? The
- goal of this section is to show you how Hana can be used to do it quite easily.
- To allow working with user-defined types, Hana defines the `Struct` concept.
- Once a user-defined type is a model of that concept, one can iterate over the
- members of an object of that type and query other useful information. To turn
- a user-defined type into a `Struct`, a couple of options are available. First,
- you may define the members of your user-defined type with the
- `BOOST_HANA_DEFINE_STRUCT` macro:
- @snippet example/tutorial/introspection.adapt.cpp BOOST_HANA_DEFINE_STRUCT
- This macro defines two members (`name` and `age`) with the given types. Then,
- it defines some boilerplate inside a `Person::hana` nested `struct`, which is
- required to make `Person` a model of the `Struct` concept. No constructors are
- defined (so [POD-ness][POD] is retained), the members are defined in the same
- order as they appear here and the macro can be used with template `struct`s
- just as well, and at any scope. Also note that you are free to add more
- members to the `Person` type after or before you use the macro. However,
- only members defined with the macro will be picked up when introspecting the
- `Person` type. Easy enough? Now, a `Person` can be accessed programmatically:
- @snippet example/tutorial/introspection.adapt.cpp for_each
- Iteration over a `Struct` is done as if the `Struct` was a sequence of pairs,
- where the first element of a pair is the key associated to a member, and the
- second element is the member itself. When a `Struct` is defined through the
- `BOOST_HANA_DEFINE_STRUCT` macro, the key associated to any member is a
- compile-time `hana::string` representing the name of that member. This is why
- the function used with `for_each` takes a single argument `pair`, and then
- uses `first` and `second` to access the subparts of the pair. Also, notice
- how the `to<char const*>` function is used on the name of the member? This
- converts the compile-time string to a `constexpr char const*` so it can
- `cout`ed. Since it can be annoying to always use `first` and `second` to
- fetch the subparts of the pair, we can also use the `fuse` function to wrap
- our lambda and make it a binary lambda instead:
- @snippet example/tutorial/introspection.adapt.cpp for_each.fuse
- Now, it looks much cleaner. As we just mentioned, `Struct`s are seen as a kind
- of sequence of pairs for the purpose of iteration. In fact, a `Struct` can
- even be searched like an associative data structure whose keys are the names
- of the members, and whose values are the members themselves:
- @snippet example/tutorial/introspection.adapt.cpp at_key
- @note
- The `_s` user-defined literal creates a compile-time `hana::string`. It is
- located in the `boost::hana::literals` namespace. Note that it is not part
- of the standard yet, but it is supported by Clang and GCC. If you want to
- stay 100% standard, you can use the `BOOST_HANA_STRING` macro instead.
- The main difference between a `Struct` and a `hana::map` is that a map can be
- modified (keys can be added and removed), while a `Struct` is immutable.
- However, you can easily convert a `Struct` into a `hana::map` with `to<map_tag>`,
- and then you can manipulate it in a more flexible way.
- @snippet example/tutorial/introspection.adapt.cpp to<map_tag>
- Using the `BOOST_HANA_DEFINE_STRUCT` macro to adapt a `struct` is convenient,
- but sometimes one can't modify the type that needs to be adapted. In these
- cases, the `BOOST_HANA_ADAPT_STRUCT` macro can be used to adapt a `struct` in
- a ad-hoc manner:
- @snippet example/tutorial/introspection.adapt.cpp BOOST_HANA_ADAPT_STRUCT
- @note
- The `BOOST_HANA_ADAPT_STRUCT` macro must be used at global scope.
- The effect is exactly the same as with the `BOOST_HANA_DEFINE_STRUCT` macro,
- except you do not need to modify the type you want to adapt, which is
- sometimes useful. Finally, it is also possible to define custom accessors
- by using the `BOOST_HANA_ADAPT_ADT` macro:
- @snippet example/tutorial/introspection.adapt.cpp BOOST_HANA_ADAPT_ADT
- This way, the names used to access the members of the `Struct` will be those
- specified, and the associated function will be called on the `Struct` when
- retrieving that member. Before we move on to a concrete example of using these
- introspection features, it should also be mentioned that `struct`s can be
- adapted without using macros. This advanced interface for defining `Struct`s
- can be used for example to specify keys that are not compile-time strings.
- The advanced interface is described in the documentation of the `Struct`
- concept.
- @subsection tutorial-introspection-json Example: generating JSON
- Let's now move on with a concrete example of using the introspection
- capabilities we just presented for printing custom objects as JSON.
- Our end goal is to have something like this:
- @snippet example/tutorial/introspection.json.cpp usage
- And the output, after passing it through a JSON pretty-printer,
- should look like
- @code{.json}
- [
- {
- "name": "John",
- "last_name": "Doe",
- "age": 30
- },
- {
- "brand": "Audi",
- "model": "A4"
- },
- {
- "brand": "BMW",
- "model": "Z3"
- }
- ]
- @endcode
- First, let's define a couple of utility functions to make string manipulation
- easier:
- @snippet example/tutorial/introspection.json.cpp utilities
- The `quote` and the `to_json` overloads are pretty self-explanatory. The
- `join` function, however, might need a bit of explanation. Basically, the
- `intersperse` function takes a sequence and a separator, and returns a new
- sequence with the separator in between each pair of elements of the original
- sequence. In other words, we take a sequence of the form `[x1, ..., xn]` and
- turn it into a sequence of the form `[x1, sep, x2, sep, ..., sep, xn]`.
- Finally, we fold the resulting sequence with the `_ + _` function object,
- which is equivalent to `std::plus<>{}`. Since our sequence contains
- `std::string`s (we assume it does), this has the effect of concatenating
- all the strings of the sequence into one big string. Now, let's define
- how to print a `Sequence`:
- @snippet example/tutorial/introspection.json.cpp Sequence
- First, we use the `transform` algorithm to turn our sequence of objects into
- a sequence of `std::string`s in JSON format. Then, we join that sequence with
- commas and we enclose it with `[]` to denote a sequence in JSON notation.
- Simple enough? Let's now take a look at how to print user-defined types:
- @snippet example/tutorial/introspection.json.cpp Struct
- Here, we use the `keys` method to retrieve a `tuple` containing the names of
- the members of the user-defined type. Then, we `transform` that sequence into
- a sequence of `"name" : member` strings, which we then `join` and enclose with
- `{}`, which is used to denote objects in JSON notation. And that's it!
- @section tutorial-containers Generalities on containers
- ------------------------------------------------------------------------------
- This section explains several important notions about Hana's containers: how
- to create them, the lifetime of their elements and other concerns.
- @subsection tutorial-containers-creating Container creation
- While the usual way of creating an object in C++ is to use its constructor,
- heterogeneous programming makes things a bit more complicated. Indeed, in
- most cases, one is not interested in (or even aware of) the actual type of
- the heterogeneous container to be created. At other times, one could write
- out that type explicitly, but it would be redundant or cumbersome to do so.
- For this reason, Hana uses a different approach borrowed from `std::make_tuple`
- to create new containers. Much like one can create a `std::tuple` with
- `std::make_tuple`, a `hana::tuple` can be created with `hana::make_tuple`.
- However, more generally, containers in Hana may be created with the `make`
- function:
- @snippet example/tutorial/containers.cpp make<tuple_tag>
- In fact, `make_tuple` is just a shortcut for `make<tuple_tag>` so you don't
- have to type `boost::hana::make<boost::hana::tuple_tag>` when you are out of
- Hana's namespace. Simply put, `make<...>` is is used all around the library
- to create different types of objects, thus generalizing the `std::make_xxx`
- family of functions. For example, one can create a `hana::range` of
- compile-time integers with `make<range_tag>`:
- @snippet example/tutorial/containers.cpp make<range_tag>
- > These types with a trailing `_tag` are dummy types __representing__ a family
- > of heterogeneous containers (`hana::tuple`, `hana::map`, etc..). Tags are
- > documented in the section on [Hana's core](@ref tutorial-core-tags).
- For convenience, whenever a component of Hana provides a `make<xxx_tag>`
- function, it also provides the `make_xxx` shortcut to reduce typing. Also, an
- interesting point that can be raised in this example is the fact that `r` is
- `constexpr`. In general, whenever a container is initialized with constant
- expressions only (which is the case for `r`), that container may be marked
- as `constexpr`.
- So far, we have only created containers with the `make_xxx` family of
- functions. However, some containers do provide constructors as part of
- their interface. For example, one can create a `hana::tuple` just like
- one would create a `std::tuple`:
- @snippet example/tutorial/containers.cpp tuple_constructor
- When constructors (or any member function really) are part of the public
- interface, they will be documented on a per-container basis. However,
- in the general case, one should not take for granted that a container
- can be constructed as the tuple was constructed above. For example,
- trying to create a `hana::range` that way will __not__ work:
- @code{.cpp}
- hana::range<???> xs{hana::int_c<3>, hana::int_c<10>};
- @endcode
- In fact, we can't even specify the type of the object we'd like to create in
- that case, because the exact type of a `hana::range` is implementation-defined,
- which brings us to the next section.
- @subsection tutorial-containers-types Container types
- The goal of this section is to clarify what can be expected from the types of
- Hana's containers. Indeed, so far, we always let the compiler deduce the
- actual type of containers by using the `make_xxx` family of functions along
- with `auto`. But in general, what can we say about the type of a container?
- @snippet example/tutorial/containers.cpp types
- The answer is that it depends. Some containers have well defined types, while
- others do not specify their representation. In this example, the type of the
- object returned by `make_tuple` is well-defined, while the type returned by
- `make_range` is implementation-defined:
- @snippet example/tutorial/containers.cpp types_maximally_specified
- This is documented on a per-container basis; when a container has an
- implementation-defined representation, a note explaining exactly what
- can be expected from that representation is included in the container's
- description. There are several reasons for leaving the representation of
- a container unspecified; they are explained in the
- [rationales](@ref tutorial-rationales-container_representation).
- When the representation of a container is implementation-defined, one must
- be careful not to make any assumptions about it, unless those assumption
- are explicitly allowed in the documentation of the container. For example,
- assuming that one can safely inherit from a container or that the elements
- in the container are stored in the same order as specified in its template
- argument list is generally not safe.
- @subsubsection tutorial-containers-types-overloading Overloading on container types
- While necessary, leaving the type of some containers unspecified makes some
- things very difficult to achieve, like overloading functions on heterogeneous
- containers:
- @code{cpp}
- template <typename T>
- void f(std::vector<T> xs) {
- // ...
- }
- template <typename ...???>
- void f(unspecified-range-type<???> r) {
- // ...
- }
- @endcode
- The `is_a` utility is provided for this reason (and others). `is_a` allows
- checking whether a type is a precise kind of container using its tag,
- regardless of the actual type of the container. For example, the above
- example could be rewritten as
- @snippet example/tutorial/containers.cpp overloading
- This way, the second overload of `f` will only match when `R` is a type whose
- tag is `range_tag`, regardless of the exact representation of that range. Of
- course, `is_a` can be used with any kind of container: `tuple`, `map`, `set`
- and so on.
- @subsection tutorial-containers-elements Container elements
- In Hana, containers own their elements. When a container is created, it makes
- a _copy_ of the elements used to initialize it and stores them inside the
- container. Of course, unnecessary copies are avoided by using move semantics.
- Because of those owning semantics, the lifetime of the objects inside the
- container is the same as that of the container.
- @snippet example/tutorial/containers.cpp lifetime
- Much like containers in the standard library, containers in Hana expect their
- elements to be objects. For this reason, references _may not_ be stored in
- them. When references must be stored inside a container, one should use a
- `std::reference_wrapper` instead:
- @snippet example/tutorial/containers.cpp reference_wrapper
- @section tutorial-algorithms Generalities on algorithms
- ------------------------------------------------------------------------------
- Much like the previous section introduced general but important notions about
- heterogeneous containers, this section introduces general notions about
- heterogeneous algorithms.
- @subsection tutorial-algorithms-value By-value semantics
- Algorithms in Hana always return a new container holding the result. This
- allows one to easily chain algorithms by simply using the result of the first
- as the input of the second. For example, to apply a function to every element
- of a tuple and then reverse the result, one simply has to connect the `reverse`
- and `transform` algorithms:
- @snippet example/tutorial/algorithms.cpp reverse_transform
- This is different from the algorithms of the standard library, where one has
- to provide iterators to the underlying sequence. For reasons documented in the
- [rationales](@ref tutorial-rationales-iterators), an iterator-based design was
- considered but was quickly dismissed in favor of composable and efficient
- abstractions better suited to the very particular context of heterogeneous
- programming.
- One might also think that returning full sequences that own their elements
- from an algorithm would lead to tons of undesirable copies. For example, when
- using `reverse` and `transform`, one could think that an intermediate copy is
- made after the call to `transform`:
- @snippet example/tutorial/algorithms.cpp reverse_transform_copy
- To make sure this does not happen, Hana uses perfect forwarding and move
- semantics heavily so it can provide an almost optimal runtime performance.
- So instead of doing a copy, a move occurs between `reverse` and `transform`:
- @snippet example/tutorial/algorithms.cpp reverse_transform_move
- Ultimately, the goal is that code written using Hana should be equivalent to
- clever hand-written code, except it should be enjoyable to write. Performance
- considerations are explained in depth in their own [section]
- (@ref tutorial-performance).
- @subsection tutorial-algorithms-laziness (Non-)Laziness
- Algorithms in Hana are not lazy. When an algorithm is called, it does its
- job and returns a new sequence containing the result, end of the story.
- For example, calling the `permutations` algorithm on a large sequence is
- a stupid idea, because Hana will actually compute all the permutations:
- @code{cpp}
- auto perms = hana::permutations(hana::make_tuple(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
- // perms has 3 628 800 elements, and your compiler just crashed
- @endcode
- To contrast, algorithms in Boost.Fusion return views which hold the original
- sequence by reference and apply the algorithm on demand, as the elements of
- the sequence are accessed. This leads to subtle lifetime issues, like having
- a view that refers to a sequence that was destroyed. Hana's design assumes
- that most of the time, we want to access all or almost all the elements in a
- sequence anyway, and hence performance is not a big argument in favor of
- laziness.
- @subsection tutorial-algorithms-codegen What is generated?
- Algorithms in Hana are a bit special with respect to the runtime code they are
- expanded into. The goal of this subsection is not to explain exactly what code
- is generated, which depends on the compiler anyway, but to give a feel for
- things. Basically, a Hana algorithm is like an unrolled version of an
- equivalent classical algorithm. Indeed, since the bounds of the processed
- sequence are known at compile-time, it makes sense that we can unroll the
- loop over the sequence. For example, let's consider the `for_each` algorithm:
- @code{cpp}
- auto xs = hana::make_tuple(0, 1, 2, 3);
- hana::for_each(xs, f);
- @endcode
- If `xs` was a runtime sequence instead of a tuple, its length would only be
- known at runtime and the above code would have to be implemented as a loop:
- @code{cpp}
- for (int i = 0; i < xs.size(); ++i) {
- f(xs[i]);
- }
- @endcode
- However, in our case, the length of the sequence is known at compile-time and
- so we don't have to check the index at each iteration. Hence, we can just
- write:
- @code{cpp}
- f(xs[0_c]);
- f(xs[1_c]);
- f(xs[2_c]);
- f(xs[3_c]);
- @endcode
- The main difference here is that no bound checking and index increment is done
- at each step, because there is no index anymore; the loop was effectively
- unrolled. In some cases, this can be desirable for performance reasons. In
- other cases, this can be detrimental to performance because it causes the
- code size to grow. As always, performance is a tricky subject and whether
- you actually want loop unrolling to happen should be tackled on a case-by-case
- basis. As a rule of thumb, algorithms processing all (or a subset) of the
- elements of a container are unrolled. In fact, if you think about it, this
- unrolling is the only way to go for heterogeneous sequences, because different
- elements of the sequence may have different types. As you might have noticed,
- we're not using normal indices into the tuple, but compile-time indices, which
- can't be generated by a normal `for` loop. In other words, the following does
- not make sense:
- @code{cpp}
- for (??? i = 0_c; i < xs.size(); ++i) {
- f(xs[i]);
- }
- @endcode
- @subsection tutorial-algorithms-effects Side effects and purity
- By default, Hana assumes functions to be pure. A pure function is a function
- that has no side-effects at all. In other words, it is a function whose effect
- on the program is solely determined by its return value. In particular, such a
- function may not access any state that outlives a single invocation of the
- function. These functions have very nice properties, like the ability to
- reason mathematically about them, to reorder or even eliminate calls, and
- so on. Except where specified otherwise, all functions used with Hana (i.e.
- used in higher order algorithms) should be pure. In particular, functions
- passed to higher order algorithms are not guaranteed to be called any
- specific number of times. Furthermore, the order of execution is generally
- not specified and should therefore not be taken for granted. If this lack of
- guarantees about function invocations seems crazy, consider the following use
- of the `any_of` algorithm:
- @snippet example/tutorial/algorithms.cpp effects
- @note
- For this to work, the external adapters for `std::integral_constant` contained
- in `<boost/hana/ext/std/integral_constant.hpp>` must be included.
- According to the previous section on unrolling, this algorithm should be
- expanded into something like:
- @code{cpp}
- auto xs = hana::make_tuple("hello"s, 1.2, 3);
- auto pred = [](auto x) { return std::is_integral<decltype(x)>{}; };
- auto r = hana::bool_c<
- pred(xs[0_c]) ? true :
- pred(xs[1_c]) ? true :
- pred(xs[2_c]) ? true :
- false
- >;
- BOOST_HANA_CONSTANT_CHECK(r);
- @endcode
- Of course, the above code can't work as-is, because we're calling `pred` inside
- something that would have to be a constant expression, but `pred` is a lambda
- (and lambdas can't be called in constant expressions). However, whether any of
- these objects has an integral type is clearly known at compile-time, and hence
- we would expect that computing the answer only involves compile-time
- computations. In fact, this is exactly what Hana does, and the above
- algorithm is expanded into something like:
- @snippet example/tutorial/algorithms.cpp effects.codegen
- @note
- As you will be able to deduce from the next section on cross-phase computations,
- the implementation of `any_of` must actually be more general than this. However,
- this [lie-to-children][] is perfect for educational purposes.
- As you can see, the predicate is never even executed; only its result type on
- a particular object is used. Regarding the order of evaluation, consider the
- `transform` algorithm, which is specified (for tuples) as:
- @code{cpp}
- hana::transform(hana::make_tuple(x1, ..., xn), f) == hana::make_tuple(f(x1), ..., f(xn))
- @endcode
- Since `make_tuple` is a function, and since the evaluation order for the
- arguments of a function is unspecified, the order in which `f` is called
- on each element of the tuple is unspecified too. If one sticks to pure
- functions, everything works fine and the resulting code is often easier
- to understand. However, some exceptional algorithms like `for_each` do
- expect impure functions, and they guarantee an order of evaluation. Indeed,
- a `for_each` algorithm that would only take pure functions would be pretty
- much useless. When an algorithm can accept an impure function or guarantees
- some order of evaluation, the documentation for that algorithm will mention
- it explicitly. However, by default, no guarantees may be taken for granted.
- @subsection tutorial-algorithms-cross_phase Cross-phase algorithms
- This section introduces the notion of cross-phase computations and algorithms.
- In fact, we have already used cross-phase algorithms in the [quick start]
- (@ref tutorial-quickstart), for example with `filter`, but we did not explain
- exactly what was happening at that time. But before we introduce cross-phase
- algorithms, let's define what we mean by _cross-phase_. The phases we're
- referring to here are the compilation and the execution of a program. In C++
- as in most statically typed languages, there is a clear distinction between
- compile-time and runtime; this is called phase distinction. When we speak of
- a cross-phase computation, we mean a computation that is somehow performed
- across those phases; i.e. that is partly executed at compile-time and partly
- executed at runtime.
- Like we saw in earlier examples, some functions are able to return something
- that can be used at compile-time even when they are called on a runtime value.
- For example, let's consider the `length` function applied to a non-`constexpr`
- container:
- @snippet example/tutorial/algorithms.cpp cross_phase.setup
- Obviously, the tuple can't be made `constexpr`, since it contains runtime
- `std::string`s. Still, even though it is not called on a constant expression,
- `length` returns something that can be used at compile-time. If you think of
- it, the size of the tuple is known at compile-time regardless of its content,
- and hence it would only make sense for this information to be available to us
- at compile-time. If that seems surprising, think about `std::tuple` and
- `std::tuple_size`:
- @snippet example/tutorial/algorithms.cpp cross_phase.std::tuple_size
- Since the size of the tuple is encoded in its type, it is always available
- at compile-time regardless of whether the tuple is `constexpr` or not. In Hana,
- this is implemented by having `length` return an `IntegralConstant`. Since an
- `IntegralConstant`'s value is encoded in its type, the result of `length` is
- contained in the type of the object it returns, and the length is therefore
- known at compile-time. Because `length` goes from a runtime value (the
- container) to a compile-time value (the `IntegralConstant`), `length` is a
- trivial example of a cross-phase algorithm (trivial because it does not really
- manipulate the tuple). Another algorithm that is very similar to `length` is
- the `is_empty` algorithm, which returns whether a container is empty:
- @snippet example/tutorial/algorithms.cpp cross_phase.is_empty
- More generally, any algorithm that takes a container whose value is known at
- runtime but queries something that can be known at compile-time should be able
- to return an `IntegralConstant` or another similar compile-time value. Let's
- make things slightly more complicated by considering the `any_of` algorithm,
- which we already encountered in the previous section:
- @snippet example/tutorial/algorithms.cpp cross_phase.any_of_runtime
- In this example, the result can't be known at compile-time, because the
- predicate returns a `bool` that is the result of comparing two `std::string`s.
- Since `std::string`s can't be compared at compile-time, the predicate must
- operate at runtime, and the overall result of the algorithm can then only be
- known at runtime too. However, let's say we used `any_of` with the following
- predicate instead:
- @snippet example/tutorial/algorithms.cpp cross_phase.any_of_compile_time
- @note
- For this to work, the external adapters for `std::integral_constant` contained
- in `<boost/hana/ext/std/integral_constant.hpp>` must be included.
- First, since the predicate is only querying information about the type of each
- element of the tuple, it is clear that its result can be known at compile-time.
- Since the number of elements in the tuple is also known at compile-time, the
- overall result of the algorithm can, in theory, be known at compile-time. More
- precisely, what happens is that the predicate returns a value initialized
- `std::is_same<...>`, which inherits from `std::integral_constant`. Hana
- recognizes these objects, and the algorithm is written in such a way that it
- preserves the `compile-time`ness of the predicate's result. In the end,
- `any_of` hence returns an `IntegralConstant` holding the result of the
- algorithm, and we use the compiler's type deduction in a clever way to make
- it look easy. Hence, it would be equivalent to write (but then you would need
- to already know the result of the algorithm!):
- @snippet example/tutorial/algorithms.cpp cross_phase.any_of_explicit
- Ok, so some algorithms are able to return compile-time values when their input
- satisfies some constraints with respect to `compile-time`ness. However, other
- algorithms are more restrictive and they _require_ their inputs to satisfy some
- constraints regarding `compile-time`ness, without which they are not able to
- operate at all. An example of this is `filter`, which takes a sequence and a
- predicate, and returns a new sequence containing only those elements for which
- the predicate is satisfied. `filter` requires the predicate to return an
- `IntegralConstant`. While this requirement may seem stringent, it really makes
- sense if you think about it. Indeed, since we're removing some elements from
- the heterogeneous sequence, the type of the resulting sequence depends on the
- result of the predicate. Hence, the result of the predicate has to be known at
- compile-time for the compiler to be able to assign a type to the returned
- sequence. For example, consider what happens when we try to filter a
- heterogeneous sequence as follows:
- @code{cpp}
- auto animals = hana::make_tuple(Fish{"Nemo"}, Cat{"Garfield"}, Dog{"Snoopy"});
- auto no_garfield = hana::filter(animals, [](auto animal) {
- return animal.name != "Garfield"s;
- });
- @endcode
- Clearly, we know that the predicate will only return false on the second
- element, and hence the result _should be_ a `[Fish, Dog]` tuple. However,
- the compiler has no way of knowing this since the predicate's result is the
- result of a runtime computation, which happens way after the compiler has
- finished its job. Hence, the compiler does not have enough information to
- determine the return type of the algorithm. However, we could `filter` the
- same sequence with any predicate whose result is available at compile-time:
- @snippet example/tutorial/algorithms.cpp cross_phase.filter
- Since the predicate returns an `IntegralConstant`, we know which elements
- of the heterogeneous sequence we'll be keeping at compile-time. Hence, the
- compiler is able to figure out the return type of the algorithm. Other
- algorithms like `partition` and `sort` work similarly; special algorithm
- requirements are always documented, just read the reference documentation
- of an algorithm before using it to avoid surprises.
- This is the end of the section on algorithms. While this constitutes a fairly
- complete explanation of phase interaction inside algorithms, a deeper
- understanding can be gained by reading the [advanced section]
- (@ref tutorial-appendix-constexpr) on `constexpr` and the reference
- for `Constant` and `IntegralConstant`.
- @warning
- Hana's algorithms are `constexpr` function objects instead of being template
- functions. This allows passing them to higher-order algorithms, which is very
- useful. However, since those function objects are defined at namespace scope
- in the header files, this causes each translation unit to see a different
- algorithm object. Hence, the address of an algorithm function object is not
- guaranteed to be unique across translation units, which can cause an ODR
- violation if one relies on such an address. So, in short, do not rely on the
- uniqueness of the address of any global object provided by Hana, which does
- not make sense in the general case anyway because such objects are `constexpr`.
- See [issue #76](https://github.com/boostorg/hana/issues/76) for more information.
- @section tutorial-performance Performance considerations
- ------------------------------------------------------------------------------
- C++ programmers love performance, so here's a whole section dedicated to it.
- Since Hana lives on the frontier between runtime and compile-time computations,
- we are not only interested in runtime performance, but also compile-time
- performance. Since both topics are pretty much disjoint, we treat them
- separately below.
- @note
- The benchmarks presented in this section are updated automatically when we
- push to the repository. If you notice results that do not withstand the
- claims made here, open a [GitHub issue][Hana.issues]; it could be a
- performance regression.
- @warning
- As of writing this, not all of Hana's containers are optimized. Implementing
- Hana was a big enough challenge that containers were initially written naively
- and are now in the process of being rigorously optimized. In particular, the
- associative containers (`hana::map` and `hana::set`) have a pretty bad
- compile-time behavior because of their naive implementation, and their runtime
- behavior also seems to be problematic in some cases. Improving this situation
- is in the TODO list.
- @subsection tutorial-performance-compile Compile-time performance
- C++ metaprogramming brings its share of awful things. One of the most annoying
- and well-known problem associated to it is interminable compilation times.
- Hana claims to be more compile-time efficient than its predecessors; this is
- a bold claim and we will now try to back it. Of course, Hana can't do miracles;
- metaprogramming is a byproduct of the C++ template system and the compiler is
- not meant to be used as an interpreter for some meta language. However, by
- using cutting edge and intensely benchmarked techniques, Hana is able to
- minimize the strain on the compiler.
- @note
- While Hana has better compile-times than pre-C++11 metaprogramming libraries,
- modern libraries supporting only type-level computations (such as [Brigand][])
- can provide better compile-times, at the cost of generality. Indeed, Hana's
- ability to manipulate runtime values comes at a compile-time cost, no matter
- how hard we try to mitigate it. If you want to use Hana for intensive type-level
- computations, you should benchmark and see whether it suits you.
- Before we dive, let me make a quick note on the methodology used to measure
- compile-time performance in Hana. Previous metaprogramming libraries measured
- the compile-time complexity of their meta-algorithms and meta-sequences by
- looking at the number of instantiations the compiler had to perform. While
- easy to understand, this way of measuring the compile-time complexity actually
- does not give us a lot of information regarding the compilation time, which
- is what we're interested in minimizing at the end of the day. Basically, the
- reason for this is that template metaprogramming is such a twisted model of
- computation that it's very hard to find a standard way of measuring the
- performance of algorithms. Hence, instead of presenting meaningless complexity
- analyses, we prefer to benchmark everything on every supported compiler and to
- pick the best implementation on that compiler. Also note that the benchmarks
- we present here are quite precise. Indeed, even though we do not take multiple
- measurements and take their mean or something similar to reduce incertitude,
- the benchmarks are very stable when they are regenerated, which suggests a
- reasonably good precision. Now, let's dive.
- First, Hana minimizes its dependency on the preprocessor. In addition to
- yielding cleaner error messages in many cases, this reduces the overall
- parsing and preprocessing time for header files. Also, because Hana only
- supports cutting edge compilers, there are very few workarounds in the
- library, which results in a cleaner and smaller library. Finally, Hana
- minimizes reliance on any kind of external dependencies. In particular,
- it only uses other Boost libraries in a few specific cases, and it does
- not rely on the standard library for the largest part. There are several
- reasons (other than include times) for doing so; they are documented in
- the [rationales](@ref tutorial-rationales-dependencies).
- Below is a chart showing the time required to include different libraries. The
- chart shows the time for including everything in the (non-external) public API
- of each library. For example, for Hana this means the `<boost/hana.hpp>` header,
- which excludes the external adapters. For other libraries like Boost.Fusion,
- this means including all the public headers in the `boost/fusion/` directory,
- but not the adapters for external libraries like the MPL.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.including.compile.json">
- </div>
- In addition to reduced preprocessing times, Hana uses modern techniques to
- implement heterogeneous sequences and algorithms in the most compile-time
- efficient way possible. Before jumping to the compile-time performance of
- the algorithms, we will have a look at the compile-time cost of creating
- heterogeneous sequences. Indeed, since we will be presenting algorithms that
- work on sequences, we must be aware of the cost of creating the sequences
- themselves, since that will influence the benchmarks for the algorithms.
- The following chart presents the compile-time cost of creating a sequence
- of `n` heterogeneous elements.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.make.compile.json">
- </div>
- @note
- You can zoom on the chart by selecting an area to zoom into. Also, you can
- hide a series of points by clicking on it in the legend on the right.
- The benchmark methodology is to always create the sequences in the most
- efficient way possible. For Hana and `std::tuple`, this simply means using
- the appropriate `make_tuple` function. However, for the MPL, this means
- creating a `mpl::vectorN` of size up to 20, and then using `mpl::push_back`
- to create larger vectors. We use a similar technique for Fusion sequences.
- The reason for doing so is that Fusion and MPL sequences have fixed size
- limits, and the techniques used here have been found to be the fastest way
- to create longer sequences.
- For completeness, we also present the compile-time cost of creating a
- `std::array` with `n` elements. However, note that `std::array` can only
- hold elements with a single type, so we're comparing apples and oranges
- here. As you can see, the cost of creating a `std::array` is constant and
- essentially inexistent (the non-zero overhead is that of simply including
- the `<array>` header). Hence, while Hana provides improved compile-times
- over other heterogeneous containers, please stick with normal homogeneous
- containers if that's all you need for your application; your compile-times
- will be much faster that way.
- You can also see that creating sequences has a non-negligible cost. Actually,
- this is really the most expensive part of doing heterogeneous computations,
- as you will see in the following charts. Hence, when you look at the charts
- below, keep in mind the cost of merely creating the sequences. Also note that
- only the most important algorithms will be presented here, but the [Metabench][]
- project provides micro benchmarks for compile-time performance for almost all
- of Hana's algorithms. Also, the benchmarks we present compare several different
- libraries. However, since Hana and Fusion can work with values and not only
- types, comparing their algorithms with type-only libraries like MPL is not
- really fair. Indeed, Hana and Fusion algorithms are more powerful since they
- also allow runtime effects to be performed. However, the comparison between
- Fusion and Hana is fair, because both libraries are just as powerful (strictly
- speaking). Finally, we can't show benchmarks of the algorithms for `std::tuple`,
- because the standard does not provide equivalent algorithms. Of course, we
- could use Hana's external adapters, but that would not be a faithful comparison.
- The first algorithm which is ubiquitous in metaprogramming is `transform`.
- It takes a sequence and a function, and returns a new sequence containing the
- result of applying the function to each element. The following chart presents
- the compile-time performance of applying `transform` to a sequence of `n`
- elements. The `x` axis represents the number of elements in the sequence, and
- the `y` axis represents the compilation time in seconds. Also note that we're
- using the `transform` equivalent in each library; we're not using Hana's
- `transform` through the Boost.Fusion adapters, for example, because we really
- want to benchmark their implementation against ours.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.transform.compile.json">
- </div>
- Here, we can see that Hana's tuple performs better than all the other
- alternatives. This is mainly due to the fact that we use C++11 variadic
- parameter pack expansion to implement this algorithm under the hood, which
- is quite efficient.
- Before we move on, it is important to mention something regarding the benchmark
- methodology for Fusion algorithms. Some algorithms in Fusion are lazy, which
- means that they don't actually perform anything, but simply return a modified
- view to the original data. This is the case of `fusion::transform`, which
- simply returns a transformed view that applies the function to each element
- of the original sequence as those elements are accessed. If we want to
- benchmark anything at all, we need to force the evaluation of that view, as
- would eventually happen when accessing the elements of the sequence in real
- code. However, for complex computations with multiple layers, a lazy approach
- may yield a substantially different compile-time profile. Of course, this
- difference is poorly represented in micro benchmarks, so keep in mind that
- these benchmarks only give a part of the big picture. For completeness in the
- rest of the section, we will mention when a Fusion algorithm is lazy, so that
- you know when we're _artificially_ forcing the evaluation of the algorithm for
- the purpose of benchmarking.
- @note
- We are currently considering adding lazy views to Hana. If this feature is
- important to you, please let us know by commenting
- [this issue](https://github.com/boostorg/hana/issues/193).
- The second important class of algorithms are folds. Folds can be used to
- implement many other algorithms like `count_if`, `minimum` and so on.
- Hence, a good compile-time performance for fold algorithms ensures a good
- compile-time performance for those derived algorithms, which is why we're
- only presenting folds here. Also note that all the non-monadic fold variants
- are somewhat equivalent in terms of compile-time, so we only present the left
- folds. The following chart presents the compile-time performance of applying
- `fold_left` to a sequence of `n` elements. The `x` axis represents the number
- of elements in the sequence, and the `y` axis represents the compilation time
- in seconds. The function used for folding is a dummy function that does nothing.
- In real code, you would likely fold with a nontrivial operation, so the curves
- would be worse than that. However, these are micro benchmarks and hence they
- only show the performance of the algorithm itself.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.fold_left.compile.json">
- </div>
- The third and last algorithm that we present here is the `find_if` algorithm.
- This algorithm is difficult to implement efficiently, because it requires
- stopping at the first element which satisfies the given predicate. For the
- same reason, modern techniques don't really help us here, so this algorithm
- constitutes a good test of the implementation quality of Hana, without taking
- into account the free lunch given to use by C++14.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.find_if.compile.json">
- </div>
- As you can see, Hana performs better than Fusion, and as well as MPL, yet
- Hana's `find_if` can be used with values too, unlike MPL's. This concludes
- the section on compile-time performance. In case you want to see the
- performance of an algorithm that we have not presented here, the [Metabench][]
- project provides compile-time benchmarks for most of Hana's algorithms.
- @subsection tutorial-performance-runtime Runtime performance
- Hana was designed to be very efficient at runtime. But before we dive into the
- details, let's clarify one thing. Hana being a metaprogramming library which
- allows manipulating both types and values, it does not always make sense to
- even talk about runtime performance. Indeed, for type-level computations and
- computations on `IntegralConstant`s, runtime performance is simply not a
- concern, because the result of the computation is contained in a _type_, which
- is a purely compile-time entity. In other words, these computations involve
- only compile-time work, and no code is even generated to perform these
- computations at runtime. The only case where it makes sense to discuss runtime
- performance is when manipulating runtime values in heterogeneous containers
- and algorithms, because this is the only case where the compiler has to
- generate some runtime code. It is therefore only computations of this sort
- that we will be studying in the remainder of this section.
- Like we did for compile-time benchmarks, the methodology used to measure
- runtime performance in Hana is data driven rather than analytical. In other
- words, instead of trying to determine the complexity of an algorithm by
- counting the number of basic operations it does as a function of the input
- size, we simply take measurements for the most interesting cases and see how
- it behaves. There are a couple of reasons for doing so. First, we do not
- expect Hana's algorithms to be called on large inputs since those algorithms
- work on heterogeneous sequences whose length must be known at compile-time.
- For example, if you tried to call the `find_if` algorithm on a sequence of
- 100k elements, your compiler would simply die while trying to generate the
- code for this algorithm. Hence, algorithms can't be called on very large inputs
- and the analytical approach then loses a lot of its attractiveness. Secondly,
- processors have evolved into pretty complex beasts, and the actual performance
- you'll be able to squeeze out is actually controlled by much more than the
- mere number of steps your algorithm is doing. For example, bad cache behavior
- or branch misprediction could turn a theoretically efficient algorithm into a
- slowpoke, especially for small inputs. Since Hana causes a lot of unrolling to
- happen, these factors must be considered even more carefully and any analytical
- approach would probably only comfort us into thinking we're efficient. Instead,
- we want hard data, and pretty charts to display it!
- @note
- Like for compile-time performance, we're forcing the evaluation of some Fusion
- algorithms that are normally lazy. Again, depending on the complexity of the
- computation, a lazy algorithm may cause substantially different code to be
- generated or a different design to be used, for better or worse. Keep this
- in mind when you look at these runtime benchmarks. If performance is absolutely
- critical to your application, you should profile _before_ and _after_ switching
- from Fusion to Hana. And let us know if Hana performs worse; we'll fix it!
- There are a couple of different aspects we will want to benchmark. First, we
- will obviously want to benchmark the execution time of the algorithms.
- Secondly, because of the by-value semantics used throughout the library, we
- will also want to make sure that the minimum amount of data is copied around.
- Finally, we will want to make sure that using Hana does not cause too much
- code bloat because of unrolling, as explained in the [section]
- (@ref tutorial-algorithms-codegen) on algorithms.
- Just like we studied only a couple of key algorithms for compile-time
- performance, we will focus on the runtime performance of a few algorithms.
- For each benchmarked aspect, we will compare the algorithm as implemented by
- different libraries. Our goal is to always be at least as efficient as
- Boost.Fusion, which is near from optimality in terms of runtime performance.
- For comparison, we also show the same algorithm as executed on a runtime
- sequence, and on a sequence whose length is known at compile-time but whose
- `transform` algorithm does not use explicit loop unrolling. All the benchmarks
- presented here are done in a _Release_ CMake configuration, which takes care
- of passing the proper optimization flags (usually `-O3`). Let's start with the
- following chart, which shows the execution time required to `transform`
- different kinds of sequences:
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.transform.execute.json">
- </div>
- @note
- Keep in mind that `fusion::transform` is usually lazy, and we're forcing its
- evaluation for the purpose of benchmarking.
- As you can see, Hana and Fusion are pretty much on the same line. `std::array`
- is slightly slower for larger collections data sets, and `std::vector` is
- noticeably slower for larger collections. Since we also want to look out for
- code bloat, let's take a look at the size of the executable generated for the
- exact same scenario:
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.transform.bloat.json">
- </div>
- As you can see, code bloat does not seem to be an issue, at least not one that
- can be detected in micro benchmarks such as this one. Let's now take a look at
- the `fold` algorithm, which is used very frequently:
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.fold_left.execute.json">
- </div>
- Here, you can see that everybody is performing pretty much the same, which
- is a good sign that Hana is at least not screwing things up.
- Again, let's look at the executable size:
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.fold_left.bloat.json">
- </div>
- Here again, the code size did not explode. So at least for moderate usages of
- Hana (and Fusion for that matter, since they have the same problem), code
- bloat should not be a major concern. The containers in the charts we just
- presented contain randomly generated `int`s, which is cheap to copy around and
- lends itself well to micro benchmarks. However, what happens when we chain
- multiple algorithms on a container whose elements are expensive to copy? More
- generally, the question is: when an algorithm is passed a temporary object,
- does it seize the opportunity to avoid unnecessary copies? Consider:
- @code{cpp}
- auto xs = hana::make_tuple("some"s, "huge"s, "string"s);
- // No copy of xs's elements should be made: they should only be moved around.
- auto ys = hana::reverse(std::move(xs));
- @endcode
- To answer this question, we'll look at the chart generated when benchmarking
- the above code for strings of about 1k characters. However, note that it does
- not really make sense to benchmark this for standard library algorithms,
- because they do not return containers.
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.reverse.move.json">
- </div>
- @note
- Keep in mind that `fusion::reverse` is usually lazy, and we're forcing its
- evaluation for the purpose of benchmarking.
- As you can see, Hana is faster than Fusion, probably because of a more
- consistent use of move semantics in the implementation. If we had not
- provided a temporary container to `reverse`, no move could have been
- performed by Hana and both libraries would have performed similarly:
- <div class="benchmark-chart"
- style="min-width: 310px; height: 400px; margin: 0 auto"
- data-dataset="benchmark.reverse.nomove.json">
- </div>
- This concludes the section on runtime performance. Hopefully you are now
- convinced that Hana was built for speed. Performance is important to us:
- if you ever encounter a scenario where Hana causes bad code to be generated
- (and the fault is not on the compiler), please open an [issue][Hana.issues]
- so the problem can be addressed.
- @section tutorial-ext Integration with external libraries
- ------------------------------------------------------------------------------
- Hana provides out-of-the-box integration with some existing libraries.
- Specifically, this means that you can use some containers from these
- libraries in Hana's algorithms by simply including the appropriate header
- making the bridge between Hana and the external component. This can be
- very useful for porting existing code from e.g. Fusion/MPL to Hana:
- @snippet example/tutorial/ext/fusion_to_hana.cpp main
- @note
- - At this time, only adapters to use data types from other libraries inside Hana
- are provided; adapters for the other way around (using Hana containers inside
- other libraries) are not provided.
- - The Fusion and MPL adapters are only guaranteed to work on the version of
- Boost matching the version of Hana being used.
- However, using external adapters has a couple of pitfalls. For example, after
- a while using Hana, you might become used to comparing Hana tuples using the
- normal comparison operators, or doing arithmetic with Hana `integral_constant`s.
- Of course, nothing guarantees that these operators are defined for external
- adapters too (and in general they won't be). Hence, you'll have to stick to
- the functions provided by Hana that implement these operators. For example:
- @code{cpp}
- auto r = std::ratio<3, 4>{} + std::ratio<4, 5>{}; // error, the operator is not defined!
- @endcode
- Instead, you should use the following:
- @snippet example/tutorial/ext/ratio_plus.cpp main
- But sometimes, it's much worse. Some external components define operators, but
- they don't necessarily have the same semantics as those from Hana. For example,
- comparing two `std::tuple`s of different lengths will give an error when using
- `operator==`:
- @code{cpp}
- std::make_tuple(1, 2, 3) == std::make_tuple(1, 2); // compiler error
- @endcode
- On the other hand, comparing Hana tuples of different lengths will just return
- a false `IntegralConstant`:
- @code{cpp}
- hana::make_tuple(1, 2, 3) == hana::make_tuple(1, 2); // hana::false_c
- @endcode
- This is because `std::tuple` defines its own operators, and their semantics
- are different from that of Hana's operators. The solution is to stick with
- Hana's named functions instead of using operators when you know you'll have
- to work with other libraries:
- @code{cpp}
- hana::equal(std::make_tuple(1, 2, 3), std::make_tuple(1, 2)); // hana::false_c
- @endcode
- When using external adapters, one should also be careful not to forget
- including the proper bridge headers. For example, suppose I want to use
- a Boost.MPL vector with Hana. I include the appropriate bridge header:
- @snippet example/tutorial/ext/mpl_vector.cpp front
- @note
- The exact layout of these bridge headers is documented in the section about
- [Header organization](@ref tutorial-header_organization).
- Now, however, suppose that I use `mpl::size` to query the size of the vector
- and then compare it to some value. I could also use `hana::length` and
- everything would be fine, but bear with me for the sake of the example:
- @snippet example/tutorial/ext/mpl_vector.cpp size
- The reason why this breaks is that `mpl::size` returns a MPL IntegralConstant,
- and Hana has no way of knowing about these unless you include the proper
- bridge header. Hence, you should do the following instead:
- @snippet example/tutorial/ext/mpl_vector.cpp size-fixed
- The morale is that when working with external libraries, you have to be a bit
- careful about what objects you are manipulating. The final pitfall is about
- implementation limits in external libraries. Many older libraries have limits
- regarding the maximum size of the heterogeneous containers that can be created
- with them. For example, one may not create a Fusion list of more than
- `FUSION_MAX_LIST_SIZE` elements in it. Obviously, these limits are inherited
- by Hana and for example, trying to compute the permutations of a `fusion::list`
- containing 5 elements (the resulting list would contain 120 elements) will
- fail in a gruesome way:
- @code{cpp}
- auto list = fusion::make_list(1, 2, 3, 4, 5);
- auto oh_jeez = hana::permutations(list); // probably won't make it
- @endcode
- Apart from the pitfalls explained in this section, using external adapters
- should be just as straightforward as using normal Hana containers. Of course,
- whenever possible, you should try to stick with Hana's containers because they
- are usually more friendly to work with and are often more optimized.
- @section tutorial-core Hana's core
- ------------------------------------------------------------------------------
- The goal of this section is to give a high-level overview of Hana's core.
- This core is based on the notion of _tag_, which is borrowed from the
- Boost.Fusion and Boost.MPL libraries but taken much further by Hana. These
- tags are then used for several purposes, like algorithm customization,
- documentation grouping, improving error messages and converting containers
- into other containers. Because of its modular design, Hana can be extended
- in a ad-hoc manner very easily. In fact, all the functionality of the library
- is provided through an ad-hoc customization mechanism, which is explained here.
- @subsection tutorial-core-tags Tags
- Heterogeneous programming is basically programming with objects having
- different types. However, it is clear that some families of objects, while
- having different representations (C++ types), are strongly related. For
- example, the `std::integral_constant<int, n>` types are different for each
- different `n`, but conceptually they all represent the same thing; a
- compile-time number. The fact that `std::integral_constant<int, 1>{}` and
- `std::integral_constant<int, 2>{}` have different types is just a side effect
- of the fact that we're using their type to encode the _value_ of these objects.
- Indeed, when manipulating a sequence of `std::integral_constant<int, ...>`s,
- chances are that you actually think of it as a homogeneous sequence of an
- imaginary `integral_constant` type, disregarding the actual types of the
- objects and pretending they are all just `integral_constant`s with different
- values.
- To reflect this reality, Hana provides _tags_ representing its heterogeneous
- containers and other compile-time entities. For example, all of Hana's
- `integral_constant<int, ...>`s have different types, but they all share
- the same tag, `integral_constant_tag<int>`. This allows the programmer to
- think in terms of that single type instead of trying to think in terms of the
- actual types of the objects. Concretely, tags are implemented as empty `struct`s.
- To make them stand out, Hana adopts the convention of naming these tags by
- adding the `_tag` suffix.
- @note
- The tag of an object of type `T` can be obtained by using `tag_of<T>::%type`,
- or equivalently `tag_of_t<T>`.
- Tags are an extension to normal C++ types. Indeed, by default, the tag of a
- type `T` is `T` itself, and the core of the library is designed to work in
- those cases. For example, `hana::make` expects either a tag or an actual type;
- if you send it a type `T`, it will do the logical thing and construct an
- object of type `T` with the arguments you pass it. If you pass a tag to it,
- however, you should specialize `make` for that tag and provide your own
- implementation, as explained below. Because tags are an extension to usual
- types, we end up mostly reasoning in terms of tags instead of usual types,
- and the documentation sometimes uses the words _type_, _data type_ and _tag_
- interchangeably.
- @subsection tutorial-core-tag_dispatching Tag dispatching
- Tag dispatching is a generic programming technique for picking the right
- implementation of a function depending on the type of the arguments passed
- to the function. The usual mechanism for overriding a function's behavior
- is overloading. Unfortunately, this mechanism is not always convenient when
- dealing with families of related types having different base templates, or
- when the kind of template parameters is not known (is it a type or a non-type
- template parameter?). For example, consider trying to overload a function for
- all Boost.Fusion vectors:
- @code{cpp}
- template <typename ...T>
- void function(boost::fusion::vector<T...> v) {
- // whatever
- }
- @endcode
- If you know Boost.Fusion, then you probably know that it won't work. This is
- because Boost.Fusion vectors are not necessarily specializations of the
- `boost::fusion::vector` template. Fusion vectors also exist in numbered
- forms, which are all of different types:
- @code{cpp}
- boost::fusion::vector1<T>
- boost::fusion::vector2<T, U>
- boost::fusion::vector3<T, U, V>
- ...
- @endcode
- This is an implementation detail required by the lack of variadic templates in
- C++03 that leaks into the interface. This is unfortunate, but we need a way to
- work around it. To do so, we use an infrastructure with three distinct
- components:
- 1. A metafunction associating a single tag to every type in a family of
- related types. In Hana, this tag can be accessed using the `tag_of`
- metafunction. Specifically, for any type `T`, `tag_of<T>::%type` is
- the tag used to dispatch it.
- 2. A function belonging to the public interface of the library, for which
- we'd like to be able to provide a customized implementation. In Hana,
- these functions are the algorithms associated to a concept, like
- `transform` or `unpack`.
- 3. An implementation for the function, parameterized with the tag(s) of the
- argument(s) passed to the function. In Hana, this is usually done by having
- a separate template called `xxx_impl` (for an interface function `xxx`)
- with a nested `apply` static function, as will be shown below.
- When the public interface function `xxx` is called, it will get the tag of the
- argument(s) it wishes to dispatch the call on, and then forward the call to
- the `xxx_impl` implementation associated to those tags. For example, let's
- implement a basic setup for tag dispatching of a function that prints its
- argument to a stream. First, we define the public interface function and the
- implementation that can be specialized:
- @snippet example/tutorial/tag_dispatching.cpp setup
- Now, let's define a type that needs tag dispatching to customize the behavior
- of `print`. While some C++14 examples exist, they are too complicated to show
- in this tutorial and we will therefore use a C++03 tuple implemented as several
- different types to illustrate the technique:
- @snippet example/tutorial/tag_dispatching.cpp vector
- The nested `using hana_tag = vector_tag;` part is a terse way of controling
- the result of the `tag_of` metafunction, and hence the tag of the `vectorN`
- type. This is explained in the reference for `tag_of`. Finally, if you wanted
- to customize the behavior of the `print` function for all the `vectorN` types,
- you would normally have to write something along the lines of
- @snippet example/tutorial/tag_dispatching.cpp old_way
- Now, with tag dispatching, you can rely on the `vectorN`s all sharing the same
- tag and specialize only the `print_impl` struct instead:
- @snippet example/tutorial/tag_dispatching.cpp customize
- One upside is that all `vectorN`s can now be treated uniformly by the `print`
- function, at the cost of some boilerplate when creating the data structure
- (to specify the tag of each `vectorN`) and when creating the initial `print`
- function (to setup the tag dispatching system with `print_impl`). There are
- also other advantages to this technique, like the ability to check for
- preconditions in the interface function without having to do it in each
- custom implementation, which would be tedious:
- @snippet example/tutorial/tag_dispatching.cpp preconditions
- @note
- Checking preconditions does not make much sense for a `print` function, but
- consider for example a function to get the `n`th element of a sequence; you
- might want to make sure that the index is not out-of-bounds.
- This technique also makes it easier to provide interface functions as function
- objects instead of normal overloaded functions, because only the interface
- function itself must go through the trouble of defining a function object.
- Function objects have several advantages over overloaded functions, like the
- ability to be used in higher order algorithms or as variables:
- @snippet example/tutorial/tag_dispatching.cpp function_objects
- As you are probably aware of, being able to implement an algorithm for many
- types at the same time is tremendously useful (that's precisely the goal of
- C++ templates!). However, even more useful is the ability to implement an
- algorithm for many types _that satisfy some condition_. C++ templates are
- currently missing this ability to constrain their template parameters, but a
- language feature called [concepts][C++17.clite] is being rolled out with the
- goal of addressing this issue.
- With something similar in mind, Hana's algorithms support an additional layer
- of tag-dispatching to what was explained above. This layer allows us to
- "specialize" an algorithm for all types that satisfy some predicate. For
- example, let's say we wanted to implement the `print` function above for all
- types that represent some kind of sequence. Right now, we wouldn't have an
- easy way to do this. However, the tag dispatching for Hana's algorithms is
- set up slightly differently than what was shown above, and we could hence
- write the following:
- @snippet example/tutorial/tag_dispatching.cpp customize-when
- where `Tag represents some kind of sequence` would only need to be a boolean
- expression representing whether `Tag` is a sequence. We'll see how such
- predicates can be created in the next section, but for now let's assume that
- it _just works_. Without going into the details of how this tag-dispatching is
- set up, the above specialization will only be picked up when the predicate is
- satisfied, and if no better match can be found. Hence, for example, if our
- `vector_tag` was to satisfy the predicate, our initial implementation for
- `vector_tag` would still be preferred over the `hana::when`-based specialization,
- because it represents a better match. In general, any specialization (whether
- explicit or partial) _not_ using `hana::when` will be preferred over a
- specialization using `hana::when`, which was designed to be as unsurprising
- as possible from a user point of view. This covers pretty much all there's
- to say about tag-dispatching in Hana. The next section will explain how we
- can create C++ concepts for metaprogramming, which could then be used in
- conjunction with `hana::when` to achieve a great deal of expressiveness.
- @subsection tutorial-core-concepts Emulation of C++ concepts
- The implementation of concepts in Hana is very simple. At its heart, a concept
- is just a template `struct` that inherits from a boolean `integral_constant`
- representing whether the given type is a _model_ of the concept:
- @code{cpp}
- template <typename T>
- struct Concept
- : hana::integral_constant<bool, whether T models Concept>
- { };
- @endcode
- Then, one can test whether a type `T` is a model of `Concept` by looking at
- `Concept<T>::%value`. Simple enough, right? Now, while the way one might
- implement the check does not have to be anything specific as far as Hana
- is concerned, the rest of this section will explain how it is usually done
- in Hana, and how it interacts with tag dispatching. You should then be able
- to define your own concepts if you so desire, or at least to understand better
- how Hana works internally.
- Usually, a concept defined by Hana will require that any model implements some
- tag-dispatched functions. For example, the `Foldable` concept requires that
- any model defines at least one of `hana::unpack` and `hana::fold_left`. Of
- course, concepts usually also define semantic requirements (called laws) that
- must be satisfied by their models, but these laws are not (and couldn't be)
- checked by the concept. But how do we check that some functions are properly
- implemented? For this, we'll have to slightly modify the way we defined
- tag-dispatched methods as shown in the previous section. Let's go back to
- our `print` example and try to define a `Printable` concept for those objects
- that can be `print`ed. Our end goal is to have a template struct such as
- @code{cpp}
- template <typename T>
- struct Printable
- : hana::integral_constant<bool, whether print_impl<tag of T> is defined>
- { };
- @endcode
- To know whether `print_impl<...>` has been defined, we'll modify `print_impl`
- so that it inherits from a special base class when it is not overridden, and
- we'll simply check whether `print_impl<T>` inherits from that base class:
- @snippet example/tutorial/concepts.cpp special_base_class
- Of course, when we specialize `print_impl` with a custom type, we don't
- inherit from that `special_base_class` type:
- @snippet example/tutorial/concepts.cpp special_base_class_customize
- As you can see, `Printable<T>` really only checks whether the `print_impl<T>`
- struct was specialized by a custom type. In particular, it does not even check
- whether the nested `::%apply` function is defined or if it is syntactically
- valid. It is assumed that if one specializes `print_impl` for a custom type,
- the nested `::%apply` function exists and is correct. If it is not, a compilation
- error will be triggered when one tries to call `print` on an object of that type.
- Concepts in Hana make the same assumptions.
- Since this pattern of inheriting from a special base class is quite abundant
- in Hana, the library provides a dummy type called `hana::default_` that can be
- used in place of `special_base_class`. Then, instead of using `std::is_base_of`,
- one can use `hana::is_default`, which looks nicer. With this syntactic sugar,
- the code now becomes:
- @snippet example/tutorial/concepts.cpp actual
- This is all that there's to know about the interaction between tag-dispatched
- functions and concepts. However, some concepts in Hana do not rely solely on
- the definition of specific tag-dispatched functions to determine if a type is
- a model of the concept. This can happen when a concept merely introduces
- semantic guarantees through laws and refined concepts, but no additional
- syntactic requirements. Defining such a concept can be useful for several
- reasons. First, it sometimes happen that an algorithm can be implemented
- more efficiently if we can assume some semantic guarantees X or Y, so we
- might create a concept to enforce those guarantees. Secondly, it is sometimes
- possible to automatically define the models for several concepts when we have
- additional semantic guarantees, which saves the user the trouble of defining
- those models manually. For example, this is the case of the `Sequence` concept,
- which basically adds semantic guarantees to `Iterable` and `Foldable`, and in
- turn allows us to define the models for a myriad of concepts ranging from
- `Comparable` to `Monad`.
- For these concepts, it is usually necessary to specialize the corresponding
- template struct in the `boost::hana` namespace to provide a model for a custom
- type. Doing so is like providing a seal saying that the semantic guarantees
- required by the concept are respected by the custom type. The concepts that
- require being explicitly specialized will document that fact. So that's it!
- This is all that there's to know about concepts in Hana, which ends this
- section about the core of Hana.
- @section tutorial-header_organization Header organization
- ------------------------------------------------------------------------------
- The library is designed to be modular while keeping the number of headers that
- must be included to get basic functionality reasonably low. The structure of
- the library was also intentionally kept simple, because we all love simplicity.
- What follows is a general overview of the header organization. A list of all
- the headers provided by the library is also available in the panel on the left
- (under the [Headers](files.html) label) in case you need more details.
- - `boost/hana.hpp`\n
- This is the master header of the library, which includes the whole public
- interface of the library. Note that external adapters, experimental features
- and implementation details are not included by this header, however, since
- some of them require additional dependencies.
- - `boost/hana/`\n
- This is the main directory of the library containing the definitions of
- everything provided by the library. Each algorithm and container provided
- by the library has its own header. For a container or an algorithm named
- `XXX`, the corresponding header is `boost/hana/XXX.hpp`.
- - `boost/hana/concept/`\n
- This subdirectory contains the definition of Hana's concepts. These
- headers provide a way to check whether an object is a model of the
- corresponding concept, and they sometimes also provide default
- implementations for other related concepts, which are documented
- on a per-concept basis. They also include all the algorithms associated
- to that concept.
- - `boost/hana/core/`\n
- This subdirectory contains the machinery for tag dispatching and other
- related utilities like `make` and `to`.
- - `boost/hana/fwd/`\n
- This subdirectory contains the forward declaration of everything in the
- library. It is essentially a mirror of the `boost/hana/` directory, except
- all the headers contain only forward declarations and documentation. For
- example, to include the `hana::tuple` container, one can use the
- `boost/hana/tuple.hpp` header. However, if one only wants the
- forward declaration of that container, the `boost/hana/fwd/tuple.hpp`
- header can be used instead. Note that forward declarations for headers
- in `boost/hana/ext/` and `boost/hana/functional/` are not provided.
- - `boost/hana/functional/`\n
- This subdirectory contains various function objects that are often useful,
- but that do not necessarily belong to a concept.
- - `boost/hana/ext/`\n
- This directory contains adapters for external libraries. For a component
- named `xxx` in a namespace `ns`, the external adapter lives in the
- `boost/hana/ext/ns/xxx.hpp` header. For example, the external adapter
- for `std::tuple` lives in the `boost/hana/ext/std/tuple.hpp` header,
- while the external adapter for `boost::mpl::vector` is in
- `boost/hana/ext/boost/mpl/vector.hpp`.
- Note that only the strict minimum required to adapt the external components
- is included in these headers (e.g. a forward declaration). This means that
- the definition of the external component should still be included when one
- wants to use it. For example:
- @snippet example/tutorial/include_ext.cpp main
- - `boost/hana/experimental/`\n
- This directory contains experimental features that may or may not make it
- into the library at some point, but that were deemed useful enough to be
- made available to the public. Features in this subdirectory reside in the
- `hana::experimental` namespace. Also, do not expect these features to be
- stable; they may be moved, renamed, changed or removed between releases of
- the library. These features may also require additional external dependencies;
- each feature documents the additional dependencies it requires, if any.
- Because of the potential additional dependencies, these headers are also
- not included by the master header of the library.
- - `boost/hana/detail/`\n
- This directory contains utilities required internally. Nothing in `detail/`
- is guaranteed to be stable, so you should not use it.
- @section tutorial-conclusion Conclusion
- ------------------------------------------------------------------------------
- You now have everything you need to start using the library. From this point
- forward, mastering the library is only a matter of understanding how to use
- the general purpose concepts and containers provided with it, which is best
- done by looking at the reference documentation. At some point, you will
- probably also want to create your own concepts and data types that fit your
- needs better; go ahead, the library was designed to be used that way.
- @subsection tutorial-conclusion-warning Fair warning: functional programming ahead
- Programming with heterogeneous objects is inherently functional -- since it is
- impossible to modify the type of an object, a new object must be introduced
- instead, which rules out mutation. Unlike previous metaprogramming libraries
- whose design was modeled on the STL, Hana uses a functional style of
- programming which is the source for a good portion of its expressiveness.
- However, as a result, many concepts presented in the reference will be
- unfamiliar to C++ programmers without a knowledge of functional programming.
- The reference attempts to make these concepts approachable by using intuition
- whenever possible, but bear in mind that the highest rewards are usually the
- fruit of some effort.
- @subsection tutorial-conclusion-related_material Related material
- Through the years, I have produced some material about Hana and metaprogramming
- more generally. You may find some of it useful:
- - Keynote on metaprogramming at [Meeting C++][] 2016 ([slides](http://ldionne.com/meetingcpp-2016)/[video](https://youtu.be/X_p9X5RzBJE))
- - Talk on advanced metaprogramming techniques used in Hana at [C++Now][] 2016 ([slides](http://ldionne.com/cppnow-2016-metaprogramming-for-the-brave)/[video](https://youtu.be/UXwWXHrvTug))
- - Introduction to metaprogramming with Hana at [C++Now][] 2016 ([slides](http://ldionne.com/cppnow-2016-metaprogramming-for-dummies)/[video](https://youtu.be/a1doqFAumCk))
- - Talk on the [MPL11][] library at [C++Now][] 2014. This is how Hana started out. ([slides](http://ldionne.com/mpl11-cppnow-2014)/[video](https://youtu.be/8c0aWLuEO0Y))
- - My bachelor's thesis was a formalization of C++ metaprogramming using category
- theory. The thesis is available [here](https://github.com/ldionne/hana-thesis/blob/gh-pages/main.pdf),
- and the slides of a related presentation are available [here](http://ldionne.com/hana-thesis).
- Unfortunately, both are in french only.
- The complete list of talks I've done on Hana and metaprogramming is [here][ldionne.talks].
- There is also an unofficial translation of Hana's documentation to Chinese
- available [here](https://github.com/freezestudio/hana.zh).
- @subsection tutorial-conclusion-projects_using_hana Projects using Hana
- There is a growing number of projects using Hana. It can be useful to look
- at them to get a sense of how to best use the library. Here's a few of those
- projects ([open an issue][Hana.issues] if you want your project to be listed
- here):
- - [Dyno](https://github.com/ldionne/dyno): A policy-based type erasure library.
- Uses Hana for vtable generation and concept map emulation under the hood.
- - [yap](https://github.com/tzlaine/yap): An expression template library built
- on top of Hana.
- - [NBDL](https://github.com/ricejasonf/nbdl): Library for managing application
- state across network. Uses Hana for some things under the hood.
- - [ECST](https://github.com/SuperV1234/ecst): An experimental multithreaded
- compile-time entity-component system using Hana under the hood for a few
- things.
- This finishes the tutorial part of the documentation. I hope you enjoy using
- the library, and please consider [contributing][Hana.contributing] to make it
- even better!
- -- Louis
- @section tutorial-reference Using the reference
- ------------------------------------------------------------------------------
- As for most generic libraries, algorithms in Hana are documented by the
- concept to which they belong (`Foldable`, `Iterable`, `Searchable`, `Sequence`,
- etc...). The different containers are then documented on their own page, and
- the concepts that they model are documented there. The concepts modeled by
- some container defines what algorithms can be used with such a container.
- More specifically, the structure of the reference (available in the menu to
- the left) goes as follow:
- - @ref group-core \n
- Documentation for the core module, which contains everything needed to
- create concepts, data types and related utilities. This is relevant
- if you need to extend the library, but otherwise you can probably
- ignore this.
- - @ref group-concepts \n
- Documentation for all the concepts provided with the library. Each concept:
- - Documents which functions must be implemented absolutely in order to
- model that concept. The set of functions that must be provided is called
- a _minimal complete definition_.
- - Documents semantic constraints that any model of that concept must satisfy.
- These constraints are usually called laws and they are expressed in a
- semi-formal mathematical language. Of course, those laws can't be checked
- automatically but you should still make sure you satisfy them.
- - Documents the concept(s) it refines, if any. Sometimes, a concept is
- powerful enough to provide a model of a concept it refines, or at least
- the implementation for some of its associated functions. When this is the
- case, the concept will document which functions of the refined concept it
- provides, and how it does so. Also, it is sometimes possible that the
- model for a refined concept is unique, in which case it can be provided
- automatically. When this happens, it will be documented but you don't have
- to do anything special to get that model.
- - @ref group-datatypes \n
- Documentation for all the data structures provided with the library. Each
- data structure documents the concept(s) it models, and how it does so. It
- also documents the methods tied to it but not to any concept, for example
- `maybe` for `optional`.
- - @ref group-functional \n
- General purpose function objects that are generally useful in a purely
- functional setting. These are currently not tied to any concept or container.
- - @ref group-ext \n
- Documentation for all the adapters for external libraries. These adapters
- are documented as if they were native types provided by Hana, but obviously
- Hana only provides the compatibility layer between them and the library.
- - @ref group-config \n
- Macros that can be used to tweak the global behavior of the library.
- - @ref group-assertions \n
- Macros to perform various types of assertions.
- - [<b>Alphabetical index</b>](functions.html)\n
- Alphabetical index of everything provided in the library.
- - [<b>Headers</b>](files.html)\n
- A list of all the headers provided by the library.
- - @ref group-details \n
- Implementation details; don't go there. Anything not documented at all or
- documented in this group is not guaranteed to be stable.
- After you get to know Hana a bit better, it will probably happen that you just
- want to find the reference for a precise function, concept or container. If
- you know the name of what you're looking for, you can use the _search_ box
- located in the upper right corner of any page of the documentation. My
- personal experience is that this is by far the quickest way of finding
- what you want when you already know its name.
- @subsection tutorial-reference-signatures Function signatures
- As you will see in the reference, several functions provide signatures
- documented in a semi-formal mathematical language. We are in the process
- of documenting all functions in this way, but this may take a while. The
- notation used is the usual mathematical notation for defining functions.
- Specifically, a function `Return f(Arg1, ..., ArgN);` can be defined
- equivalently using mathematical notation as
- @f[
- \mathtt{f} : \mathtt{Arg}_1 \times \dots \times \mathtt{Arg}_n
- \to \mathtt{Return}
- @f]
- However, instead of documenting the actual argument and return types of
- functions, those signatures are written in terms of argument and return tags.
- This is done because of the heterogeneous setting, where the actual type of
- an object is usually pretty meaningless and does not help to reason about
- what's being returned or taken by a function. For example, instead of
- documenting the `equal` function for `integral_constant`s as
- @f[
- \mathtt{equal} : \mathtt{integral\_constant<T, n>} \times
- \mathtt{integral\_constant<T, m>}
- \to \mathtt{integral\_constant<bool, n == m>}
- @f]
- which is not really helpful (as it really presents nothing but the
- implementation), it is instead documented using `integral_constant_tag`,
- which acts as the "type" of all `integral_constant`s. Note that since `equal`
- is part of the `Comparable` concept, it is not _actually_ documented for
- `hana::integral_constant` specifically, but the idea is there:
- @f[
- \mathtt{equal} : \mathtt{integral\_constant\_tag<T>} \times
- \mathtt{integral\_constant\_tag<T>}
- \to \mathtt{integral\_constant\_tag<bool>}
- @f]
- This clearly conveys the intention that comparing two `integral_constant`s
- gives back another `integral_constant` holding a `bool`. In general, this
- abstraction of the actual representation of objects makes it possible for
- us to reason in a high level manner about functions, even though their
- actual return and argument types are heterogeneous and not helpful. Finally,
- most functions expect container elements to have some properties. For example,
- this is the case of the `sort` algorithm, which obviously requires the
- container elements to be `Orderable`. Normally, we would write the signature
- for the non-predicated version of `sort` as
- @f[
- \mathtt{sort} : \mathtt{S} \to \mathtt{S} \\
- \text{where S is a Sequence}
- @f]
- However, this fails to express the requirement that the contents of `S` are
- `Orderable`. To express this, we use the following notation:
- @f[
- \mathtt{sort} : \mathtt{S(T)} \to \mathtt{S(T)} \\
- \text{where S is a Sequence and T is Orderable}
- @f]
- One way to see this is to pretend that `S`, the sequence tag, is actually
- parameterized by the tag of the sequence's elements, `T`. We're also pretending
- that the elements all have the same tag `T`, which is not the case in general.
- Now, by stating that `T` must be `Orderable`, we're expressing the fact that
- the sequence's elements must be `Orderable`. This notation is used in different
- flavors to express different kinds of requirements. For example, the
- `cartesian_product` algorithm takes a sequence of sequences and returns the
- cartesian product of those sequences as a sequence of sequences. Using our
- notation, this can be conveyed very easily:
- @f[
- \mathtt{cartesian\_product} : \mathtt{S(S(T))} \to \mathtt{S(S(T))} \\
- \text{where S is a Sequence}
- @f]
- @section tutorial-acknowledgements Acknowledgements
- ------------------------------------------------------------------------------
- I'd like to thank the following persons and organizations for contributing to
- Hana in one way or another:
- - Zach Laine and Matt Calabrese for the original idea of using function call
- syntax to do type-level computations, as presented in their BoostCon
- [presentation][video.inst_must_go] ([slides 1][slides.inst_must_go1])
- ([slides 2][slides.inst_must_go2]).
- - Joel Falcou for mentoring me two consecutive years during my work on Hana
- as part of the [Google Summer of Code][GSoC] program, Niall Douglas for
- being the GSoC admin for Boost and helping me get in the program, and
- finally Google for their awesome GSoC program.
- - The [Boost Steering committee][Boost.Steering] for unlocking a grant for me
- to work on Hana in the winter of 2015, as an extension to the previous
- year's GSoC.
- - Several [C++Now][] attendees and members of the [Boost mailing list]
- [Boost.Devel] for insightful conversations, comments and questions
- about the project.
- @section tutorial-glossary Glossary
- ------------------------------------------------------------------------------
- The reference documentation uses a couple of terms that are specific to this
- library. Also, a simplified implementation of functions is sometimes provided
- in pseudo-code, the actual implementation sometimes being slightly hard to
- understand. This section defines terms used in the reference and in the
- pseudo-code used to describe some functions.
- @anchor tutorial-glossary-forwarded
- #### `forwarded(x)`
- Means that the object is forwarded optimally. This means that if `x` is a
- parameter, it is `std::forward`ed, and if it is a captured variable, it is
- moved from whenever the enclosing lambda is an rvalue.
- Also note that when `x` can be moved from, the statement `return forwarded(x);`
- in a function with `decltype(auto)` does not mean that an rvalue reference to
- `x` will be returned, which would create a dangling reference. Rather, it
- means that `x` is returned by value, the value being constructed with the
- `std::forward`ed `x`.
- @anchor tutorial-glossary-perfect_capture
- #### `perfect-capture`
- This is used in lambdas to signify that the captured variables are
- initialized using perfect forwarding, as if `[x(forwarded(x))...]() { }`
- had been used.
- @anchor tutorial-glossary-tag_dispatched
- #### `tag-dispatched`
- This means that the documented function uses [tag dispatching]
- (@ref tutorial-core-tag_dispatching), and hence the exact
- implementation depends on the model of the concept associated
- to the function.
- @anchor tutorial-glossary-implementation_defined
- #### `implementation-defined`
- This expresses the fact that the exact implementation of an entity (usually a
- type) should not be relied upon by users. In particular, this means that one
- can not assume anything beyond what is written explicitly in the documentation.
- Usually, the concepts satisfied by an implementation-defined entity will be
- documented, because one could otherwise do nothing with it. Concretely,
- assuming too much about an implementation-defined entity will probably
- not kill you, but it will very probably break your code when you update
- to a newer version of Hana.
- @section tutorial-rationales Rationales/FAQ
- ------------------------------------------------------------------------------
- This section documents the rationale for some design choices. It also serves
- as a FAQ for some (not so) frequently asked questions. If you think something
- should be added to this list, open a GitHub issue and we'll consider either
- improving the documentation or adding the question here.
- @subsection tutorial-rationales-dependencies Why restrict usage of external dependencies?
- There are several reasons for doing so. First, Hana is a very fundamental
- library; we are basically reimplementing the core language and the standard
- library with support for heterogeneous types. When going through the code,
- one quickly realizes that other libraries are rarely needed, and that almost
- everything has to be implemented from scratch. Also, since Hana is very
- fundamental, there is even more incentive for keeping the dependencies
- minimal, because those dependencies will be handed down to the users.
- Regarding the minimal reliance on Boost in particular, one big argument
- for using it is portability. However, as a cutting edge library, Hana only
- targets very recent compilers. Hence, we can afford to depend on modern
- constructs and the portability given to us by using Boost would mostly
- represent dead weight.
- @subsection tutorial-rationales-iterators Why no iterators?
- Iterator based designs have their own merits, but they are also known to
- reduce the composability of algorithms. Furthermore, the context of
- heterogeneous programming brings a lot of points that make iterators much
- less interesting. For example, incrementing an iterator would have to return
- a new iterator with a different type, because the type of the new object it
- is pointing to in the sequence might be different. It also turns out that
- implementing most algorithms in terms of iterators leads to a worse
- compile-time performance, simply because the execution model of metaprogramming
- (using the compiler as an interpreter) is so different from the runtime
- execution model of C++ (a processor accessing contiguous memory).
- @subsection tutorial-rationales-container_representation Why leave some container's representation implementation-defined?
- First, it gives much more wiggle room for the implementation to perform
- compile-time and runtime optimizations by using clever representations for
- specific containers. For example, a tuple containing homogeneous objects of
- type `T` could be implemented as an array of type `T` instead, which is more
- efficient at compile-time. Secondly, and most importantly, it turns out that
- knowing the type of a _heterogeneous_ container is not as useful as you would
- think. Indeed, in the context of heterogeneous programming, the type of the
- object returned by a computation is usually part of the computation too. In
- other words, there is no way to know the type of the object returned by an
- algorithm without actually performing the algorithm. For example, consider
- the `find_if` algorithm:
- @snippet example/tutorial/rationale.container.cpp hana
- If the predicate is satisfied for some element of the tuple, result will be
- equal to `just(x)`. Otherwise, `result` will be equal to `nothing`. However,
- the `nothing`ness of the result is known at compile-time, which requires
- `just(x)` and `nothing` to have different types. Now, say you wanted to
- explicitly write the type of the result:
- @snippet example/tutorial/rationale.container.cpp hana-explicit
- In order to possess the knowledge of what `some_type` is, you would need to
- actually perform the algorithm, because `some_type` depends on whether the
- predicate is satisfied or not for some element in the container. In other
- words, if you were able to write the above, then you would already know what
- the result of the algorithm is and you would not need to perform the algorithm
- in the first place. In Boost.Fusion, this problem is addressed by having a
- separate `result_of` namespace, which contains a metafunction computing the
- result type of any algorithm given the types of the arguments passed to it.
- For example, the above example could be rewritten with Fusion as:
- @snippet example/tutorial/rationale.container.cpp fusion
- Notice that we're basically doing the computation twice; once in the `result_of`
- namespace and once in the normal `fusion` namespace, which is highly redundant.
- Before the days of `auto` and `decltype`, such techniques were necessary to
- perform heterogeneous computations. However, since the advent of modern C++,
- the need for explicit return types in the context of heterogeneous programming
- is largely obsolete, and knowing the actual type of containers is usually not
- that useful.
- @subsection tutorial-rationales-why_Hana Why Hana?
- No, it isn't the name of my girlfriend! I just needed a short and good looking
- name that people would easily remember, and Hana came up. It also came to my
- attention that Hana means _flower_ in Japanese, and _one_ in Korean. Since
- Hana is pretty and it unifies type-level and heterogeneous programming under
- a single paradigm, the name appears to be quite well chosen in retrospect :-).
- @subsection tutorial-rationales-tuple Why define our own tuple?
- Since Hana defines a lot of algorithms on tuples, a possible way to go would
- have been to simply use `std::tuple` and provide the algorithms only, instead
- of also providing our own tuple. The reason for providing our own tuple is
- principally performance. Indeed, all the `std::tuple` implementations tested
- so far have a very bad compile-time performance. Also, to get truly amazing
- compile-time performance, we need to take advantage of the tuple's internal
- representation in some algorithms, which requires defining our own. Finally,
- some sugar like `operator[]` could not be provided if we were using a
- `std::tuple`, since that operator must be defined as a member function.
- @subsection tutorial-rationales-naming How are names chosen?
- When deciding upon a name `X`, I try to balance the following things
- (in no specific order):
- - How idiomatic is `X` in C++?
- - How idiomatic is `X` in the rest of the programming world?
- - How good of a name `X` _actually is_, regardless of historical reasons
- - How do I, as the library author, feel about `X`
- - How do users of the library feel about `X`
- - Are there technical reasons not to use `X`, like name clashes or names
- reserved by the standard
- Of course, good naming is and will always be hard. Names are and will always
- be tainted by the author's own bias. Still, I try to choose names in a
- reasonable manner.
- @subsection tutorial-rationales-parameters How is the parameter order decided?
- Unlike naming, which is fairly subjective, the order of the parameters of a
- function is usually pretty straightforward to determine. Basically, the rule
- of thumb is "the container goes first". It has always been this way in Fusion
- and MPL, and this is intuitive for most C++ programmers. Also, in higher-order
- algorithms, I try to put the function parameter last, so that multi-line
- lambdas look nice:
- @code{cpp}
- algorithm(container, [](auto x) {
- return ...;
- });
- // is nicer than
- algorithm([](auto x) {
- return ...;
- }, container);
- @endcode
- @subsection tutorial-rationales-tag_dispatching Why tag dispatching?
- There are several different techniques we could have used to provide
- customization points in the library, and tag-dispatching was chosen. Why?
- First, I wanted a two-layer dispatching system because this allows functions
- from the first layer (the ones that are called by users) to actually be
- function objects, which allows passing them to higher-order algorithms.
- Using a dispatching system with two layers also allows adding some
- compile-time sanity checks to the first layer, which improves error messages.
- Now, tag-dispatching was chosen over other techniques with two layers for a
- couple of reasons. First, having to explicitly state how some tag is a model
- of a concept gives the responsibility of making sure that the semantic
- requirements of the concept are respected to the user. Secondly, when checking
- whether a type is a model of some concept, we basically check that some key
- functions are implemented. In particular, we check that the functions from the
- minimal complete definition of that concept are implemented. For example,
- `Iterable<T>` checks whether the `is_empty`, `at` and `drop_front` functions
- implemented for `T`. However, the only way to detect this without
- tag-dispatching is to basically check whether the following expressions
- are valid in a SFINAE-able context:
- @code{cpp}
- implementation_of_at(std::declval<T>(), std::declval<N>())
- implementation_of_is_empty(std::declval<T>())
- implementation_of_drop_front(std::declval<T>())
- @endcode
- Unfortunately, this requires actually doing the algorithms, which might either
- trigger a hard compile-time error or hurt compile-time performance. Also, this
- requires picking an arbitrary index `N` to call `at` with: what if the `Iterable`
- is empty? With tag dispatching, we can just ask whether `at_impl<T>`,
- `is_empty_impl<T>` and `drop_front_impl<T>` are defined, and nothing happens
- until we actually call their nested `::%apply` function.
- @subsection tutorial-rationales-zip_longest Why not provide zip_longest?
- It would require either (1) padding the shortest sequences with an arbitrary
- object, or (2) padding the shortest sequences with an object provided by the
- user when calling `zip_longest`. Since there is no requirement that all the
- zipped sequences have elements of similar types, there is no way to provide a
- single consistent padding object in all cases. A tuple of padding objects
- should be provided, but I find it perhaps too complicated to be worth it for
- now. If you need this functionality, open a GitHub issue.
- @subsection tutorial-rationales-concepts Why aren't concepts constexpr functions?
- Since the C++ concept proposal maps concepts to boolean `constexpr` functions,
- it would make sense that Hana defines its concepts as such too, instead of as
- structs with a nested `::%value`. Indeed, this was the first choice, but it
- had to be revised because template functions have one limitation that makes
- them less flexible. Specifically, a template function can't be passed to a
- higher-order metafunction. In other words, it is not possible to write the
- following
- @code{cpp}
- template <??? Concept>
- struct some_metafunction {
- // ...
- };
- @endcode
- This sort of code is very useful in some contexts, such as checking whether
- two types have a common embedding modeling a concept:
- @code{cpp}
- template <??? Concept, typename T, typename U>
- struct have_common_embedding {
- // whether T and U both model Concept, and share a common type that also models Concept
- };
- @endcode
- With concepts as boolean `constexpr` functions, this can't be written
- generically. When concepts are just template structs, however, we can
- use template template parameters:
- @code{cpp}
- template <template <typename ...> class Concept, typename T, typename U>
- struct have_common_embedding {
- // whether T and U both model Concept, and share a common type that also models Concept
- };
- @endcode
- @section tutorial-appendix-constexpr Appendix I: Advanced constexpr
- ------------------------------------------------------------------------------
- In C++, the border between compile-time and runtime is hazy, a fact that is
- even more true with the introduction of [generalized constant expressions]
- [C++14.gconstexpr] in C++14. However, being able to manipulate heterogeneous
- objects is all about understanding that border and then crossing it at one's
- will. The goal of this section is to set things straight with `constexpr`; to
- understand which problems it can solve and which ones it can't. This section
- covers advanced concepts about to constant expressions; only readers with a
- good understanding of `constexpr` should attempt to read this.
- @subsection tutorial-appendix-constexpr-stripping Constexpr stripping
- Let's start with a challenging question. Should the following code compile?
- @code{cpp}
- template <typename T>
- void f(T t) {
- static_assert(t == 1, "");
- }
- constexpr int one = 1;
- f(one);
- @endcode
- The answer is no, and the error given by Clang goes like
- @code{cpp}
- error: static_assert expression is not an integral constant expression
- static_assert(t == 1, "");
- ^~~~~~
- @endcode
- The explanation is that inside of `f`'s body, `t` is not a constant expression,
- and hence it can't be used as the operand to a `static_assert`. The reason is
- that such a function simply can't be generated by the compiler. To understand
- the issue, consider what should happen when we instantiate the `f` template
- with a concrete type:
- @code{cpp}
- // Here, the compiler should generate the code for f<int> and store the
- // address of that code into fptr.
- void (*fptr)(int) = f<int>;
- @endcode
- Clearly, the compiler can't generate `f<int>`'s code, which should trigger a
- `static_assert` if `t != 1`, because we haven't specified `t` yet. Even worse,
- the generated function should work on both constant and non-constant
- expressions:
- @code{cpp}
- void (*fptr)(int) = f<int>; // assume this was possible
- int i = ...; // user input
- fptr(i);
- @endcode
- Clearly, `fptr`'s code can't be generated, because it would require being able
- to `static_assert` on a runtime value, which does not make sense. Furthermore,
- note that it does not matter whether you make the function `constexpr` or not;
- making `f` `constexpr` would only state that the _result_ of `f` is a constant
- expression whenever its argument is a constant expression, but it still does
- not give you the ability to know whether you were called with a constant
- expression from `f`'s body. In other words, what we would want is something
- like:
- @code{cpp}
- template <typename T>
- void f(constexpr T t) {
- static_assert(t == 1, "");
- }
- constexpr int one = 1;
- f(one);
- @endcode
- In this hypothetical scenario, the compiler would know that `t` is a constant
- expression from the body of `f`, and the `static_assert` could be made to work.
- However, `constexpr` parameters do not exist in the current language, and
- adding them would bring up very challenging design and implementation issues.
- The conclusion of this little experiment is that __argument passing strips
- away `constexpr`-ness__. What might be unclear by now are the consequences
- of this stripping, which are explained next.
- @subsection tutorial-tutorial-appendix-constexpr-preservation Constexpr preservation
- The fact that an argument is not a constant expression means that we can't use
- it as a non-type template parameter, as an array bound, inside a `static_assert`
- or anything else that requires a constant expression. In addition, this means
- that the return type of a function can't depend on the _value_ of an argument
- which is nothing new if you think about it:
- @code{cpp}
- template <int i>
- struct foo { };
- auto f(int i) -> foo<i>; // obviously won't work
- @endcode
- In fact, the return type of a function may only depend on the types of its
- arguments, and `constexpr` can't change this fact. This is of utmost importance
- to us, because we're interested in manipulating heterogeneous objects, which
- eventually means returning objects with different types depending on the
- argument of the function. For example, a function might want to return an
- object of type `T` in one case and an object of type `U` in the other;
- from our analysis, we now know that these "cases" will have to depend on
- information encoded in the _types_ of the arguments, not in their _values_.
- To preserve `constexpr`-ness through argument passing, we have to encode the
- `constexpr` value into a type, and then pass a not-necessarily-`constexpr`
- object of that type to the function. The function, which must be a template,
- may then access the `constexpr` value encoded inside that type.
- @todo
- Improve this explanation and talk about non-integral constant expressions
- wrapped into types.
- @subsection tutorial-appendix-constexpr-effects Side effects
- Let me ask a tricky question. Is the following code valid?
- @code{cpp}
- template <typename T>
- constexpr int f(T& n) { return 1; }
- int n = 0;
- constexpr int i = f(n);
- @endcode
- The answer is _yes_, but the reason might not be obvious at first. What
- happens here is that we have a non-`constexpr` `int n`, and a `constexpr`
- function `f` taking a reference to its argument. The reason why most people
- think it shouldn't work is that `n` is not `constexpr`. However, we're not
- doing anything with `n` inside of `f`, so there is no actual reason why this
- shouldn't work! This is a bit like `throw`ing inside of a `constexpr` function:
- @code{cpp}
- constexpr int sqrt(int i) {
- if (i < 0) throw "i should be non-negative";
- return ...;
- }
- constexpr int two = sqrt(4); // ok: did not attempt to throw
- constexpr int error = sqrt(-4); // error: can't throw in a constant expression
- @endcode
- As long as the code path where `throw` appears is not executed, the result of
- the invocation can be a constant expression. Similarly, we can do whatever we
- want inside of `f`, as long as we don't execute a code path that requires
- accessing its argument `n`, which is not a constant expression:
- @code{cpp}
- template <typename T>
- constexpr int f(T& n, bool touch_n) {
- if (touch_n) n + 1;
- return 1;
- }
- int n = 0;
- constexpr int i = f(n, false); // ok
- constexpr int j = f(n, true); // error
- @endcode
- The error given by Clang for the second invocation is
- @code{cpp}
- error: constexpr variable 'j' must be initialized by a constant expression
- constexpr int j = f(n, true); // error
- ^ ~~~~~~~~~~
- note: read of non-const variable 'n' is not allowed in a constant expression
- if (touch_n) n + 1;
- ^
- @endcode
- Let's now step the game up a bit and consider a more subtle example.
- Is the following code valid?
- @code{cpp}
- template <typename T>
- constexpr int f(T n) { return 1; }
- int n = 0;
- constexpr int i = f(n);
- @endcode
- The only difference with our initial scenario is that `f` now takes its
- argument by value instead of by reference. However, this makes a world of
- difference. Indeed, we're now asking the compiler to make a copy of `n`
- and to pass this copy to `f`. However, `n` is not `constexpr`, so its
- value is only known at runtime. How could the compiler make a copy (at
- compile-time) of a variable whose value is only known at runtime? Of
- course, it can't. Indeed, the error message given by Clang is pretty
- explicit about what's happening:
- @code{cpp}
- error: constexpr variable 'i' must be initialized by a constant expression
- constexpr int i = f(n);
- ^ ~~~~
- note: read of non-const variable 'n' is not allowed in a constant expression
- constexpr int i = f(n);
- ^
- @endcode
- @todo
- Explain how side-effects may not appear inside constant expressions, even
- if the expression they yield are not accessed.
- <!-------------------
- Let me ask a tricky question. Is the following code valid?
- @code{cpp}
- template <typename X>
- auto identity(X x) { return x; }
- static_assert(value(identity(bool_c<true>)), "");
- @endcode
- The answer is "no", but the reason might not be obvious at first. Even more
- puzzling is that the following code is perfectly valid:
- @snippet example/tutorial/constant_side_effects.cpp pure
- To understand why the compiler can't possibly evaluate the first assertion
- at compile-time, notice that `identity` was not marked `constexpr` and
- consider the following alternative (but valid) definition for `identity`:
- @snippet example/tutorial/constant_side_effects.cpp impure_identity
- The signature of the function did not change; the function could even have
- been defined in a separate source file. However, it is now obvious that the
- compiler can't evaluate that expression at compile-time. On the other hand,
- when we write
- @snippet example/tutorial/constant_side_effects.cpp impure
- we're telling the compiler to perform those potential side effects during the
- dynamic initialization phase! Then, we use `value` to return the compile-time
- value associated to its argument. Also note that `value` takes a `const&` to
- its argument; if it tried taking it by value, we would be reading from a
- non-`constexpr` variable to do the copying, and that could hide side-effects.
- ------>
- @section tutorial-appendix-MPL Appendix II: A minimal MPL
- ------------------------------------------------------------------------------
- This section presents a mini reimplementation of the MPL library. The goal is
- to be as backward compatible as possible with the MPL, while still using Hana
- under the hood. Only the "Algorithms" part of the MPL is implemented as a case
- study, but it should be possible to implement many (but not all) metafunctions
- of the MPL.
- Scroll down to the `main` function to see the tests. The tests are exactly
- the examples in the MPL documentation that were copy/pasted and then
- modified as little as possible to work with this reimplementation.
- @include example/tutorial/appendix_mpl.cpp
- <!-- Links -->
- [Boost.Devel]: http://lists.boost.org/Archives/boost
- [Boost.Fusion]: http://www.boost.org/doc/libs/release/libs/fusion/doc/html/index.html
- [Boost.MPL]: http://www.boost.org/doc/libs/release/libs/mpl/doc/index.html
- [Boost.Steering]: https://sites.google.com/a/boost.org/steering/home
- [Boost]: http://www.boost.org
- [Brigand]: https://github.com/edouarda/brigand
- [C++14.auto_rt]: http://en.wikipedia.org/wiki/C%2B%2B14#Function_return_type_deduction
- [C++14.gconstexpr]: http://en.wikipedia.org/wiki/C%2B%2B11#constexpr_.E2.80.93_Generalized_constant_expressions
- [C++14.glambda]: http://en.wikipedia.org/wiki/C%2B%2B14#Generic_lambdas
- [C++14.ice]: http://en.cppreference.com/w/cpp/types/integral_constant
- [C++14.udl]: http://en.wikipedia.org/wiki/C%2B%2B11#User-defined_literals
- [C++14.vtemplate]: http://en.wikipedia.org/wiki/C%2B%2B14#Variable_templates
- [C++14]: http://en.wikipedia.org/wiki/C%2B%2B14
- [C++17.clite]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3580.pdf
- [C++Now]: http://cppnow.org
- [Chandler.MeetingC++]: https://youtu.be/qkzaZumt_uk?t=4478
- [CMake]: http://www.cmake.org
- [constexpr_throw]: http://stackoverflow.com/a/8626450/627587
- [CopyConstructible]: http://en.cppreference.com/w/cpp/named_req/CopyConstructible
- [CppCon]: http://cppcon.org
- [GOTW]: http://www.gotw.ca/gotw/index.htm
- [GSoC]: http://www.google-melange.com/gsoc/homepage/google/gsoc2014
- [Hana.chat]: https://gitter.im/boostorg/hana
- [Hana.contributing]: https://github.com/boostorg/hana/blob/master/CONTRIBUTING.md#how-to-contribute
- [Hana.hacking]: https://github.com/boostorg/hana/blob/master/README.md#hacking-on-hana
- [Hana.issues]: https://github.com/boostorg/hana/issues
- [Hana.repository]: https://github.com/boostorg/hana
- [Hana.StackOverflow]: http://stackoverflow.com/questions/tagged/boost-hana
- [Hana.wiki]: https://github.com/boostorg/hana/wiki
- [Homebrew]: http://brew.sh
- [ldionne.talks]: http://ldionne.com/talks
- [lie-to-children]: http://en.wikipedia.org/wiki/Lie-to-children
- [Meeting C++]: https://meetingcpp.com
- [Metabench]: http://metaben.ch
- [MPL.arithmetic]: http://www.boost.org/doc/libs/release/libs/mpl/doc/refmanual/arithmetic-operations.html
- [MPL.metafunction]: http://www.boost.org/doc/libs/release/libs/mpl/doc/refmanual/metafunction.html
- [MPL.mfc]: http://www.boost.org/doc/libs/release/libs/mpl/doc/refmanual/metafunction-class.html
- [MPL11]: http://github.com/ldionne/mpl11
- [N4461]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4461.html
- [N4487]: https://isocpp.org/files/papers/N4487.pdf
- [pkg-config]: http://www.freedesktop.org/wiki/Software/pkg-config/
- [POD]: http://en.cppreference.com/w/cpp/named_req/PODType
- [SFINAE]: http://en.cppreference.com/w/cpp/language/sfinae
- [slides.inst_must_go1]: https://github.com/boostcon/2010_presentations/raw/master/mon/instantiations_must_go.pdf
- [slides.inst_must_go2]: https://github.com/boostcon/2010_presentations/raw/master/mon/instantiations_must_go_2.pdf
- [SO.sfinae]: http://stackoverflow.com/a/257382/627587
- [Sprout]: https://github.com/bolero-MURAKAMI/Sprout
- [StackOverflow]: http://stackoverflow.com
- [video.inst_must_go]: https://www.youtube.com/watch?v=x7UmrRzKAXU
- */
|