C++ S The Guide Addison Wesley (2017)
C%2B%2B%20s_%20The%20%20Guide-Addison-Wesley%20(2017)
C%2B%2B%20s_%20The%20%20Guide-Addison-Wesley%20(2017)
C%2B%2B%20s_%20The%20%20Guide-Addison-Wesley%20(2017)
User Manual: Pdf
Open the PDF directly: View PDF .
Page Count: 2543 [warning: Documents this large are best viewed by clicking the View PDF Link!]
- Cover Page
- Title Page
- Copyright Page
- Dedication
- Contents
- Preface
- Acknowledgments for the Second Edition
- Acknowledgments for the First Edition
- About This Book
- Part I: The Basics
- 1 Function Templates
- 2 Class Templates
- 2.1 Implementation of Class Template Stack
- 2.2 Use of Class Template Stack
- 2.3 Partial Usage of Class Templates
- 2.4 Friends
- 2.5 Specializations of Class Templates
- 2.6 Partial Specialization
- 2.7 Default Class Template Arguments
- 2.8 Type Aliases
- 2.9 Class Template Argument Deduction
- 2.10 Templatized Aggregates
- 2.11 Summary
- 3 Nontype Template Parameters
- 4 Variadic Templates
- 5 Tricky Basics
- 6 Move Semantics and enable_if<>
- 7 By Value or by Reference?
- 8 Compile-Time Programming
- 9 Using Templates in Practice
- 10 Basic Template Terminology
- 11 Generic Libraries
- Part II: Templates in Depth
- 12 Fundamentals in Depth
- 13 Names in Templates
- 14 Instantiation
- 15 Template Argument Deduction
- 15.1 The Deduction Process
- 15.2 Deduced Contexts
- 15.3 Special Deduction Situations
- 15.4 Initializer Lists
- 15.5 Parameter Packs
- 15.6 Rvalue References
- 15.7 SFINAE (Substitution Failure Is Not An Error)
- 15.8 Limitations of Deduction
- 15.9 Explicit Function Template Arguments
- 15.10 Deduction from Initializers and Expressions
- 15.11 Alias Templates
- 15.12 Class Template Argument Deduction
- 15.13 Afternotes
- 16 Specialization and Overloading
- 17 Future Directions
- 17.1 Relaxed typename Rules
- 17.2 Generalized Nontype Template Parameters
- 17.3 Partial Specialization of Function Templates
- 17.4 Named Template Arguments
- 17.5 Overloaded Class Templates
- 17.6 Deduction for Nonfinal Pack Expansions
- 17.7 Regularization of void
- 17.8 Type Checking for Templates
- 17.9 Reflective Metaprogramming
- 17.10 Pack Facilities
- 17.11 Modules
- Part III: Templates and Design
- 18 The Polymorphic Power of Templates
- 19 Implementing Traits
- 20 Overloading on Type Properties
- 21 Templates and Inheritance
- 22 Bridging Static and Dynamic Polymorphism
- 23 Metaprogramming
- 24 Typelists
- 25 Tuples
- 26 Discriminated Unions
- 27 Expression Templates
- 28 Debugging Templates
- Appendixes
- A The One-Definition Rule
- B Value Categories
- C Overload Resolution
- D Standard Type Utilities
- E Concepts
- Bibliography
- Glossary
- Index
C++Templates
TheCompleteGuide
SecondEdition
DavidVandevoorde
NicolaiM.JosuttisDouglasGregor
Boston•Columbus•Indianapolis•NewYork•SanFrancisco•Amsterdam•
CapeTownDubai•London•Madrid•Milan•Munich•Paris•Montreal•
Toronto•Delhi•MexicoCitySãoPaulo•Sydney•HongKong•Seoul•
Singapore•Taipei•Tokyo
Manyofthedesignationsusedbymanufacturersandsellerstodistinguishtheir
productsareclaimedastrademarks.Wherethosedesignationsappearinthis
book,andthepublisherwasawareofatrademarkclaim,thedesignationshave
beenprintedwithinitialcapitallettersorinallcapitals.
Theauthorsandpublisherhavetakencareinthepreparationofthisbook,but
makenoexpressedorimpliedwarrantyofanykindandassumenoresponsibility
forerrorsoromissions.Noliabilityisassumedforincidentalorconsequential
damagesinconnectionwithorarisingoutoftheuseoftheinformationor
programscontainedherein.
Forinformationaboutbuyingthistitleinbulkquantities,orforspecialsales
opportunities(whichmayincludeelectronicversions;customcoverdesigns;and
contentparticulartoyourbusiness,traininggoals,marketingfocus,orbranding
interests),pleasecontactourcorporatesalesdepartmentat
corpsales@pearsoned.comor(800)382-3419.
Forgovernmentsalesinquiries,pleasecontact
governmentsales@pearsoned.com.
ForquestionsaboutsalesoutsidetheU.S.,pleasecontact
intlcs@pearson.com.
VisitusontheWeb:informit.com/aw
LibraryofCongressCatalogNumber:2017946531
Copyright©2018PearsonEducation,Inc.
ThisbookwastypesetbyNicolaiM.JosuttisusingtheLATEXdocument
processingsystem.Allrightsreserved.PrintedintheUnitedStatesofAmerica.
Thispublicationisprotectedbycopyright,andpermissionmustbeobtained
fromthepublisherpriortoanyprohibitedreproduction,storageinaretrieval
system,ortransmissioninanyformorbyanymeans,electronic,mechanical,
photocopying,recording,orlikewise.Forinformationregardingpermissions,
requestformsandtheappropriatecontactswithinthePearsonEducationGlobal
Rights&PermissionsDepartment,pleasevisit
www.pearsoned.com/permissions/.
ISBN-13:978-0-321-71412-1
ISBN-10:0-321-71412-1
117
ToAlessandra&Cassandra
—David
Tothosewhocareforpeopleandmankind
—Nico
ToAmy,Tessa&Molly
—Doug
Contents
Preface
AcknowledgmentsfortheSecondEdition
AcknowledgmentsfortheFirstEdition
AboutThisBook
WhatYouShouldKnowBeforeReadingThisBook
OverallStructureoftheBook
HowtoReadThisBook
SomeRemarksAboutProgrammingStyle
TheC++11,C++14,andC++17Standards
ExampleCodeandAdditionalInformation
Feedback
PartI:TheBasics
1FunctionTemplates
1.1AFirstLookatFunctionTemplates
1.1.1DefiningtheTemplate
1.1.2UsingtheTemplate
1.1.3Two-PhaseTranslation
1.2TemplateArgumentDeduction
1.3MultipleTemplateParameters
1.3.1TemplateParametersforReturnTypes
1.3.2DeducingtheReturnType
1.3.3ReturnTypeasCommonType
1.4DefaultTemplateArguments
1.5OverloadingFunctionTemplates
1.6But,Shouldn’tWe…?
1.6.1PassbyValueorbyReference?
1.6.2WhyNotinline?
1.6.3WhyNotconstexpr?
1.7Summary
2ClassTemplates
2.1ImplementationofClassTemplateStack
2.1.1DeclarationofClassTemplates
2.1.2ImplementationofMemberFunctions
2.2UseofClassTemplateStack
2.3PartialUsageofClassTemplates
2.3.1Concepts
2.4Friends
2.5SpecializationsofClassTemplates
2.6PartialSpecialization
2.7DefaultClassTemplateArguments
2.8TypeAliases
2.9ClassTemplateArgumentDeduction
2.10TemplatizedAggregates
2.11Summary
3NontypeTemplateParameters
3.1NontypeClassTemplateParameters
3.2NontypeFunctionTemplateParameters
3.3RestrictionsforNontypeTemplateParameters
3.4TemplateParameterTypeauto
3.5Summary
4VariadicTemplates
4.1VariadicTemplates
4.1.1VariadicTemplatesbyExample
4.1.2OverloadingVariadicandNonvariadicTemplates
4.1.3Operatorsizeof…
4.2FoldExpressions
4.3ApplicationofVariadicTemplates
4.4VariadicClassTemplatesandVariadicExpressions
4.4.1VariadicExpressions
4.4.2VariadicIndices
4.4.3VariadicClassTemplates
4.4.4VariadicDeductionGuides
4.4.5VariadicBaseClassesandusing
4.5Summary
5TrickyBasics
5.1Keywordtypename
5.2ZeroInitialization
5.3Usingthis->
5.4TemplatesforRawArraysandStringLiterals
5.5MemberTemplates
5.5.1The.templateConstruct
5.5.2GenericLambdasandMemberTemplates
5.6VariableTemplates
5.7TemplateTemplateParameters
5.8Summary
6MoveSemanticsandenable_if<>
6.1PerfectForwarding
6.2SpecialMemberFunctionTemplates
6.3DisableTemplateswithenable_if<>
6.4Usingenable_if<>
6.5UsingConceptstoSimplifyenable_if<>Expressions
6.6Summary
7ByValueorbyReference?
7.1PassingbyValue
7.2PassingbyReference
7.2.1PassingbyConstantReference
7.2.2PassingbyNonconstantReference
7.2.3PassingbyForwardingReference
7.3Usingstd::ref()andstd::cref()
7.4DealingwithStringLiteralsandRawArrays
7.4.1SpecialImplementationsforStringLiteralsandRawArrays
7.5DealingwithReturnValues
7.6RecommendedTemplateParameterDeclarations
7.7Summary
8Compile-TimeProgramming
8.1TemplateMetaprogramming
8.2Computingwithconstexpr
8.3ExecutionPathSelectionwithPartialSpecialization
8.4SFINAE(SubstitutionFailureIsNotAnError)
8.4.1ExpressionSFINAEwithdecltype
8.5Compile-Timeif
8.6Summary
9UsingTemplatesinPractice
9.1TheInclusionModel
9.1.1LinkerErrors
9.1.2TemplatesinHeaderFiles
9.2Templatesandinline
9.3PrecompiledHeaders
9.4DecodingtheErrorNovel
9.5Afternotes
9.6Summary
10BasicTemplateTerminology
10.1“ClassTemplate”or“TemplateClass”?
10.2Substitution,Instantiation,andSpecialization
10.3DeclarationsversusDefinitions
10.3.1CompleteversusIncompleteTypes
10.4TheOne-DefinitionRule
10.5TemplateArgumentsversusTemplateParameters
10.6Summary
11GenericLibraries
11.1Callables
11.1.1SupportingFunctionObjects
11.1.2DealingwithMemberFunctionsandAdditionalArguments
11.1.3WrappingFunctionCalls
11.2OtherUtilitiestoImplementGenericLibraries
11.2.1TypeTraits
11.2.2std::addressof()
11.2.3std::declval()
11.3PerfectForwardingTemporaries
11.4ReferencesasTemplateParameters
11.5DeferEvaluations
11.6ThingstoConsiderWhenWritingGenericLibraries
11.7Summary
PartII:TemplatesinDepth
12FundamentalsinDepth
12.1ParameterizedDeclarations
12.1.1VirtualMemberFunctions
12.1.2LinkageofTemplates
12.1.3PrimaryTemplates
12.2TemplateParameters
12.2.1TypeParameters
12.2.2NontypeParameters
12.2.3TemplateTemplateParameters
12.2.4TemplateParameterPacks
12.2.5DefaultTemplateArguments
12.3TemplateArguments
12.3.1FunctionTemplateArguments
12.3.2TypeArguments
12.3.3NontypeArguments
12.3.4TemplateTemplateArguments
12.3.5Equivalence
12.4VariadicTemplates
12.4.1PackExpansions
12.4.2WhereCanPackExpansionsOccur?
12.4.3FunctionParameterPacks
12.4.4MultipleandNestedPackExpansions
12.4.5Zero-LengthPackExpansions
12.4.6FoldExpressions
12.5Friends
12.5.1FriendClassesofClassTemplates
12.5.2FriendFunctionsofClassTemplates
12.5.3FriendTemplates
12.6Afternotes
13NamesinTemplates
13.1NameTaxonomy
13.2LookingUpNames
13.2.1Argument-DependentLookup
13.2.2Argument-DependentLookupofFriendDeclarations
13.2.3InjectedClassNames
13.2.4CurrentInstantiations
13.3ParsingTemplates
13.3.1ContextSensitivityinNontemplates
13.3.2DependentNamesofTypes
13.3.3DependentNamesofTemplates
13.3.4DependentNamesinUsingDeclarations
13.3.5ADLandExplicitTemplateArguments
13.3.6DependentExpressions
13.3.7CompilerErrors
13.4InheritanceandClassTemplates
13.4.1NondependentBaseClasses
13.4.2DependentBaseClasses
13.5Afternotes
14Instantiation
14.1On-DemandInstantiation
14.2LazyInstantiation
14.2.1PartialandFullInstantiation
14.2.2InstantiatedComponents
14.3TheC++InstantiationModel
14.3.1Two-PhaseLookup
14.3.2PointsofInstantiation
14.3.3TheInclusionModel
14.4ImplementationSchemes
14.4.1GreedyInstantiation
14.4.2QueriedInstantiation
14.4.3IteratedInstantiation
14.5ExplicitInstantiation
14.5.1ManualInstantiation
14.5.2ExplicitInstantiationDeclarations
14.6Compile-TimeifStatements
14.7IntheStandardLibrary
14.8Afternotes
15TemplateArgumentDeduction
15.1TheDeductionProcess
15.2DeducedContexts
15.3SpecialDeductionSituations
15.4InitializerLists
15.5ParameterPacks
15.5.1LiteralOperatorTemplates
15.6RvalueReferences
15.6.1ReferenceCollapsingRules
15.6.2ForwardingReferences
15.6.3PerfectForwarding
15.6.4DeductionSurprises
15.7SFINAE(SubstitutionFailureIsNotAnError)
15.7.1ImmediateContext
15.8LimitationsofDeduction
15.8.1AllowableArgumentConversions
15.8.2ClassTemplateArguments
15.8.3DefaultCallArguments
15.8.4ExceptionSpecifications
15.9ExplicitFunctionTemplateArguments
15.10DeductionfromInitializersandExpressions
15.10.1TheautoTypeSpecifier
15.10.2ExpressingtheTypeofanExpressionwithdecltype
15.10.3decltype(auto)
15.10.4SpecialSituationsforautoDeduction
15.10.5StructuredBindings
15.10.6GenericLambdas
15.11AliasTemplates
15.12ClassTemplateArgumentDeduction
15.12.1DeductionGuides
15.12.2ImplicitDeductionGuides
15.12.3OtherSubtleties
15.13Afternotes
16SpecializationandOverloading
16.1When“GenericCode”Doesn’tQuiteCutIt
16.1.1TransparentCustomization
16.1.2SemanticTransparency
16.2OverloadingFunctionTemplates
16.2.1Signatures
16.2.2PartialOrderingofOverloadedFunctionTemplates
16.2.3FormalOrderingRules
16.2.4TemplatesandNontemplates
16.2.5VariadicFunctionTemplates
16.3ExplicitSpecialization
16.3.1FullClassTemplateSpecialization
16.3.2FullFunctionTemplateSpecialization
16.3.3FullVariableTemplateSpecialization
16.3.4FullMemberSpecialization
16.4PartialClassTemplateSpecialization
16.5PartialVariableTemplateSpecialization
16.6Afternotes
17FutureDirections
17.1RelaxedtypenameRules
17.2GeneralizedNontypeTemplateParameters
17.3PartialSpecializationofFunctionTemplates
17.4NamedTemplateArguments
17.5OverloadedClassTemplates
17.6DeductionforNonfinalPackExpansions
17.7Regularizationofvoid
17.8TypeCheckingforTemplates
17.9ReflectiveMetaprogramming
17.10PackFacilities
17.11Modules
PartIII:TemplatesandDesign
18ThePolymorphicPowerofTemplates
18.1DynamicPolymorphism
18.2StaticPolymorphism
18.3DynamicversusStaticPolymorphism
18.4UsingConcepts
18.5NewFormsofDesignPatterns
18.6GenericProgramming
18.7Afternotes
19ImplementingTraits
19.1AnExample:AccumulatingaSequence
19.1.1FixedTraits
19.1.2ValueTraits
19.1.3ParameterizedTraits
19.2TraitsversusPoliciesandPolicyClasses
19.2.1TraitsandPolicies:What’stheDifference?
19.2.2MemberTemplatesversusTemplateTemplateParameters
19.2.3CombiningMultiplePoliciesand/orTraits
19.2.4AccumulationwithGeneralIterators
19.3TypeFunctions
19.3.1ElementTypes
19.3.2TransformationTraits
19.3.3PredicateTraits
19.3.4ResultTypeTraits
19.4SFINAE-BasedTraits
19.4.1SFINAEOutFunctionOverloads
19.4.2SFINAEOutPartialSpecializations
19.4.3UsingGenericLambdasforSFINAE
19.4.4SFINAE-FriendlyTraits
19.5IsConvertibleT
19.6DetectingMembers
19.6.1DetectingMemberTypes
19.6.2DetectingArbitraryMemberTypes
19.6.3DetectingNontypeMembers
19.6.4UsingGenericLambdastoDetectMembers
19.7OtherTraitsTechniques
19.7.1If-Then-Else
19.7.2DetectingNonthrowingOperations
19.7.3TraitsConvenience
19.8TypeClassification
19.8.1DeterminingFundamentalTypes
19.8.2DeterminingCompoundTypes
19.8.3IdentifyingFunctionTypes
19.8.4DeterminingClassTypes
19.8.5DeterminingEnumerationTypes
19.9PolicyTraits
19.9.1Read-OnlyParameterTypes
19.10IntheStandardLibrary
19.11Afternotes
20OverloadingonTypeProperties
20.1AlgorithmSpecialization
20.2TagDispatching
20.3Enabling/DisablingFunctionTemplates
20.3.1ProvidingMultipleSpecializations
20.3.2WhereDoestheEnableIfGo?
20.3.3Compile-Timeif
20.3.4Concepts
20.4ClassSpecialization
20.4.1Enabling/DisablingClassTemplates
20.4.2TagDispatchingforClassTemplates
20.5Instantiation-SafeTemplates
20.6IntheStandardLibrary
20.7Afternotes
21TemplatesandInheritance
21.1TheEmptyBaseClassOptimization(EBCO)
21.1.1LayoutPrinciples
21.1.2MembersasBaseClasses
21.2TheCuriouslyRecurringTemplatePattern(CRTP)
21.2.1TheBarton-NackmanTrick
21.2.2OperatorImplementations
21.2.3Facades
21.3Mixins
21.3.1CuriousMixins
21.3.2ParameterizedVirtuality
21.4NamedTemplateArguments
21.5Afternotes
22BridgingStaticandDynamicPolymorphism
22.1FunctionObjects,Pointers,andstd::function<>
22.2GeneralizedFunctionPointers
22.3BridgeInterface
22.4TypeErasure
22.5OptionalBridging
22.6PerformanceConsiderations
22.7Afternotes
23Metaprogramming
23.1TheStateofModernC++Metaprogramming
23.1.1ValueMetaprogramming
23.1.2TypeMetaprogramming
23.1.3HybridMetaprogramming
23.1.4HybridMetaprogrammingforUnitTypes
23.2TheDimensionsofReflectiveMetaprogramming
23.3TheCostofRecursiveInstantiation
23.3.1TrackingAllInstantiations
23.4ComputationalCompleteness
23.5RecursiveInstantiationversusRecursiveTemplateArguments
23.6EnumerationValuesversusStaticConstants
23.7Afternotes
24Typelists
24.1AnatomyofaTypelist
24.2TypelistAlgorithms
24.2.1Indexing
24.2.2FindingtheBestMatch
24.2.3AppendingtoaTypelist
24.2.4ReversingaTypelist
24.2.5TransformingaTypelist
24.2.6AccumulatingTypelists
24.2.7InsertionSort
24.3NontypeTypelists
24.3.1DeducibleNontypeParameters
24.4OptimizingAlgorithmswithPackExpansions
24.5Cons-styleTypelists
24.6Afternotes
25Tuples
25.1BasicTupleDesign
25.1.1Storage
25.1.2Construction
25.2BasicTupleOperations
25.2.1Comparison
25.2.2Output
25.3TupleAlgorithms
25.3.1TuplesasTypelists
25.3.2AddingtoandRemovingfromaTuple
25.3.3ReversingaTuple
25.3.4IndexLists
25.3.5ReversalwithIndexLists
25.3.6ShuffleandSelect
25.4ExpandingTuples
25.5OptimizingTuple
25.5.1TuplesandtheEBCO
25.5.2Constant-timeget()
25.6TupleSubscript
25.7Afternotes
26DiscriminatedUnions
26.1Storage
26.2Design
26.3ValueQueryandExtraction
26.4ElementInitialization,AssignmentandDestruction
26.4.1Initialization
26.4.2Destruction
26.4.3Assignment
26.5Visitors
26.5.1VisitResultType
26.5.2CommonResultType
26.6VariantInitializationandAssignment
26.7Afternotes
27ExpressionTemplates
27.1TemporariesandSplitLoops
27.2EncodingExpressionsinTemplateArguments
27.2.1OperandsoftheExpressionTemplates
27.2.2TheArrayType
27.2.3TheOperators
27.2.4Review
27.2.5ExpressionTemplatesAssignments
27.3PerformanceandLimitationsofExpressionTemplates
27.4Afternotes
28DebuggingTemplates
28.1ShallowInstantiation
28.2StaticAssertions
28.3Archetypes
28.4Tracers
28.5Oracles
28.6Afternotes
Appendixes
ATheOne-DefinitionRule
A.1TranslationUnits
A.2DeclarationsandDefinitions
A.3TheOne-DefinitionRuleinDetail
A.3.1One-per-ProgramConstraints
A.3.2One-per-TranslationUnitConstraints
A.3.3Cross-TranslationUnitEquivalenceConstraints
BValueCategories
B.1TraditionalLvaluesandRvalues
B.1.1Lvalue-to-RvalueConversions
B.2ValueCategoriesSinceC++11
B.2.1TemporaryMaterialization
B.3CheckingValueCategorieswithdecltype
B.4ReferenceTypes
COverloadResolution
C.1WhenDoesOverloadResolutionKickIn?
C.2SimplifiedOverloadResolution
C.2.1TheImpliedArgumentforMemberFunctions
C.2.2RefiningthePerfectMatch
C.3OverloadingDetails
C.3.1PreferNontemplatesorMoreSpecializedTemplates
C.3.2ConversionSequences
C.3.3PointerConversions
C.3.4InitializerLists
C.3.5FunctorsandSurrogateFunctions
C.3.6OtherOverloadingContexts
DStandardTypeUtilities
D.1UsingTypeTraits
D.1.1std::integral_constantandstd::bool_constant
D.1.2ThingsYouShouldKnowWhenUsingTraits
D.2PrimaryandCompositeTypeCategories
D.2.1TestingforthePrimaryTypeCategory
D.2.2TestforCompositeTypeCategories
D.3TypePropertiesandOperations
D.3.1OtherTypeProperties
D.3.2TestforSpecificOperations
D.3.3RelationshipsBetweenTypes
D.4TypeConstruction
D.5OtherTraits
D.6CombiningTypeTraits
D.7OtherUtilities
EConcepts
E.1UsingConcepts
E.2DefiningConcepts
E.3OverloadingonConstraints
E.3.1ConstraintSubsumption
E.3.2ConstraintsandTagDispatching
E.4ConceptTips
E.4.1TestingConcepts
E.4.2ConceptGranularity
E.4.3BinaryCompatibility
Bibliography
Forums
BooksandWebSites
Glossary
Index
Preface
ThenotionoftemplatesinC++isover30yearsold.C++templateswerealready
documentedin1990in“TheAnnotatedC++ReferenceManual”(ARM;see
[EllisStroustrupARM]),andtheyhadbeendescribedbeforetheninmore
specializedpublications.However,welloveradecadelater,wefoundadearthof
literaturethatconcentratesonthefundamentalconceptsandadvanced
techniquesofthisfascinating,complex,andpowerfulC++feature.Withthefirst
editionofthisbook,wewantedtoaddressthisissueanddecidedtowritethe
bookabouttemplates(withperhapsaslightlackofhumility).
MuchhaschangedinC++sincethatfirsteditionwaspublishedinlate2002.
NewiterationsoftheC++standardhaveaddednewfeatures,andcontinued
innovationintheC++communityhasuncoverednewtemplate-based
programmingtechniques.Thesecondeditionofthisbookthereforeretainsthe
samegoalsasthefirstedition,butfor“ModernC++.”
Weapproachedthetaskofwritingthisbookwithdifferentbackgroundsand
withdifferentintentions.David(aka“Daveed”),anexperiencedcompiler
implementerandactiveparticipantoftheC++StandardCommitteeworking
groupsthatevolvethecorelanguage,wasinterestedinapreciseanddetailed
descriptionofallthepower(andproblems)oftemplates.Nico,an“ordinary”
applicationprogrammerandmemberoftheC++StandardCommitteeLibrary
WorkingGroup,wasinterestedinunderstandingallthetechniquesoftemplates
inawaythathecoulduseandbenefitfromthem.Doug,atemplatelibrary
developerturnedcompilerimplementerandlanguagedesigner,wasinterestedin
collecting,categorizing,andevaluatingthemyriadtechniquesusedtobuild
templatelibraries.Inaddition,weallwantedtosharethisknowledgewithyou,
thereader,andthewholecommunitytohelptoavoidfurthermisunderstanding,
confusion,orapprehension.
Asaconsequence,youwillseebothconceptualintroductionswithday-to-day
examplesanddetaileddescriptionsoftheexactbehavioroftemplates.Starting
fromthebasicprinciplesoftemplatesandworkinguptothe“artoftemplate
programming,”youwilldiscover(orrediscover)techniquessuchasstatic
polymorphism,typetraits,metaprogramming,andexpressiontemplates.You
willalsogainadeeperunderstandingoftheC++standardlibrary,inwhich
almostallcodeinvolvestemplates.
Welearnedalotandwehadmuchfunwhilewritingthisbook.Wehopeyou
willhavethesameexperiencewhilereadingit.Enjoy!
AcknowledgmentsfortheSecondEdition
Writingabookishard.Maintainingabookisevenharder.Ittookusmorethan
fiveyears—spreadoverthepastdecade—tocomeupwiththissecondedition,
anditcouldn’thavebeendonewithoutthesupportandpatienceofalotof
people.
First,we’dliketothankeveryoneintheC++communityandontheC++
standardizationcommittee.Inadditiontoalltheworktoaddnewlanguageand
libraryfeatures,theyspentmany,manyhoursexplaininganddiscussingtheir
workwithus,andtheydidsowithpatienceandenthusiasm.
Partofthiscommunityalsoincludestheprogrammerswhogavefeedbackfor
errorsandpossibleimprovementforthefirsteditionoverthepast15years.
Therearesimplytoomanytolistthemall,butyouknowwhoyouareandwe’re
trulygratefultoyoufortakingthetimetowriteupyourthoughtsand
observations.Pleaseacceptourapologiesifouranswersweresometimesless
thanprompt.
We’dalsoliketothankeveryonewhorevieweddraftsofthisbookand
provideduswithvaluablefeedbackandclarifications.Thesereviewsbroughtthe
booktoasignificantlyhigherlevelofquality,anditagainprovedthatgood
thingsneedtheinputofmany“wiseguys.”Forthisreason,hugethankstoSteve
Dewhurst,HowardHinnant,MikaelKilpel¨ainen,DietmarKühl,DanielKrügler,
NevinLieber,AndreasNeiser,EricNiebler,RichardSmith,AndrewSutton,
HubertTong,andVilleVoutilainen.
Ofcourse,thankstoallthepeoplewhosupportedusfromAddison-
Wesley/Pearson.Thesedays,youcannolongertakeprofessionalsupportfor
bookauthorsforgranted.Buttheywerepatient,naggeduswhenappropriate,
andwereofgreathelpwhenknowledgeandprofessionalismwerenecessary.So,
manythankstoPeterGordon,KimBoedigheimer,GregDoench,JulieNahil,
DanaWilson,andCarolLallier.
AspecialthanksgoestotheLaTeXcommunityforagreattextsystemandto
FrankMittelbachforsolvingourLATEXissues(itwasalmostalwaysourfault).
David’sAcknowledgmentsfortheSecondEdition
Thissecondeditionwasalongtimeinthewaiting,andasweputthefinishing
touchestoit,Iamgratefulforthepeopleinmylifewhomadeitpossibledespite
manyobligationsvyingforattention.First,I’mindebtedtomywife(Karina)and
daughters(AlessandraandCassandra),foragreeingtoletmetakesignificant
timeoutofthe“familyschedule”tocompletethisedition,particularlyinthelast
yearofwork.Myparentshavealwaysshowninterestinmygoals,andwhenever
Ivisitthem,theydonotforgetthisparticularproject.
Clearly,thisisatechnicalbook,anditscontentsreflectknowledgeand
experienceaboutaprogrammingtopic.However,thatisnotenoughtopulloff
completingthiskindofwork.I’mthereforeextremelygratefultoNicofor
havingtakenuponhimselfthe“management”and“production”aspectsofthis
edition(inadditiontoallofhistechnicalcontributions).Ifthisbookisusefulto
youandyourunintoNicosomeday,besuretotellhimthanksforkeepingusall
going.I’malsothankfultoDougforhavingagreedtocomeonboardseveral
yearsagoandtokeepgoingevenasdemandsonhisownschedulemadefor
toughgoing.
ManyprogrammersinourC++communityhavesharednuggetsofinsight
overtheyears,andIamgratefultoallofthem.However,Iowespecialthanksto
RichardSmith,whohasbeenefficientlyansweringmye-mailswitharcane
technicalissuesforyearsnow.Inthesamevein,thankstomycolleaguesJohn
Spicer,MikeMiller,andMikeHerrick,forsharingtheirknowledgeandcreating
anencouragingworkenvironmentthatallowsustolearnevermore.
Nico’sAcknowledgmentsfortheSecondEdition
First,Iwanttothankthetwohard-coreexperts,DavidandDoug,because,asan
applicationprogrammerandlibraryexpert,Iaskedsomanysillyquestionsand
learnedsomuch.Inowfeellikebecomingacoreexpert(onlyuntilthenext
issue,ofcourse).Itwasfun,guys.
AllmyotherthanksgotoJuttaEckstein.Juttahasthewonderfulabilityto
forceandsupportpeopleintheirideals,ideas,andgoals.Whilemostpeople
experiencethisonlyoccasionallywhenmeetingherorworkingwithherinour
ITindustry,Ihavethehonortobenefitfromherinmyday-to-daylife.Afterall
theseyears,Istillhopethiswilllastforever.
Doug’sAcknowledgmentsfortheSecondEdition
Myheartfeltthanksgotomywonderfulandsupportivewife,Amy,andourtwo
littlegirls,MollyandTessa.Theirloveandcompanionshipbringmedailyjoy
andtheconfidencetotacklethegreatestchallengesinlifeandwork.I’dalsolike
tothankmyparents,whoinstilledinmeagreatloveoflearningandencouraged
methroughouttheseyears.
ItwasapleasuretoworkwithbothDavidandNico,whoaresodifferentin
personalityyetcomplementeachothersowell.Davidbringsagreatclarityto
technicalwriting,honinginondescriptionsthatarepreciseandilluminating.
Nico,beyondhisexceptionalorganizationalskillsthatkepttwocoauthorsfrom
wanderingoffintotheweeds,bringsauniqueabilitytotakeapartacomplex
technicaldiscussionandmakeitsimpler,moreaccessible,andfar,farclearer.
AcknowledgmentsfortheFirstEdition
Thisbookpresentsideas,concepts,solutions,andexamplesfrommanysources.
We’dliketothankallthepeopleandcompanieswhohelpedandsupportedus
duringthepastfewyears.
First,we’dliketothankallthereviewersandeveryoneelsewhogaveustheir
opiniononearlymanuscripts.Thesepeopleendowthebookwithaqualityit
wouldneverhavehadwithouttheirinput.ThereviewersforthisbookwereKyle
Blaney,ThomasGschwind,DennisMancl,PatrickMcKillen,andJanChristiaan
vanWinkel.SpecialthankstoDietmarKühl,whometiculouslyreviewedand
editedthewholebook.Hisfeedbackwasanincrediblecontributiontothe
qualityofthisbook.
We’dalsoliketothankallthepeopleandcompanieswhogaveusthe
opportunitytotestourexamplesondifferentplatformswithdifferentcompilers.
ManythankstotheEdisonDesignGroupfortheirgreatcompilerandtheir
support.Itwasabighelpduringthestandardizationprocessandthewritingof
thisbook.ManythanksalsogotoallthedevelopersofthefreeGNUandegcs
compilers(JasonMerrillwasespeciallyresponsive)andtoMicrosoftforan
evaluationversionofVisualC++(JonathanCaves,HerbSutter,andJasonShirk
wereourcontactsthere).
Muchoftheexisting“C++wisdom”wascollectivelycreatedbytheonline
C++community.MostofitcomesfromthemoderatedUsenetgroups
comp.lang.c++.moderatedandcomp.std.c++.Wearetherefore
especiallyindebtedtotheactivemoderatorsofthosegroups,whokeepthe
discussionsusefulandconstructive.Wealsomuchappreciateallthosewhoover
theyearshavetakenthetimetodescribeandexplaintheirideasforusallto
share.
TheAddison-Wesleyteamdidanothergreatjob.Wearemostindebtedto
DebbieLafferty(oureditor)forhergentleprodding,goodadvice,andrelentless
hardworkinsupportofthisbook.ThanksalsogotoTyrrellAlbaugh,Bunny
Ames,MelanieBuck,JacquelynDoucette,ChandaLeary-Coutu,Catherine
Ohala,andMartyRabinowitz.We’regratefulaswelltoMarinaLang,whofirst
sponsoredthisbookwithinAddison-Wesley.SusanWinercontributedanearly
roundofeditingthathelpedshapeourlaterwork.
Nico’sAcknowledgmentsfortheFirstEdition
Myfirstpersonalthanksgowithalotofkissestomyfamily:Ulli,Lucas,Anica,
andFredericsupportedthisbookwithalotofpatience,consideration,and
encouragement.
Inaddition,IwanttothankDavid.Hisexpertiseturnedouttobeincredible,
buthispatiencewasevenbetter(sometimesIaskreallysillyquestions).Itisa
lotoffuntoworkwithhim.
David’sAcknowledgmentsfortheFirstEdition
Mywife,Karina,hasbeeninstrumentalinthisbookcomingtoaconclusion,and
Iamimmenselygratefulfortherolethatsheplaysinmylife.Writing“inyour
sparetime”quicklybecomeserraticwhenmanyotheractivitiesvieforyour
schedule.Karinahelpedmetomanagethatschedule,taughtmetosay“no”in
ordertomakethetimeneededtomakeregularprogressinthewritingprocess,
andaboveallwasamazinglysupportiveofthisproject.IthankGodeveryday
forherfriendshipandlove.
I’malsotremendouslygratefultohavebeenabletoworkwithNico.Besides
hisdirectlyvisiblecontributionstothetext,hisexperienceanddisciplinemoved
usfrommypitifuldoodlingtoawell-organizedproduction.
John“Mr.Template”SpicerandSteve“Mr.Overload”Adamczykare
wonderfulfriendsandcolleagues,butinmyopiniontheyare(together)alsothe
ultimateauthorityregardingthecoreC++language.Theyclarifiedmanyofthe
trickierissuesdescribedinthisbook,andshouldyoufindanerrorinthe
descriptionofaC++languageelement,itisalmostcertainlyattributabletomy
failingtoconsultwiththem.
Finally,Iwanttoexpressmyappreciationtothosewhoweresupportiveof
thisprojectwithoutnecessarilycontributingtoitdirectly(thepowerofcheer
cannotbeunderstated).First,myparents:Theirloveformeandtheir
encouragementmadeallthedifference.Andthentherearethenumerousfriends
inquiring:“Howisthebookgoing?”They,too,wereasourceofencouragement:
MichaelBeckmann,BrettandJulieBeene,JarranCarr,SimonChang,Hoand
SarahCho,ChristopheDeDinechin,EwaDeelman,NeilEberle,Sassan
Hazeghi,VikramKumar,JimandLindsayLong,R.J.Morgan,MikePuritano,
RaguRaghavendra,JimandPhuongSharp,GreggVaughn,andJohnWiegley.
AboutThisBook
Thefirsteditionofthisbookwaspublishedalmost15yearsago.Wehadsetout
towritethedefinitiveguidetoC++templates,withtheexpectationthatitwould
beusefultopracticingC++programmers.Thatprojectwassuccessful:It’sbeen
tremendouslygratifyingtohearfromreaderswhofoundourmaterialhelpful,to
seeourbooktimeandagainbeingrecommendedasaworkofreference,andto
beuniversallywellreviewed.
Thatfirsteditionhasagedwell,withmostmaterialremainingentirelyrelevant
tothemodernC++programmer,butthereisnodenyingthattheevolutionofthe
language—culminatinginthe“ModernC++”standards,C++11,C++14,and
C++17—hasraisedtheneedforarevisionofthematerialinthefirstedition.
Sowiththissecondedition,ourhigh-levelgoalhasremainedunchanged:to
providethedefinitiveguidetoC++templates,includingbothasolidreference
andanaccessibletutorial.Thistime,however,weworkwiththe“ModernC++”
language,whichisasignificantlybiggerbeast(still!)thanthelanguageavailable
atthetimeoftheprioredition.
We’realsoacutelyawarethatC++programmingresourceshavechanged(for
thebetter)sincethefirsteditionwaspublished.Forexample,severalbookshave
appearedthatdevelopspecifictemplate-basedapplicationsingreatdepth.More
important,farmoreinformationaboutC++templatesandtemplate-based
techniquesiseasilyavailableonline,asareexamplesofadvancedusesofthese
techniques.Sointhisedition,wehavedecidedtoemphasizeabreadthof
techniquesthatcanbeusedinavarietyofapplications.
Someofthetechniqueswepresentedinthefirsteditionhavebecomeobsolete
becausetheC++languagenowoffersmoredirectwaysofachievingthesame
effects.Thosetechniqueshavebeendropped(orrelegatedtominornotes),and
insteadyou’llfindnewtechniquesthatshowthestate-ofthe-artusesofthenew
language.
We’venowlivedover20yearswithC++templates,buttheC++
programmers’communitystillregularlyfindsnewfundamentalinsightsintothe
waytheycanfitinoursoftwaredevelopmentneeds.Ourgoalwiththisbookis
tosharethatknowledgebutalsotofullyequipthereadertodevelopnew
understandingand,perhaps,discoverthenextmajorC++technique.
WhatYouShouldKnowBeforeReadingThisBook
Togetthemostfromthisbook,youshouldalreadyknowC++.Wedescribethe
detailsofaparticularlanguagefeature,notthefundamentalsofthelanguage
itself.Youshouldbefamiliarwiththeconceptsofclassesandinheritance,and
youshouldbeabletowriteC++programsusingcomponentssuchasIOstreams
andcontainersfromtheC++standardlibrary.Youshouldalsobefamiliarwith
thebasicfeaturesof“ModernC++”,suchasauto,decltype,move
semantics,andlambdas.Nevertheless,wereviewmoresubtleissuesastheneed
arises,evenwhensuchissuesaren’tdirectlyrelatedtotemplates.Thisensures
thatthetextisaccessibletoexpertsandintermediateprogrammersalike.
WedealprimarilywiththeC++languagerevisionsstandardizedin2011,
2014,and2017.However,atthetimeofthiswriting,theinkisbarelydryonthe
C++17revision,andweexpectthatmostofourreaderswillnotbeintimately
familiarwithitsdetails.Allrevisionshadasignificantimpactonthebehavior
andusageoftemplates.Wethereforeprovideshortintroductionstothosenew
featuresthathavethegreatestbearingonoursubjectmatter.However,ourgoal
isneithertointroducethemodernC++standardsnortoprovideanexhaustive
descriptionofthechangesfromthepriorversionsofthisstandard([C++98]and
[C++03]).Instead,wefocusontemplatesasdesignedandusedinC++,using
themodernC++standards([C++11],[C++14],and[C++17])asourbasis,and
weoccasionallycalloutcaseswherethemodernC++standardsenableor
encouragedifferenttechniquesthanthepriorstandards.
OverallStructureoftheBook
Ourgoalistoprovidetheinformationnecessarytostartusingtemplatesand
benefitfromtheirpower,aswellastoprovideinformationthatwillenable
experiencedprogrammerstopushthelimitsofthestate-of-the-art.Toachieve
this,wedecidedtoorganizeourtextinparts:•PartIintroducesthebasic
conceptsunderlyingtemplates.Itiswritteninatutorialstyle.
•PartIIpresentsthelanguagedetailsandisahandyreferencetotemplate-
relatedconstructs.
•PartIIIexplainsfundamentaldesignandcodingtechniquessupportedbyC++
templates.Theyrangefromnear-trivialideastosophisticatedidioms.
Eachofthesepartsconsistsofseveralchapters.Inaddition,weprovideafew
appendixesthatcovermaterialnotexclusivelyrelatedtotemplates(e.g.,an
overviewofoverloadresolutioninC++).Anadditionalappendixcovers
concepts,whichisafundamentalextensiontotemplatesthathasbeenincluded
inthedraftforafuturestandard(C++20,presumably).
ThechaptersofPartIaremeanttobereadinsequence.Forexample,Chapter
3buildsonthematerialcoveredinChapter2.Intheotherparts,however,the
connectionbetweenchaptersisconsiderablylooser.Crossreferenceswillhelp
readersjumpthroughthedifferenttopics.
Last,weprovidearathercompleteindexthatencouragesadditionalwaysto
readthisbookoutofsequence.
HowtoReadThisBook
IfyouareaC++programmerwhowantstolearnorreviewtheconceptsof
templates,carefullyreadPartI,TheBasics.Evenifyou’requitefamiliarwith
templatesalready,itmayhelptoskimthroughthispartquicklytofamiliarize
yourselfwiththestyleandterminologythatweuse.Thispartalsocoverssome
ofthelogisticalaspectsoforganizingyoursourcecodewhenitcontains
templates.
Dependingonyourpreferredlearningmethod,youmaydecidetoabsorbthe
manydetailsoftemplatesinPartII,orinsteadyoucouldreadaboutpractical
codingtechniquesinPartIII(andreferbacktoPartIIforthemoresubtle
languageissues).Thelatterapproachisprobablyparticularlyusefulifyou
boughtthisbookwithconcreteday-to-daychallengesinmind.
Theappendixescontainmuchusefulinformationthatisoftenreferredtoin
themaintext.Wehavealsotriedtomaketheminterestingintheirownright.
Inourexperience,thebestwaytolearnsomethingnewistolookatexamples.
Therefore,you’llfindalotofexamplesthroughoutthebook.Somearejusta
fewlinesofcodeillustratinganabstractconcept,whereasothersarecomplete
programsthatprovideaconcreteapplicationofthematerial.Thelatterkindof
exampleswillbeintroducedbyaC++commentdescribingthefilecontaining
theprogramcode.YoucanfindthesefilesattheWebsiteofthisbookat
http://www.tmplbook.com.
SomeRemarksAboutProgrammingStyle
C++programmersusedifferentprogrammingstyles,andsodowe:Theusual
questionsaboutwheretoputwhitespace,delimiters(braces,parentheses),andso
forthcameup.Wetriedtobeconsistentingeneral,althoughweoccasionally
makeconcessionstothetopicathand.Forexample,intutorialsections,wemay
prefergeneroususeofwhitespaceandconcretenamestohelpvisualizecode,
whereasinmoreadvanceddiscussions,amorecompactstylecouldbemore
appropriate.
Wedowanttodrawyourattentiontooneslightlyuncommondecision
regardingthedeclarationoftypes,parameters,andvariables.Clearly,several
stylesarepossible:voidfoo(constint&x);
voidfoo(constint&x);
voidfoo(intconst&x);
voidfoo(intconst&x);Althoughitisabitlesscommon,wedecidedtousethe
orderintconstratherthanconstintfor“constantinteger.”Wehave
tworeasonsforusingthisorder.First,itprovidesforaneasieranswertothe
question,“Whatisconstant?”It’salwayswhatisinfrontoftheconstqualifier.
Indeed,althoughconstintN=100;isequivalentto
intconstN=100;thereisnoequivalentformforClickheretoview
codeimage
int*constbookmark;//thepointercannotchange,butthevalue
pointedtocan
thatwouldplacetheconstqualifierbeforethepointeroperator*.Inthis
example,itisthepointeritselfthatisconstant,nottheinttowhichitpoints.
Oursecondreasonhastodowithasyntacticalsubstitutionprinciplethatis
verycommonwhendealingwithtemplates.Considerthefollowingtwotype
declarationsusingthetypedefkeyword:1
Clickheretoviewcodeimage
typedefchar*CHARS;
typedefCHARSconstCPTR;//constantpointertochars
orusingtheusingkeyword:Clickheretoviewcodeimage
usingCHARS=char*;
usingCPTR=CHARSconst;//constantpointertochars
Themeaningoftheseconddeclarationispreservedwhenwetextuallyreplace
CHARSwithwhatitstandsfor:Clickheretoviewcodeimage
typedefchar*constCPTR;//constantpointertochars
or:
Clickheretoviewcodeimage
usingCPTR=char*const;//constantpointertochars
However,ifwewriteconstbeforethetypeitqualifies,thisprincipledoesn’t
apply.Considerthealternativetoourfirsttwotypedefinitionspresentedearlier:
Clickheretoviewcodeimage
typedefchar*CHARS;
typedefconstCHARSCPTR;//constantpointertochars
TextuallyreplacingCHARSresultsinatypewithadifferentmeaning:Clickhere
toviewcodeimage
typedefconstchar*CPTR;//pointertoconstantchars
Thesameobservationappliestothevolatilespecifier,ofcourse.
Regardingwhitespaces,wedecidedtoputthespacebetweentheampersand
andtheparametername:voidfoo(intconst&x);Bydoingthis,weemphasize
theseparationbetweentheparametertypeandtheparametername.Thisis
admittedlymoreconfusingfordeclarationssuchaschar*a,b;where,according
totherulesinheritedfromC,aisapointerbutbisanordinarychar.Toavoid
suchconfusion,wesimplyavoiddeclaringmultipleentitiesinthisway.
Thisisprimarilyabookaboutlanguagefeatures.However,manytechniques,
features,andhelpertemplatesnowappearintheC++standardlibrary.To
connectthesetwo,wethereforedemonstratetemplatetechniquesbyillustrating
howtheyareusedtoimplementcertainlibrarycomponents,andweusestandard
libraryutilitiestobuildourownmorecomplexexamples.Hence,weusenot
onlyheaderssuchas<iostream>and<string>(whichcontaintemplates
butarenotparticularlyrelevanttodefineothertemplates)butalso<cstddef>,
<utilities>,<functional>,and<type_traits>(whichdoprovide
buildingblocksformorecomplextemplates).
Inaddition,weprovideareference,AppendixD,aboutthemajortemplate
utilitiesprovidedbytheC++standardlibrary,includingadetaileddescriptionof
allthestandardtypetraits.Thesearecommonlyusedatthecoreofsophisticated
templateprogramming
TheC++11,C++14,andC++17Standards
TheoriginalC++standardwaspublishedin1998andsubsequentlyamendedby
atechnicalcorrigendumin2003,whichprovidedminorcorrectionsand
clarificationstotheoriginalstandard.This“oldC++standard”isknownas
C++98orC++03.
TheC++11standardwasthefirstmajorrevisionofC++drivenbytheISO
C++standardizationcommittee,bringingawealthofnewfeaturestothe
language.Anumberofthesenewfeaturesinteractwithtemplatesandare
describedinthisbook,including:•Variadictemplates
•Aliastemplates
•Movesemantics,rvaluereferences,andperfectforwarding•Standardtype
traits
C++14andC++17followed,bothintroducingsomenewlanguagefeatures,
althoughthechangesbroughtaboutbythesestandardswerenotquiteas
dramaticasthoseofC++11.2Newfeaturesinteractingwithtemplatesand
describedinthisbookincludebutarenotlimitedto:•Variabletemplates
(C++14)•GenericLambdas(C++14)
•Classtemplateargumentdeduction(C++17)•Compile-timeif(C++17)•
Foldexpressions(C++17)
Weevendescribeconcepts(templateinterfaces),whicharecurrentlyslatedfor
inclusionintheforthcomingC++20standard.
Atthetimeofthiswriting,theC++11andC++14standardsarebroadly
supportedbythemajorcompilers,andC++17islargelysupportedalso.Still,
compilersdiffergreatlyintheirsupportofthedifferentlanguagefeatures.
Severalwillcompilemostofthecodeinthisbook,butafewcompilersmaynot
beabletohandlesomeofourexamples.However,weexpectthatthisproblem
willsoonberesolvedasprogrammerseverywheredemandstandardsupport
fromtheirvendors.
Evenso,theC++programminglanguageislikelytocontinuetoevolveas
timepasses.TheexpertsoftheC++community(regardlessofwhetherthey
participateintheC++StandardizationCommittee)arediscussingvariousways
toimprovethelanguage,andalreadyseveralcandidateimprovementsaffect
templates.Chapter17presentssometrendsinthisarea.
ExampleCodeandAdditionalInformation
Youcanaccessallexampleprogramsandfindmoreinformationaboutthisbook
fromitsWebsite,whichhasthefollowingURL:http://www.tmplbook.com
Feedback
Wewelcomeyourconstructiveinput—boththenegativeandthepositive.We
workedveryhardtobringyouwhatwehopeyou’llfindtobeanexcellentbook.
However,atsomepointwehadtostopwriting,reviewing,andtweakingsowe
could“releasetheproduct.”Youmaythereforefinderrors,inconsistencies,and
presentationsthatcouldbeimproved,ortopicsthataremissingaltogether.Your
feedbackgivesusachancetoinformallreadersthroughthebook’sWebsiteand
toimproveanysubsequenteditions.
Thebestwaytoreachusisbyemail.Youwillfindtheemailaddressatthe
Websiteofthisbook:http://www.tmplbook.com
Please,besuretocheckthebook’sWebsiteforthecurrentlyknownerrata
beforesubmittingreports.Manythanks.
1NotethatinC++,atypedefinitiondefinesa“typealias”ratherthananew
type(seeSection2.8onpage38).
Forexample:
>typedefintLength;//defineLengthasanaliasforint
inti=42;
Lengthl=88;
i=l;//OK
l=i;//OK
2Thecommitteenowaimsatissuinganewstandardroughlyevery3years.
Clearly,thatleaveslesstimeformassiveadditions,butitbringsthechanges
morequicklytothebroaderprogrammingcommunity.Thedevelopmentof
largerfeatures,then,spanstimeandmightcovermultiplestandards.
PartI
TheBasics
ThispartintroducesthegeneralconceptsandlanguagefeaturesofC++
templates.Itstartswithadiscussionofthegeneralgoalsandconceptsby
showingexamplesoffunctiontemplatesandclasstemplates.Itcontinueswith
someadditionalfundamentaltemplatefeaturessuchasnontypetemplate
parameters,variadictemplates,thekeywordtypename,andmember
templates.Alsoitdiscusseshowtodealwithmovesemantics,howtodeclare
parameters,andhowtousegenericcodeforcompile-timeprogramming.Itends
withsomegeneralhintsaboutterminologyandregardingtheuseandapplication
oftemplatesinpracticebothasapplicationprogrammerandauthorofgeneric
libraries.
WhyTemplates?
C++requiresustodeclarevariables,functions,andmostotherkindsofentities
usingspecifictypes.However,alotofcodelooksthesamefordifferenttypes.
Forexample,theimplementationofthealgorithmquicksortlooksstructurally
thesamefordifferentdatastructures,suchasarraysofintsorvectorsof
strings,aslongasthecontainedtypescanbecomparedtoeachother.
Ifyourprogramminglanguagedoesn’tsupportaspeciallanguagefeaturefor
thiskindofgenericity,youonlyhavebadalternatives:1.Youcanimplementthe
samebehavioragainandagainforeachtypethatneedsthisbehavior.
2.YoucanwritegeneralcodeforacommonbasetypesuchasObjector
void*.
3.Youcanusespecialpreprocessors.
Ifyoucomefromotherlanguages,youprobablyhavedonesomeorallofthis
before.However,eachoftheseapproacheshasitsdrawbacks:1.Ifyou
implementabehavioragainandagain,youreinventthewheel.Youmakethe
samemistakes,andyoutendtoavoidcomplicatedbutbetteralgorithmsbecause
theyleadtoevenmoremistakes.
2.Ifyouwritegeneralcodeforacommonbaseclass,youlosethebenefitof
typechecking.Inaddition,classesmayberequiredtobederivedfromspecial
baseclasses,whichmakesitmoredifficulttomaintainyourcode.
3.Ifyouuseaspecialpreprocessor,codeisreplacedbysome“stupidtext
replacementmechanism”thathasnoideaofscopeandtypes,whichmight
resultinstrangesemanticerrors.Templatesareasolutiontothisproblem
withoutthesedrawbacks.Theyarefunctionsorclassesthatarewrittenforone
ormoretypesnotyetspecified.Whenyouuseatemplate,youpassthetypes
asarguments,explicitlyorimplicitly.Becausetemplatesarelanguage
features,youhavefullsupportoftypecheckingandscope.
Intoday’sprograms,templatesareusedalot.Forexample,insidetheC++
standardlibraryalmostallcodeistemplatecode.Thelibraryprovidessort
algorithmstosortobjectsandvaluesofaspecifiedtype,datastructures(also
calledcontainerclasses)tomanageelementsofaspecifiedtype,stringsfor
whichthetypeofacharacterisparameterized,andsoon.However,thisisonly
thebeginning.Templatesalsoallowustoparameterizebehavior,tooptimize
code,andtoparameterizeinformation.Theseapplicationsarecoveredinlater
chapters.Let’sfirststartwithsomesimpletemplates.
Chapter1
FunctionTemplates
Thischapterintroducesfunctiontemplates.Functiontemplatesarefunctionsthat
areparameterizedsothattheyrepresentafamilyoffunctions.
1.1AFirstLookatFunctionTemplates
Functiontemplatesprovideafunctionalbehaviorthatcanbecalledfordifferent
types.Inotherwords,afunctiontemplaterepresentsafamilyoffunctions.The
representationlooksalotlikeanordinaryfunction,exceptthatsomeelementsof
thefunctionareleftundetermined:Theseelementsareparameterized.To
illustrate,let’slookatasimpleexample.
1.1.1DefiningtheTemplate
Thefollowingisafunctiontemplatethatreturnsthemaximumoftwovalues:
Clickheretoviewcodeimage
basics/max1.hpp
template<typenameT>
Tmax(Ta,Tb)
{
//ifb<athenyieldaelseyieldb
returnb<a?a:b;
}
Thistemplatedefinitionspecifiesafamilyoffunctionsthatreturnthemaximum
oftwovalues,whicharepassedasfunctionparametersaandb.1Thetypeof
theseparametersisleftopenastemplateparameterT.Asseeninthisexample,
templateparametersmustbeannouncedwithsyntaxofthefollowingform:
Clickheretoviewcodeimage
template<comma-separated-list-of-parameters>Inourexample,the
listofparametersistypenameT.Notehowthe<and>tokensare
usedasbrackets;werefertotheseasanglebrackets.Thekeyword
typenameintroducesatypeparameter.Thisisbyfarthemostcommon
kindoftemplateparameterinC++programs,butotherparametersare
possible,andwediscussthemlater(seeChapter3).
Here,thetypeparameterisT.Youcanuseanyidentifierasaparametername,
butusingTistheconvention.Thetypeparameterrepresentsanarbitrarytype
thatisdeterminedbythecallerwhenthecallercallsthefunction.Youcanuse
anytype(fundamentaltype,class,andsoon)aslongasitprovidesthe
operationsthatthetemplateuses.Inthiscase,typeThastosupportoperator<
becauseaandbarecomparedusingthisoperator.Perhapslessobviousfromthe
definitionofmax()isthatvaluesoftypeTmustalsobecopyableinordertobe
returned.2
Forhistoricalreasons,youcanalsousethekeywordclassinsteadof
typenametodefineatypeparameter.Thekeywordtypenamecame
relativelylateintheevolutionoftheC++98standard.Priortothat,thekeyword
classwastheonlywaytointroduceatypeparameter,andthisremainsavalid
waytodoso.Hence,thetemplatemax()couldbedefinedequivalentlyas
follows:Clickheretoviewcodeimage
template<classT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
Semanticallythereisnodifferenceinthiscontext.So,evenifyouuseclass
here,anytypemaybeusedfortemplatearguments.However,becausethisuse
ofclasscanbemisleading(notonlyclasstypescanbesubstitutedforT),you
shouldprefertheuseoftypenameinthiscontext.However,notethatunlike
classtypedeclarations,thekeywordstructcannotbeusedinplaceof
typenamewhendeclaringtypeparameters.
1.1.2UsingtheTemplate
Thefollowingprogramshowshowtousethemax()functiontemplate:Click
heretoviewcodeimage
basics/max1.cpp
#include"max1.hpp"
#include<iostream>
#include<string>
intmain()
{
inti=42;
std::cout<<"max(7,i):"<<::max(7,i)<<’\n’;
doublef1=3.4;doublef2=-6.7;
std::cout<<"max(f1,f2):"<<::max(f1,f2)<<’\n’;
std::strings1="mathematics";std::strings2="math";
std::cout<<"max(s1,s2):"<<::max(s1,s2)<<’\n’;
}
Insidetheprogram,max()iscalledthreetimes:oncefortwoints,oncefor
twodoubles,andoncefortwostd::strings.Eachtime,themaximumis
computed.Asaresult,theprogramhasthefollowingoutput:Clickheretoview
codeimage
max(7,i):42
max(f1,f2):3.4
max(s1,s2):mathematics
Notethateachcallofthemax()templateisqualifiedwith::.Thisistoensure
thatourmax()templateisfoundintheglobalnamespace.Thereisalsoa
std::max()templateinthestandardlibrary,whichundersomecircumstances
maybecalledormayleadtoambiguity.3
Templatesaren’tcompiledintosingleentitiesthatcanhandleanytype.
Instead,differententitiesaregeneratedfromthetemplateforeverytypefor
whichthetemplateisused.4Thus,max()iscompiledforeachofthesethree
types.Forexample,thefirstcallofmax()
inti=42;
…max(7,i)…
usesthefunctiontemplatewithintastemplateparameterT.Thus,ithasthe
semanticsofcallingthefollowingcode:intmax(inta,intb)
{
returnb<a?a:b;
}
Theprocessofreplacingtemplateparametersbyconcretetypesiscalled
instantiation.Itresultsinaninstanceofatemplate.5
Notethatthemereuseofafunctiontemplatecantriggersuchaninstantiation
process.Thereisnoneedfortheprogrammertorequesttheinstantiation
separately.
Similarly,theothercallsofmax()instantiatethemaxtemplatefordouble
andstd::stringasiftheyweredeclaredandimplementedindividually:
Clickheretoviewcodeimage
doublemax(double,double);
std::stringmax(std::string,std::string);
Notealsothatvoidisavalidtemplateargumentprovidedtheresultingcodeis
valid.Forexample:Clickheretoviewcodeimage
template<typenameT>
Tfoo(T*)
{
}
void*vp=nullptr;
foo(vp);//OK:deducesvoidfoo(void*)
1.1.3Two-PhaseTranslation
Anattempttoinstantiateatemplateforatypethatdoesn’tsupportallthe
operationsusedwithinitwillresultinacompile-timeerror.Forexample:Click
heretoviewcodeimage
std::complex<float>c1,c2;//doesn’tprovideoperator<
…
::max(c1,c2);//ERRORatcompiletime
Thus,templatesare“compiled”intwophases:1.Withoutinstantiationat
definitiontime,thetemplatecodeitselfischeckedforcorrectnessignoringthe
templateparameters.Thisincludes:–Syntaxerrorsarediscovered,suchas
missingsemicolons.
–Usingunknownnames(typenames,functionnames,…)thatdon’tdepend
ontemplateparametersarediscovered.
–Staticassertionsthatdon’tdependontemplateparametersarechecked.
2.Atinstantiationtime,thetemplatecodeischecked(again)toensurethatall
codeisvalid.Thatis,nowespecially,allpartsthatdependontemplate
parametersaredouble-checked.
Forexample:
Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tt)
{
undeclared();//first-phasecompile-timeerrorifundeclared()
unknown
undeclared(t);//second-phasecompile-timeerrorifundeclared(T)
unknown
static_assert(sizeof(int)>10,//alwaysfailsifsizeof(int)<=10
"inttoosmall");
static_assert(sizeof(T)>10,//failsifinstantiatedforTwithsize
<=10
"Ttoosmall");
}
Thefactthatnamesarecheckedtwiceiscalledtwo-phaselookupanddiscussed
indetailinSection14.3.1onpage249.
Notethatsomecompilersdon’tperformthefullchecksofthefirstphase.6So
youmightnotseegeneralproblemsuntilthetemplatecodeisinstantiatedat
leastonce.
CompilingandLinking
Two-phasetranslationleadstoanimportantprobleminthehandlingoftemplates
inpractice:Whenafunctiontemplateisusedinawaythattriggersits
instantiation,acompilerwill(atsomepoint)needtoseethattemplate’s
definition.Thisbreakstheusualcompileandlinkdistinctionforordinary
functions,whenthedeclarationofafunctionissufficienttocompileitsuse.
MethodsofhandlingthisproblemarediscussedinChapter9.Forthemoment,
let’stakethesimplestapproach:Implementeachtemplateinsideaheaderfile.
1.2TemplateArgumentDeduction
Whenwecallafunctiontemplatesuchasmax()forsomearguments,the
templateparametersaredeterminedbytheargumentswepass.Ifwepasstwo
intstotheparametertypesT,theC++compilerhastoconcludethatTmustbe
int.
However,Tmightonlybe“part”ofthetype.Forexample,ifwedeclare
max()touseconstantreferences:Clickheretoviewcodeimage
template<typenameT>
Tmax(Tconst&a,Tconst&b)
{
returnb<a?a:b;
}
andpassint,againTisdeducedasint,becausethefunctionparameters
matchforintconst&.
TypeConversionsDuringTypeDeduction
Notethatautomatictypeconversionsarelimitedduringtypededuction:•When
declaringcallparametersbyreference,eventrivialconversionsdonotapplyto
typededuction.TwoargumentsdeclaredwiththesametemplateparameterT
mustmatchexactly.
•Whendeclaringcallparametersbyvalue,onlytrivialconversionsthatdecay
aresupported:Qualificationswithconstorvolatileareignored,
referencesconverttothereferencedtype,andrawarraysorfunctionsconvert
tothecorrespondingpointertype.Fortwoargumentsdeclaredwiththesame
templateparameterTthedecayedtypesmustmatch.
Forexample:
Clickheretoviewcodeimage
template<typenameT>
Tmax(Ta,Tb);
…
intconstc=42;
max(i,c);//OK:Tisdeducedasint
max(c,c);//OK:Tisdeducedasint
int&ir=i;
max(i,ir);//OK:Tisdeducedasint
intarr[4];
foo(&i,arr);//OK:Tisdeducedasint*
However,thefollowingareerrors:Clickheretoviewcodeimage
max(4,7.2);//ERROR:Tcanbededucedasintordouble
std::strings;
foo("hello",s);//ERROR:Tcanbededucedascharconst[6]or
std::string
Therearethreewaystohandlesucherrors:1.Casttheargumentssothatthey
bothmatch:Clickheretoviewcodeimage
max(static_cast<double>(4),7.2);//OK
2.Specify(orqualify)explicitlythetypeofTtopreventthecompilerfrom
attemptingtypededuction:Clickheretoviewcodeimage
max<double>(4,7.2);//OK
3.Specifythattheparametersmayhavedifferenttypes.
Section1.3onpage9willelaborateontheseoptions.Section7.2onpage108
andChapter15willdiscusstherulesfortypeconversionsduringtypededuction
indetail.
TypeDeductionforDefaultArguments
Notealsothattypedeductiondoesnotworkfordefaultcallarguments.For
example:Clickheretoviewcodeimage
template<typenameT>
voidf(T="");
…
Clickheretoviewcodeimage
f(1);//OK:deducedTtobeint,sothatitcallsf<int>(1)
f();//ERROR:cannotdeduceT
Tosupportthiscase,youalsohavetodeclareadefaultargumentforthetemplate
parameter,whichwillbediscussedinSection1.4onpage13:Clickheretoview
codeimage
template<typenameT=std::string>
voidf(T="");
…
f();//OK
1.3MultipleTemplateParameters
Aswehaveseensofar,functiontemplateshavetwodistinctsetsofparameters:
1.Templateparameters,whicharedeclaredinanglebracketsbeforethefunction
templatename:Clickheretoviewcodeimage
template<typenameT>//Tistemplateparameter
2.Callparameters,whicharedeclaredinparenthesesafterthefunctiontemplate
name:Clickheretoviewcodeimage
Tmax(Ta,Tb)//aandbarecallparameters
Youmayhaveasmanytemplateparametersasyoulike.Forexample,youcould
definethemax()templateforcallparametersoftwopotentiallydifferenttypes:
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
T1max(T1a,T2b)
{
returnb<a?a:b;}
…
autom=::max(4,7.2);//OK,buttypeoffirstargumentdefines
returntype
Itmayappeardesirabletobeabletopassparametersofdifferenttypestothe
max()template,but,asthisexampleshows,itraisesaproblem.Ifyouuseone
oftheparametertypesasreturntype,theargumentfortheotherparametermight
getconvertedtothistype,regardlessofthecaller’sintention.Thus,thereturn
typedependsonthecallargumentorder.Themaximumof66.66and42willbe
thedouble66.66,whilethemaximumof42and66.66willbetheint66.
C++providesdifferentwaystodealwiththisproblem:•Introduceathird
templateparameterforthereturntype.
•Letthecompilerfindoutthereturntype.
•Declarethereturntypetobethe“commontype”ofthetwoparametertypes.
Alltheseoptionsarediscussednext.
1.3.1TemplateParametersforReturnTypes
Ourearlierdiscussionshowedthattemplateargumentdeductionallowsustocall
functiontemplateswithsyntaxidenticaltothatofcallinganordinaryfunction:
Wedonothavetoexplicitlyspecifythetypescorrespondingtothetemplate
parameters.
Wealsomentioned,however,thatwecanspecifythetypestouseforthe
templateparametersexplicitly:Clickheretoviewcodeimage
template<typenameT>
Tmax(Ta,Tb);
…
::max<double>(4,7.2);//instantiateTasdouble
Incaseswhenthereisnoconnectionbetweentemplateandcallparametersand
whentemplateparameterscannotbedetermined,youmustspecifythetemplate
argumentexplicitlywiththecall.Forexample,youcanintroduceathird
templateargumenttypetodefinethereturntypeofafunctiontemplate:Click
heretoviewcodeimage
template<typenameT1,typenameT2,typenameRT>
RTmax(T1a,T2b);
However,templateargumentdeductiondoesnottakereturntypesintoaccount,7
andRTdoesnotappearinthetypesofthefunctioncallparameters.Therefore,
RTcannotbededuced.8
Asaconsequence,youhavetospecifythetemplateargumentlistexplicitly.
Forexample:Clickheretoviewcodeimage
template<typenameT1,typenameT2,typenameRT>
RTmax(T1a,T2b);
…
::max<int,double,double>(4,7.2);//OK,buttedious
Sofar,wehavelookedatcasesinwhicheitherallornoneofthefunction
templateargumentswerementionedexplicitly.Anotherapproachistospecify
onlythefirstargumentsexplicitlyandtoallowthedeductionprocesstoderive
therest.Ingeneral,youmustspecifyalltheargumenttypesuptothelast
argumenttypethatcannotbedeterminedimplicitly.Thus,ifyouchangethe
orderofthetemplateparametersinourexample,thecallerneedstospecifyonly
thereturntype:Clickheretoviewcodeimage
template<typenameRT,typenameT1,typenameT2>
RTmax(T1a,T2b);
…
::max<double>(4,7.2)//OK:returntypeisdouble,T1andT2are
deduced
Inthisexample,thecalltomax<double>explicitlysetsRTtodouble,but
theparametersT1andT2arededucedtobeintanddoublefromthe
arguments.
Notethatthesemodifiedversionsofmax()don’tleadtosignificant
advantages.Fortheone-parameterversionyoucanalreadyspecifytheparameter
(andreturn)typeiftwoargumentsofadifferenttypearepassed.Thus,it’sa
goodideatokeepitsimpleandusetheone-parameterversionofmax()(aswe
dointhefollowingsectionswhendiscussingothertemplateissues).
SeeChapter15fordetailsofthedeductionprocess.
1.3.2DeducingtheReturnType
Ifareturntypedependsontemplateparameters,thesimplestandbestapproach
todeducethereturntypeistoletthecompilerfindout.SinceC++14,thisis
possiblebysimplynotdeclaringanyreturntype(youstillhavetodeclarethe
returntypetobeauto):Clickheretoviewcodeimage
basics/maxauto.hpp
template<typenameT1,typenameT2>
automax(T1a,T2b)
{
returnb<a?a:b;
}
Infact,theuseofautoforthereturntypewithoutacorrespondingtrailing
returntype(whichwouldbeintroducedwitha->attheend)indicatesthatthe
actualreturntypemustbededucedfromthereturnstatementsinthefunction
body.Ofcourse,deducingthereturntypefromthefunctionbodyhastobe
possible.Therefore,thecodemustbeavailableandmultiplereturnstatements
havetomatch.
BeforeC++14,itisonlypossibletoletthecompilerdeterminethereturntype
bymoreorlessmakingtheimplementationofthefunctionpartofits
declaration.InC++11wecanbenefitfromthefactthatthetrailingreturntype
syntaxallowsustousethecallparameters.Thatis,wecandeclarethatthe
returntypeisderivedfromwhatoperator?:yields:Clickheretoviewcode
image
basics/maxdecltype.hpp
template<typenameT1,typenameT2>
automax(T1a,T2b)->decltype(b<a?a:b)
{
returnb<a?a:b;
}
Here,theresultingtypeisdeterminedbytherulesforoperator?:,whichare
fairlyelaboratebutgenerallyproduceanintuitivelyexpectedresult(e.g.,ifa
andbhavedifferentarithmetictypes,acommonarithmetictypeisfoundforthe
result).
Notethat
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
automax(T1a,T2b)->decltype(b<a?a:b);isadeclaration,sothat
thecompilerusestherulesofoperator?:calledforparametersaand
btofindoutthereturntypeofmax()atcompiletime.The
implementationdoesnotnecessarilyhavetomatch.Infact,using
trueastheconditionforoperator?:inthedeclarationisenough:
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
automax(T1a,T2b)->decltype(true?a:b);However,inanycase
thisdefinitionhasasignificantdrawback:Itmighthappenthatthe
returntypeisareferencetype,becauseundersomeconditionsT
mightbeareference.Forthisreasonyoushouldreturnthetype
decayedfromT,whichlooksasfollows:Clickheretoviewcodeimage
basics/maxdecltypedecay.hpp
#include<type_traits>
template<typenameT1,typenameT2>
automax(T1a,T2b)->typenamestd::decay<decltype(true?a:b)>::type
{returnb<a?a:b;
}
Here,thetypetraitstd::decay<>isused,whichreturnstheresultingtypein
amembertype.Itisdefinedbythestandardlibraryin<type_trait>(see
SectionD.5onpage732).Becausethemembertypeisatype,youhaveto
qualifytheexpressionwithtypenametoaccessit(seeSection5.1onpage67).
Notethataninitializationoftypeautoalwaysdecays.Thisalsoappliesto
returnvalueswhenthereturntypeisjustauto.autoasareturntypebehaves
justasinthefollowingcode,whereaisdeclaredbythedecayedtypeofi,int:
Clickheretoviewcodeimage
inti=42;
intconst&ir=i;//irrefersto
iautoa=ir;//aisdeclaredasnewobjectoftypeint
1.3.3ReturnTypeasCommonType
SinceC++11,theC++standardlibraryprovidesameanstospecifychoosing
“themoregeneraltype.”std::common_type<>::typeyieldsthe
“commontype”oftwo(ormore)differenttypespassedastemplatearguments.
Forexample:Clickheretoviewcodeimage
basics/maxcommon.hpp
#include<type_traits>
template<typenameT1,typenameT2>
std::common_type_t<T1,T2>max(T1a,T2b)
{
returnb<a?a:b;
}
Again,std::common_typeisatypetrait,definedin<type_traits>,
whichyieldsastructurehavingatypememberfortheresultingtype.Thus,its
coreusageisasfollows:Clickheretoviewcodeimage
typenamestd::common_type<T1,T2>::type//sinceC++11
However,sinceC++14youcansimplifytheusageoftraitslikethisby
appending_ttothetraitnameandskippingtypenameand::type(see
Section2.8onpage40fordetails),sothatthereturntypedefinitionsimply
becomes:Clickheretoviewcodeimage
std::common_type_t<T1,T2>//equivalentsinceC++14
Thewaystd::common_type<>isimplementedusessometrickytemplate
programming,whichisdiscussedinSection26.5.2onpage622.Internally,it
choosestheresultingtypeaccordingtothelanguagerulesofoperator?:or
specializationsforspecifictypes.Thus,both::max(4,7.2)and
::max(7.2,4)yieldthesamevalue7.2oftypedouble.Notethat
std::common_type<>alsodecays.SeeSectionD.5onpage732fordetails.
1.4DefaultTemplateArguments
Youcanalsodefinedefaultvaluesfortemplateparameters.Thesevaluesare
calleddefaulttemplateargumentsandcanbeusedwithanykindoftemplate.9
Theymayevenrefertoprevioustemplateparameters.
Forexample,ifyouwanttocombinetheapproachestodefinethereturntype
withtheabilitytohavemultipleparametertypes(asdiscussedinthesection
before),youcanintroduceatemplateparameterRTforthereturntypewiththe
commontypeofthetwoargumentsasdefault.Again,wehavemultipleoptions:
1.Wecanuseoperator?:directly.However,becausewehavetoapply
operator?:beforethecallparametersaandbaredeclared,wecanonlyuse
theirtypes:Clickheretoviewcodeimage
basics/maxdefault1.hpp
#include<type_traits>
template<typenameT1,typenameT2,
typenameRT=std::decay_t<decltype(true?T1():T2())>>
RTmax(T1a,T2b)
{
returnb<a?a:b;
}
Noteagaintheusageofstd::decay_t<>toensurethatnoreferencecanbe
returned.10
Notealsothatthisimplementationrequiresthatweareabletocalldefault
constructorsforthepassedtypes.Thereisanothersolution,using
std::declval,which,however,makesthedeclarationevenmore
complicated.SeeSection11.2.3onpage166foranexample.
2.Wecanalsousethestd::common_type<>typetraittospecifythedefault
valueforthereturntype:Clickheretoviewcodeimage
basics/maxdefault3.hpp
#include<type_traits>
template<typenameT1,typenameT2,
typenameRT=std::common_type_t<T1,T2>>
RTmax(T1a,T2b)
{
returnb<a?a:b;
}
Again,notethatstd::common_type<>decayssothatthereturnvalue
can’tbecomeareference.
Inallcases,asacaller,youcannowusethedefaultvalueforthereturntype:
Clickheretoviewcodeimage
autoa=::max(4,7.2);orspecifythereturntypeafterallother
argumenttypesexplicitly:Clickheretoviewcodeimage
autob=::max<double,int,longdouble>(7.2,4);However,againwe
havetheproblemthatwehavetospecifythreetypestobeableto
specifythereturntypeonly.Instead,wewouldneedtheabilityto
havethereturntypeasthefirsttemplateparameter,whilestill
beingabletodeduceitfromtheargumenttypes.Inprinciple,itis
possibletohavedefaultargumentsforleadingfunctiontemplate
parametersevenifparameterswithoutdefaultargumentsfollow:Click
heretoviewcodeimage
template<typenameRT=long,typenameT1,typenameT2>
RTmax(T1a,T2b)
{
returnb<a?a:b;
}
Withthisdefinition,forexample,youcancall:Clickheretoviewcodeimage
inti;longl;
…
max(i,l);//returnslong(defaultargumentoftemplateparameter
forreturntype)
max<int>(4,42);//returnsintasexplicitlyrequested
However,thisapproachonlymakessense,ifthereisa“natural”defaultfora
templateparameter.Here,weneedthedefaultargumentforthetemplate
parametertodependfromprevioustemplateparameters.Inprinciple,thisis
possibleaswediscussinSection26.5.1onpage621,butthetechniquedepends
ontypetraitsandcomplicatesthedefinition.
Forallthesereasons,thebestandeasiestsolutionistoletthecompilerdeduce
thereturntypeasproposedinSection1.3.2onpage11.
1.5OverloadingFunctionTemplates
Likeordinaryfunctions,functiontemplatescanbeoverloaded.Thatis,youcan
havedifferentfunctiondefinitionswiththesamefunctionnamesothatwhen
thatnameisusedinafunctioncall,aC++compilermustdecidewhichoneof
thevariouscandidatestocall.Therulesforthisdecisionmaybecomerather
complicated,evenwithouttemplates.Inthissectionwediscussoverloading
whentemplatesareinvolved.Ifyouarenotfamiliarwiththebasicrulesof
overloadingwithouttemplates,pleaselookatAppendixC,whereweprovidea
reasonablydetailedsurveyoftheoverloadresolutionrules.
Thefollowingshortprogramillustratesoverloadingafunctiontemplate:Click
heretoviewcodeimage
basics/max2.cpp
//maximumoftwointvalues:
intmax(inta,
intb)
{
returnb<a?a:b;
}
//maximumoftwovaluesofanytype:
template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
intmain()
{
::max(7,42);//callsthenontemplatefortwoints
::max(7.0,42.0);//callsmax<double>(byargumentdeduction)
::max(’a’,’b’);//callsmax<char>(byargumentdeduction)
::max<>(7,42);//callsmax<int>(byargumentdeduction)
::max<double>(7,42);//callsmax<double>(noargumentdeduction)
::max(’a’,42.7);//callsthenontemplatefortwoints
}
Asthisexampleshows,anontemplatefunctioncancoexistwithafunction
templatethathasthesamenameandcanbeinstantiatedwiththesametype.All
otherfactorsbeingequal,theoverloadresolutionprocessprefersthe
nontemplateoveronegeneratedfromthetemplate.Thefirstcallfallsunderthis
rule:Clickheretoviewcodeimage
::max(7,42);//bothintvaluesmatchthenontemplatefunction
perfectly
Ifthetemplatecangenerateafunctionwithabettermatch,however,thenthe
templateisselected.Thisisdemonstratedbythesecondandthirdcallsof
max():Clickheretoviewcodeimage
::max(7.0,42.0);//callsthemax<double>(byargumentdeduction)
::max(’a’,’b’);//callsthemax<char>(byargumentdeduction)
Here,thetemplateisabettermatchbecausenoconversionfromdoubleor
chartointisrequired(seeSectionC.2onpage682fortherulesofoverload
resolution).
Itisalsopossibletospecifyexplicitlyanemptytemplateargumentlist.This
syntaxindicatesthatonlytemplatesmayresolveacall,butallthetemplate
parametersshouldbededucedfromthecallarguments:Clickheretoviewcode
image
::max<>(7,42);//callsmax<int>(byargumentdeduction)
Becauseautomatictypeconversionisnotconsideredfordeducedtemplate
parametersbutisconsideredforordinaryfunctionparameters,thelastcalluses
thenontemplatefunction(while’a’and42.7bothareconvertedtoint):
Clickheretoviewcodeimage
::max(’a’,42.7);//onlythenontemplatefunctionallowsnontrivial
conversions
Aninterestingexamplewouldbetooverloadthemaximumtemplatetobeable
toexplicitlyspecifythereturntypeonly:Clickheretoviewcodeimage
basics/maxdefault4.hpp
template<typenameT1,typenameT2>
automax(T1a,T2b)
{
returnb<a?a:b;
}
template<typenameRT,typenameT1,typenameT2>
RTmax(T1a,T2b)
{
returnb<a?a:b;
}
Now,wecancallmax(),forexample,asfollows:Clickheretoviewcode
image
autoa=::max(4,7.2);//usesfirsttemplate
autob=::max<longdouble>(7.2,4);//usessecondtemplate
However,whencalling:
Clickheretoviewcodeimage
autoc=::max<int>(4,7.2);//ERROR:bothfunctiontemplatesmatch
bothtemplatesmatch,whichcausestheoverloadresolutionprocessnormallyto
prefernoneandresultinanambiguityerror.Thus,whenoverloadingfunction
templates,youshouldensurethatonlyoneofthemmatchesforanycall.
Ausefulexamplewouldbetooverloadthemaximumtemplateforpointers
andordinaryC-strings:Clickheretoviewcodeimage
basics/max3val.cpp
#include<cstring>
#include<string>
//maximumoftwovaluesofanytype:
template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
//maximumoftwopointers:
template<typenameT>
T*max(T*a,T*b)
{
return*b<*a?a:b;
}
//maximumoftwoC-strings:
charconst*max(charconst*a,charconst*b)
{
returnstd::strcmp(b,a)<0?a:b;
}
intmain()
{
inta=7;
intb=42;
autom1=::max(a,b);//max()fortwovaluesoftypeint
std::strings1="hey";"
std::strings2="you";"
autom2=::max(s1,s2);//max()fortwovaluesoftypestd::string
int*p1=&b;
int*p2=&a;
autom3=::max(p1,p2);//max()fortwopointers
charconst*x=hello";"
charconst*y="world";"
autom4=::max(x,y);//max()fortwoC-strings
}
Notethatinalloverloadsofmax()wepasstheargumentsbyvalue.Ingeneral,
itisagoodideanottochangemorethannecessarywhenoverloadingfunction
templates.Youshouldlimityourchangestothenumberofparametersorto
specifyingtemplateparametersexplicitly.Otherwise,unexpectedeffectsmay
happen.Forexample,ifyouimplementyourmax()templatetopassthe
argumentsbyreferenceandoverloaditfortwoC-stringspassedbyvalue,you
can’tusethethree-argumentversiontocomputethemaximumofthreeC-
strings:Clickheretoviewcodeimage
basics/max3ref.cpp
#include<cstring>
//maximumoftwovaluesofanytype(call-by-reference)
template<typename
T>Tconst&max(Tconst&a,Tconst&b)
{
returnb<a?a:b;
}
//maximumoftwoC-strings(call-by-value)
charconst*max(charconst*a,charconst*b)
{
returnstd::strcmp(b,a)<0?a:b;
}
//maximumofthreevaluesofanytype(call-by-reference)
template<typenameT>
Tconst&max(Tconst&a,Tconst&b,Tconst&c)
{
returnmax(max(a,b),c);//errorifmax(a,b)usescall-by-value
}
intmain()
{
autom1=::max(7,42,68);//OK
charconst*s1="frederic";
charconst*s2="anica";
charconst*s3="lucas";
autom2=::max(s1,s2,s3);//run-timeERROR
}
Theproblemisthatifyoucallmax()forthreeC-strings,thestatementClick
heretoviewcodeimage
returnmax(max(a,b),c);becomesarun-timeerrorbecauseforC-
strings,max(a,b)createsanew,temporarylocalvaluethatis
returnedbyreference,butthattemporaryvalueexpiresassoonas
thereturnstatementiscomplete,leavingmain()withadangling
reference.Unfortunately,theerrorisquitesubtleandmaynot
manifestitselfinallcases.11
Note,incontrast,thatthefirstcalltomax()inmain()doesn’tsufferfrom
thesameissue.Theretemporariesarecreatedforthearguments(7,42,and68),
butthosetemporariesarecreatedinmain()wheretheypersistuntilthe
statementisdone.
Thisisonlyoneexampleofcodethatmightbehavedifferentlythanexpected
asaresultofdetailedoverloadresolutionrules.Inaddition,ensurethatall
overloadedversionsofafunctionaredeclaredbeforethefunctioniscalled.This
isbecausethefactthatnotalloverloadedfunctionsarevisiblewhena
correspondingfunctioncallismademaymatter.Forexample,definingathree-
argumentversionofmax()withouthavingseenthedeclarationofaspecial
two-argumentversionofmax()forintscausesthetwo-argumenttemplateto
beusedbythethree-argumentversion:basics/max4.cpp
Clickheretoviewcodeimage
#include<iostream>
//maximumoftwovaluesofanytype:
template<typenameT>
Tmax(Ta,Tb)
{
std::cout<<"max<T>()\n";
returnb<a?a:b;
}
//maximumofthreevaluesofanytype:
template<typenameT>
Tmax(Ta,Tb,Tc)
{
returnmax(max(a,b),c);//usesthetemplateversionevenforints
}//becausethefollowingdeclarationcomes
//toolate:
//maximumoftwointvalues:
intmax(inta,
intb)
{
std::cout<<"max(int,int)\n";
returnb<a?a:b;
}
intmain()
{
::max(47,11,33);//OOPS:usesmax<T>()insteadofmax(int,int)
}
WediscussdetailsinSection13.2onpage217.
1.6But,Shouldn’tWe…?
Probably,eventhesesimplefunctiontemplateexamplesmightraisefurther
questions.Threequestionsareprobablysocommonthatweshoulddiscussthem
herebriefly.
1.6.1PassbyValueorbyReference?
Youmightwonder,whyweingeneraldeclarethefunctionstopassthe
argumentsbyvalueinsteadofusingreferences.Ingeneral,passingbyreference
isrecommendedfortypesotherthancheapsimpletypes(suchasfundamental
typesorstd::string_view),becausenounnecessarycopiesarecreated.
However,foracoupleofreasons,passingbyvalueingeneralisoftenbetter:•
Thesyntaxissimple.
•Compilersoptimizebetter.
•Movesemanticsoftenmakescopiescheap.
•Andsometimesthereisnocopyormoveatall.
Inaddition,fortemplates,specificaspectscomeintoplay:•Atemplatemightbe
usedforbothsimpleandcomplextypes,sochoosingtheapproachforcomplex
typesmightbecounter-productiveforsimpletypes.
•Asacalleryoucanoftenstilldecidetopassargumentsbyreference,using
std::ref()andstd::cref()(seeSection7.3onpage112).
•Althoughpassingstringliteralsorrawarraysalwayscanbecomeaproblem,
passingthembyreferenceoftenisconsideredtobecomethebiggerproblem.
AllthiswillbediscussedindetailinChapter7.Forthemomentinsidethe
bookwewillusuallypassargumentsbyvalueunlesssomefunctionalityisonly
possiblewhenusingreferences.
1.6.2WhyNotinline?
Ingeneral,functiontemplatesdon’thavetobedeclaredwithinline.Unlike
ordinarynoninlinefunctions,wecandefinenoninlinefunctiontemplatesina
headerfileandincludethisheaderfileinmultipletranslationunits.
Theonlyexceptiontothisrulearefullspecializationsoftemplatesforspecific
types,sothattheresultingcodeisnolongergeneric(alltemplateparametersare
defined).SeeSection9.2onpage140formoredetails.
Fromastrictlanguagedefinitionperspective,inlineonlymeansthata
definitionofafunctioncanappearmultipletimesinaprogram.However,itis
alsomeantasahinttothecompilerthatcallstothatfunctionshouldbe
“expandedinline”:Doingsocanproducemoreefficientcodeforcertaincases,
butitcanalsomakethecodelessefficientformanyothercases.Nowadays,
compilersusuallyarebetteratdecidingthiswithoutthehintimpliedbythe
inlinekeyword.However,compilersstillaccountforthepresenceof
inlineinthatdecision.
1.6.3WhyNotconstexpr?
SinceC++11,youcanuseconstexprtoprovidetheabilitytousecodeto
computesomevaluesatcompiletime.Foralotoftemplatesthismakessense.
Forexample,tobeabletousethemaximumfunctionatcompiletime,you
havetodeclareitasfollows:Clickheretoviewcodeimage
basics/maxconstexpr.hpp
template<typenameT1,typenameT2>
constexprautomax(T1a,T2b)
{
returnb<a?a:b;
}
Withthis,youcanusethemaximumfunctiontemplateinplaceswithcompile-
timecontext,suchaswhendeclaringthesizeofarawarray:Clickheretoview
codeimage
inta[::max(sizeof(char),1000u)];orthesizeofastd::array<>:
Clickheretoviewcodeimage
std::array<std::string,::max(sizeof(char),1000u)>arr;Notethatwe
pass1000asunsignedinttoavoidwarningsaboutcomparingasigned
withanunsignedvalueinsidethetemplate.
Section8.2onpage125willdiscussotherexamplesofusingconstexpr.
However,tokeepourfocusonthefundamentals,weusuallywillskip
constexprwhendiscussingothertemplatefeatures.
1.7Summary
•Functiontemplatesdefineafamilyoffunctionsfordifferenttemplate
arguments.
•Whenyoupassargumentstofunctionparametersdependingontemplate
parameters,functiontemplatesdeducethetemplateparameterstobe
instantiatedforthecorrespondingparametertypes.
•Youcanexplicitlyqualifytheleadingtemplateparameters.
•Youcandefinedefaultargumentsfortemplateparameters.Thesemayreferto
previoustemplateparametersandbefollowedbyparametersnothaving
defaultarguments.
•Youcanoverloadfunctiontemplates.
•Whenoverloadingfunctiontemplateswithotherfunctiontemplates,you
shouldensurethatonlyoneofthemmatchesforanycall.
•Whenyouoverloadfunctiontemplates,limityourchangestospecifying
templateparametersexplicitly.
•Ensurethecompilerseesalloverloadedversionsoffunctiontemplatesbefore
youcallthem.
1Notethatthemax()templateaccordingto[StepanovNotes]intentionally
returns“b<a?a:b”insteadof“a<b?b:a”toensurethat
thefunctionbehavescorrectlyevenifthetwovaluesareequivalentbutnot
equal.
2BeforeC++17,typeTalsohadtobecopyabletobeabletopassinarguments,
butsinceC++17youcanpasstemporaries(rvalues,seeAppendixB)evenif
neitheracopynoramoveconstructorisvalid.
3Forexample,ifoneargumenttypeisdefinedinnamespacestd(suchas
std::string),accordingtothelookuprulesofC++,boththeglobaland
themax()templateinstdarefound(seeAppendixC).
4A“one-entity-fits-all”alternativeisconceivablebutnotusedinpractice(it
wouldbelessefficientatruntime).Alllanguagerulesarebasedonthe
principlethatdifferententitiesaregeneratedfordifferenttemplatearguments.
5Thetermsinstanceandinstantiateareusedinadifferentcontextinobject-
orientedprogramming—namely,foraconcreteobjectofaclass.However,
becausethisbookisabouttemplates,weusethistermforthe“use”of
templatesunlessotherwisespecified.
6Forexample,TheVisualC++compilerinsomeversions(suchasVisual
Studio20133and2015)allowundeclarednamesthatdon’tdependon
templateparametersandevensomesyntaxflaws(suchasamissing
semicolon).
7Deductioncanbeseenaspartofoverloadresolution—aprocessthatisnot
basedonselectionofreturntypeseither.Thesoleexceptionisthereturntype
ofconversionoperatormembers.
8InC++,thereturntypealsocannotbededucedfromthecontextinwhichthe
callerusesthecall.
9PriortoC++11,defaulttemplateargumentswereonlypermittedinclass
templates,duetoahistoricalglitchinthedevelopmentoffunctiontemplates.
10Again,inC++11youhadtousetypenamestd::decay<…>::type
insteadofstd::decay_t<…>(seeSection2.8onpage40).
11Ingeneral,aconformingcompilerisn’tevenpermittedtorejectthiscode.
Chapter2
ClassTemplates
Similartofunctions,classescanalsobeparameterizedwithoneormoretypes.
Containerclasses,whichareusedtomanageelementsofacertaintype,area
typicalexampleofthisfeature.Byusingclasstemplates,youcanimplement
suchcontainerclasseswhiletheelementtypeisstillopen.Inthischapterweuse
astackasanexampleofaclasstemplate.
2.1ImplementationofClassTemplateStack
Aswedidwithfunctiontemplates,wedeclareanddefineclassStack<>ina
headerfileasfollows:Clickheretoviewcodeimage
basics/stack1.hpp
#include<vector>
#include<cassert>
template<typenameT>
classStack{
private:
std::vector<T>elems;//elements
public:
voidpush(Tconst&elem);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
};
template<typenameT>
voidStack<T>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem}
template<typenameT>
voidStack<T>::pop()
{
assert(!elems.empty());
elems.pop_back();//removelastelement
}
template<typenameT>
Tconst&Stack<T>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
Asyoucansee,theclasstemplateisimplementedbyusingaclasstemplateof
theC++standardlibrary:vector<>.Asaresult,wedon’thavetoimplement
memorymanagement,copyconstructor,andassignmentoperator,sowecan
concentrateontheinterfaceofthisclasstemplate.
2.1.1DeclarationofClassTemplates
Declaringclasstemplatesissimilartodeclaringfunctiontemplates:Beforethe
declaration,youhavetodeclareoneormultipleidentifiersasatype
parameter(s).Again,Tisusuallyusedasanidentifier:Clickheretoviewcode
image
template<typenameT>
classStack{
…
};
Hereagain,thekeywordclasscanbeusedinsteadoftypename:Clickhere
toviewcodeimage
template<classT>
classStack{
…
};
Insidetheclasstemplate,Tcanbeusedjustlikeanyothertypetodeclare
membersandmemberfunctions.Inthisexample,Tisusedtodeclarethetypeof
theelementsasvectorofTs,todeclarepush()asamemberfunctionthatuses
aTasanargument,andtodeclaretop()asafunctionthatreturnsaT:Click
heretoviewcodeimage
template<
typenameT>
classStack{
private:
std::vector<T>elems;//elements
public:
voidpush(Tconst&elem);//pushelement
voidpop();//popelement
Tconst&top()
const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
};
ThetypeofthisclassisStack<T>,withTbeingatemplateparameter.Thus,
youhavetouseStack<T>wheneveryouusethetypeofthisclassina
declarationexceptincaseswherethetemplateargumentscanbededuced.
However,insideaclasstemplateusingtheclassnamenotfollowedbytemplate
argumentsrepresentstheclasswithitstemplateparametersasitsarguments(see
Section13.2.3onpage221fordetails).
If,forexample,youhavetodeclareyourowncopyconstructorand
assignmentoperator,ittypicallylookslikethis:Clickheretoviewcodeimage
template<typenameT>
classStack{
…
Stack(Stackconst&);//copyconstructor
Stack&operator=(Stackconst&);//assignmentoperator
…
};
whichisformallyequivalentto:Clickheretoviewcodeimage
template<typenameT>
classStack{
…
Stack(Stack<T>const&);//copyconstructor
Stack<T>&operator=(Stack<T>const&);//assignmentoperator
…
};
butusuallythe<T>signalsspecialhandlingofspecialtemplateparameters,so
it’susuallybettertousethefirstform.
However,outsidetheclassstructureyou’dneed:Clickheretoviewcode
image
template<typenameT>
booloperator==(Stack<T>const&lhs,Stack<T>const&rhs);Notethat
inplaceswherethenameandnotthetypeoftheclassisrequired,
onlyStackmaybeused.Thisisespeciallythecasewhenyouspecify
thenameofconstructors(nottheirarguments)andthedestructor.
Notealsothat,unlikenontemplateclasses,youcan’tdeclareordefineclass
templatesinsidefunctionsorblockscope.Ingeneral,templatescanonlybe
definedinglobal/namespacescopeorinsideclassdeclarations(seeSection12.1
onpage177fordetails).
2.1.2ImplementationofMemberFunctions
Todefineamemberfunctionofaclasstemplate,youhavetospecifythatitisa
template,andyouhavetousethefulltypequalificationoftheclasstemplate.
Thus,theimplementationofthememberfunctionpush()fortypeStack<T>
lookslikethis:Clickheretoviewcodeimage
template<typenameT>
voidStack<T>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
Inthiscase,push_back()oftheelementvectoriscalled,whichappendsthe
elementattheendofthevector.
Notethatpop_back()ofavectorremovesthelastelementbutdoesn’t
returnit.Thereasonforthisbehaviorisexceptionsafety.Itisimpossibleto
implementacompletelyexception-safeversionofpop()thatreturnsthe
removedelement(thistopicwasfirstdiscussedbyTomCargillin
[CargillExceptionSafety]andisdiscussedasItem10in[SutterExceptional]).
However,ignoringthisdanger,wecouldimplementapop()thatreturnsthe
elementjustremoved.Todothis,wesimplyuseTtodeclarealocalvariableof
theelementtype:Clickheretoviewcodeimage
template<typenameT>
TStack<T>::pop()
{
assert(!elems.empty());
Telem=elems.back();//savecopyoflastelement
elems.pop_back();//removelastelement
returnelem;//returncopyofsavedelement
}
Becauseback()(whichreturnsthelastelement)andpop_back()(which
removesthelastelement)haveundefinedbehaviorwhenthereisnoelementin
thevector,wedecidedtocheckwhetherthestackisempty.Ifitisempty,we
assert,becauseitisausageerrortocallpop()onanemptystack.Thisisalso
doneintop(),whichreturnsbutdoesnotremovethetopelement,onattempts
toremoveanonexistenttopelement:Clickheretoviewcodeimage
template<typenameT>
Tconst&Stack<T>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
Ofcourse,asforanymemberfunction,youcanalsoimplementmember
functionsofclasstemplatesasaninlinefunctioninsidetheclassdeclaration.For
example:Clickheretoviewcodeimage
template<typenameT>
classStack{
…
voidpush(Tconst&elem){
elems.push_back(elem);//appendcopyofpassedelem
}
…
};
2.2UseofClassTemplateStack
Touseanobjectofaclasstemplate,untilC++17youmustalwaysspecifythe
templateargumentsexplicitly.1Thefollowingexampleshowshowtousethe
classtemplateStack<>:Clickheretoviewcodeimage
basics/stack1test.cpp
#include"stack1.hpp"
#include<iostream>
#include<string>
intmain()
{
Stack<int>intStack;//stackofints
Stack<std::string>stringStack;//stackofstrings
//manipulateintstack
intStack.push(7);
std::cout<<intStack.top()<<’\n’;
//manipulatestringstack
stringStack.push("hello");
std::cout<<stringStack.top()<<’\n’;
stringStack.pop();
}
BydeclaringtypeStack<int>,intisusedastypeTinsidetheclass
template.Thus,intStackiscreatedasanobjectthatusesavectorofintsas
elementsand,forallmemberfunctionsthatarecalled,codeforthistypeis
instantiated.Similarly,bydeclaringandusingStack<std::string>,an
objectthatusesavectorofstringsaselementsiscreated,andforallmember
functionsthatarecalled,codeforthistypeisinstantiated.
Notethatcodeisinstantiatedonlyfortemplate(member)functionsthatare
called.Forclasstemplates,memberfunctionsareinstantiatedonlyiftheyare
used.This,ofcourse,savestimeandspaceandallowsuseofclasstemplates
onlypartially,whichwewilldiscussinSection2.3onpage29.
Inthisexample,thedefaultconstructor,push(),andtop()areinstantiated
forbothintandstrings.However,pop()isinstantiatedonlyforstrings.Ifa
classtemplatehasstaticmembers,thesearealsoinstantiatedonceforeachtype
forwhichtheclasstemplateisused.
Aninstantiatedclasstemplate’stypecanbeusedjustlikeanyothertype.You
canqualifyitwithconstorvolatileorderivearrayandreferencetypes
fromit.Youcanalsouseitaspartofatypedefinitionwithtypedeforusing
(seeSection2.8onpage38fordetailsabouttypedefinitions)oruseitasatype
parameterwhenbuildinganothertemplatetype.Forexample:Clickheretoview
codeimage
voidfoo(Stack<int>const&s)//parametersisintstack
{
usingIntStack=Stack<int>;//IntStackisanothernamefor
Stack<int>
Stack<int>istack[10];//istackisarrayof10intstacks
IntStackistack2[10];//istack2isalsoanarrayof10intstacks
(sametype)
…
}
Templateargumentsmaybeanytype,suchaspointerstofloatsorevenstacks
ofints:Clickheretoviewcodeimage
Stack<float*>floatPtrStack;//stackoffloatpointers
Stack<Stack<int>>intStackStack;//stackofstackofints
Theonlyrequirementisthatanyoperationthatiscalledispossibleaccordingto
thistype.
NotethatbeforeC++11youhadtoputwhitespacebetweenthetwoclosing
templatebrackets:Clickheretoviewcodeimage
Stack<Stack<int>>intStackStack;//OKwithallC++versions
Ifyoudidn’tdothis,youwereusingoperator>>,whichresultedinasyntax
error:Clickheretoviewcodeimage
Stack<Stack<int>>intStackStack;//ERRORbeforeC++11
ThereasonfortheoldbehaviorwasthatithelpedthefirstpassofaC++
compilertotokenizethesourcecodeindependentofthesemanticsofthecode.
However,becausethemissingspacewasatypicalbug,whichrequired
correspondingerrormessages,thesemanticsofthecodemoreandmorehadto
gettakenintoaccountanyway.So,withC++11theruletoputaspacebetween
twoclosingtemplatebracketswasremovedwiththe“anglebrackethack”(see
Section13.3.1onpage226fordetails).
2.3PartialUsageofClassTemplates
Aclasstemplateusuallyappliesmultipleoperationsonthetemplateargumentsit
isinstantiatedfor(includingconstructionanddestruction).Thismightleadtothe
impressionthatthesetemplateargumentshavetoprovidealloperations
necessaryforallmemberfunctionsofaclasstemplate.Butthisisnotthecase:
Templateargumentsonlyhavetoprovideallnecessaryoperationsthatare
needed(insteadofthatcouldbeneeded).
If,forexample,classStack<>wouldprovideamemberfunctionprintOn()
toprintthewholestackcontent,whichcallsoperator<<foreachelement:
Clickheretoviewcodeimage
template<typenameT>
classStack{
…
voidprintOn()(std::ostream&strm)const{
for(Tconst&elem:elems){
strm<<elem<<’’;//call<<foreachelement
}
}
};
Youcanstillusethisclassforelementsthatdon’thaveoperator<<defined:
Clickheretoviewcodeimage
Stack<std::pair<int,int>>ps;//note:std::pair<>hasno
operator<<defined
ps.push({4,5});//OK
ps.push({6,7});//OK
std::cout<<ps.top().first<<’\n’;//OK
std::cout<<ps.top().second<<’\n’;//OK
OnlyifyoucallprintOn()forsuchastack,thecodewillproduceanerror,
becauseitcan’tinstantiatethecallofoperator<<forthisspecificelement
type:Clickheretoviewcodeimage
ps.printOn(std::cout);//ERROR:operator<<notsupportedforelement
type
2.3.1Concepts
Thisraisesthequestion:Howdoweknowwhichoperationsarerequiredfora
templatetobeabletogetinstantiated?Thetermconceptisoftenusedtodenote
asetofconstraintsthatisrepeatedlyrequiredinatemplatelibrary.Forexample,
theC++standardlibraryreliesonsuchconceptsasrandomaccessiteratorand
defaultconstructible.
Currently(i.e.,asofC++17),conceptscanmoreorlessonlybeexpressedin
thedocumentation(e.g.,codecomments).Thiscanbecomeasignificant
problembecausefailurestofollowconstraintscanleadtoterribleerrormessages
(seeSection9.4onpage143).
Foryears,therehavealsobeenapproachesandtrialstosupportthedefinition
andvalidationofconceptsasalanguagefeature.However,uptoC++17nosuch
approachwasstandardizedyet.
SinceC++11,youcanatleastcheckforsomebasicconstraintsbyusingthe
static_assertkeywordandsomepredefinedtypetraits.Forexample:
Clickheretoviewcodeimage
template<typenameT>
classC
{
static_assert(std::is_default_constructible<T>::value,
"ClassCrequiresdefault-constructibleelements");
…
};
Withoutthisassertionthecompilationwillstillfail,ifthedefaultconstructoris
required.However,theerrormessagethenmightcontaintheentiretemplate
instantiationhistoryfromtheinitialcauseoftheinstantiationdowntotheactual
templatedefinitioninwhichtheerrorwasdetected(seeSection9.4onpage
143).
However,morecomplicatedcodeisnecessarytocheck,forexample,objects
oftypeTprovideaspecificmemberfunctionorthattheycanbecomparedusing
operator<.SeeSection19.6.3onpage436foradetailedexampleofsuchcode.
SeeAppendixEforadetaileddiscussionofconceptsforC++.
2.4Friends
InsteadofprintingthestackcontentswithprintOn()itisbettertoimplement
operator<<forthestack.However,asusualoperator<<hastobe
implementedasnonmemberfunction,whichthencouldcallprintOn()inline:
Clickheretoviewcodeimage
template<typenameT>
classStack{
…
voidprintOn()(std::ostream&strm)const{
…
}
friendstd::ostream&operator<<(std::ostream&strm,
Stack<T>const&s){
s.printOn(strm);
returnstrm;
}
};
Notethatthismeansthatoperator<<forclassStack<>isnotafunction
template,butan“ordinary”functioninstantiatedwiththeclasstemplateif
needed.2
However,whentryingtodeclarethefriendfunctionanddefineitafterwards,
thingsbecomemorecomplicated.Infact,wehavetwooptions:1.Wecan
implicitlydeclareanewfunctiontemplate,whichmustuseadifferenttemplate
parameter,suchasU:Clickheretoviewcodeimage
template<typenameT>
classStack{
…
template<typenameU>
friendstd::ostream&operator<<(std::ostream&,Stack<U>const&);
};
NeitherusingTagainnorskippingthetemplateparameterdeclarationwould
work(eithertheinnerThidestheouterTorwedeclareanontemplate
functioninnamespacescope).
2.WecanforwarddeclaretheoutputoperatorforaStack<T>tobeatemplate,
which,however,meansthatwefirsthavetoforwarddeclareStack<T>:
Clickheretoviewcodeimage
template<typenameT>
classStack;
template<typenameT>
std::ostream&operator<<(std::ostream&,Stack<T>const&);Then,we
candeclarethisfunctionasfriend:Clickheretoviewcodeimage
template<typenameT>
classStack{
…
friendstd::ostream&operator<<<T>(std::ostream&,
Stack<T>const&);
};
Notethe<T>behindthe“functionname”operator<<.Thus,wedeclarea
specializationofthenonmemberfunctiontemplateasfriend.Without<T>we
woulddeclareanewnontemplatefunction.SeeSection12.5.2onpage211for
details.
Inanycase,youcanstillusethisclassforelementsthatdon’thaveoperator<<
defined.Onlycallingoperator<<forthisstackresultsinanerror:Clickhereto
viewcodeimage
Stack<std::pair<int,int>>ps;//std::pair<>hasnooperator<<
defined
ps.push({4,5});//OK
ps.push({6,7});//OK
std::cout<<
ps.top().first<<’\n’;//OK
std::cout<<
ps.top().second<<’\n’;//OK
std::cout<<ps<<’\n’;//ERROR:operator<<notsupported
//forelementtype
2.5SpecializationsofClassTemplates
Youcanspecializeaclasstemplateforcertaintemplatearguments.Similartothe
overloadingoffunctiontemplates(seeSection1.5onpage15),specializing
classtemplatesallowsyoutooptimizeimplementationsforcertaintypesorto
fixamisbehaviorofcertaintypesforaninstantiationoftheclasstemplate.
However,ifyouspecializeaclasstemplate,youmustalsospecializeallmember
functions.Althoughitispossibletospecializeasinglememberfunctionofa
classtemplate,onceyouhavedoneso,youcannolongerspecializethewhole
classtemplateinstancethatthespecializedmemberbelongsto.
Tospecializeaclasstemplate,youhavetodeclaretheclasswithaleading
template<>andaspecificationofthetypesforwhichtheclasstemplateis
specialized.Thetypesareusedasatemplateargumentandmustbespecified
directlyfollowingthenameoftheclass:Clickheretoviewcodeimage
template<>
classStack<std::string>{
…
};
Forthesespecializations,anydefinitionofamemberfunctionmustbedefined
asan“ordinary”memberfunction,witheachoccurrenceofTbeingreplacedby
thespecializedtype:Clickheretoviewcodeimage
voidStack<std::string>::push(std::stringconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
HereisacompleteexampleofaspecializationofStack<>fortype
std::string:Clickheretoviewcodeimage
basics/stack2.hpp
#include"stack1.hpp"
#include<deque>
#include<string>
#include<cassert>
template<>
classStack<std::string>{
private:
std::deque<std::string>elems;//elements
public:
voidpush(std::stringconst&);//pushelement
voidpop();//popelement
std::stringconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
};
voidStack<std::string>::push(std::stringconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
voidStack<std::string>::pop()
{
assert(!elems.empty());
elems.pop_back();//removelastelement
}
std::stringconst&Stack<std::string>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
Inthisexample,thespecializationusesreferencesemanticstopassthestring
argumenttopush(),whichmakesmoresenseforthisspecifictype(weshould
evenbetterpassaforwardingreference,though,whichisdiscussedinSection
6.1onpage91).
Anotherdifferenceistouseadequeinsteadofavectortomanagethe
elementsinsidethestack.Althoughthishasnoparticularbenefithere,itdoes
demonstratethattheimplementationofaspecializationmightlookverydifferent
fromtheimplementationoftheprimarytemplate.
2.6PartialSpecialization
Classtemplatescanbepartiallyspecialized.Youcanprovidespecial
implementationsforparticularcircumstances,butsometemplateparameters
muststillbedefinedbytheuser.Forexample,wecandefineaspecial
implementationofclassStack<>forpointers:Clickheretoviewcodeimage
basics/stackpartspec.hpp
#include"stack1.hpp"
//partialspecializationofclassStack<>forpointers:
template<typenameT>
classStack<T*>{
private:
std::vector<T*>elems;//elements
public:
voidpush(T*);//pushelement
T*pop();//popelement
T*top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
};
template<typenameT>
voidStack<T*>::push(T*elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
template<typenameT>
T*Stack<T*>::pop()
{
assert(!elems.empty());
T*p=elems.back();
elems.pop_back();//removelastelement
returnp;//andreturnit(unlikeinthegeneralcase)
}
template<typenameT>
T*Stack<T*>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
With
Clickheretoviewcodeimage
template<typenameT>
classStack<T*>{
};
wedefineaclasstemplate,stillparameterizedforTbutspecializedforapointer
(Stack<T*>).
Noteagainthatthespecializationmightprovidea(slightly)differentinterface.
Here,forexample,pop()returnsthestoredpointer,sothatauseroftheclass
templatecancalldeletefortheremovedvalue,whenitwascreatedwithnew:
Clickheretoviewcodeimage
Stack<int*>ptrStack;//stackofpointers(specialimplementation)
ptrStack.push(newint{42});
std::cout<<*ptrStack.top()<<’\n’;
deleteptrStack.pop();
PartialSpecializationwithMultipleParameters
Classtemplatesmightalsospecializetherelationshipbetweenmultipletemplate
parameters.Forexample,forthefollowingclasstemplate:Clickheretoview
codeimage
template<typenameT1,typenameT2>
classMyClass{
…
};
thefollowingpartialspecializationsarepossible:Clickheretoviewcodeimage
//partialspecialization:bothtemplateparametershavesametype
template<typenameT>
classMyClass<T,T>{
…
};
//partialspecialization:secondtypeisint
template<typenameT>
classMyClass<T,int>{
…
};
//partialspecialization:bothtemplateparametersarepointertypes
template<typenameT1,typenameT2>
classMyClass<T1*,T2*>{
…
};
Thefollowingexamplesshowwhichtemplateisusedbywhichdeclaration:
Clickheretoviewcodeimage
MyClass<int,float>mif;//usesMyClass<T1,T2>
MyClass<float,float>mff;//usesMyClass<T,T>
MyClass<float,int>mfi;//usesMyClass<T,int>
MyClass<int*,float*>mp;//usesMyClass<T1*,T2*>
Ifmorethanonepartialspecializationmatchesequallywell,thedeclarationis
ambiguous:Clickheretoviewcodeimage
MyClass<int,int>m;//ERROR:matchesMyClass<T,T>
//andMyClass<T,int>
MyClass<int*,int*>m;//ERROR:matchesMyClass<T,T>
//andMyClass<T1*,T2*>
Toresolvethesecondambiguity,youcouldprovideanadditionalpartial
specializationforpointersofthesametype:Clickheretoviewcodeimage
template<typenameT>
classMyClass<T*,T*>{
…
};
Fordetailsofpartialspecialization,seeSection16.4onpage347.
2.7DefaultClassTemplateArguments
Asforfunctiontemplates,youcandefinedefaultvaluesforclasstemplate
parameters.Forexample,inclassStack<>youcandefinethecontainerthatis
usedtomanagetheelementsasasecondtemplateparameter,using
std::vector<>asthedefaultvalue:Clickheretoviewcodeimage
basics/stack3.hpp
#include<vector>
#include<cassert>
template<typenameT,typenameCont=std::vector<T>>
classStack{
private:
Contelems;//elements
public:
voidpush(Tconst&elem);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
};
template<typenameT,typenameCont>
voidStack<T,Cont>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem}
template<typenameT,
typenameCont>
voidStack<T,Cont>::pop()
{
assert(!elems.empty());
elems.pop_back();//removelastelement
}
template<typenameT,typenameCont>
Tconst&Stack<T,Cont>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
Notethatwenowhavetwotemplateparameters,soeachdefinitionofamember
functionmustbedefinedwiththesetwoparameters:Clickheretoviewcode
image
template<typenameT,typenameCont>
voidStack<T,Cont>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
Youcanusethisstackthesamewayitwasusedbefore.Thus,ifyoupassafirst
andonlyargumentasanelementtype,avectorisusedtomanagetheelements
ofthistype:Clickheretoviewcodeimage
template<typenameT,typenameCont=std::vector<T>>
classStack{
private:
Contelems;//elements
…
};
Inaddition,youcouldspecifythecontainerfortheelementswhenyoudeclarea
Stackobjectinyourprogram:Clickheretoviewcodeimage
basics/stack3test.cpp
#include"stack3.hpp"
#include<iostream>
#include<deque>
intmain()
{
//stackofints:
Stack<int>intStack;
//stackofdoublesusingastd::deque<>tomanagetheelements
Stack<double,std::deque<double>>dblStack;
//manipulateintstack
intStack.push(7);
std::cout<<intStack.top()<<’\n’;
intStack.pop();
//manipulatedoublestack
dblStack.push(42.42);
std::cout<<dblStack.top()<<’\n’;
dblStack.pop();
}
With
Clickheretoviewcodeimage
Stack<double,std::deque<double>>
youdeclareastackfordoublesthatusesastd::deque<>tomanagethe
elementsinternally.
2.8TypeAliases
Youcanmakeusingaclasstemplatemoreconvenientbydefininganewname
forthewholetype.
TypedefsandAliasDeclarations
Tosimplydefineanewnameforacompletetype,therearetwowaystodoit:1.
Byusingthekeywordtypedef:Clickheretoviewcodeimage
typedefStack
typedefStack<int>IntStack;//typedef
voidfoo(IntStackconst&s);//sisstackofints
IntStackistack[10];//istackisarrayof10stacksofints
Wecallthisdeclarationatypedef3andtheresultingnameiscalledatypedef-
name.
2.Byusingthekeywordusing(sinceC++11):Clickheretoviewcodeimage
usingIntStack=Stack<int>;//aliasdeclaration
voidfoo(IntStackconst&s);//sisstackofints
IntStackistack[10];//istackisarrayof10stacksofints
Introducedby[DosReisMarcusAliasTemplates],thisiscalledanalias
declaration.
Notethatinbothcaseswedefineanewnameforanexistingtyperatherthana
newtype.Thus,afterthetypedefClickheretoviewcodeimage
typedefStack<int>IntStack;or
Clickheretoviewcodeimage
usingIntStack=Stack<int>;IntStackandStack<int>aretwo
interchangeablenotationsforthesametype.
Asacommontermforbothalternativestodefineanewnameforanexisting
type,weusethetermtypealiasdeclaration.Thenewnameisatypealiasthen.
Becauseitismorereadable(alwayshavingthedefinedtypenameontheleft
sideofthe=,fortheremainderofthisbook,wepreferthealiasdeclaration
syntaxwhendeclaringantypealias.
AliasTemplates
Unlikeatypedef,analiasdeclarationcanbetemplatedtoprovidea
convenientnameforafamilyoftypes.ThisisalsoavailablesinceC++11andis
calledanaliastemplate.4
ThefollowingaliastemplateDequeStack,parameterizedovertheelement
typeT,expandstoaStackthatstoresitselementsinastd::deque:Click
heretoviewcodeimage
template<typenameT>
usingDequeStack=Stack<T,std::deque<T>>;Thus,bothclass
templatesandaliastemplatescanbeusedasaparameterizedtype.
Butagain,analiastemplatesimplygivesanewnametoanexisting
type,whichcanstillbeused.BothDequeStack<int>andStack<int,
std::deque<int>>representthesametype.
Noteagainthat,ingeneral,templatescanonlybedeclaredanddefinedin
global/namespacescopeorinsideclassdeclarations.
AliasTemplatesforMemberTypes
Aliastemplatesareespeciallyhelpfultodefineshortcutsfortypesthatare
membersofclasstemplates.AfterClickheretoviewcodeimage
structC{
typedef…iterator;
…
};
or:
Clickheretoviewcodeimage
structMyType{
usingiterator=…;
…
};
adefinitionsuchas
Clickheretoviewcodeimage
template<typenameT>
usingMyTypeIterator=typenameMyType<T>::iterator;allowstheuse
of
Clickheretoviewcodeimage
MyTypeIterator<int>pos;
insteadofthefollowing:5
Clickheretoviewcodeimage
typenameMyType<T>::iteratorpos;
TypeTraitsSuffix_t
SinceC++14,thestandardlibraryusesthistechniquetodefineshortcutsforall
typetraitsinthestandardlibrarythatyieldatype.Forexample,tobeableto
writeClickheretoviewcodeimage
std::add_const_t<T>//sinceC++14
insteadof
Clickheretoviewcodeimage
typenamestd::add_const<T>::type//sinceC++11
thestandardlibrarydefines:
Clickheretoviewcodeimage
namespacestd{
template<typenameT>usingadd_const_t=typenameadd_const<T>::type;
}
2.9ClassTemplateArgumentDeduction
UntilC++17,youalwayshadtopassalltemplateparametertypestoclass
templates(unlesstheyhavedefaultvalues).SinceC++17,theconstraintthatyou
alwayshavetospecifythetemplateargumentsexplicitlywasrelaxed.Instead,
youcanskiptodefinethetemplatesargumentsexplicitly,iftheconstructoris
abletodeducealltemplateparameters(thatdon’thaveadefaultvalue),For
example,inallpreviouscodeexamples,youcanuseacopyconstructorwithout
specifyingthetemplatearguments:Clickheretoviewcodeimage
Stack<int>intStack1;//stackofstrings
Stack<int>intStack2=intStack1;//OKinallversions
StackintStack3=intStack1;//OKsinceC++17
Byprovidingconstructorsthatpasssomeinitialarguments,youcansupport
deductionoftheelementtypeofastack.Forexample,wecouldprovideastack
thatcanbeinitializedbyasingleelement:Clickheretoviewcodeimage
template<typenameT>
classStack{
private:
std::vector<T>elems;//elements
public:
Stack()=default;
Stack(Tconst&elem)//initializestackwithoneelement
:elems({elem}){
}
…
};
Thisallowsyoutodeclareastackasfollows:Clickheretoviewcodeimage
StackintStack=0;//Stack<int>deducedsinceC++17
Byinitializingthestackwiththeinteger0,thetemplateparameterTisdeduced
tobeint,sothataStack<int>isinstantiated.
Notethefollowing:•Duetothedefinitionoftheintconstructor,youhaveto
requestthedefaultconstructorstobeavailablewithitsdefaultbehavior,because
thedefaultconstructorisavailableonlyifnootherconstructorisdefined:
Stack()=default;
•Theargumentelemispassedtoelemswithbracesaroundtoinitializethe
vectorelemswithaninitializerlistwithelemastheonlyargument::
elems({elem})
Thereisnoconstructorforavectorthatisabletotakeasingleparameteras
initialelementdirectly.6
Notethat,unlikeforfunctiontemplates,classtemplateargumentsmaynotbe
deducedonlypartially(byexplicitlyspecifyingonlysomeofthetemplate
arguments).SeeSection15.12onpage314fordetails.
ClassTemplateArgumentsDeductionwithStringLiterals
Inprinciple,youcaneveninitializethestackwithastringliteral:Clickhereto
viewcodeimage
StackstringStack="bottom";//Stack<charconst[7]>deducedsince
C++17
Butthiscausesalotoftrouble:Ingeneral,whenpassingargumentsofa
templatetypeTbyreference,theparameterdoesn’tdecay,whichisthetermfor
themechanismtoconvertarawarraytypetothecorrespondingrawpointer
type.ThismeansthatwereallyinitializeaStack<charconst[7]>
andusetypecharconst[7]whereverTisused.Forexample,wemaynot
pushastringofdifferentsize,becauseithasadifferenttype.Foradetailed
discussionseeSection7.4onpage115.
However,whenpassingargumentsofatemplatetypeTbyvalue,the
parameterdecays,whichisthetermforthemechanismtoconvertarawarray
typetothecorrespondingrawpointertype.Thatis,thecallparameterTofthe
constructorisdeducedtobecharconst*sothatthewholeclassisdeduced
tobeaStack<charconst*>.
Forthisreason,itmightbeworthwhiletodeclaretheconstructorsothatthe
argumentispassedbyvalue:Clickheretoviewcodeimage
template<typenameT>
classStack{
private:
std::vector<T>elems;//elements
public:
Stack(Telem)//initializestackwithoneelementbyvalue
:elems({elem}){//todecayonclasstmplargdeduction
}
…
};
Withthis,thefollowinginitializationworksfine:Clickheretoviewcodeimage
StackstringStack="bottom";//Stack<charconst*>deducedsince
C++17
Inthiscase,however,weshouldbettermovethetemporaryelemintothestack
toavoidunnecessarilycopyingit:Clickheretoviewcodeimage
template<typenameT>
classStack{
private:
std::vector<T>elems;//elements
public:
Stack(Telem)//initializestackwithoneelementbyvalue
:elems({std::move(elem)}){
}
…
};
DeductionGuides
Insteadofdeclaringtheconstructortobecalledbyvalue,thereisadifferent
solution:Becausehandlingrawpointersincontainersisasourceoftrouble,we
shoulddisableautomaticallydeducingrawcharacterpointersforcontainer
classes.
Youcandefinespecificdeductionguidestoprovideadditionalorfixexisting
classtemplateargumentdeductions.Forexample,youcandefinethatwhenever
astringliteralorCstringispassed,thestackisinstantiatedforstd::string:
Clickheretoviewcodeimage
Stack(charconst*)->Stack<std::string>;
Thisguidehastoappearinthesamescope(namespace)astheclassdefinition.
Usually,itfollowstheclassdefinition.Wecallthetypefollowingthe->the
guidedtypeofthedeductionguide.
Now,thedeclarationwith
Clickheretoviewcodeimage
StackstringStack{"bottom"};//OK:Stack<std::string>deducedsince
C++17
deducesthestacktobeaStack<std::string>.However,thefollowing
stilldoesn’twork:Clickheretoviewcodeimage
StackstringStack="bottom";//Stack<std::string>deduced,but
stillnotvalid
Wededucestd::stringsothatweinstantiateaStack<std::string>:
Clickheretoviewcodeimage
classStack{
private:
std::vector<std::string>elems;//elements
public:
Stack(std::stringconst&elem)//initializestackwithoneelement
:elems({elem}){
}
…
};
However,bylanguagerules,youcan’tcopyinitialize(initializeusing=)an
objectbypassingastringliteraltoaconstructorexpectingastd::string.So
youhavetoinitializethestackasfollows:Clickheretoviewcodeimage
StackstringStack{"bottom"};//Stack<std::string>deducedandvalid
Notethat,ifindoubt,classtemplateargumentdeductioncopies.Afterdeclaring
stringStackasStack<std::string>thefollowinginitializations
declarethesametype(thus,callingthecopyconstructor)insteadofinitializinga
stackbyelementsthatarestringstacks:Clickheretoviewcodeimage
Stackstack2{stringStack};//Stack<std::string>deduced
Stackstack3(stringStack);//Stack<std::string>deduced
Stackstack4={stringStack};//Stack<std::string>deduced
SeeSection15.12onpage313formoredetailsaboutclasstemplateargument
deduction.
2.10TemplatizedAggregates
Aggregateclasses(classes/structswithnouser-provided,explicit,orinherited
constructors,noprivateorprotectednonstaticdatamembers,novirtual
functions,andnovirtual,private,orprotectedbaseclasses)canalsobe
templates.Forexample:Clickheretoviewcodeimage
template<typenameT>
structValueWithComment{
Tvalue;
std::stringcomment;
};
definesanaggregateparameterizedforthetypeofthevaluevalitholds.You
candeclareobjectsasforanyotherclasstemplateandstilluseitasaggregate:
Clickheretoviewcodeimage
ValueWithComment<int>vc;
vc.value=42;
vc.comment="initialvalue";SinceC++17,youcanevendefine
deductionguidesforaggregateclasstemplates:Clickheretoview
codeimage
ValueWithComment(
charconst*,charconst*)
->ValueWithComment<std::string>;
ValueWithCommentvc2={"hello","initialvalue"};Withoutthe
deductionguide,theinitializationwouldnotbepossible,because
ValueWithCommenthasnoconstructortoperformthedeductionagainst.
Thestandardlibraryclassstd::array<>isalsoanaggregate,
parameterizedforboththeelementtypeandthesize.TheC++17standard
libraryalsodefinesadeductionguideforit,whichwediscussinSection4.4.4
onpage64.
2.11Summary
•Aclasstemplateisaclassthatisimplementedwithoneormoretype
parametersleftopen.
•Touseaclasstemplate,youpasstheopentypesastemplatearguments.The
classtemplateistheninstantiated(andcompiled)forthesetypes.
•Forclasstemplates,onlythosememberfunctionsthatarecalledare
instantiated.
•Youcanspecializeclasstemplatesforcertaintypes.
•Youcanpartiallyspecializeclasstemplatesforcertaintypes.
•SinceC++17,classtemplateargumentscanautomaticallybededucedfrom
constructors.
•Youcandefineaggregateclasstemplates.
•Callparametersofatemplatetypedecayifdeclaredtobecalledbyvalue.
•Templatescanonlybedeclaredanddefinedinglobal/namespacescopeor
insideclassdeclarations.
1C++17introducedclassargumenttemplatededuction,whichallowsskipping
templateargumentsiftheycanbederivedfromtheconstructor.Thiswillbe
discussedinSection2.9onpage40.
2Itisatemplatedentity,seeSection12.1onpage181.
3Usingthewordtypedefinsteadof“typedefinition”inintentional.The
keywordtypedefwasoriginallymeanttosuggest“typedefinition.”
However,inC++,“typedefinition”reallymeanssomethingelse(e.g.,the
definitionofaclassorenumerationtype).Instead,atypedefshouldjustbe
thoughtofasanalternativename(an“alias”)foranexistingtype,whichcan
bedonebyatypedef.
4Aliastemplatesaresometimes(incorrectly)referredtoastypedeftemplates
becausetheyfulfillthesamerolethatatypedefwouldifitcouldbemade
intoatemplate.
5Thetypenameisnecessaryherebecausethememberisatype.SeeSection
5.1onpage67fordetails.
6Evenworse,thereisavectorconstructortakingoneintegralargumentas
initialsize,sothatforastackwiththeinitialvalue5,thevectorwouldgetan
initialsizeoffiveelementswhen:elems(elem)isused.
Chapter3
NontypeTemplateParameters
Forfunctionandclasstemplates,templateparametersdon’thavetobetypes.
Theycanalsobeordinaryvalues.Aswithtemplatesusingtypeparameters,you
definecodeforwhichacertaindetailremainsopenuntilthecodeisused.
However,thedetailthatisopenisavalueinsteadofatype.Whenusingsucha
template,youhavetospecifythisvalueexplicitly.Theresultingcodethengets
instantiated.Thischapterillustratesthisfeatureforanewversionofthestack
classtemplate.Inaddition,weshowanexampleofnontypefunctiontemplate
parametersanddiscusssomerestrictionstothistechnique.
3.1NontypeClassTemplateParameters
Incontrasttothesampleimplementationsofastackinpreviouschapters,you
canalsoimplementastackbyusingafixed-sizearrayfortheelements.An
advantageofthismethodisthatthememorymanagementoverhead,whether
performedbyyouorbyastandardcontainer,isavoided.However,determining
thebestsizeforsuchastackcanbechallenging.Thesmallerthesizeyou
specify,themorelikelyitisthatthestackwillgetfull.Thelargerthesizeyou
specify,themorelikelyitisthatmemorywillbereservedunnecessarily.Agood
solutionistolettheuserofthestackspecifythesizeofthearrayasthe
maximumsizeneededforstackelements.
Todothis,definethesizeasatemplateparameter:Clickheretoviewcode
image
basics/stacknontype.hpp
#include<array>
#include<cassert>
template<typenameT,std::size_tMaxsize>
classStack{
private:
std::array<T,Maxsize>elems;//elements
std::size_tnumElems;//currentnumberofelements
public:
Stack();//constructor
voidpush(Tconst&elem);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnnumElems==0;
}
std::size_tsize()const{//returncurrentnumberofelements
returnnumElems;
}
};
template<typenameT,std::size_tMaxsize>
Stack<T,Maxsize>::Stack()
:numElems(0)//startwithnoelements
{
//nothingelsetodo
}
template<typenameT,std::size_tMaxsize>
voidStack<T,Maxsize>::push(Tconst&elem)
{
assert(numElems<Maxsize);
elems[numElems]=elem;//appendelement
++numElems;//incrementnumberofelements
}
template<typenameT,std::size_tMaxsize>
voidStack<T,Maxsize>::pop()
{
assert(!elems.empty());
--numElems;//decrementnumberofelements
}
template<typenameT,std::size_tMaxsize>
Tconst&Stack<T,Maxsize>::top()const
{
assert(!elems.empty());
returnelems[numElems-1];//returnlastelement
}
Thenewsecondtemplateparameter,Maxsize,isoftypeint.Itspecifiesthe
sizeoftheinternalarrayofstackelements:Clickheretoviewcodeimage
template<typenameT,std::size_tMaxsize>
classStack{
private:
std::array<T,Maxsize>elems;//elements
…
};
Inaddition,itisusedinpush()tocheckwhetherthestackisfull:Clickhereto
viewcodeimage
template<typenameT,std::size_tMaxsize>
voidStack<T,Maxsize>::push(Tconst&elem)
{
assert(numElems<Maxsize);
elems[numElems]=elem;//appendelement++numElems;//increment
numberofelements
}
Tousethisclasstemplateyouhavetospecifyboththeelementtypeandthe
maximumsize:Clickheretoviewcodeimage
basics/stacknontype.cpp
#include"stacknontype.hpp"
#include<iostream>
#include<string>
intmain()
{
Stack<int,20>int20Stack;//stackofupto20ints
Stack<int,40>int40Stack;//stackofupto40ints
Stack<std::string,40>stringStack;//stackofupto40strings
//manipulatestackofupto20ints
int20Stack.push(7);
std::cout<<int20Stack.top()<<’\n’;
int20Stack.pop();
//manipulatestackofupto40strings
stringStack.push("hello");
std::cout<<stringStack.top()<<’\n’;
stringStack.pop();
}
Notethateachtemplateinstantiationisitsowntype.Thus,int20Stackand
int40Stackaretwodifferenttypes,andnoimplicitorexplicittype
conversionbetweenthemisdefined.Thus,onecannotbeusedinsteadofthe
other,andyoucannotassignonetotheother.
Again,defaultargumentsforthetemplateparameterscanbespecified:Click
heretoviewcodeimage
template<typenameT=int,std::size_tMaxsize=100>
classStack{
…
};
However,fromaperspectiveofgooddesign,thismaynotbeappropriateinthis
example.Defaultargumentsshouldbeintuitivelycorrect.Butneithertypeint
noramaximumsizeof100seemsintuitiveforageneralstacktype.Thus,itis
betterwhentheprogrammerhastospecifybothvaluesexplicitlysothatthese
twoattributesarealwaysdocumentedduringadeclaration.
3.2NontypeFunctionTemplateParameters
Youcanalsodefinenontypeparametersforfunctiontemplates.Forexample,the
followingfunctiontemplatedefinesagroupoffunctionsforwhichacertain
valuecanbeadded:Clickheretoviewcodeimage
basics/addvalue.hpp
template<intVal,typenameT>
TaddValue(Tx)
{
returnx+Val;
}
Thesekindsoffunctionscanbeusefuliffunctionsoroperationsareusedas
parameters.Forexample,ifyouusetheC++standardlibraryyoucanpassan
instantiationofthisfunctiontemplatetoaddavaluetoeachelementofa
collection:Clickheretoviewcodeimage
std::transform(source.begin(),source.end(),//startandendof
source
dest.begin(),//startofdestination
addValue<5,int>);//operation
ThelastargumentinstantiatesthefunctiontemplateaddValue<>()toadd5
toapassedintvalue.Theresultingfunctioniscalledforeachelementinthe
sourcecollectionsource,whileitistranslatedintothedestinationcollection
dest.
NotethatyouhavetospecifytheargumentintforthetemplateparameterT
ofaddValue<>().Deductiononlyworksforimmediatecallsand
std::transform()needacompletetypetodeducethetypeofitsfourth
parameter.Thereisnosupporttosubstitute/deduceonlysometemplate
parametersandthesee,whatcouldfit,anddeducetheremainingparameters.
Again,youcanalsospecifythatatemplateparameterisdeducedfromthe
previousparameter.Forexample,toderivethereturntypefromthepassed
nontype:Clickheretoviewcodeimage
template<autoVal,typenameT=decltype(Val)>
Tfoo();
ortoensurethatthepassedvaluehasthesametypeasthepassedtype:Click
heretoviewcodeimage
template<typenameT,TVal=T{}>
Tbar();
3.3RestrictionsforNontypeTemplateParameters
Notethatnontypetemplateparameterscarrysomerestrictions.Ingeneral,they
canbeonlyconstantintegralvalues(includingenumerations),pointersto
objects/functions/members,lvaluereferencestoobjectsorfunctions,or
std::nullptr_t(thetypeofnullptr).
Floating-pointnumbersandclass-typeobjectsarenotallowedasnontype
templateparameters:Clickheretoviewcodeimage
template<doubleVAT>//ERROR:floating-pointvaluesarenot
doubleprocess(doublev)//allowedastemplateparameters
{
returnv*VAT;
}
template<std::stringname>//ERROR:class-typeobjectsarenot
classMyClass{//allowedastemplateparameters
…
};
Whenpassingtemplateargumentstopointersorreferences,theobjectsmustnot
bestringliterals,temporaries,ordatamembersandothersubobjects.Because
theserestrictionswererelaxedwitheachandeveryC++versionbeforeC++17,
additionalconstraintsapply:•InC++11,theobjectsalsohadtohaveexternal
linkage.
•InC++14,theobjectsalsohadtohaveexternalorinternallinkage.
Thus,thefollowingisnotpossible:Clickheretoviewcodeimage
template<charconst*name>
classMyClass{
…
};
MyClass<"hello">x;//ERROR:stringliteral"hello"notallowed
Howeverthereareworkarounds(againdependingontheC++version):Click
heretoviewcodeimage
externcharconsts03[]="hi";//externallinkage
charconsts11[]="hi";//internallinkage
intmain()
{
Message<s03>m03;//OK(allversions)
Message<s11>m11;//OKsinceC++11
staticcharconsts17[]="hi";//nolinkage
Message<s17>m17;//OKsinceC++17
}
Inallthreecasesaconstantcharacterarrayisinitializedby"hello"andthis
objectisusedasatemplateparameterdeclaredwithcharconst*.Thisis
validinallC++versionsiftheobjecthasexternallinkage(s03),inC++11and
C++14alsoifithasinternallinkage(s11),andsinceC++17ifithasnolinkage
atall.
SeeSection12.3.3onpage194foradetaileddiscussionandSection17.2on
page354foradiscussionofpossiblefuturechangesinthisarea.
AvoidingInvalidExpressions
Argumentsfornontypetemplateparametersmightbeanycompile-time
expressions.Forexample:Clickheretoviewcodeimage
template<intI,boolB>
classC;
…
C<sizeof(int)+4,sizeof(int)==4>c;However,notethatifoperator>
isusedintheexpression,youhavetoputthewholeexpressioninto
parenthesessothatthenested>endstheargumentlist:Clickhere
toviewcodeimage
C<42,sizeof(int)>4>c;//ERROR:first>endsthetemplate
argumentlist
C<42,(sizeof(int)>4)>c;//OK
3.4TemplateParameterTypeauto
SinceC++17,youcandefineanontypetemplateparametertogenericallyaccept
anytypethatisallowedforanontypeparameter.Usingthisfeature,wecan
provideanevenmoregenericstackclasswithfixedsize:Clickheretoview
codeimage
basics/stackauto.hpp
#include<array>
#include<cassert>
template<typenameT,autoMaxsize>
classStack{
public:
usingsize_type=decltype(Maxsize);
private:
std::array<T,Maxsize>elems;//elements
size_typenumElems;//currentnumberofelements
public:
Stack();//constructor
voidpush(Tconst&elem);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnnumElems==0;
}
size_typesize()const{//returncurrentnumberofelements
returnnumElems;
}
};
//constructor
template<typenameT,autoMaxsize>
Stack<T,Maxsize>::Stack()
:numElems(0)//startwithnoelements
{
//nothingelsetodo
}
template<typenameT,autoMaxsize>
voidStack<T,Maxsize>::push(Tconst&elem)
{
assert(numElems<Maxsize);
elems[numElems]=elem;//appendelement
++numElems;//incrementnumberofelements
}
template<typenameT,autoMaxsize>
voidStack<T,Maxsize>::pop()
{
assert(!elems.empty());
--numElems;//decrementnumberofelements
}
template<typenameT,autoMaxsize>
Tconst&Stack<T,Maxsize>::top()const
{
assert(!elems.empty());
returnelems[numElems-1];//returnlastelement
}
BydefiningClickheretoviewcodeimage
template<typenameT,autoMaxsize>
classStack{
…
};
byusingtheplaceholdertypeauto,youdefineMaxsizetobeavalueofa
typenotspecifiedyet.Itmightbeanytypethatisallowedtobeanontype
templateparametertype.
Internallyyoucanuseboththevalue:Clickheretoviewcodeimage
std::array<T,Maxsize>elems;//elements
anditstype:
Clickheretoviewcodeimage
usingsize_type=decltype(Maxsize);whichisthen,forexample,
usedasreturntypeofthesize()memberfunction:Clickheretoview
codeimage
size_typesize()const{//returncurrentnumberofelements
returnnumElems;
}
SinceC++14,youcouldalsojustuseautohereasreturntypetoletthe
compilerfindoutthereturntype:Clickheretoviewcodeimage
autosize()const{//returncurrentnumberofelements
returnnumElems;
}
Withthisclassdeclarationthetypeofthenumberofelementsisdefinedbythe
typeusedforthenumberofelements,whenusingastack:Clickheretoview
codeimage
basics/stackauto.cpp
#include<iostream>
#include<string>
#include"stackauto.hpp"
intmain()
{
Stack<int,20u>int20Stack;//stackofupto20ints
Stack<std::string,40>stringStack;//stackofupto40strings
//manipulatestackofupto20ints
int20Stack.push(7);
std::cout<<int20Stack.top()<<’\n’;
autosize1=int20Stack.size();
//manipulatestackofupto40strings
stringStack.push("hello");
std::cout<<stringStack.top()<<’\n’;
autosize2=stringStack.size();
if(!std::is_same_v<decltype(size1),decltype(size2)>){
std::cout<<"sizetypesdiffer"<<’\n’;
}
}
With
Clickheretoviewcodeimage
Stack<int,20u>int20Stack;//stackofupto20ints
theinternalsizetypeisunsignedint,because20uispassed.
With
Clickheretoviewcodeimage
Stack<std::string,40>stringStack;//stackofupto40strings
theinternalsizetypeisint,because40ispassed.
size()forthetwostackswillhavedifferentreturntypes,sothatafterClick
heretoviewcodeimage
autosize1=int20Stack.size();
…
autosize2=stringStack.size();thetypesofsize1andsize2differ.
Byusingthestandardtypetraitstd::is_same(seeSectionD.3.3on
page726)anddecltype,wecancheckthat:Clickheretoviewcode
image
if(!std::is_same<decltype(size1),decltype(size2)>::value){
std::cout<<"sizetypesdiffer"<<’\n’;
}
Thus,theoutputwillbe:sizetypesdiffer
SinceC++17,fortraitsreturningvalues,youcanalsousethesuffix_vandskip
::value(seeSection5.6onpage83fordetails):Clickheretoviewcode
image
if(!std::is_same_v<decltype(size1),decltype(size2)>){
std::cout<<"sizetypesdiffer"<<’\n’;
}
Notethatotherconstraintsonthetypeofnontypetemplateparametersremainin
effect.Especially,therestrictionsaboutpossibletypesfornontypetemplate
argumentsdiscussedinSection3.3onpage49stillapply.Forexample:Click
heretoviewcodeimage
Stack<int,3.14>sd;//ERROR:Floating-pointnontypeargument
And,becauseyoucanalsopassstringsasconstantarrays(sinceC++17even
staticlocallydeclared;seeSection3.3onpage49),thefollowingispossible:
basics/message.cpp
Clickheretoviewcodeimage
#include<iostream>
template<autoT>//takevalueofanypossiblenontypeparameter
(sinceC++17)
classMessage{
public:
voidprint(){
std::cout<<T<<’\n’;
}
};
intmain()
{
Message<42>msg1;
msg1.print();//initializewithint42andprintthatvalue
staticcharconsts[]="hello";
Message<s>msg2;//initializewithcharconst[6]"hello"
msg2.print();//andprintthatvalue
}
Notealsothateventemplate<decltype(auto)N>ispossible,which
allowsinstantiationofNasareference:Clickheretoviewcodeimage
template<decltype(auto)N>
classC{
…
};
inti;
C<(i)>x;//Nisint&
SeeSection15.10.1onpage296fordetails.
3.5Summary
•Templatescanhavetemplateparametersthatarevaluesratherthantypes.
•Youcannotusefloating-pointnumbersorclass-typeobjectsasargumentsfor
nontypetemplateparameters.Forpointers/referencestostringliterals,
temporaries,andsubobjects,restrictionsapply.
•Usingautoenablestemplatestohavenontypetemplateparametersthatare
valuesofgenerictypes.
Chapter4
VariadicTemplates
SinceC++11,templatescanhaveparametersthatacceptavariablenumberof
templatearguments.Thisfeatureallowstheuseoftemplatesinplaceswhereyou
havetopassanarbitrarynumberofargumentsofarbitrarytypes.Atypical
applicationistopassanarbitrarynumberofparametersofarbitrarytypethrough
aclassorframework.Anotherapplicationistoprovidegenericcodetoprocess
anynumberofparametersofanytype.
4.1VariadicTemplates
Templateparameterscanbedefinedtoacceptanunboundednumberoftemplate
arguments.Templateswiththisabilityarecalledvariadictemplates.
4.1.1VariadicTemplatesbyExample
Forexample,youcanusethefollowingcodetocallprint()foravariable
numberofargumentsofdifferenttypes:Clickheretoviewcodeimage
basics/varprint1.hpp
#include<iostream>
voidprint()
{
}
template<typenameT,typename…Types>
voidprint(TfirstArg,Types…args)
{
std::cout<<firstArg<<’\n’;//printfirstargument
print(args…);//callprint()forremainingarguments
}
Ifoneormoreargumentsarepassed,thefunctiontemplateisused,whichby
specifyingthefirstargumentseparatelyallowsprintingofthefirstargument
beforerecursivelycallingprint()fortheremainingarguments.These
remainingargumentsnamedargsareafunctionparameterpack:Clickhereto
viewcodeimage
voidprint(TfirstArg,Types…args)usingdifferent“Types”
specifiedbyatemplateparameterpack:Clickheretoviewcodeimage
template<typenameT,typename…Types>Toendtherecursion,the
nontemplateoverloadofprint()isprovided,whichisissuedwhenthe
parameterpackisempty.
Forexample,acallsuchasClickheretoviewcodeimage
std::strings("world");
print(7.5,"hello",s);wouldoutputthefollowing:Clickhereto
viewcodeimage
7.5
hello
world
ThereasonisthatthecallfirstexpandstoClickheretoviewcodeimage
print<double,charconst*,std::string>(7.5,"hello",s);with
•firstArghavingthevalue7.5sothattypeTisadoubleand•args
beingavariadictemplateargumenthavingthevalues"hello"oftypechar
const*and"world"oftypestd::string.
Afterprinting7.5asfirstArg,itcallsprint()againfortheremaining
arguments,whichthenexpandsto:Clickheretoviewcodeimage
print<charconst*,std::string>("hello",s);with
•firstArghavingthevalue"hello"sothattypeTisacharconst*
hereand•argsbeingavariadictemplateargumenthavingthevalueoftype
std::string.
Afterprinting"hello"asfirstArg,itcallsprint()againforthe
remainingarguments,whichthenexpandsto:Clickheretoviewcodeimage
print<std::string>(s);with
•firstArghavingthevalue"world"sothattypeTisastd::string
nowand•argsbeinganemptyvariadictemplateargumenthavingnovalue.
Thus,afterprinting"world"asfirstArg,wecallsprint()withno
arguments,whichresultsincallingthenontemplateoverloadofprint()doing
nothing.
4.1.2OverloadingVariadicandNonvariadic
Templates
Notethatyoucanalsoimplementtheexampleaboveasfollows:Clickhereto
viewcodeimage
basics/varprint2.hpp
#include<iostream>
template<typenameT>
voidprint(Targ)
{
std::cout<<arg<<’\n’;//printpassedargument
}
template<typenameT,typename…Types>
voidprint(TfirstArg,Types…args)
{
print(firstArg);//callprint()forthefirstargument
print(args…);//callprint()forremainingarguments
}
Thatis,iftwofunctiontemplatesonlydifferbyatrailingparameterpack,the
functiontemplatewithoutthetrailingparameterpackispreferred.1SectionC.3.1
onpage688explainsthemoregeneraloverloadresolutionrulethatapplieshere.
4.1.3Operatorsizeof…
C++11alsointroducedanewformofthesizeofoperatorforvariadic
templates:sizeof….Itexpandstothenumberofelementsaparameterpack
contains.Thus,Clickheretoviewcodeimage
template<typenameT,typename…Types>
voidprint(TfirstArg,Types…args)
{
std::cout<<sizeof…(Types)<<’\n’;//printnumberofremaining
types
std::cout<<sizeof…(args)<<’\n’;//printnumberofremainingargs
…
}
twiceprintsthenumberofremainingargumentsafterthefirstargumentpassed
toprint().Asyoucansee,youcancallsizeof…forbothtemplate
parameterpacksandfunctionparameterpacks.
Thismightleadustothinkwecanskipthefunctionfortheendofthe
recursionbynotcallingitincasetherearenomorearguments:Clickhereto
viewcodeimage
template<typenameT,typename…Types>
voidprint(TfirstArg,Types…args)
{
std::cout<<firstArg<<’\n’;
if(sizeof…(args)>0){//errorifsizeof…(args)==0
print(args…);//andnoprint()fornoargumentsdeclared
}
}
However,thisapproachdoesn’tworkbecauseingeneralbothbranchesofallif
statementsinfunctiontemplatesareinstantiated.Whethertheinstantiatedcode
isusefulisarun-timedecision,whiletheinstantiationofthecallisacompile-
timedecision.Forthisreason,ifyoucalltheprint()functiontemplatefor
one(last)argument,thestatementwiththecallofprint(args…)stillis
instantiatedfornoargument,andifthereisnofunctionprint()forno
argumentsprovided,thisisanerror.
However,notethatsinceC++17,acompile-timeifisavailable,which
achieveswhatwasexpectedherewithaslightlydifferentsyntax.Thiswillbe
discussedinSection8.5onpage134.
4.2FoldExpressions
SinceC++17,thereisafeaturetocomputetheresultofusingabinaryoperator
overalltheargumentsofaparameterpack(withanoptionalinitialvalue).
Forexample,thefollowingfunctionreturnsthesumofallpassedarguments:
Clickheretoviewcodeimage
template<typename…T>
autofoldSum(T…s){
return(…+s);//((s1+s2)+s3)…
}
Iftheparameterpackisempty,theexpressionisusuallyill-formed(withthe
exceptionthatforoperator&&thevalueistrue,foroperator||thevalueis
false,andforthecommaoperatorthevalueforanemptyparameterpackis
void()).
Table4.1liststhepossiblefoldexpressions.
FoldExpression Evaluation
(…oppack)(((pack1oppack2)oppack3)…oppackN)
(packop…) (pack1op(…(packN-1oppackN)))
(initop…oppack)(((initoppack1)oppack2)…oppackN)
(packop…opinit) (pack1op(…(packNopinit)))
Table4.1.FoldExpressions(sinceC++17)
Youcanusealmostallbinaryoperatorsforfoldexpressions(seeSection
12.4.6onpage208fordetails).Forexample,youcanuseafoldexpressionto
traverseapathinabinarytreeusingoperator->*:Clickheretoviewcode
image
basics/foldtraverse.cpp
//definebinarytreestructureandtraversehelpers:
structNode{
intvalue;
Node*left;
Node*right;
Node(inti=0):value(i),left(nullptr),right(nullptr){
}
…
};
autoleft=&Node::left;
autoright=&Node::right;
//traversetree,usingfoldexpression:
template<typenameT,typename…TP>
Node*traverse(Tnp,TP…paths){
return(np->*…->*paths);//np->*paths1->*paths2…
}
intmain()
{
//initbinarytreestructure:
Node*root=newNode{0};
root->left=newNode{1};
root->left->right=newNode{2};
…
//traversebinarytree:
Node*node=traverse(root,left,right);
…
}
Here,
(np->*…->*paths)
usesafoldexpressiontotraversethevariadicelementsofpathsfromnp.
Withsuchafoldexpressionusinganinitializer,wemightthinkabout
simplifyingthevariadictemplatetoprintallarguments,introducedabove:Click
heretoviewcodeimage
template<typename…Types>
voidprint(Typesconst&…args)
{
(std::cout<<…<<args)<<’\n’;
}
However,notethatinthiscasenowhitespaceseparatesalltheelementsfromthe
parameterpack.Todothat,youneedanadditionalclasstemplate,whichensures
thatanyoutputofanyargumentisextendedbyaspace:
basics/addspace.hpp
Clickheretoviewcodeimage
template<typenameT>
classAddSpace
{
private:
Tconst&ref;//refertoargumentpassedinconstructor
public:
AddSpace(Tconst&r):ref(r){
}
friendstd::ostream&operator<<(std::ostream&os,AddSpace<T>s){
returnos<<s.ref<<’’;//outputpassedargumentandaspace
}
};
template<typename…Args>
voidprint(Args…args){
(std::cout<<…<<AddSpace(args))<<’\n’;
}
NotethattheexpressionAddSpace(args)usesclasstemplateargument
deduction(seeSection2.9onpage40)tohavetheeffectofAddSpace<Args>
(args),whichforeachargumentcreatesanAddSpaceobjectthatrefersto
thepassedargumentandaddsaspacewhenitisusedinoutputexpressions.
SeeSection12.4.6onpage207fordetailsaboutfoldexpressions.
4.3ApplicationofVariadicTemplates
Variadictemplatesplayanimportantrolewhenimplementinggenericlibraries,
suchastheC++standardlibrary.
Onetypicalapplicationistheforwardingofavariadicnumberofarguments
ofarbitrarytype.Forexample,weusethisfeaturewhen:•Passingargumentsto
theconstructorofanewheapobjectownedbyasharedpointer:Clickhereto
viewcodeimage
//createsharedpointertocomplex<float>initializedby4.2and
7.7:
autosp=std::make_shared<std::complex<float>>(4.2,7.7);
•Passingargumentstoathread,whichisstartedbythelibrary:Clickhereto
viewcodeimage
std::threadt(foo,42,"hello");//callfoo(42,"hello")ina
separatethread
•Passingargumentstotheconstructorofanewelementpushedintoavector:
Clickheretoviewcodeimage
std::vector<Customer>v;
…
v.emplace("Tim","Jovi",1962);//insertaCustomerinitializedby
threearguments
Usually,theargumentsare“perfectlyforwarded”withmovesemantics(see
Section6.1onpage91),sothatthecorrespondingdeclarationsare,forexample:
Clickheretoviewcodeimage
namespacestd{
template<typenameT,typename…Args>shared_ptr<T>
make_shared(Args&&…args);
classthread{
public:
template<typenameF,typename…Args>
explicitthread(F&&f,Args&&…args);
…
};
template<typenameT,typenameAllocator=allocator<T>>
classvector{
public:
template<typename…Args>referenceemplace_back(Args&&…args);
…
};
}
Notealsothatthesamerulesapplytovariadicfunctiontemplateparametersas
forordinaryparameters.Forexample,ifpassedbyvalue,argumentsarecopied
anddecay(e.g.,arraysbecomepointers),whileifpassedbyreference,
parametersrefertotheoriginalparameteranddon’tdecay:Clickheretoview
codeimage
//argsarecopieswithdecayedtypes:
template<typename…Args>voidfoo(Args…args);
//argsarenondecayedreferencestopassedobjects:
template<typename…Args>voidbar(Argsconst&…args);
4.4VariadicClassTemplatesandVariadic
Expressions
Besidestheexamplesabove,parameterpackscanappearinadditionalplaces,
including,forexample,expressions,classtemplates,usingdeclarations,and
evendeductionguides.Section12.4.2onpage202hasacompletelist.
4.4.1VariadicExpressions
Youcandomorethanjustforwardalltheparameters.Youcancomputewith
them,whichmeanstocomputewithalltheparametersinaparameterpack.
Forexample,thefollowingfunctiondoubleseachparameteroftheparameter
packargsandpasseseachdoubledargumenttoprint():Clickheretoview
codeimage
template<typename…T>
voidprintDoubled(Tconst&…args)
{
print(args+args…);
}
If,forexample,youcallClickheretoviewcodeimage
printDoubled(7.5,std::string("hello"),std::complex<float>(4,2));
thefunctionhasthefollowingeffect(exceptforanyconstructorsideeffects):
Clickheretoviewcodeimage
print(7.5+7.5,
std::string("hello")+std::string("hello"),
std::complex<float>(4,2)+std::complex<float>(4,2);Ifyoujustwant
toadd1toeachargument,notethatthedotsfromtheellipsismay
notdirectlyfollowanumericliteral:Clickheretoviewcodeimage
template<typename…T>
voidaddOne(Tconst&…args)
{
print(args+1…);//ERROR:1…isaliteralwithtoomanydecimal
points
print(args+1…);//OK
print((args+1)…);//OK
}
Compile-timeexpressionscanincludetemplateparameterpacksinthesame
way.Forexample,thefollowingfunctiontemplatereturnswhetherthetypesof
alltheargumentsarethesame:Clickheretoviewcodeimage
template<typenameT1,typename…TN>
constexprboolisHomogeneous(T1,TN…)
{
return(std::is_same<T1,TN>::value&&…);//sinceC++17
}
Thisisanapplicationoffoldexpressions(seeSection4.2onpage58):ForClick
heretoviewcodeimage
isHomogeneous(43,-1,"hello")
theexpressionforthereturnvalueexpandstoClickheretoviewcodeimage
std::is_same<int,int>::value&&std::is_same<int,charconst*>::value
andyieldsfalse,whileClickheretoviewcodeimage
isHomogeneous("hello","","world","!")yieldstruebecauseall
passedargumentsarededucedtobecharconst*(notethatthe
argumenttypesdecaybecausethecallargumentsarepassedbyvalue).
4.4.2VariadicIndices
Asanotherexample,thefollowingfunctionusesavariadiclistofindicesto
accessthecorrespondingelementofthepassedfirstargument:Clickhereto
viewcodeimage
template<typenameC,typename…Idx>
voidprintElems(Cconst&coll,Idx…idx)
{
print(coll[idx]…);
}
Thatis,whencallingClickheretoviewcodeimage
std::vector<std::string>coll={"good","times","say","bye"};
printElems(coll,2,0,3);theeffectistocallClickheretoviewcode
image
print(coll[2],coll[0],coll[3]);
Youcanalsodeclarenontypetemplateparameterstobeparameterpacks.For
example:Clickheretoviewcodeimage
template<std::size_t…Idx,typenameC>
voidprintIdx(Cconst&coll)
{
print(coll[Idx]…);
}
allowsyoutocallClickheretoviewcodeimage
std::vector<std::string>coll={"good","times","say","bye"};
printIdx<2,0,3>(coll);
whichhasthesameeffectasthepreviousexample.
4.4.3VariadicClassTemplates
Variadictemplatescanalsobeclasstemplates.Animportantexampleisaclass
whereanarbitrarynumberoftemplateparametersspecifythetypesof
correspondingmembers:Clickheretoviewcodeimage
template<typename…Elements>
classTuple;
Tuple<int,std::string,char>t;//tcanholdinteger,string,and
character
ThiswillbediscussedinChapter25.
Anotherexampleistobeabletospecifythepossibletypesobjectscanhave:
Clickheretoviewcodeimage
template<typename…Types>
classVariant;Variant<int,std::string,char>v;//vcanhold
integer,string,orcharacter
ThiswillbediscussedinChapter26.
Youcanalsodefineaclassthatasatyperepresentsalistofindices:Click
heretoviewcodeimage
//typeforarbitrarynumberofindices:
template<std::size_t…>
structIndices{
};
Thiscanbeusedtodefineafunctionthatcallsprint()fortheelementsofa
std::arrayorstd::tupleusingthecompile-timeaccesswithget<>()
forthegivenindices:Clickheretoviewcodeimage
template<typenameT,std::size_t…Idx>
voidprintByIdx(Tt,Indices<Idx…>)
{
print(std::get<Idx>(t)…);
}
Thistemplatecanbeusedasfollows:Clickheretoviewcodeimage
std::array<std::string,5>arr={"Hello","my","new","!",
"World"};printByIdx(arr,Indices<0,4,3>());orasfollows:
Clickheretoviewcodeimage
autot=std::make_tuple(12,"monkeys",2.0);
printByIdx(t,Indices<0,1,2>());
Thisisafirststeptowardsmeta-programming,whichwillbediscussedin
Section8.1onpage123andChapter23.
4.4.4VariadicDeductionGuides
Evendeductionguides(seeSection2.9onpage42)canbevariadic.For
example,theC++standardlibrarydefinesthefollowingdeductionguidefor
std::arrays:Clickheretoviewcodeimage
namespacestd{
template<typenameT,typename…U>array(T,U…)
->array<enable_if_t<(is_same_v<T,U>&&…),T>,
(1+sizeof…(U))>;
}
Aninitializationsuchasstd::arraya{42,45,77};
deducesTintheguidetothetypeoftheelement,andthevariousU…typestothe
typesofthesubsequentelements.Thetotalnumberofelementsistherefore1+
sizeof…(U):Clickheretoviewcodeimage
std::array<int,3>a{42,45,77};Thestd::enable_if<>expressionfor
thefirstarrayparameterisafoldexpressionthat(asintroducedas
isHomogeneous()inSection4.4.1onpage62)expandsto:Clickhere
toviewcodeimage
is_same_v<T,U1>&&is_same_v<T,U2>&&is_same_v<T,U3>…
Iftheresultisnottrue(i.e.,notalltheelementtypesarethesame),the
deductionguideisdiscardedandtheoveralldeductionfails.Thisway,the
standardlibraryensuresthatallelementsmusthavethesametypeforthe
deductionguidetosucceed.
4.4.5VariadicBaseClassesandusing
Finally,considerthefollowingexample:Clickheretoviewcodeimage
basics/varusing.cpp
#include<string>
#include<unordered_set>
classCustomer
{
private:
std::stringname;
public:
Customer(std::stringconst&n):name(n){}
std::stringgetName()const{returnname;}
};
structCustomerEq{
booloperator()(Customerconst&c1,Customerconst&c2)const{
returnc1.getName()==c2.getName();
}
};
structCustomerHash{
std::size_toperator()(Customerconst&c)const{
returnstd::hash<std::string>()(c.getName());
}
};
//defineclassthatcombinesoperator()forvariadicbaseclasses:
template<typename…Bases>
structOverloader:Bases…
{
usingBases::operator()…;//OKsinceC++17
};
intmain()
{
//combinehasherandequalityforcustomersinonetype:
usingCustomerOP=Overloader<CustomerHash,CustomerEq>;
std::unordered_set<Customer,CustomerHash,CustomerEq>coll1;
std::unordered_set<Customer,CustomerOP,CustomerOP>coll2;
…
}
Here,wefirstdefineaclassCustomerandindependentfunctionobjectsto
hashandcompareCustomerobjects.WithClickheretoviewcodeimage
template<typename…Bases>
structOverloader:Bases…
{
usingBases::operator()…;//OKsinceC++17
};
wecandefineaclassderivedfromavariadicnumberofbaseclassesthatbrings
intheoperator()declarationsfromeachofthosebaseclasses.WithClick
heretoviewcodeimage
usingCustomerOP=Overloader<CustomerHash,CustomerEq>;weusethis
featuretoderiveCustomerOPfromCustomerHashandCustomerEqand
enablebothimplementationsofoperator()inthederivedclass.
SeeSection26.4onpage611foranotherapplicationofthistechnique.
4.5Summary
•Byusingparameterpacks,templatescanbedefinedforanarbitrarynumberof
templateparametersofarbitrarytype.
•Toprocesstheparameters,youneedrecursionand/oramatchingnonvariadic
function.
•Operatorsizeof…yieldsthenumberofargumentsprovidedforaparameter
pack.
•Atypicalapplicationofvariadictemplatesisforwardinganarbitrarynumberof
argumentsofarbitrarytype.
•Byusingfoldexpressions,youcanapplyoperatorstoallargumentsofa
parameterpack.
1Initially,inC++11andC++14thiswasanambiguity,whichwasfixedlater
(see[CoreIssue1395]),butallcompilershandleitthiswayinallversions.
Chapter5
TrickyBasics
Thischaptercoverssomefurtherbasicaspectsoftemplatesthatarerelevantto
thepracticaluseoftemplates:anadditionaluseofthetypenamekeyword,
definingmemberfunctionsandnestedclassesastemplates,templatetemplate
parameters,zeroinitialization,andsomedetailsaboutusingstringliteralsas
argumentsforfunctiontemplates.Theseaspectscanbetrickyattimes,butevery
day-to-dayprogrammershouldhaveheardofthem.
5.1Keywordtypename
ThekeywordtypenamewasintroducedduringthestandardizationofC++to
clarifythatanidentifierinsideatemplateisatype.Considerthefollowing
example:Clickheretoviewcodeimage
template<typenameT>
classMyClass{
public:
…
voidfoo(){
typenameT::SubType*ptr;
}
};
Here,thesecondtypenameisusedtoclarifythatSubTypeisatypedefined
withinclassT.Thus,ptrisapointertothetypeT::SubType.
Withouttypename,SubTypewouldbeassumedtobeanontypemember
(e.g.,astaticdatamemberoranenumeratorconstant).Asaresult,the
expressionT::SubType*ptr
wouldbeamultiplicationofthestaticSubTypememberofclassTwithptr,
whichisnotanerror,becauseforsomeinstantiationsofMyClass<>thiscould
bevalidcode.
Ingeneral,typenamehastobeusedwheneveranamethatdependsona
templateparameterisatype.ThisisdiscussedindetailinSection13.3.2onpage
228.
Oneapplicationoftypenameisthedeclarationtoiteratorsofstandard
containersingenericcode:Clickheretoviewcodeimage
basics/printcoll.hpp
#include<iostream>
//printelementsofanSTLcontainer
template<typenameT>
voidprintcoll(Tconst&coll)
{
typenameT::const_iteratorpos;//iteratortoiterateovercoll
typenameT::const_iteratorend(coll.end());//endposition
for(pos=coll.begin();pos!=end;++pos){
std::cout<<*pos<<’’;
}
std::cout<<’\n’;
}
Inthisfunctiontemplate,thecallparameterisanstandardcontaineroftypeT.
Toiterateoverallelementsofthecontainer,theiteratortypeofthecontaineris
used,whichisdeclaredastypeconst_iteratorinsideeachstandard
containerclass:Clickheretoviewcodeimage
classstlcontainer{
public:
usingiterator=…;//iteratorforread/writeaccess
usingconst_iterator=…;//iteratorforreadaccess
…
};
Thus,toaccesstypeconst_iteratoroftemplatetypeT,youhaveto
qualifyitwithaleadingtypename:Clickheretoviewcodeimage
typenameT::const_iteratorpos;SeeSection13.3.2onpage228for
moredetailsabouttheneedfortypenameuntilC++17.NotethatC++20
willprobablyremovetheneedfortypenameinmanycommoncases(see
Section17.1onpage354fordetails).
5.2ZeroInitialization
Forfundamentaltypessuchasint,double,orpointertypes,thereisno
defaultconstructorthatinitializesthemwithausefuldefaultvalue.Instead,any
noninitializedlocalvariablehasanundefinedvalue:Clickheretoviewcode
image
voidfoo()
{
intx;//xhasundefinedvalue
int*ptr;//ptrpointstoanywhere(insteadofnowhere)
}
Nowifyouwritetemplatesandwanttohavevariablesofatemplatetype
initializedbyadefaultvalue,youhavetheproblemthatasimpledefinition
doesn’tdothisforbuilt-intypes:Clickheretoviewcodeimage
template<typenameT>
voidfoo()
{
Tx;//xhasundefinedvalueifTisbuilt-intype
}
Forthisreason,itispossibletocallexplicitlyadefaultconstructorforbuilt-in
typesthatinitializesthemwithzero(orfalseforboolornullptrfor
pointers).Asaconsequence,youcanensureproperinitializationevenforbuilt-
intypesbywritingthefollowing:Clickheretoviewcodeimage
template<typenameT>
voidfoo()
{
Tx{};//xiszero(orfalse)ifTisabuilt-intype
}
Thiswayofinitializationiscalledvalueinitialization,whichmeanstoeithercall
aprovidedconstructororzeroinitializeanobject.Thisevenworksifthe
constructorisexplicit.
BeforeC++11,thesyntaxtoensureproperinitializationwasClickhereto
viewcodeimage
Tx=T();//xiszero(orfalse)ifTisabuilt-intype
PriortoC++17,thismechanism(whichisstillsupported)onlyworkedifthe
constructorselectedforthecopy-initializationisnotexplicit.InC++17,
mandatorycopyelisionavoidsthatlimitationandeithersyntaxcanwork,butthe
bracedinitializednotationcanuseaninitializer-listconstructor1ifnodefault
constructorisavailable.
Toensurethatamemberofaclasstemplate,forwhichthetypeis
parameterized,getsinitialized,youcandefineadefaultconstructorthatusesa
bracedinitializertoinitializethemember:Clickheretoviewcodeimage
template<typenameT>
classMyClass{
private:
Tx;
public:
MyClass():x{}{//ensuresthatxisinitializedevenforbuilt-in
types
}
…
};
Thepre-C++11syntax
Clickheretoviewcodeimage
MyClass():x(){//ensuresthatxisinitializedevenforbuilt-in
types
}
alsostillworks.
SinceC++11,youcanalsoprovideadefaultinitializationforanonstatic
member,sothatthefollowingisalsopossible:Clickheretoviewcodeimage
template<typenameT>
classMyClass{
private:
Tx{};//zero-initializexunlessotherwisespecified
…
};
However,notethatdefaultargumentscannotusethatsyntax.Forexample,Click
heretoviewcodeimage
template<typenameT>
voidfoo(Tp{}){//ERROR
…
}
Instead,wehavetowrite:
Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tp=T{}){//OK(mustuseT()beforeC++11)
…
}
5.3Usingthis->
Forclasstemplateswithbaseclassesthatdependontemplateparameters,using
anamexbyitselfisnotalwaysequivalenttothis->x,eventhoughamember
xisinherited.Forexample:Clickheretoviewcodeimage
template<typenameT>
classBase{
public:
voidbar();
};
template<typenameT>
classDerived:Base<T>{
public:
voidfoo(){
bar();//callsexternalbar()orerror
}
};
Inthisexample,forresolvingthesymbolbarinsidefoo(),bar()definedin
Baseisneverconsidered.Therefore,eitheryouhaveanerror,oranother
bar()(suchasaglobalbar())iscalled.
WediscussthisissueinSection13.4.2onpage237indetail.Forthemoment,
asaruleofthumb,werecommendthatyoualwaysqualifyanysymbolthatis
declaredinabasethatissomehowdependentonatemplateparameterwith
this->orBase<T>::.
5.4TemplatesforRawArraysandStringLiterals
Whenpassingrawarraysorstringliteralstotemplates,somecarehastobe
taken.First,ifthetemplateparametersaredeclaredasreferences,thearguments
don’tdecay.Thatis,apassedargumentof"hello"hastypechar
const[6].Thiscanbecomeaproblemifrawarraysorstringargumentsof
differentlengtharepassedbecausethetypesdiffer.Onlywhenpassingthe
argumentbyvalue,thetypesdecay,sothatstringliteralsareconvertedtotype
charconst*.ThisisdiscussedindetailinChapter7.
Notethatyoucanalsoprovidetemplatesthatspecificallydealwithrawarrays
orstringliterals.Forexample:Clickheretoviewcodeimage
basics/lessarray.hpp
template<typenameT,
intN,
intM>boolless(T(&a)[N],T(&b)[M])
{
for(inti=0;i<N&&i<M;++i)
{
if(a[i]<b[i])returntrue;if(b[i]<a[i])returnfalse;}returnN<
M;
}
Here,whencalling
Clickheretoviewcodeimage
intx[]={1,2,3};
inty[]={1,2,3,4,5};
std::cout<<less(x,y)<<’\n’;less<>()isinstantiatedwithTbeing
int,Nbeing3,andMbeing5.
Youcanalsousethistemplateforstringliterals:Clickheretoviewcode
image
std::cout<<less("ab","abc")<<’\n’;Inthiscase,less<>()is
instantiatedwithTbeingcharconst,Nbeing3andMbeing4.
Ifyouonlywanttoprovideafunctiontemplateforstringliterals(andother
chararrays),youcandothisasfollows:Clickheretoviewcodeimage
basics/lessstring.hpp
template<intN,intM>
boolless(charconst(&a)[N],charconst(&b)[M])
{
for(inti=0;i<N&&i<M;++i){
if(a[i]<b[i])returntrue;
if(b[i]<a[i])returnfalse;
}
returnN<M;
}
Notethatyoucanandsometimeshavetooverloadorpartiallyspecializefor
arraysofunknownbounds.Thefollowingprogramillustratesallpossible
overloadsforarrays:Clickheretoviewcodeimage
basics/arrays.hpp
#include<iostream>
template<typenameT>
structMyClass;//primarytemplate
template<typenameT,std::size_tSZ>
structMyClass<T[SZ]>//partialspecializationforarraysofknown
bounds
{
staticvoidprint(){std::cout<<"print()forT["<<SZ<<"]\n";}
};
template<typenameT,std::size_tSZ>
structMyClass<T(&)[SZ]>//partialspec.forreferencestoarraysof
knownbounds
{
staticvoidprint(){std::cout<<"print()forT(&)["<<SZ<<"]\n";
}
};
template<typenameT>
structMyClass<T[]>//partialspecializationforarraysofunknown
bounds
{
staticvoidprint(){std::cout<<"print()forT[]\n";}
};
template<typenameT>
structMyClass<T(&)[]>//partialspec.forreferencestoarraysof
unknownbounds
{
staticvoidprint(){std::cout<<"print()forT(&)[]\n";}
};
template<typenameT>
structMyClass<T*>//partialspecializationforpointers
{
staticvoidprint(){std::cout<<"print()forT*\n";}
};
Here,theclasstemplateMyClass<>isspecializedforvarioustypes:arraysof
knownandunknownbound,referencestoarraysofknownandunknown
bounds,andpointers.Eachcaseisdifferentandcanoccurwhenusingarrays:
Clickheretoviewcodeimage
basics/arrays.cpp
#include"arrays.hpp"
template<typenameT1,typenameT2,typenameT3>
voidfoo(inta1[7],inta2[],//pointersbylanguagerules
int(&a3)[42],//referencetoarrayofknownbound
int(&x0)[],//referencetoarrayofunknownbound
T1x1,//passingbyvaluedecays
T2&x2,T3&&x3)//passingbyreference
{
MyClass<decltype(a1)>::print();//usesMyClass<T*>
MyClass<decltype(a2)>::print();//usesMyClass<T*>
MyClass<decltype(a3)>::print();//usesMyClass<T(&)[SZ]>
MyClass<decltype(x0)>::print();//usesMyClass<T(&)[]>
MyClass<decltype(x1)>::print();//usesMyClass<T*>
MyClass<decltype(x2)>::print();//usesMyClass<T(&)[]>
MyClass<decltype(x3)>::print();//usesMyClass<T(&)[]>
}
intmain()
{
inta[42];
MyClass<decltype(a)>::print();//usesMyClass<T[SZ]>
externintx[];//forwarddeclarearray
MyClass<decltype(x)>::print();//usesMyClass<T[]>
foo(a,a,a,x,x,x,x);
}
intx[]={0,8,15};//defineforward-declaredarray
Notethatacallparameterdeclaredasanarray(withorwithoutlength)by
languagerulesreallyhasapointertype.Notealsothattemplatesforarraysof
unknownboundscanbeusedforanincompletetypesuchasexterninti[];And
whenthisispassedbyreference,itbecomesaint(&)[],whichcanalsobe
usedasatemplateparameter.2
SeeSection19.3.1onpage401foranotherexampleusingthedifferentarray
typesingenericcode.
5.5MemberTemplates
Classmemberscanalsobetemplates.Thisispossibleforbothnestedclasses
andmemberfunctions.Theapplicationandadvantageofthisabilitycanagain
bedemonstratedwiththeStack<>classtemplate.Normallyyoucanassign
stackstoeachotheronlywhentheyhavethesametype,whichimpliesthatthe
elementshavethesametype.However,youcan’tassignastackwithelementsof
anyothertype,evenifthereisanimplicittypeconversionfortheelementtypes
defined:Clickheretoviewcodeimage
Stack<int>intStack1,intStack2;//stacksforints
Stack<float>floatStack;//stackforfloats
…
intStack1=intStack2;//OK:stackshavesametype
floatStack=intStack1;//ERROR:stackshavedifferenttypes
Thedefaultassignmentoperatorrequiresthatbothsidesoftheassignment
operatorhavethesametype,whichisnotthecaseifstackshavedifferent
elementtypes.
Bydefininganassignmentoperatorasatemplate,however,youcanenable
theassignmentofstackswithelementsforwhichanappropriatetypeconversion
isdefined.TodothisyouhavetodeclareStack<>asfollows:Clickhereto
viewcodeimage
basics/stack5decl.hpp
template<typenameT>
classStack{
private:
std::deque<T>elems;//elements
public:
voidpush(Tconst&);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
//assignstackofelementsoftypeT2
template<typenameT2>
Stack&operator=(Stack<T2>const&);
};
Thefollowingtwochangeshavebeenmade:1.Weaddedadeclarationofan
assignmentoperatorforstacksofelementsofanothertypeT2.
2.Thestacknowusesastd::deque<>asaninternalcontainerforthe
elements.Again,thisisaconsequenceoftheimplementationofthenew
assignmentoperator.
Theimplementationofthenewassignmentoperatorlookslikethis:3
Clickheretoviewcodeimage
basics/stack5assign.hpp
template<typenameT>
template<typenameT2>
Stack<T>&Stack<T>::operator=(Stack<T2>const&op2)
{
Stack<T2>tmp(op2);//createacopyoftheassignedstack
elems.clear();//removeexistingelements
while(!tmp.empty()){//copyallelements
elems.push_front(tmp.top());
tmp.pop();
}
return*this;
}
Firstlet’slookatthesyntaxtodefineamembertemplate.Insidethetemplate
withtemplateparameterT,aninnertemplatewithtemplateparameterT2is
defined:Clickheretoviewcodeimage
template<typenameT>
template<typenameT2>
…
Insidethememberfunction,youmayexpectsimplytoaccessallnecessarydata
fortheassignedstackop2.However,thisstackhasadifferenttype(ifyou
instantiateaclasstemplatefortwodifferentargumenttypes,yougettwo
differentclasstypes),soyouarerestrictedtousingthepublicinterface.It
followsthattheonlywaytoaccesstheelementsisbycallingtop().However,
eachelementhastobecomeatopelement,then.Thus,acopyofop2mustfirst
bemade,sothattheelementsaretakenfromthatcopybycallingpop().
Becausetop()returnsthelastelementpushedontothestack,wemightprefer
touseacontainerthatsupportstheinsertionofelementsattheotherendofthe
collection.Forthisreason,weuseastd::deque<>,whichprovides
push_front()toputanelementontheothersideofthecollection.
Togetaccesstoallthemembersofop2youcandeclarethatallotherstack
instancesarefriends:Clickheretoviewcodeimage
basics/stack6decl.hpp
template<typenameT>
classStack{
private:
std::deque<T>elems;//elements
public:
voidpush(Tconst&);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
//assignstackofelementsoftypeT2
template<typenameT2>
Stack&operator=(Stack<T2>const&);
//togetaccesstoprivatemembersofStack<T2>foranytypeT2:
template<typename>friendclassStack;
};
Asyoucansee,becausethenameofthetemplateparameterisnotused,youcan
omitit:Clickheretoviewcodeimage
template<typename>friendclassStack;Now,thefollowing
implementationofthetemplateassignmentoperatorispossible:Click
heretoviewcodeimage
basics/stack6assign.hpp
template<typenameT>
template<typenameT2>
Stack<T>&Stack<T>::operator=(Stack<T2>const&op2)
{
elems.clear();//removeexistingelements
elems.insert(elems.begin(),//insertatthebeginning
op2.elems.begin(),//allelementsfromop2
op2.elems.end());
return*this;
}
Whateveryourimplementationis,havingthismembertemplate,youcannow
assignastackofintstoastackoffloats:Clickheretoviewcodeimage
Stack<int>intStack;//stackforints
Stack<float>floatStack;//stackforfloats
…
floatStack=intStack;//OK:stackshavedifferenttypes,
//butintconvertstofloat
Ofcourse,thisassignmentdoesnotchangethetypeofthestackandits
elements.Aftertheassignment,theelementsofthefloatStackarestill
floatsandthereforetop()stillreturnsafloat.
Itmayappearthatthisfunctionwoulddisabletypecheckingsuchthatyou
couldassignastackwithelementsofanytype,butthisisnotthecase.The
necessarytypecheckingoccurswhentheelementofthe(copyofthe)source
stackismovedtothedestinationstack:elems.push_front(tmp.top());
If,forexample,astackofstringsgetsassignedtoastackoffloats,the
compilationofthislineresultsinanerrormessagestatingthatthestringreturned
bytmp.top()cannotbepassedasanargumenttoelems.push_front()
(themessagevariesdependingonthecompiler,butthisisthegistofwhatis
meant):Clickheretoviewcodeimage
Stack<std::string>stringStack;//stackofstrings
Stack<float>floatStack;//stackoffloats
…
floatStack=stringStack;//ERROR:std::stringdoesn’tconvertto
float
Again,youcouldchangetheimplementationtoparameterizetheinternal
containertype:Clickheretoviewcodeimage
basics/stack7decl.hpp
template<typenameT,typenameCont=std::deque<T>>
classStack{
private:
Contelems;//elements
public:
voidpush(Tconst&);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
//assignstackofelementsoftypeT2
template<typenameT2,typenameCont2>
Stack&operator=(Stack<T2,Cont2>const&);
//togetaccesstoprivatemembersofStack<T2>foranytypeT2:
template<typename,typename>friendclassStack;
};
Thenthetemplateassignmentoperatorisimplementedlikethis:Clickhereto
viewcodeimage
basics/stack7assign.hpp
template<typenameT,typenameCont>
template<typenameT2,typenameCont2>
Stack<T,Cont>&
Stack<T,Cont>::operator=(Stack<T2,Cont2>const&op2)
{
elems.clear();//removeexistingelements
elems.insert(elems.begin(),//insertatthebeginning
op2.elems.begin(),//allelementsfromop2
op2.elems.end());
return*this;
}
Remember,forclasstemplates,onlythosememberfunctionsthatarecalledare
instantiated.Thus,ifyouavoidassigningastackwithelementsofadifferent
type,youcouldevenuseavectorasaninternalcontainer:Clickheretoview
codeimage
//stackforintsusingavectorasaninternalcontainer
Stack<int,std::vector<int>>vStack;
…
vStack.push(42);vStack.push(7);
std::cout<<vStack.top()<<’\n’;Becausetheassignmentoperator
templateisn’tnecessary,noerrormessageofamissingmember
functionpush_front()occursandtheprogramisfine.
Forthecompleteimplementationofthelastexample,seeallthefileswitha
namethatstartswithstack7inthesubdirectorybasics.
SpecializationofMemberFunctionTemplates
Memberfunctiontemplatescanalsobepartiallyorfullyspecialized.For
example,forthefollowingclass:Clickheretoviewcodeimage
basics/boolstring.hpp
classBoolString{
private:
std::stringvalue;
public:
BoolString(std::stringconst&s)
:value(s){
}
template<typenameT=std::string>
Tget()const{//getvalue(convertedtoT)
returnvalue;
}
};
youcanprovideafullspecializationforthememberfunctiontemplateas
follows:Clickheretoviewcodeimage
basics/boolstringgetbool.hpp
//fullspecializationforBoolString::getValue<>()forbool
template<>
inlineboolBoolString::get<bool>()const{
returnvalue=="true"||value=="1"||value=="on";
}
Notethatyoudon’tneedandalsocan’tdeclarethespecializations;youonly
definethem.Becauseitisafullspecializationanditisinaheaderfileyouhave
todeclareitwithinlinetoavoiderrorsifthedefinitionisincludedby
differenttranslationunits.
Youcanuseclassandthefullspecializationasfollows:Clickheretoview
codeimage
std::cout<<std::boolalpha;
BoolStrings1("hello");
std::cout<<s1.get()<<’\n’;//printshello
std::cout<<s1.get<bool>()<<’\n’;//printsfalse
BoolStrings2("on");
std::cout<<s2.get<bool>()<<’\n’;//printstrue
SpecialMemberFunctionTemplates
Templatememberfunctionscanbeusedwhereverspecialmemberfunctions
allowcopyingormovingobjects.Similartoassignmentoperatorsasdefined
above,theycanalsobeconstructors.However,notethattemplateconstructorsor
templateassignmentoperatorsdon’treplacepredefinedconstructorsor
assignmentoperators.Membertemplatesdon’tcountasthespecialmember
functionsthatcopyormoveobjects.Inthisexample,forassignmentsofstacks
ofthesametype,thedefaultassignmentoperatorisstillcalled.
Thiseffectcanbegoodandbad:•Itcanhappenthatatemplateconstructoror
assignmentoperatorisabettermatchthanthepredefinedcopy/moveconstructor
orassignmentoperator,althoughatemplateversionisprovidedforinitialization
ofothertypesonly.SeeSection6.2onpage95fordetails.
•Itisnoteasyto“templify”acopy/moveconstructor,forexample,tobeableto
constrainitsexistence.SeeSection6.4onpage102fordetails.
5.5.1The.templateConstruct
Sometimes,itisnecessarytoexplicitlyqualifytemplateargumentswhencalling
amembertemplate.Inthatcase,youhavetousethetemplatekeywordto
ensurethata<isthebeginningofthetemplateargumentlist.Considerthe
followingexampleusingthestandardbitsettype:Clickheretoviewcode
image
template<unsignedlongN>
voidprintBitset(std::bitset<N>const&bs){
std::cout<<bs.templateto_string<char,std::char_traits<char>,
std::allocator<char>>();
}
Forthebitsetbswecallthememberfunctiontemplateto_string(),while
explicitlyspecifyingthestringtypedetails.Withoutthatextrauseof
.template,thecompilerdoesnotknowthattheless-thantoken(<)that
followsisnotreallyless-thanbutthebeginningofatemplateargumentlist.Note
thatthisisaproblemonlyiftheconstructbeforetheperioddependsona
templateparameter.Inourexample,theparameterbsdependsonthetemplate
parameterN.
The.templatenotation(andsimilarnotationssuchas->templateand
::template)shouldbeusedonlyinsidetemplatesandonlyiftheyfollow
somethingthatdependsonatemplateparameter.SeeSection13.3.3onpage230
fordetails.
5.5.2GenericLambdasandMemberTemplates
Notethatgenericlambdas,introducedwithC++14,areshortcutsformember
templates.Asimplelambdacomputingthe“sum”oftwoargumentsofarbitrary
types:Clickheretoviewcodeimage
[](autox,autoy){
returnx+y;
}
isashortcutforadefault-constructedobjectofthefollowingclass:Clickhereto
viewcodeimage
classSomeCompilerSpecificName{
public:
SomeCompilerSpecificName();//constructoronlycallablebycompiler
template<typenameT1,typenameT2>
autooperator()(T1x,T2y)const{
returnx+y;
}
};
SeeSection15.10.6onpage309fordetails.
5.6VariableTemplates
SinceC++14,variablesalsocanbeparameterizedbyaspecifictype.Sucha
thingiscalledavariabletemplate.4
Forexample,youcanusethefollowingcodetodefinethevalueofˇwhile
stillnotdefiningthetypeofthevalue:Clickheretoviewcodeimage
template<typenameT>
constexprTpi{3.1415926535897932385};Notethat,asforall
templates,thisdeclarationmaynotoccurinsidefunctionsorblock
scope.
Touseavariabletemplate,youhavetospecifyitstype.Forexample,the
followingcodeusestwodifferentvariablesofthescopewherepi<>is
declared:Clickheretoviewcodeimage
std::cout<<pi<double><<’\n’;
std::cout<<pi<float><<’\n’;Youcanalsodeclarevariable
templatesthatareusedindifferenttranslationunits:Clickhereto
viewcodeimage
//==header.hpp:
template<typenameT>Tval{};//zeroinitializedvalue
//==translationunit1:
#include"header.hpp"
intmain()
{
val<long>=42;
print();
}
//==translationunit2:
#include"header.hpp"
voidprint()
{
std::cout<<val<long><<’\n’;//OK:prints42
}
Variabletemplatescanalsohavedefaulttemplatearguments:Clickheretoview
codeimage
template<typenameT=longdouble>
constexprTpi=T{3.1415926535897932385};Youcanusethedefaultor
anyothertype:Clickheretoviewcodeimage
std::cout<<pi<><<’\n’;//outputsalongdouble
std::cout<<pi<float><<’\n’;//outputsafloat
However,notethatyoualwayshavetospecifytheanglebrackets.Justusingpi
isanerror:Clickheretoviewcodeimage
std::cout<<pi<<’\n’;//ERROR
Variabletemplatescanalsobeparameterizedbynontypeparameters,whichalso
maybeusedtoparameterizetheinitializer.Forexample:Clickheretoviewcode
image
#include<iostream>
#include<array>
template<intN>
std::array<int,N>arr{};//arraywithNelements,zero-initialized
template<autoN>
constexprdecltype(N)dval=N;//typeofdvaldependsonpassed
value
intmain()
{
std::cout<<dval<’c’><<’\n’;//Nhasvalue’c’oftypechar
arr<10>[0]=42;//setsfirstelementofglobalarr
for(std::size_ti=0;i<arr<10>.size();++i){//usesvaluessetin
arr
std::cout<<arr<10>[i]<<’\n’;
}
}
Again,notethatevenwhentheinitializationofanditerationoverarrhappens
indifferenttranslationunitsthesamevariablestd::array<int,10>arr
ofglobalscopeisstillused.
VariableTemplatesforDataMembers
Ausefulapplicationofvariabletemplatesistodefinevariablesthatrepresent
membersofclasstemplates.Forexample,ifaclasstemplateisdefinedas
follows:Clickheretoviewcodeimage
template<typenameT>
classMyClass{
public:
staticconstexprintmax=1000;
};
whichallowsyoutodefinedifferentvaluesfordifferentspecializationsof
MyClass<>,thenyoucandefineClickheretoviewcodeimage
template<typenameT>
intmyMax=MyClass<T>::max;sothatapplicationprogrammerscanjust
writeClickheretoviewcodeimage
autoi=myMax<std::string>;insteadof
Clickheretoviewcodeimage
autoi=MyClass<std::string>::max;Thismeans,forastandardclass
suchasClickheretoviewcodeimage
namespacestd{
template<typenameT>classnumeric_limits{
public:
…
staticconstexprboolis_signed=false;
…
};
}
youcandefineClickheretoviewcodeimage
template<typenameT>
constexprboolisSigned=std::numeric_limits<T>::is_signed;tobe
abletowrite
isSigned<char>insteadof
Clickheretoviewcodeimage
std::numeric_limits<char>::is_signed
TypeTraitsSuffix_v
SinceC++17,thestandardlibraryusesthetechniqueofvariabletemplatesto
defineshortcutsforalltypetraitsinthestandardlibrarythatyielda(Boolean)
value.Forexample,tobeabletowriteClickheretoviewcodeimage
std::is_const_v<T>//sinceC++17
insteadof
Clickheretoviewcodeimage
std::is_const<T>::value//sinceC++11
thestandardlibrarydefines
Clickheretoviewcodeimage
namespacestd{
template<typenameT>constexprboolis_const_v=is_const<T>::value;
}
5.7TemplateTemplateParameters
Itcanbeusefultoallowatemplateparameteritselftobeaclasstemplate.
Again,ourstackclasstemplatecanbeusedasanexample.
Touseadifferentinternalcontainerforstacks,theapplicationprogrammer
hastospecifytheelementtypetwice.Thus,tospecifythetypeoftheinternal
container,youhavetopassthetypeofthecontainerandthetypeofitselements
again:Clickheretoviewcodeimage
Stack<int,std::vector<int>>vStack;//integerstackthatusesa
vector
UsingtemplatetemplateparametersallowsyoutodeclaretheStackclass
templatebyspecifyingthetypeofthecontainerwithoutrespecifyingthetypeof
itselements:Clickheretoviewcodeimage
Stack<int,std::vector>vStack;//integerstackthatusesavector
Todothis,youmustspecifythesecondtemplateparameterasatemplate
templateparameter.Inprinciple,thislooksasfollows:5
Clickheretoviewcodeimage
basics/stack8decl.hpp
template<typenameT,
template<typenameElem>classCont=std::deque>
classStack{
private:
Cont<T>elems;//elements
public:
voidpush(Tconst&);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
…
};
Thedifferenceisthatthesecondtemplateparameterisdeclaredasbeingaclass
template:Clickheretoviewcodeimage
template<typenameElem>classContThedefaultvaluehaschangedfrom
std::deque<T>tostd::deque.Thisparameterhastobeaclass
template,whichisinstantiatedforthetypethatispassedasthe
firsttemplateparameter:Cont<T>elems;
Thisuseofthefirsttemplateparameterfortheinstantiationofthesecond
templateparameterisparticulartothisexample.Ingeneral,youcaninstantiatea
templatetemplateparameterwithanytypeinsideaclasstemplate.
Asusual,insteadoftypenameyoucouldusethekeywordclassfor
templateparameters.BeforeC++11,Contcouldonlybesubstitutedbythe
nameofaclasstemplate.
Clickheretoviewcodeimage
template<typenameT,
template<classElem>classCont=std::deque>
classStack{//OK
…
};
SinceC++11,wecanalsosubstituteContwiththenameofanaliastemplate,
butitwasn’tuntilC++17thatacorrespondingchangewasmadetopermitthe
useofthekeywordtypenameinsteadofclasstodeclareatemplatetemplate
parameter:Clickheretoviewcodeimage
template<typenameT,
template<typenameElem>typenameCont=std::deque>
classStack{//ERRORbeforeC++17
…
};
Thosetwovariantsmeanexactlythesamething:Usingclassinsteadof
typenamedoesnotpreventusfromspecifyinganaliastemplateasthe
argumentcorrespondingtotheContparameter.
Becausethetemplateparameterofthetemplatetemplateparameterisnot
used,itiscustomarytoomititsname(unlessitprovidesusefuldocumentation):
Clickheretoviewcodeimage
template<typenameT,
template<typename>classCont=std::deque>
classStack{
…
};
Memberfunctionsmustbemodifiedaccordingly.Thus,youhavetospecifythe
secondtemplateparameterasthetemplatetemplateparameter.Thesameapplies
totheimplementationofthememberfunction.Thepush()memberfunction,
forexample,isimplementedasfollows:Clickheretoviewcodeimage
template<typenameT,template<typename>classCont>
voidStack<T,Cont>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
Notethatwhiletemplatetemplateparametersareplaceholdersforclassoralias
templates,thereisnocorrespondingplaceholderforfunctionorvariable
templates.
TemplateTemplateArgumentMatching
IfyoutrytousethenewversionofStack,youmaygetanerrormessage
sayingthatthedefaultvaluestd::dequeisnotcompatiblewiththetemplate
templateparameterCont.TheproblemisthatpriortoC++17atemplate
templateargumenthadtobeatemplatewithparametersthatexactlymatchthe
parametersofthetemplatetemplateparameteritsubstitutes,withsome
exceptionsrelatedtovariadictemplateparameters(seeSection12.3.4onpage
197).Defaulttemplateargumentsoftemplatetemplateargumentswerenot
considered,sothatamatchcouldn’tbeachievedbyleavingoutargumentsthat
havedefaultvalues(inC++17,defaultargumentsareconsidered).
Thepre-C++17probleminthisexampleisthatthestd::dequetemplateof
thestandardlibraryhasmorethanoneparameter:Thesecondparameter(which
describesanallocator)hasadefaultvalue,butpriortoC++17thiswasnot
consideredwhenmatchingstd::dequetotheContparameter.
Thereisaworkaround,however.Wecanrewritetheclassdeclarationsothat
theContparameterexpectscontainerswithtwotemplateparameters:Clickhere
toviewcodeimage
template<typenameT,
template<typenameElem,
typenameAlloc=std::allocator<Elem>>
classCont=std::deque>
classStack{
private:
Cont<T>elems;//elements
…
};
Again,wecouldomitAllocbecauseitisnotused.
ThefinalversionofourStacktemplate(includingmembertemplatesfor
assignmentsofstacksofdifferentelementtypes)nowlooksasfollows:Click
heretoviewcodeimage
basics/stack9.hpp
#include<deque>
#include<cassert>
#include<memory>
template<typenameT,
template<typenameElem,
typename=std::allocator<Elem>>
classCont=std::deque>
classStack{
private:
Cont<T>elems;//elements
public:
voidpush(Tconst&);//pushelement
voidpop();//popelement
Tconst&top()const;//returntopelement
boolempty()const{//returnwhetherthestackisempty
returnelems.empty();
}
//assignstackofelementsoftypeT2
template<typenameT2,
template<typenameElem2,
typename=std::allocator<Elem2>
>classCont2>
Stack<T,Cont>&operator=(Stack<T2,Cont2>const&);
//togetaccesstoprivatemembersofanyStackwithelementsoftype
T2:
template<typename,template<typename,typename>class>
friendclassStack;
};
template<typenameT,template<typename,typename>classCont>
voidStack<T,Cont>::push(Tconst&elem)
{
elems.push_back(elem);//appendcopyofpassedelem
}
template<typenameT,template<typename,typename>classCont>
voidStack<T,Cont>::pop()
{
assert(!elems.empty());
elems.pop_back();//removelastelement
}
template<typenameT,template<typename,typename>classCont>
Tconst&Stack<T,Cont>::top()const
{
assert(!elems.empty());
returnelems.back();//returncopyoflastelement
}
template<typenameT,template<typename,typename>classCont>
template<typenameT2,template<typename,typename>classCont2>
Stack<T,Cont>&
Stack<T,Cont>::operator=(Stack<T2,Cont2>const&op2)
{
elems.clear();//removeexistingelements
elems.insert(elems.begin(),//insertatthebeginning
op2.elems.begin(),//allelementsfromop2
op2.elems.end());
return*this;
}
Noteagainthattogetaccesstoallthemembersofop2wedeclarethatallother
stackinstancesarefriends(omittingthenamesofthetemplateparameters):
Clickheretoviewcodeimage
template<typename,template<typename,typename>class>
friendclassStack;Still,notallstandardcontainertemplatescan
beusedforContparameter.Forexample,std::arraywillnotwork
becauseitincludesanontypetemplateparameterforthearraylength
thathasnomatchinourtemplatetemplateparameterdeclaration.
Thefollowingprogramusesallfeaturesofthisfinalversion:Clickhereto
viewcodeimage
basics/stack9test.cpp
#include"stack9.hpp"
#include<iostream>
#include<vector>
intmain()
{
Stack<int>iStack;//stackofints
Stack<float>fStack;//stackoffloats
//manipulateintstack
iStack.push(1);
iStack.push(2);
std::cout<<"iStack.top():"<<iStack.top()<<’\n’;
//manipulatefloatstack:
fStack.push(3.3);
std::cout<<"fStack.top():"<<fStack.top()<<’\n’;
//assignstackofdifferenttypeandmanipulateagain
fStack=iStack;
fStack.push(4.4);
std::cout<<"fStack.top():"<<fStack.top()<<’\n’;
//stackfordoublessusingavectorasaninternalcontainer
Stack<double,std::vector>vStack;
vStack.push(5.5);
vStack.push(6.6);
std::cout<<"vStack.top():"<<vStack.top()<<’\n’;
vStack=fStack;
std::cout<<"vStack:";
while(!vStack.empty()){
std::cout<<vStack.top()<<’’;
vStack.pop();
}
std::cout<<’\n’;
}
Theprogramhasthefollowingoutput:Clickheretoviewcodeimage
iStack.top():2
fStack.top():3.3
fStack.top():4.4
vStack.top():6.6
vStack:4.421
Forfurtherdiscussionandexamplesoftemplatetemplateparameters,see
Section12.2.3onpage187,Section12.3.4onpage197,andSection19.2.2on
page398.
5.8Summary
•Toaccessatypenamethatdependsonatemplateparameter,youhaveto
qualifythenamewithaleadingtypename.
•Toaccessmembersofbasesclassesthatdependontemplateparameters,you
havetoqualifytheaccessbythis->ortheirclassname.
•Nestedclassesandmemberfunctionscanalsobetemplates.Oneapplicationis
theabilitytoimplementgenericoperationswithinternaltypeconversions.
•Templateversionsofconstructorsorassignmentoperatorsdon’treplace
predefinedconstructorsorassignmentoperators.
•Byusingbracedinitializationorexplicitlycallingadefaultconstructor,you
canensurethatvariablesandmembersoftemplatesareinitializedwitha
defaultvalueeveniftheyareinstantiatedwithabuilt-intype.
•Youcanprovidespecifictemplatesforrawarrays,whichcanalsobeapplicable
tostringliterals.
•Whenpassingrawarraysorstringliterals,argumentsdecay(performanarray-
to-pointerconversion)duringargumentdeductionifandonlyiftheparameter
isnotareference.
•Youcandefinevariabletemplates(sinceC++14).
•Youcanalsouseclasstemplatesastemplateparameters,astemplatetemplate
parameters.
•Templatetemplateargumentsmustusuallymatchtheirparametersexactly.
1Thatis,aconstructorwithaparameteroftype
std::initializer_list<X>,forsometypeX.
2ParametersoftypeX(&)[]—forsomearbitrarytypeX—havebecome
validonlyinC++17,throughtheresolutionofCoreissue393.However,
manycompilersacceptedsuchparametersinearlierversionsofthelanguage.
3Thisisabasicimplementationtodemonstratethetemplatefeatures.Issues
likeproperexceptionhandlingarecertainlymissing.
4Yes,wehaveverysimilartermsforverydifferentthings:Avariabletemplate
isavariablethatisatemplate(variableisanounhere).Avariadictemplateis
atemplateforavariadicnumberoftemplateparameters(variadicisan
adjectivehere).
5BeforeC++17,thereisanissuewiththisversionthatweexplaininaminute.
However,thisaffectsonlythedefaultvaluestd::deque.Thus,wecan
illustratethegeneralfeaturesoftemplatetemplateparameterswiththisdefault
valuebeforewediscusshowtodealwithitbeforeC++17.
Chapter6
MoveSemanticsandenable_if<>
OneofthemostprominentfeaturesC++11introducedwasmovesemantics.You
canuseittooptimizecopyingandassignmentsbymoving(“stealing”)internal
resourcesfromasourceobjecttoadestinationobjectinsteadofcopyingthose
contents.Thiscanbedoneprovidedthesourcenolongerneedsitsinternalvalue
orstate(becauseitisabouttobediscarded).
Movesemanticshasasignificantinfluenceonthedesignoftemplates,and
specialruleswereintroducedtosupportmovesemanticsingenericcode.This
chapterintroducesthesefeatures.
6.1PerfectForwarding
Supposeyouwanttowritegenericcodethatforwardsthebasicpropertyof
passedarguments:•Modifyableobjectsshouldbeforwardedsothattheystill
canbemodified.
•Constantobjectsshouldbeforwardedasread-onlyobjects.
•Movableobjects(objectswecan“steal”frombecausetheyareabouttoexpire)
shouldbeforwardedasmovableobjects.
Toachievethisfunctionalitywithouttemplates,wehavetoprogramallthree
cases.Forexample,toforwardacalloff()toacorrespondingfunctiong():
Clickheretoviewcodeimage
basics/move1.cpp
#include<utility>
#include<iostream>
classX{
…
};
voidg(X&){
std::cout<<"g()forvariable\n";
}
voidg(Xconst&){
std::cout<<"g()forconstant\n";
}
voidg(X&&){
std::cout<<"g()formovableobject\n";
}
//letf()forwardargumentvaltog():
voidf(X&val){
g(val);//valisnon-constlvalue=>callsg(X&)
}
voidf(Xconst&val){
g(val);//valisconstlvalue=>callsg(Xconst&)
}
voidf(X&&val){
g(std::move(val));//valisnon-constlvalue=>needsstd::move()to
callg(X&&)
}
intmain()
{
Xv;//createvariable
Xconstc;//createconstant
f(v);//f()fornonconstantobjectcallsf(X&)=>callsg(X&)
f(c);//f()forconstantobjectcallsf(Xconst&)=>callsg(X
const&)
f(X());//f()fortemporarycallsf(X&&)=>callsg(X&&)
f(std::move(v));//f()formovablevariablecallsf(X&&)=>calls
g(X&&)
}
Here,weseethreedifferentimplementationsoff()forwardingitsargumentto
g():Clickheretoviewcodeimage
voidf(X&val){
g(val);//valisnon-constlvalue=>callsg(X&)
}
voidf(Xconst&val){
g(val);//valisconstlvalue=>callsg(Xconst&)
}
voidf(X&&val){
g(std::move(val));//valisnon-constlvalue=>needsstd::move()to
callg(X&&)
}
Notethatthecodeformovableobjects(viaanrvaluereference)differsfromthe
othercode:Itneedsastd::move()becauseaccordingtolanguagerules,
movesemanticsisnotpassedthrough.1Althoughvalinthethirdf()is
declaredasrvaluereferenceitsvaluecategorywhenusedasexpressionisa
nonconstantlvalue(seeAppendixB)andbehavesasvalinthefirstf().
Withoutthemove(),g(X&)fornonconstantlvaluesinsteadofg(&&)would
becalled.
Ifwewanttocombineallthreecasesingenericcode,wehaveaproblem:
Clickheretoviewcodeimage
template<typenameT>
voidf(Tval){
g(T);
}
worksforthefirsttwocases,butnotforthe(third)casewheremovableobjects
arepassed.
C++11forthisreasonintroducesspecialrulesforperfectforwarding
parameters.Theidiomaticcodepatterntoachievethisisasfollows:Clickhere
toviewcodeimage
template<typenameT>
voidf(T&&val){
g(std::forward<T>(val));//perfectforwardvaltog()
}
Notethatstd::move()hasnotemplateparameterand“triggers”move
semanticsforthepassedargument,whilestd::forward<>()“forwards”
potentialmovesemanticdependingonapassedtemplateargument.
Don’tassumethatT&&foratemplateparameterTbehavesasX&&fora
specifictypeX.Differentrulesapply!However,syntacticallytheylookidentical:
•X&&foraspecifictypeXdeclaresaparametertobeanrvaluereference.Itcan
onlybeboundtoamovableobject(aprvalue,suchasatemporaryobject,andan
xvalue,suchasanobjectpassedwithstd::move();seeAppendixBfor
details).Itisalwaysmutableandyoucanalways“steal”itsvalue.2
•T&&foratemplateparameterTdeclaresaforwardingreference(alsocalled
universalreference).3Itcanbeboundtoamutable,immutable(i.e.,const),
ormovableobject.Insidethefunctiondefinition,theparametermaybe
mutable,immutable,orrefertoavalueyoucan“steal”theinternalsfrom.
NotethatTmustreallybethenameofatemplateparameter.Dependingona
templateparameterisnotsufficient.ForatemplateparameterT,adeclaration
suchastypenameT::iterator&&isjustanrvaluereference,nota
forwardingreference.
So,thewholeprogramtoperfectforwardargumentswilllookasfollows:
Clickheretoviewcodeimage
basics/move2.cpp
#include<utility>
#include<iostream>
classX{
…
};
voidg(X&){
std::cout<<"g()forvariable\n";
}
voidg(Xconst&){
std::cout<<"g()forconstant\n";
}
voidg(X&&){
std::cout<<"g()formovableobject\n";
}
//letf()perfectforwardargumentvaltog():
template<typenameT>
voidf(T&&val){
g(std::forward<T>(val));//calltherightg()foranypassedargument
val
}
intmain()
{
Xv;//createvariable
Xconstc;//createconstant
f(v);//f()forvariablecallsf(X&)=>callsg(X&)
f(c);//f()forconstantcallsf(Xconst&)=>callsg(Xconst&)
f(X());//f()fortemporarycallsf(X&&)=>callsg(X&&)
f(std::move(v));//f()formove-enabledvariablecallsf(X&&)=>
callsg(X&&)
}
Ofcourse,perfectforwardingcanalsobeusedwithvariadictemplates(see
Section4.3onpage60forsomeexamples).SeeSection15.6.3onpage280for
detailsofperfectforwarding.
6.2SpecialMemberFunctionTemplates
Memberfunctiontemplatescanalsobeusedasspecialmemberfunctions,
includingasaconstructor,which,however,mightleadtosurprisingbehavior.
Considerthefollowingexample:Clickheretoviewcodeimage
basics/specialmemtmpl1.cpp
#include<utility>
#include<string>
#include<iostream>
classPerson
{
private:
std::stringname;
public:
//constructorforpassedinitialname:
explicitPerson(std::stringconst&n):name(n){
std::cout<<"copyingstring-CONSTRfor’"<<name<<"’\n";
}
explicitPerson(std::string&&n):name(std::move(n)){
std::cout<<"movingstring-CONSTRfor’"<<name<<"’\n";
}
//copyandmoveconstructor:
Person(Personconst&p):name(p.name){
std::cout<<"COPY-CONSTRPerson’"<<name<<"’\n";
}
Person(Person&&p):name(std::move(p.name)){
std::cout<<"MOVE-CONSTRPerson’"<<name<<"’\n";
}
};
intmain()
{
std::strings="sname";
Personp1(s);//initwithstringobject=>callscopyingstring-
CONSTR
Personp2("tmp");//initwithstringliteral=>callsmovingstring-
CONSTR
Personp3(p1);//copyPerson=>callsCOPY-CONSTR
Personp4(std::move(p1));//movePerson=>callsMOVE-CONST
}
Here,wehaveaclassPersonwithastringmembernameforwhichwe
provideinitializingconstructors.Tosupportmovesemantics,weoverloadthe
constructortakingastd::string:•Weprovideaversionforstringobject
thecallerstillneeds,forwhichnameisinitializedbyacopyofthepassed
argument:Clickheretoviewcodeimage
Person(std::stringconst&n):name(n){
std::cout<<"copyingstring-CONSTRfor’"<<name<<"’\n";
}
•Weprovideaversionformovablestringobject,forwhichwecall
std::move()to“steal”thevaluefrom:Clickheretoviewcodeimage
Person(std::string&&n):name(std::move(n)){
std::cout<<"movingstring-CONSTRfor’"<<name<<"’\n";
}
Asexpected,thefirstiscalledforpassedstringobjectsthatareinuse(lvalues),
whilethelatteriscalledformovableobjects(rvalues):Clickheretoviewcode
image
std::strings="sname";
Personp1(s);//initwithstringobject=>callscopyingstring-
CONSTR
Personp2("tmp");//initwithstringliteral=>callsmovingstring-
CONSTR
Besidestheseconstructors,theexamplehasspecificimplementationsforthe
copyandmoveconstructortoseewhenaPersonasawholeiscopied/moved:
Clickheretoviewcodeimage
Personp3(p1);//copyPerson=>callsCOPY-CONSTR
Personp4(std::move(p1));//movePerson=>callsMOVE-CONSTR
Nowlet’sreplacethetwostringconstructorswithonegenericconstructor
perfectforwardingthepassedargumenttothemembername:Clickheretoview
codeimage
basics/specialmemtmpl2.hpp
#include<utility>
#include<string>
#include<iostream>
classPerson
{
private:
std::stringname;
public:
//genericconstructorforpassedinitialname:
template<typenameSTR>
explicitPerson(STR&&n):name(std::forward<STR>(n)){
std::cout<<"TMPL-CONSTRfor’"<<name<<"’\n";
}
//copyandmoveconstructor:
Person(Personconst&p):name(p.name){
std::cout<<"COPY-CONSTRPerson’"<<name<<"’\n";
}
Person(Person&&p):name(std::move(p.name)){
std::cout<<"MOVE-CONSTRPerson’"<<name<<"’\n";
}
};
Constructionwithpassedstringworksfine,asexpected:Clickheretoviewcode
image
std::strings="sname";
Personp1(s);//initwithstringobject=>callsTMPL-CONSTR
Personp2("tmp");//initwithstringliteral=>callsTMPL-CONSTR
Notehowtheconstructionofp2doesnotcreateatemporarystringinthiscase:
TheparameterSTRisdeducedtobeoftypecharconst[4].Applying
std::forward<STR>tothepointerparameteroftheconstructorhasnot
muchofaneffect,andthenamememberisthusconstructedfromanull-
terminatedstring.
Butwhenweattempttocallthecopyconstructor,wegetanerror:Clickhere
toviewcodeimage
Personp3(p1);//ERROR
whileinitializinganewPersonbyamovableobjectstillworksfine:Clickhere
toviewcodeimage
Personp4(std::move(p1));//OK:movePerson=>callsMOVE-CONST
NotethatalsocopyingaconstantPersonworksfine:Clickheretoviewcode
image
Personconstp2c("ctmp");//initconstantobjectwithstringliteral
Personp3c(p2c);//OK:copyconstantPerson=>callsCOPY-CONSTR
Theproblemisthat,accordingtotheoverloadresolutionrulesofC++(see
Section16.2.4onpage333),foranonconstantlvaluePersonpthemember
templateClickheretoviewcodeimage
template<typenameSTR>
Person(STR&&n)
isabettermatchthanthe(usuallypredefined)copyconstructor:Clickhereto
viewcodeimage
Person(Personconst&p)STRisjustsubstitutedwithPerson&,while
forthecopyconstructoraconversiontoconstisnecessary.
Youmightthinkaboutsolvingthisbyalsoprovidinganonconstantcopy
constructor:Person(Person&p)However,thatisonlyapartialsolutionbecause
forobjectsofaderivedclass,themembertemplateisstillabettermatch.What
youreallywantistodisablethemembertemplateforthecasethatthepassed
argumentisaPersonoranexpressionthatcanbeconvertedtoaPerson.
Thiscanbedonebyusingstd::enable_if<>,whichisintroducedinthe
nextsection.
6.3DisableTemplateswithenable_if<>
SinceC++11,theC++standardlibraryprovidesahelpertemplate
std::enable_if<>toignorefunctiontemplatesundercertaincompile-time
conditions.
Forexample,ifafunctiontemplatefoo<>()isdefinedasfollows:Click
heretoviewcodeimage
template<typenameT>
typenamestd::enable_if<(sizeof(T)>4)>::type
foo(){
}
thisdefinitionoffoo<>()isignoredifsizeof(T)>4yieldsfalse.4If
sizeof(T)>4yieldstrue,thefunctiontemplateinstanceexpandstovoid
foo(){
}
Thatis,std::enable_if<>isatypetraitthatevaluatesagivencompile-
timeexpressionpassedasits(first)templateargumentandbehavesasfollows:•
Iftheexpressionyieldstrue,itstypemembertypeyieldsatype:–Thetype
isvoidifnosecondtemplateargumentispassed.
–Otherwise,thetypeisthesecondtemplateargumenttype.
•Iftheexpressionyieldsfalse,themembertypeisnotdefined.Duetoa
templatefeaturecalledSFINAE(substitutionfailureisnotanerror),whichis
introducedlater(seeSection8.4onpage129),thishastheeffectthatthe
functiontemplatewiththeenable_ifexpressionisignored.
AsforalltypetraitsyieldingatypesinceC++14,thereisacorrespondingalias
templatestd::enable_if_t<>,whichallowsyoutoskiptypenameand
::type(seeSection2.8onpage40fordetails).Thus,sinceC++14youcan
writeClickheretoviewcodeimage
template<typenameT>
std::enable_if_t<(sizeof(T)>4)>
foo(){
}
Ifasecondargumentispassedtoenable_if<>orenable_if_t<>:Click
heretoviewcodeimage
template<typenameT>
std::enable_if_t<(sizeof(T)>4),T>
foo(){
returnT();
}
theenable_ifconstructexpandstothissecondargumentiftheexpression
yieldstrue.So,ifMyTypeistheconcretetypepassedordeducedasT,whose
sizeislargerthan4,theeffectisMyTypefoo();
Notethathavingtheenable_ifexpressioninthemiddleofadeclarationis
prettyclumsy.Forthisreason,thecommonwaytousestd::enable_if<>
istouseanadditionalfunctiontemplateargumentwithadefaultvalue:Click
heretoviewcodeimage
template<typenameT,
typename=std::enable_if_t<(sizeof(T)>4)>>
voidfoo(){
}
whichexpandsto
Clickheretoviewcodeimage
template<typenameT,
typename=void>
voidfoo(){
}
ifsizeof(T)>4.
Ifthatisstilltooclumsy,andyouwanttomaketherequirement/constraint
moreexplicit,youcandefineyourownnameforitusinganaliastemplate:5
Clickheretoviewcodeimage
template<typenameT>
usingEnableIfSizeGreater4=std::enable_if_t<(sizeof(T)>4)>;
template<typenameT,
typename=EnableIfSizeGreater4<T>>
voidfoo(){
}
SeeSection20.3onpage469foradiscussionofhowstd::enable_ifis
implemented.
6.4Usingenable_if<>
Wecanuseenable_if<>tosolveourproblemwiththeconstructortemplate
introducedinSection6.2onpage95.
Theproblemwehavetosolveistodisablethedeclarationofthetemplate
constructorClickheretoviewcodeimage
template<typenameSTR>
Person(STR&&n);
ifthepassedargumentSTRhastherighttype(i.e.,isastd::stringoratype
convertibletostd::string).
Forthis,weuseanotherstandardtypetrait,
std::is_convertible<FROM,TO>.WithC++17,thecorresponding
declarationlooksasfollows:Clickheretoviewcodeimage
template<typenameSTR,
typename=std::enable_if_t<
std::is_convertible_v<STR,std::string>>>
Person(STR&&n);
IftypeSTRisconvertibletotypestd::string,thewholedeclaration
expandstoClickheretoviewcodeimage
template<typenameSTR,
typename=void>
Person(STR&&n);
IftypeSTRisnotconvertibletotypestd::string,thewholefunction
templateisignored.6
Again,wecandefineourownnamefortheconstraintbyusinganalias
template:Clickheretoviewcodeimage
template<typenameT>
usingEnableIfString=std::enable_if_t<
std::is_convertible_v<T,std::string>>;
…
template<typenameSTR,typename=EnableIfString<STR>>
Person(STR&&n);
Thus,thewholeclassPersonshouldlookasfollows:Clickheretoviewcode
image
basics/specialmemtmpl3.hpp
#include<utility>
#include<string>
#include<iostream>
#include<type_traits>
template<typenameT>
usingEnableIfString=std::enable_if_t<
std::is_convertible_v<T,std::string>>;
classPerson
{
private:
std::stringname;
public:
//genericconstructorforpassedinitialname:
template<typenameSTR,typename=EnableIfString<STR>>
explicitPerson(STR&&n)
:name(std::forward<STR>(n)){
std::cout<<"TMPL-CONSTRfor’"<<name<<"’\n";
}
//copyandmoveconstructor:
Person(Personconst&p):name(p.name){
std::cout<<"COPY-CONSTRPerson’"<<name<<"’\n";
}
Person(Person&&p):name(std::move(p.name)){
std::cout<<"MOVE-CONSTRPerson’"<<name<<"’\n";
}
};
Now,allcallsbehaveasexpected:Clickheretoviewcodeimage
basics/specialmemtmpl3.cpp
#include"specialmemtmpl3.hpp"
intmain()
{
std::strings="sname";
Personp1(s);//initwithstringobject=>callsTMPL-CONSTR
Personp2("tmp");//initwithstringliteral=>callsTMPL-CONSTR
Personp3(p1);//OK=>callsCOPY-CONSTR
Personp4(std::move(p1));//OK=>callsMOVE-CONST
}
NoteagainthatinC++14,wehavetodeclarethealiastemplateasfollows,
becausethe_vversionisnotdefinedfortypetraitsthatyieldavalue:Clickhere
toviewcodeimage
template<typenameT>
usingEnableIfString=std::enable_if_t<
std::is_convertible<T,std::string>::value>;
AndinC++11,wehavetodeclarethespecialmembertemplateasfollows,
becauseaswrittenthe_tversionisnotdefinedfortypetraitsthatyieldatype:
Clickheretoviewcodeimage
template<typenameT>
usingEnableIfString
=typenamestd::enable_if<std::is_convertible<T,std::string>::value
>::type;
Butthat’sallhiddennowinthedefinitionofEnableIfString<>.
Notealsothatthereisanalternativetousingstd::is_convertible<>
becauseitrequiresthatthetypesareimplicitlyconvertible.Byusing
std::is_constructible<>,wealsoallowexplicitconversionstobeused
fortheinitialization.However,theorderoftheargumentsistheoppositeisthis
case:Clickheretoviewcodeimage
template<typenameT>
usingEnableIfString=std::enable_if_t<
std::is_constructible_v<std::string,T>>;
SeeSectionD.3.2onpage719fordetailsabout
std::is_constructible<>andSectionD.3.3onpage727fordetails
aboutstd::is_convertible<>.SeeSectionD.6onpage734fordetails
andexamplestoapplyenable_if<>onvariadictemplates.
DisablingSpecialMemberFunctions
Notethatnormallywecan’tuseenable_if<>todisablethepredefined
copy/moveconstructorsand/orassignmentoperators.Thereasonisthatmember
functiontemplatesnevercountasspecialmemberfunctionsandareignored
when,forexample,acopyconstructorisneeded.Thus,withthisdeclaration:
Clickheretoviewcodeimage
classC{
public:
template<typenameT>
C(Tconst&){
std::cout<<"tmplcopyconstructor\n";
}
…
};
thepredefinedcopyconstructorisstillused,whenacopyofaCisrequested:
Clickheretoviewcodeimage
Cx;
Cy{x};//stillusesthepredefinedcopyconstructor(notthemember
template)
(Thereisreallynowaytousethemembertemplatebecausethereisnowayto
specifyordeduceitstemplateparameterT.)Deletingthepredefinedcopy
constructorisnosolution,becausethenthetrialtocopyaCresultsinanerror.
Thereisatrickysolution,though:7Wecandeclareacopyconstructorfor
constvolatileargumentsandmarkit“deleted”(i.e.,defineitwith=
delete).Doingsopreventsanothercopyconstructorfrombeingimplicitly
declared.Withthatinplace,wecandefineaconstructortemplatethatwillbe
preferredoverthe(deleted)copyconstructorfornonvolatiletypes:Clickhereto
viewcodeimage
classC
{
public:
…
//user-definethepredefinedcopyconstructorasdeleted
//(withconversiontovolatiletoenablebettermatches)
C(Cconstvolatile&)=delete;
//implementcopyconstructortemplatewithbettermatch:
template<typenameT>
C(Tconst&){
std::cout<<"tmplcopyconstructor\n";
}
…
};
Nowthetemplateconstructorsareusedevenfor“normal”copying:Clickhereto
viewcodeimage
Cx;
Cy{x};//usesthemembertemplate
Insuchatemplateconstructorwecanthenapplyadditionalconstraintswith
enable_if<>.Forexample,topreventbeingabletocopyobjectsofaclass
templateC<>ifthetemplateparameterisanintegraltype,wecanimplementthe
following:Clickheretoviewcodeimage
template<typenameT>
classC
{
public:
…
//user-definethepredefinedcopyconstructorasdeleted
//(withconversiontovolatiletoenablebettermatches)
C(Cconstvolatile&)=delete;
//ifTisnointegraltype,providecopyconstructortemplatewith
bettermatch:
template<typenameU,
typename=std::enable_if_t<!std::is_integral<U>::value>>
C(C<U>const&){
…
}
…
};
6.5UsingConceptstoSimplifyenable_if<>
Expressions
Evenwhenusingaliastemplates,theenable_ifsyntaxisprettyclumsy,
becauseitusesawork-around:Togetthedesiredeffect,weaddanadditional
templateparameterand“abuse”thatparametertoprovideaspecificrequirement
forthefunctiontemplatetobeavailableatall.Codelikethisishardtoreadand
makestherestofthefunctiontemplatehardtounderstand.
Inprinciple,wejustneedalanguagefeaturethatallowsustoformulate
requirementsorconstraintsforafunctioninawaythatcausesthefunctiontobe
ignorediftherequirements/constraintsarenotmet.
Thisisanapplicationofthelong-awaitedlanguagefeatureconcepts,which
allowsustoformulaterequirements/conditionsfortemplateswithitsownsimple
syntax.Unfortunately,althoughlongdiscussed,conceptsstilldidnotbecome
partoftheC++17standard.Somecompilersprovideexperimentalsupportfor
suchafeature,however,andconceptswilllikelybecomepartofthenext
standardafterC++17.
Withconcepts,astheiruseisproposed,wesimplyhavetowritethefollowing:
Clickheretoviewcodeimage
template<typenameSTR>
requiresstd::is_convertible_v<STR,std::string>
Person(STR&&n):name(std::forward<STR>(n)){
…
}
WecanevenspecifytherequirementasageneralconceptClickheretoview
codeimage
template<typenameT>
conceptConvertibleToString=std::is_convertible_v<T,std::string>;
andformulatethisconceptasarequirement:Clickheretoviewcode
image
template<typenameSTR>
requiresConvertibleToString<STR>
Person(STR&&n):name(std::forward<STR>(n)){
…
}
Thisalsocanbeformulatedasfollows:Clickheretoviewcodeimage
template<ConvertibleToStringSTR>
Person(STR&&n):name(std::forward<STR>(n)){
…
}
SeeAppendixEforadetaileddiscussionofconceptsforC++.
6.6Summary
•Intemplates,youcan“perfectly”forwardparametersbydeclaringthemas
forwardingreferences(declaredwithatypeformedwiththenameofa
templateparameterfollowedby&&)andusingstd::forward<>()inthe
forwardedcall.
•Whenusingperfectforwardingmemberfunctiontemplates,theymightmatch
betterthanthepredefinedspecialmemberfunctiontocopyormoveobjects.
•Withstd::enable_if<>,youcandisableafunctiontemplatewhena
compile-timeconditionisfalse(thetemplateisthenignoredoncethat
conditionhasbeendetermined).
•Byusingstd::enable_if<>youcanavoidproblemswhenconstructor
templatesorassignmentoperatortemplatesthatcanbecalledforsingle
argumentsareabettermatchthanimplicitlygeneratedspecialmember
functions.
•Youcantemplify(andapplyenable_if<>)tospecialmemberfunctionsby
deletingthepredefinedspecialmemberfunctionsforconstvolatile.
•Conceptswillallowustouseamoreintuitivesyntaxforrequirementson
functiontemplates.
1Thefactthatmovesemanticsisnotautomaticallypassedthroughis
intentionalandimportant.Ifitweren’t,wewouldlosethevalueofamovable
objectthefirsttimeweuseitinafunction.
2AtypelikeXconst&&isvalidbutprovidesnocommonsemanticsin
practicebecause“stealing”theinternalrepresentationofamovableobject
requiresmodifyingthatobject.Itmightbeused,though,toforcepassingonly
temporariesorobjectsmarkedwithstd::move()withoutbeingableto
modifythem.
3ThetermuniversalreferencewascoinedbyScottMeyersasacommonterm
thatcouldresultineitheran“lvaluereference”oran“rvaluereference.”
Because“universal”was,well,toouniversal,theC++17standardintroduced
thetermforwardingreference,becausethemajorreasontousesucha
referenceistoforwardobjects.However,notethatitdoesnotautomatically
forward.Thetermdoesnotdescribewhatitisbutwhatitistypicallyusedfor.
4Don’tforgettoplacetheconditionintoparentheses,becauseotherwisethe>
intheconditionwouldendthetemplateargumentlist.
5ThankstoStephenC.Dewhurstforpointingthatout.
6Ifyouwonderwhywedon’tinsteadcheckwhetherSTRis“notconvertibleto
Person,”beware:Wearedefiningafunctionthatmightallowustoconverta
stringtoaPerson.Sotheconstructorhastoknowwhetheritisenabled,
whichdependsonwhetheritisconvertible,whichdependsonwhetheritis
enabled,andsoon.Neveruseenable_ifinplacesthatimpactthe
conditionusedbyenable_if.Thisisalogicalerrorthatcompilersdonot
necessarilydetect.
7ThankstoPeterDimovforpointingoutthistechnique.
Chapter7
ByValueorbyReference?
Sincethebeginning,C++hasprovidedcall-by-valueandcall-by-reference,and
itisnotalwayseasytodecidewhichonetochoose:Usuallycallingbyreference
ischeaperfornontrivialobjectsbutmorecomplicated.C++11addedmove
semanticstothemix,whichmeansthatwenowhavedifferentwaystopassby
reference:1
1.Xconst&(constantlvaluereference):Theparameterreferstothepassed
object,withouttheabilitytomodifyit.
2.X&(nonconstantlvaluereference):Theparameterreferstothepassedobject,
withtheabilitytomodifyit.
3.X&&(rvaluereference):Theparameterreferstothepassedobject,withmove
semantics,meaningthatyoucanmodifyor“steal”thevalue.
Decidinghowtodeclareparameterswithknownconcretetypesiscomplicated
enough.Intemplates,typesarenotknown,andthereforeitbecomesevenharder
todecidewhichpassingmechanismisappropriate.
Nevertheless,inSection1.6.1onpage20wedidrecommendpassing
parametersinfunctiontemplatesbyvalueunlesstherearegoodreasons,suchas
thefollowing:•Copyingisnotpossible.2
•Parametersareusedtoreturndata.
•Templatesjustforwardtheparameterstosomewhereelsebykeepingallthe
propertiesoftheoriginalarguments.
•Therearesignificantperformanceimprovements.
Thischapterdiscussesthedifferentapproachestodeclareparametersin
templates,motivatingthegeneralrecommendationtopassbyvalue,and
providingargumentsforthereasonsnottodoso.Italsodiscussesthetricky
problemsyourunintowhendealingwithstringliteralsandotherrawarrays.
Whenreadingthischapter,itishelpfultobefamiliarwiththeterminologyof
valuecategories(lvalue,rvalue,prvalue,xvalue,etc.),whichisexplainedin
AppendixB.
7.1PassingbyValue
Whenpassingargumentsbyvalue,eachargumentmustinprinciplebecopied.
Thus,eachparameterbecomesacopyofthepassedargument.Forclasses,the
objectcreatedasacopygenerallyisinitializedbythecopyconstructor.
Callingacopyconstructorcanbecomeexpensive.However,therearevarious
waytoavoidexpensivecopyingevenwhenpassingparametersbyvalue:Infact,
compilersmightoptimizeawaycopyoperationscopyingobjectsandcanbecome
cheapevenforcomplexobjectsbyusingmovesemantics.
Forexample,let’slookatasimplefunctiontemplateimplementedsothatthe
argumentispassedbyvalue:Clickheretoviewcodeimage
template<typenameT>
voidprintV(Targ){
…
}
Whencallingthisfunctiontemplateforaninteger,theresultingcodeisClick
heretoviewcodeimage
voidprintV(intarg){
…
}
Parameterargbecomesacopyofanypassedargument,whetheritisanobject
oraliteraloravaluereturnedbyafunction.
Ifwedefineastd::stringandcallourfunctiontemplateforit:std::string
s="hi";
printV(s);
thetemplateparameterTisinstantiatedasstd::stringsothatwegetClick
heretoviewcodeimage
voidprintV(std::stringarg)
{
…
}
Again,whenpassingthestring,argbecomesacopyofs.Thistimethecopyis
createdbythecopyconstructorofthestringclass,whichisapotentially
expensiveoperation,becauseinprinciplethiscopyoperationcreatesafullor
“deep”copysothatthecopyinternallyallocatesitsownmemorytoholdthe
value.3
However,thepotentialcopyconstructorisnotalwayscalled.Considerthe
following:Clickheretoviewcodeimage
std::stringreturnString();
std::strings="hi";
printV(s);//copyconstructor
printV(std::string("hi"));//copyingusuallyoptimizedaway(ifnot,
moveconstructor)
printV(returnString());//copyingusuallyoptimizedaway(ifnot,
moveconstructor)
printV(std::move(s));//moveconstructor
Inthefirstcallwepassanlvalue,whichmeansthatthecopyconstructorisused.
However,inthesecondandthirdcalls,whendirectlycallingthefunction
templateforprvalues(temporaryobjectscreatedontheflyorreturnedby
anotherfunction;seeAppendixB),compilersusuallyoptimizepassingthe
argumentsothatnocopyingconstructoriscalledatall.NotethatsinceC++17,
thisoptimizationisrequired.BeforeC++17,acompilerthatdoesn’toptimizethe
copyingaway,mustatleasthavetotrytousemovesemantics,whichusually
makescopyingcheap.Inthelastcall,whenpassinganxvalue(anexisting
nonconstantobjectwithstd::move()),weforcetocallthemoveconstructor
bysignalingthatwenolongerneedthevalueofs.
Thus,callinganimplementationofprintV()thatdeclarestheparameterto
bepassedbyvalueusuallyisonlyexpensiveifwepassanlvalue(anobjectwe
createdbeforeandtypicallystilluseafterwards,aswedidn’tuse
std::move()topassit).Unfortunately,thisisaprettycommoncase.One
reasonisthatitisprettycommontocreateobjectsearlytopassthemlater(after
somemodifications)tootherfunctions.
PassingbyValueDecays
Thereisanotherpropertyofpassingbyvaluewehavetomention:Whenpassing
argumentstoaparameterbyvalue,thetypedecays.Thismeansthatrawarrays
getconvertedtopointersandthatqualifierssuchasconstandvolatileare
removed(justlikeusingthevalueasinitializerforanobjectdeclaredwith
auto):4
Clickheretoviewcodeimage
template<typenameT>
voidprintV(Targ){
…
}
Clickheretoviewcodeimage
std::stringconstc="hi";
printV(c);//cdecayssothatarghastypestd::string
printV("hi");//decaystopointersothatarghastypecharconst*
intarr[4];
printV(arr);//decaystopointersothatarghastypecharconst*
Thus,whenpassingthestringliteral"hi",itstypecharconst[3]decays
tocharconst*sothatthisisthededucedtypeofT.Thus,thetemplateis
instantiatedasfollows:Clickheretoviewcodeimage
voidprintV(charconst*arg)
{
…
}
ThisbehaviorisderivedfromCandhasitsbenefitsanddrawbacks.Oftenit
simplifiesthehandlingofpassedstringliterals,butthedrawbackisthatinside
printV()wecan’tdistinguishbetweenpassingapointertoasingleelement
andpassingarawarray.Forthisreason,wewilldiscusshowtodealwithstring
literalsandotherrawarraysinSection7.4onpage115.
7.2PassingbyReference
Nowlet’sdiscussthedifferentflavorsofpassingbyreference.Inallcases,no
copygetscreated(becausetheparameterjustreferstothepassedargument).
Also,passingtheargumentneverdecays.However,sometimespassingisnot
possible,andifpassingispossible,therearecasesinwhichtheresultingtypeof
theparametermaycauseproblems.
7.2.1PassingbyConstantReference
Toavoidany(unnecessary)copying,whenpassingnontemporaryobjects,we
canuseconstantreferences.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidprintR(Tconst&arg){
…
}
Withthisdeclaration,passinganobjectnevercreatesacopy(whetherit’scheap
ornot):Clickheretoviewcodeimage
std::stringreturnString();
std::strings="hi";
printR(s);//nocopy
printR(std::string("hi"));//nocopy
printR(returnString());//nocopy
printR(std::move(s));//nocopy
Evenanintispassedbyreference,whichisabitcounterproductivebut
shouldn’tmatterthatmuch.Thus:Clickheretoviewcodeimage
inti=42;
printR(i);//passesreferenceinsteadofjustcopyingi
resultsinprintR()beinginstantiatedas:Clickheretoviewcodeimage
voidprintR(intconst&arg){
…
}
Underthehood,passinganargumentbyreferenceisimplementedbypassingthe
addressoftheargument.Addressesareencodedcompactly,andtherefore
transferringanaddressfromthecallertothecalleeisefficientinitself.However,
passinganaddresscancreateuncertaintiesforthecompilerwhenitcompilesthe
caller’scode:Whatisthecalleedoingwiththataddress?Intheory,thecalleecan
changeallthevaluesthatare“reachable”throughthataddress.Thatmeans,that
thecompilerhastoassumethatallthevaluesitmayhavecached(usually,in
machineregisters)areinvalidafterthecall.Reloadingallthosevaluescanbe
quiteexpensive.Youmaybethinkingthatwearepassingbyconstantreference:
Cannotthecompilerdeducefromthatthatnochangecanhappen?
Unfortunately,thatisnotthecasebecausethecallermaymodifythereferenced
objectthroughitsown,non-constreference.5
Thisbadnewsismoderatedbyinlining:Ifthecompilercanexpandthecall
inline,itcanreasonaboutthecallerandthecalleetogetherandinmanycases
“see”thattheaddressisnotusedforanythingbutpassingtheunderlyingvalue.
Functiontemplatesareoftenveryshortandthereforelikelycandidatesforinline
expansion.However,ifatemplateencapsulatesamorecomplexalgorithm,
inliningislesslikelytohappen.
PassingbyReferenceDoesNotDecay
Whenpassingargumentstoparametersbyreference,theydonotdecay.This
meansthatrawarraysarenotconvertedtopointersandthatqualifierssuchas
constandvolatilearenotremoved.However,becausethecallparameter
isdeclaredasTconst&,thetemplateparameterTitselfisnotdeducedas
const.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidprintR(Tconst&arg){
…
}
std::stringconstc="hi";
printR(c);//Tdeducedasstd::string,argisstd::stringconst&
printR("hi");//Tdeducedaschar[3],argischarconst(&)[3]
intarr[4];
printR(arr);//Tdeducedasint[4],argisintconst(&)[4]
Thus,localobjectsdeclaredwithtypeTinprintR()arenotconstant.
7.2.2PassingbyNonconstantReference
Whenyouwanttoreturnvaluesthroughpassedarguments(i.e.,whenyouwant
touseoutorinoutparameters),youhavetousenonconstantreferences(unless
youprefertopassthemviapointers).Again,thismeansthatwhenpassingthe
arguments,nocopygetscreated.Theparametersofthecalledfunctiontemplate
justgetdirectaccesstothepassedargument.
Considerthefollowing:
Clickheretoviewcodeimage
template<typenameT>
voidoutR(T&arg){
…
}
NotethatcallingoutR()foratemporary(prvalue)oranexistingobjectpassed
withstd::move()(xvalue)usuallyisnotallowed:Clickheretoviewcode
image
std::stringreturnString();
std::strings="hi";
outR(s);//OK:Tdeducedasstd::string,argisstd::string&
outR(std::string("hi"));//ERROR:notallowedtopassatemporary
(prvalue)
outR(returnString());//ERROR:notallowedtopassatemporary
(prvalue)
outR(std::move(s));//ERROR:notallowedtopassanxvalue
Youcanpassrawarraysofnonconstanttypes,whichagaindon’tdecay:Click
heretoviewcodeimage
intarr[4];
outR(arr);//OK:Tdeducedasint[4],argisint(&)[4]
Thus,youcanmodifyelementsand,forexample,dealwiththesizeofthearray.
Forexample:Clickheretoviewcodeimage
template<typenameT>
voidoutR(T&arg){
if(std::is_array<T>::value){
std::cout<<"gotarrayof"<<std::extent<T>::value<<"elems\n";
}
…
}
However,templatesareabittrickyhere.Ifyoupassaconstargument,the
deductionmightresultinargbecomingadeclarationofaconstantreference,
whichmeansthatpassinganrvalueissuddenlyallowed,whereanlvalueis
expected:Clickheretoviewcodeimage
std::stringconstc="hi";
outR(c);//OK:Tdeducedasstd::stringconst
outR(returnConstString());//OK:sameifreturnConstString()returns
conststring
outR(std::move(c));//OK:Tdeducedasstd::stringconst6
outR("hi");//OK:Tdeducedascharconst[3]
Ofcourse,anyattempttomodifythepassedargumentinsidethefunction
templateisanerrorinsuchcases.Passingaconstobjectispossibleinthecall
expressionitself,butwhenthefunctionisfullyinstantiated(whichmayhappen
laterinthecompilationprocess)anyattempttomodifythevaluewilltriggeran
error(which,however,mighthappendeepinsidethecalledtemplate;seeSection
9.4onpage143).
Ifyouwanttodisablepassingconstantobjectstononconstantreferences,you
candothefollowing:•Useastaticassertiontotriggeracompile-timeerror:
Clickheretoviewcodeimage
template<typenameT>
voidoutR(T&arg){
static_assert(!std::is_const<T>::value,
"outparameteroffoo<T>(T&)isconst");
…
}
•Disablethetemplateforthiscaseeitherbyusingstd::enable_if<>(see
Section6.3onpage98):Clickheretoviewcodeimage
template<typenameT,
typename=std::enable_if_t<!std::is_const<T>::value>
voidoutR(T&arg){
…
}
orconceptsoncetheyaresupported(seeSection6.5onpage103and
AppendixE):Clickheretoviewcodeimage
template<typenameT>
requires!std::is_const_v<T>
voidoutR(T&arg){
…
}
7.2.3PassingbyForwardingReference
Onereasontousecall-by-referenceistobeabletoperfectforwardaparameter
(seeSection6.1onpage91).Butrememberthatwhenaforwardingreferenceis
used,whichisdefinedasanrvaluereferenceofatemplateparameter,special
rulesapply.Considerthefollowing:Clickheretoviewcodeimage
template<typenameT>
voidpassR(T&&arg){//argdeclaredasforwardingreference
…
}
Youcanpasseverythingtoaforwardingreferenceand,asusualwhenpassingby
reference,nocopygetscreated:Clickheretoviewcodeimage
std::strings="hi";
passR(s);//OK:Tdeducedasstd::string&(alsothetypeofarg)
passR(std::string("hi"));//OK:Tdeducedasstd::string,argis
std::string&&
passR(returnString());//OK:Tdeducedasstd::string,argis
std::string&&
passR(std::move(s));//OK:Tdeducedasstd::string,argis
std::string&&
passR(arr);//OK:Tdeducedasint(&)[4](alsothetypeofarg)
However,thespecialrulesfortypedeductionmayresultinsomesurprises:Click
heretoviewcodeimage
std::stringconstc="hi";
passR(c);//OK:Tdeducedasstd::stringconst&
passR("hi");//OK:Tdeducedascharconst(&)[3](alsothetypeof
arg)
intarr[4];
passR("hi");//OK:Tdeducedasint(&)[4](alsothetypeofarg)
Ineachofthesecases,insidepassR()theparameterarghasatypethat
“knows”whetherwepassedanrvalue(tousemovesemantics)ora
constant/nonconstantlvalue.Thisistheonlywaytopassanargument,suchthat
itcanbeusedtodistinguishbehaviorforallofthesethreecases.
Thisgivestheimpressionthatdeclaringaparameterasaforwardingreference
isalmostperfect.Butbeware,thereisnofreelunch.
Forexample,thisistheonlycasewherethetemplateparameterTimplicitly
canbecomeareferencetype.Asaconsequence,itmightbecomeanerrortouse
Ttodeclarealocalobjectwithoutinitialization:Clickheretoviewcodeimage
template<typenameT>
voidpassR(T&&arg){//argisaforwardingreference
Tx;//forpassedlvalues,xisareference,whichrequiresan
initializer
…
}
foo(42);//OK:Tdeducedasint
inti;
foo(i);//ERROR:Tdeducedasint&,whichmakesthedeclarationofx
inpassR()invalid
SeeSection15.6.2onpage279forfurtherdetailsabouthowyoucandealwith
thissituation.
7.3Usingstd::ref()andstd::cref()
SinceC++11,youcanletthecallerdecide,forafunctiontemplateargument,
whethertopassitbyvalueorbyreference.Whenatemplateisdeclaredtotake
argumentsbyvalue,thecallercanusestd::cref()andstd::ref(),
declaredinheaderfile<functional>,topasstheargumentbyreference.For
example:Clickheretoviewcodeimage
template<typenameT>
voidprintT(Targ){
…
}
std::strings="hello";
printT(s);//passsbyreference
printT(std::cref(s));//passs“asifbyreference”
However,notethatstd::cref()doesnotchangethehandlingofparameters
intemplates.Instead,itusesatrick:Itwrapsthepassedargumentsbyanobject
thatactslikeareference.Infact,itcreatesanobjectoftype
std::reference_wrapper<>referringtotheoriginalargumentand
passesthisobjectbyvalue.Thewrappermoreorlesssupportsonlyone
operation:animplicittypeconversionbacktotheoriginaltype,yieldingthe
originalobject.7So,wheneveryouhaveavalidoperatorforthepassedobject,
youcanusethereferencewrapperinstead.Forexample:Clickheretoviewcode
image
basics/cref.cpp
#include<functional>//forstd::cref()
#include<string>
#include<iostream>
voidprintString(std::stringconst&s)
{
std::cout<<s<<’\n’;
}
template<typenameT>
voidprintT(Targ)
{
printString(arg);//mightconvertargbacktostd::string
}
intmain()
{
std::strings="hello";
printT(s);//printspassedbyvalue
printT(std::cref(s));//printspassed“asifbyreference”
}
Thelastcallpassesbyvalueanobjectoftype
std::reference_wrapper<stringconst>totheparameterarg,
whichthenpassesandthereforeconvertsitbacktoitsunderlyingtype
std::string.
Notethatthecompilerhastoknowthatanimplicitconversionbacktothe
originaltypeisnecessary.Forthisreason,std::ref()andstd::cref()
usuallyworkfineonlyifyoupassobjectsthroughgenericcode.Forexample,
directlytryingtooutputthepassedobjectofthegenerictypeTwillfailbecause
thereisnooutputoperatordefinedforstd::reference_wrapper<>:
Clickheretoviewcodeimage
template<typenameT>
voidprintV(Targ){
std::cout<<arg<<’\n’;
}
…
std::strings="hello";
printV(s);//OK
printV(std::cref(s));//ERROR:nooperator<<forreferencewrapper
defined
Also,thefollowingfailsbecauseyoucan’tcompareareferencewrapperwitha
charconst*orstd::string:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
boolisless(T1arg1,T2arg2)
{
returnarg1<arg2;
}
…
std::strings="hello";
if(isless(std::cref(s)<"world"))…//ERROR
if(isless(std::cref(s)<std::string("world")))…//ERROR
Italsodoesn’thelptogivearg1andarg2acommontypeT:Clickhereto
viewcodeimage
template<typenameT>
boolisless(Targ1,Targ2)
{
returnarg1<arg2;
}
becausethenthecompilergetsconflictingtypeswhentryingtodeduceTfor
arg1andarg2.
Thus,theeffectofclassstd::reference_wrapper<>istobeableto
useareferenceasa“firstclassobject,”whichyoucancopyandthereforepass
byvaluetofunctiontemplates.Youcanalsouseitinclasses,forexample,to
holdreferencestoobjectsincontainers.Butyoualwaysfinallyneeda
conversionbacktotheunderlyingtype.
7.4DealingwithStringLiteralsandRawArrays
Sofar,wehaveseenthedifferenteffectsfortemplatesparameterswhenusing
stringliteralsandrawarrays:•Call-by-valuedecayssothattheybecome
pointerstotheelementtype.
•Anyformofcall-by-referencedoesnotdecaysothattheargumentsbecome
referencesthatstillrefertoarrays.
Bothcanbegoodandbad.Whendecayingarraystopointers,youlosetheability
todistinguishbetweenhandlingpointerstoelementsfromhandlingpassed
arrays.Ontheotherhand,whendealingwithparameterswherestringliterals
maybepassed,notdecayingcanbecomeaproblem,becausestringliteralsof
differentsizehavedifferenttypes.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tconst&arg1,Tconst&arg2)
{
…
}
foo("hi","guy");//ERROR
Here,foo("hi","guy")failstocompile,because"hi"hastypechar
const[3],while"guy"hastypecharconst[4],butthetemplate
requiresthemtohavethesametypeT.Onlyifthestringliteralsweretohavethe
samelengthwouldsuchcodecompile.Forthisreason,itisstrongly
recommendedtousestringliteralsofdifferentlengthsintestcases.
Bydeclaringthefunctiontemplatefoo()topasstheargumentbyvaluethe
callispossible:Clickheretoviewcodeimage
template<typenameT>
voidfoo(Targ1,Targ2)
{
…
}
foo("hi","guy");//compiles,but…
But,thatdoesn’tmeanthatallproblemsaregone.Evenworse,compile-time
problemsmayhavebecomerun-timeproblems.Considerthefollowingcode,
wherewecomparethepassedargumentusingoperator==:Clickheretoview
codeimage
template<typenameT>
voidfoo(Targ1,Targ2)
{
if(arg1==arg2){//OOPS:comparesaddressesofpassedarrays
…
}
}
foo("hi","guy");//compiles,but…
Aswritten,youhavetoknowthatyoushouldinterpretthepassedcharacter
pointersasstrings.Butthat’sprobablythecaseanyway,becausethetemplate
alsohastodealwithargumentscomingfromstringliteralsthathavebeen
decayedalready(e.g.,bycomingfromanotherfunctioncalledbyvalueorbeing
assignedtoanobjectdeclaredwithauto).
Nevertheless,inmanycasesdecayingishelpful,especiallyforchecking
whethertwoobjects(bothpassedasargumentsoronepassedasargumentand
theotherexpectingtheargument)haveorconverttothesametype.Onetypical
usageisperfectforwarding.Butifyouwanttouseperfectforwarding,youhave
todeclaretheparametersasforwardingreferences.Inthosecases,youmight
explicitlydecaytheargumentsusingthetypetraitstd::decay<>().Seethe
storyofstd::make_pair()inSection7.6onpage120foraconcrete
example.
Notethatothertypetraitssometimesalsoimplicitlydecay,suchas
std::common_type<>,whichyieldsthecommontypeoftwopassed
argumenttypes(seeSection1.3.3onpage12andSectionD.5onpage732).
7.4.1SpecialImplementationsforStringLiteralsand
RawArrays
Youmighthavetodistinguishyourimplementationaccordingtowhethera
pointeroranarraywaspassed.This,ofcourse,requiresthatapassedarray
wasn’tdecayedyet.
Todistinguishthesecases,youhavetodetectwhetherarraysarepassed.
Basically,therearetwooptions:•Youcandeclaretemplateparameterssothat
theyareonlyvalidforarrays:Clickheretoviewcodeimage
template<typenameT,std::size_tL1,std::size_tL2>
voidfoo(T(&arg1)[L1],T(&arg2)[L2])
{
T*pa=arg1;//decayarg1
T*pb=arg2;//decayarg2
if(compareArrays(pa,L1,pb,L2)){
…
}
}
Here,arg1andarg2havetoberawarraysofthesameelementtypeTbut
withdifferentsizesL1andL2.However,notethatyoumightneedmultiple
implementationstosupportthevariousformsofrawarrays(seeSection5.4on
page71).
•Youcanusetypetraitstodetectwhetheranarray(orapointer)waspassed:
Clickheretoviewcodeimage
template<typenameT,
typename=std::enable_if_t<std::is_array_v<T>>>
voidfoo(T&&arg1,T&&arg2)
{
…
}
Duetothesespecialhandling,oftenthebestwaytodealwitharraysindifferent
waysissimplytousedifferentfunctionnames.Evenbetter,ofcourse,isto
ensurethatthecallerofatemplateusesstd::vectororstd::array.But
aslongasstringliteralsarerawarrays,wealwayshavetotaketheminto
account.
7.5DealingwithReturnValues
Forreturnvalues,youcanalsodecidebetweenreturningbyvalueorby
reference.However,returningreferencesispotentiallyasourceoftrouble,
becauseyourefertosomethingthatisoutofyourcontrol.Thereareafewcases
wherereturningreferencesiscommonprogrammingpractice:•Returning
elementsofcontainersorstrings(e.g.,byoperator[]orfront())•
Grantingwriteaccesstoclassmembers•Returningobjectsforchainedcalls
(operator<<andoperator>>forstreamsandoperator=forclass
objectsingeneral)Inaddition,itiscommontograntreadaccesstomembersby
returningconstreferences.
Notethatallthesecasesmaycausetroubleifusedimproperly.Forexample:
Clickheretoviewcodeimage
std::string*s=newstd::string("whatever");
auto&c=(*s)[0];
deletes;
std::cout<<c;//run-timeERROR
Here,weobtainedareferencetoanelementofastring,butbythetimeweuse
thatreference,theunderlyingstringnolongerexists(i.e.,wecreatedadangling
reference),andwehaveundefinedbehavior.Thisexampleissomewhat
contrived(theexperiencedprogrammerislikelytonoticetheproblemright
away),butthingseasilybecomelessobvious.Forexample:Clickheretoview
codeimage
autos=std::make_shared<std::string>("whatever");
auto&c=(*s)[0];
s.reset();
std::cout<<c;//run-timeERROR
Weshouldthereforeensurethatfunctiontemplatesreturntheirresultbyvalue.
However,asdiscussedinthischapter,usingatemplateparameterTisno
guaranteethatitisnotareference,becauseTmightsometimesimplicitlybe
deducedasareference:Clickheretoviewcodeimage
template<typenameT>
TretR(T&&p)//pisaforwardingreference
{
returnT{…};//OOPS:returnsbyreferencewhencalledforlvalues
}
EvenwhenTisatemplateparameterdeducedfromacall-by-valuecall,itmight
becomeareferencetypewhenexplicitlyspecifyingthetemplateparametertobe
areference:Clickheretoviewcodeimage
template<typenameT>
TretV(Tp)//Note:Tmightbecomeareference
{
returnT{…};//OOPS:returnsareferenceifTisareference
}
intx;
retV<int&>(x);//retT()instantiatedforTasint&
Tobesafe,youhavetwooptions:•Usethetypetrait
std::remove_reference<>(seeSectionD.4onpage729)toconverttype
Ttoanonreference:Clickheretoviewcodeimage
template<typenameT>
typenamestd::remove_reference<T>::typeretV(Tp)
{
returnT{…};//alwaysreturnsbyvalue
}
Othertraits,suchasstd::decay<>(seeSectionD.4onpage731),mayalso
beusefulherebecausetheyalsoimplicitlyremovereferences.
•Letthecompilerdeducethereturntypebyjustdeclaringthereturntypetobe
auto(sinceC++14;seeSection1.3.2onpage11),becauseautoalways
decays:Clickheretoviewcodeimage
template<typenameT>
autoretV(Tp)//by-valuereturntypededucedbycompiler
{
returnT{…};//alwaysreturnsbyvalue
}
7.6RecommendedTemplateParameterDeclarations
Aswelearnedintheprevioussections,wehaveverydifferentwaystodeclare
parametersthatdependontemplateparameters:•Declaretopassthearguments
byvalue:Thisapproachissimple,itdecaysstringliteralsandrawarrays,butit
doesn’tprovidethebestperformanceforlargeobjects.Stillthecallercandecide
topassbyreferenceusingstd::cref()andstd::ref(),butthecaller
mustbecarefulthatdoingsoisvalid.
•Declaretopasstheargumentsby-reference:Thisapproachoftenprovides
betterperformanceforsomewhatlargeobjects,especiallywhenpassing–
existingobjects(lvalues)tolvaluereferences,–temporaryobjects(prvalues)
orobjectsmarkedasmovable(xvalue)torvaluereferences,–orbothto
forwardingreferences.
Becauseinallthesecasestheargumentsdon’tdecay,youmayneedspecial
carewhenpassingstringliteralsandotherrawarrays.Forforwarding
references,youalsohavetobewarethatwiththisapproachtemplate
parametersimplicitlycandeducetoreferencetypes.
GeneralRecommendations
Withtheseoptionsinmind,forfunctiontemplateswerecommendthefollowing:
1.Bydefault,declareparameterstobepassedbyvalue.Thisissimpleand
usuallyworksevenwithstringliterals.Theperformanceisfineforsmall
argumentsandfortemporaryormovableobjects.Thecallercansometimesuse
std::ref()andstd::cref()whenpassingexistinglargeobjects
(lvalues)toavoidexpensivecopying.
2.Iftherearegoodreasons,dootherwise:–Ifyouneedanoutorinout
parameter,whichreturnsanewobjectorallowstomodifyanargumentto/for
thecaller,passtheargumentasanonconstantreference(unlessyoupreferto
passitviaapointer).However,youmightconsiderdisablingaccidentally
acceptingconstobjectsasdiscussedinSection7.2.2onpage110.
–Ifatemplateisprovidedtoforwardanargument,useperfectforwarding.
Thatis,declareparameterstobeforwardingreferencesanduse
std::forward<>()whereappropriate.Considerusingstd::decay<>
orstd::common_type<>to“harmonize”thedifferenttypesofstring
literalsandrawarrays.
–Ifperformanceiskeyanditisexpectedthatcopyingargumentsisexpensive,
useconstantreferences.This,ofcourse,doesnotapplyifyouneedalocal
copyanyway.
3.Ifyouknowbetter,don’tfollowtheserecommendations.However,donot
makeintuitiveassumptionsaboutperformance.Evenexpertsfailiftheytry.
Instead:Measure!
Don’tBeOver-Generic
Notethat,inpractice,functiontemplatesoftenarenotforarbitrarytypesof
arguments.Instead,someconstraintsapply.Forexample,youmayknowthat
onlyvectorsofsometypearepassed.Inthiscase,itisbetternottodeclaresuch
afunctiontoogenerically,because,asdiscussed,surprisingsideeffectsmay
occur.Instead,usethefollowingdeclaration:Clickheretoviewcodeimage
template<typenameT>
voidprintVector(std::vector<T>const&v)
{
…
}
WiththisdeclarationofparametervinprintVector(),wecanbesurethat
thepassedTcan’tbecomeareferencebecausevectorscan’tusereferencesas
elementtypes.Also,itisprettyclearthatpassingavectorbyvaluealmost
alwayscanbecomeexpensivebecausethecopyconstructorof
std::vector<>createsacopyoftheelements.Forthisreason,itisprobably
neverusefultodeclaresuchavectorparametertobepassedbyvalue.Ifwe
declareparametervjustashavingtypeTdeciding,betweencall-by-valueand
call-by-referencebecomeslessobvious.
Thestd::make_pair()Example
std::make_pair<>()isagoodexampletodemonstratethepitfallsof
decidingaparameterpassingmechanism.Itisaconveniencefunctiontemplate
intheC++standardlibrarytocreatestd::pair<>objectsusingtype
deduction.ItsdeclarationchangedthroughdifferentversionsoftheC++
standard:•InthefirstC++standard,C++98,make_pair<>()wasdeclared
insidenamespacestdtousecall-by-referencetoavoidunnecessarycopying:
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
pair<T1,T2>make_pair(T1const&a,T2const&b)
{
returnpair<T1,T2>(a,b);
}
This,however,almostimmediatelycausedsignificantproblemswhenusing
pairsofstringliteralsorrawarraysofdifferentsize.8
•Asaconsequence,withC++03thefunctiondefinitionwaschangedtousecall-
by-value:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
pair<T1,T2>make_pair(T1a,T2b)
{
returnpair<T1,T2>(a,b);
}
Asyoucanreadintherationalefortheissueresolution,“itappearedthatthis
wasamuchsmallerchangetothestandardthantheothertwosuggestions,
andanyefficiencyconcernsweremorethanoffsetbytheadvantagesofthe
solution.”
•However,withC++11,make_pair()hadtosupportmovesemantics,sothat
theargumentshadtobecomeforwardingreferences.Forthisreason,the
definitionchangedroughlyagainasfollows:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
constexprpair<typenamedecay<T1>::type,typenamedecay<T2>::type>
make_pair(T1&&a,T2&&b)
{
returnpair<typenamedecay<T1>::type,
typenamedecay<T2>::type>(forward<T1>(a),forward<T2>(b));
}
Thecompleteimplementationisevenmorecomplex:Tosupport
std::ref()andstd::cref(),thefunctionalsounwrapsinstancesof
std::reference_wrapperintorealreferences.
TheC++standardlibrarynowperfectlyforwardspassedargumentsinmany
placesinsimilarway,oftencombinedwithusingstd::decay<>.
7.7Summary
•Whentestingtemplates,usestringliteralsofdifferentlength.
•Templateparameterspassedbyvaluedecay,whilepassingthembyreference
doesnotdecay.
•Thetypetraitstd::decay<>allowsyoutodecayparametersintemplates
passedbyreference.
•Insomecasesstd::cref()andstd::ref()allowyoutopass
argumentsbyreferencewhenfunctiontemplatesdeclarethemtobepassedby
value.
•Passingtemplateparametersbyvalueissimplebutmaynotresultinthebest
performance.
•Passparameterstofunctiontemplatesbyvalueunlesstherearegoodreasonsto
dootherwise.
•Ensurethatreturnvaluesareusuallypassedbyvalue(whichmightmeanthata
templateparametercan’tbespecifieddirectlyasareturntype).
•Alwaysmeasureperformancewhenitisimportant.Donotrelyonintuition;it’s
probablywrong.
1AconstantrvaluereferenceXconst&&isalsopossiblebutthereisno
establishedsemanticmeaningforit.
2NotethatsinceC++17youcanpasstemporaryentities(rvalues)byvalue
evenifnocopyormoveconstructorisavailable(seeSectionB.2.1onpage
676).So,sinceC++17theadditionalconstraintisthatcopyingforlvaluesis
notpossible.
3Theimplementationofthestringclassmightitselfhavesomeoptimizationsto
makecopyingcheaper.Oneisthesmallstringoptimization(SSO),usingsome
memorydirectlyinsidetheobjecttoholdthevaluewithoutallocatingmemory
aslongasthevalueisnottoolong.Anotheristhecopy-on-writeoptimization,
whichcreatesacopyusingthesamememoryasthesourceaslongasneither
sourcenorthecopyismodified.However,thecopy-on-writeoptimizationhas
significantdrawbacksinmultithreadedcode.Forthisreason,itisforbidden
forstandardstringssinceC++11.
4ThetermdecaycomesfromC,andalsoappliestothetypeconversionfroma
functiontoafunctionpointer(seeSection11.1.1onpage159).
5Theuseofconst_castisanother,moreexplicit,waytomodifythe
referencedobject.
6Whenpassingstd::move(c),std::move()firstconvertscto
std::stringconst&&,whichthenhastheeffectthatTisdeducedas
std::stringconst.
7Youcanalsocallget()onareferencewrapperanduseitasfunctionobject.
8SeeC++libraryissue181[LibIssue181]fordetails.
Chapter8
Compile-TimeProgramming
C++hasalwaysincludedsomesimplewaystocomputevaluesatcompiletime.
Templatesconsiderablyincreasedthepossibilitiesinthisarea,andfurther
evolutionofthelanguagehasonlyaddedtothistoolbox.
Inthesimplecase,youcandecidewhetherornottousecertainortochoose
betweendifferenttemplatecode.Butthecompilerevencancomputethe
outcomeofcontrolflowatcompiletime,providedallnecessaryinputis
available.
Infact,C++hasmultiplefeaturestosupportcompile-timeprogramming:•
SincebeforeC++98,templateshaveprovidedtheabilitytocomputeatcompile
time,includingusingloopsandexecutionpathselection.(However,some
considerthisan“abuse”oftemplatefeatures,e.g.,becauseitrequires
nonintuitivesyntax.)•Withpartialspecializationwecanchooseatcompiletime
betweendifferentclasstemplateimplementationsdependingonspecific
constraintsorrequirements.
•WiththeSFINAEprinciple,wecanallowselectionbetweendifferentfunction
templateimplementationsfordifferenttypesordifferentconstraints.
•InC++11andC++14,compile-timecomputingbecameincreasinglybetter
supportedwiththeconstexprfeatureusing“intuitive”executionpath
selectionand,sinceC++14,moststatementkinds(includingforloops,
switchstatements,etc.).
•C++17introduceda“compile-timeif”todiscardstatementsdependingon
compile-timeconditionsorconstraints.Itworksevenoutsideoftemplates.
Thischapterintroducesthesefeatureswithaspecialfocusontheroleand
contextoftemplates.
8.1TemplateMetaprogramming
Templatesareinstantiatedatcompiletime(incontrasttodynamiclanguages,
wheregenericityishandledatruntime).Itturnsoutthatsomeofthefeaturesof
C++templatescanbecombinedwiththeinstantiationprocesstoproduceasort
ofprimitiverecursive“programminglanguage”withintheC++languageitself.1
Forthisreason,templatescanbeusedto“computeaprogram.”Chapter23will
coverthewholestoryandallfeatures,buthereisashortexampleofwhatis
possible.
Thefollowingcodefindsoutatcompiletimewhetheragivennumberisa
primenumber:Clickheretoviewcodeimage
basics/isprime.hpp
template<unsignedp,unsignedd>//p:numbertocheck,d:current
divisor
structDoIsPrime{
staticconstexprboolvalue=(p%d!=0)&&DoIsPrime<p,d-1>::value;
};
template<unsignedp>//endrecursionifdivisoris2
structDoIsPrime<p,2>{
staticconstexprboolvalue=(p%2!=0);
};
template<unsignedp>//primarytemplate
structIsPrime{
//startrecursionwithdivisorfromp/2:
staticconstexprboolvalue=DoIsPrime<p,p/2>::value;
};
//specialcases(toavoidendlessrecursionwithtemplate
instantiation):
template<>
structIsPrime<0>{staticconstexprboolvalue=false;};
template<>
structIsPrime<1>{staticconstexprboolvalue=false;};
template<>
structIsPrime<2>{staticconstexprboolvalue=true;};
template<>
structIsPrime<3>{staticconstexprboolvalue=true;};The
IsPrime<>templatereturnsinmembervaluewhetherthepassedtemplate
parameterpisaprimenumber.Toachievethis,itinstantiates
DoIsPrime<>,whichrecursivelyexpandstoanexpressioncheckingfor
eachdivisordbetweenp/2and2whetherthedivisordividespwithout
remainder.
Forexample,theexpression
IsPrime<9>::value
expandsto
DoIsPrime<9,4>::value
whichexpandstoClickheretoviewcodeimage
9%4!=0&&DoIsPrime<9,3>::valuewhichexpandsto
Clickheretoviewcodeimage
9%4!=0&&9%3!=0&&DoIsPrime<9,2>::valuewhichexpandsto
Clickheretoviewcodeimage
9%4!=0&&9%3!=0&&9%2!=0
whichevaluatestofalse,because9%3is0.
Asthischainofinstantiationsdemonstrates:•Weuserecursiveexpansionsof
DoIsPrime<>toiterateoveralldivisorsfromp/2downto2tofindout
whetheranyofthesedivisorsdividethegivenintegerexactly(i.e.,without
remainder).
•ThepartialspecializationofDoIsPrime<>fordequalto2servesasthe
criteriontoendtherecursion.
Notethatallthisisdoneatcompiletime.Thatis,IsPrime<9>::value
expandstofalseatcompiletime.
Thetemplatesyntaxisarguablyclumsy,butcodesimilartothishasbeenvalid
sinceC++98(andearlier)andhasprovenusefulforquiteafewlibraries.2
SeeChapter23fordetails.
8.2Computingwithconstexpr
C++11introducedanewfeature,constexpr,thatgreatlysimplifiesvarious
formsofcompile-timecomputation.Inparticular,givenproperinput,a
constexprfunctioncanbeevaluatedatcompiletime.WhileinC++11
constexprfunctionswereintroducedwithstringentlimitations(e.g.,each
constexprfunctiondefinitionwasessentiallylimitedtoconsistofareturn
statement),mostoftheserestrictionswereremovedwithC++14.Ofcourse,
successfullyevaluatingaconstexprfunctionstillrequiresthatall
computationalstepsbepossibleandvalidatcompiletime:Currently,that
excludesthingslikeheapallocationorthrowingexceptions.
Ourexampletotestwhetheranumberisaprimenumbercouldbe
implementedasfollowsinC++11:Clickheretoviewcodeimage
basics/isprime11.hpp
constexprbool
doIsPrime(unsignedp,unsignedd)//p:numbertocheck,d:current
divisor
{
returnd!=2?(p%d!=0)&&doIsPrime(p,d-1)//checkthisandsmaller
divisors
:(p%2!=0);//endrecursionifdivisoris2
}
constexprboolisPrime(unsignedp)
{
returnp<4?!(p<2)//handlespecialcases
:doIsPrime(p,p/2);//startrecursionwithdivisorfromp/2
}
Duetothelimitationofhavingonlyonestatement,wecanonlyusethe
conditionaloperatorasaselectionmechanism,andwestillneedrecursionto
iterateovertheelements.ButthesyntaxisordinaryC++functioncode,making
itmoreaccessiblethanourfirstversionrelyingontemplateinstantiation.
WithC++14,constexprfunctionscanmakeuseofmostcontrolstructures
availableingeneralC++code.So,insteadofwritingunwieldytemplatecodeor
somewhatarcaneone-liners,wecannowjustuseaplainforloop:Clickhereto
viewcodeimage
basics/isprime14.hpp
constexprboolisPrime(unsignedintp)
{
for(unsignedintd=2;d<=p/2;++d){
if(p%d==0){
returnfalse;//founddivisorwithoutremainder
}
}
returnp>1;//nodivisorwithoutremainderfound
}
WithboththeC++11andC++14versionsofourconstexprisPrime()
implementations,wecansimplycallisPrime(9)
tofindoutwhether9isaprimenumber.Notethatitcandosoatcompiletime,
butitneednotnecessarilydoso.Inacontextthatrequiresacompile-timevalue
(e.g.,anarraylengthoranontypetemplateargument),thecompilerwillattempt
toevaluateacalltoaconstexprfunctionatcompiletimeandissueanerrorif
thatisnotpossible(sinceaconstantmustbeproducedintheend).Inother
contexts,thecompilermayormaynotattempttheevaluationatcompiletime3
butifsuchanevaluationfails,noerrorisissuedandthecallisleftasarun-time
callinstead.
Forexample:
Clickheretoviewcodeimage
constexprboolb1=isPrime(9);//evaluatedatcompiletime
willcomputethevalueatcompiletime.ThesameistruewithClickheretoview
codeimage
constboolb2=isPrime(9);//evaluatedatcompiletimeifin
namespacescope
providedb2isdefinedgloballyorinanamespace.Atblockscope,thecompiler
candecidewhethertocomputeitatcompileorruntime.4This,forexample,is
alsothecasehere:Clickheretoviewcodeimage
boolfiftySevenIsPrime(){
returnisPrime(57);//evaluatedatcompileorrunningtime
}
thecompilermayormaynotevaluatethecalltoisPrimeatcompiletime.
Ontheotherhand:
Clickheretoviewcodeimage
intx;
…
std::cout<<isPrime(x);//evaluatedatruntime
willgeneratecodethatcomputesatruntimewhetherxisaprimenumber.
8.3ExecutionPathSelectionwithPartial
Specialization
Aninterestingapplicationofacompile-timetestsuchasisPrime()istouse
partialspecializationtoselectatcompiletimebetweendifferent
implementations.
Forexample,wecanchoosebetweendifferentimplementationsdependingon
whetheratemplateargumentisaprimenumber:Clickheretoviewcodeimage
//primaryhelpertemplate:
template<intSZ,bool=isPrime(SZ)>
structHelper;
//implementationifSZisnotaprimenumber:
template<intSZ>
structHelper<SZ,false>
{
…
};
//implementationifSZisaprimenumber:
template<intSZ>
structHelper<SZ,true>
{
…
};
template<typenameT,std::size_tSZ>
longfoo(std::array<T,SZ>const&coll)
{
Helper<SZ>h;//implementationdependsonwhetherarrayhasprime
numberassize
…
}
Here,dependingonwhetherthesizeofthestd::array<>argumentisa
primenumber,weusetwodifferentimplementationsofclassHelper<>.This
kindofapplicationofpartialspecializationisbroadlyapplicabletoselectamong
differentimplementationsofafunctiontemplatedependingonpropertiesofthe
argumentsit’sbeinginvokedfor.
Above,weusedtwopartialspecializationstoimplementthetwopossible
alternatives.Instead,wecanalsousetheprimarytemplateforoneofthe
alternatives(thedefault)caseandpartialspecializationsforanyotherspecial
case:Clickheretoviewcodeimage
//primaryhelpertemplate(usedifnospecializationfits):
template<intSZ,bool=isPrime(SZ)>
structHelper
{
…
};
//specialimplementationifSZisaprimenumber:
template<intSZ>
structHelper<SZ,true>
{
…
};
Becausefunctiontemplatesdonotsupportpartialspecialization,youhavetouse
othermechanismstochangefunctionimplementationbasedoncertain
constraints.Ouroptionsincludethefollowing:•Useclasseswithstatic
functions,•Usestd::enable_if,introducedinSection6.3onpage98,•
UsetheSFINAEfeature,whichisintroducednext,or•Usethecompile-timeif
feature,availablesinceC++17,whichisintroducedbelowinSection8.5onpage
135.
Chapter20discussestechniquesforselectingafunctionimplementationbased
onconstraints.
8.4SFINAE(SubstitutionFailureIsNotAnError)
InC++itisprettycommontooverloadfunctionstoaccountforvarious
argumenttypes.Whenacompilerseesacalltoanoverloadedfunction,itmust
thereforeconsidereachcandidateseparately,evaluatingtheargumentsofthecall
andpickingthecandidatethatmatchesbest(seealsoAppendixCforsome
detailsaboutthisprocess).
Incaseswherethesetofcandidatesforacallincludesfunctiontemplates,the
compilerfirsthastodeterminewhattemplateargumentsshouldbeusedforthat
candidate,thensubstitutethoseargumentsinthefunctionparameterlistandin
itsreturntype,andthenevaluatehowwellitmatches(justlikeanordinary
function).However,thesubstitutionprocesscouldrunintoproblems:Itcould
produceconstructsthatmakenosense.Ratherthandecidingthatsuch
meaninglesssubstitutionsleadtoerrors,thelanguagerulesinsteadsaythat
candidateswithsuchsubstitutionproblemsaresimplyignored.
WecallthisprincipleSFINAE(pronouncedlikesfee-nay),whichstandsfor
“substitutionfailureisnotanerror.”
Notethatthesubstitutionprocessdescribedhereisdistinctfromtheon-
demandinstantiationprocess(seeSection2.2onpage27):Thesubstitutionmay
bedoneevenforpotentialinstantiationsthatarenotneeded(sothecompilercan
evaluatewhetherindeedtheyareunneeded).Itisasubstitutionoftheconstructs
appearingdirectlyinthedeclarationofthefunction(butnotitsbody).
Considerthefollowingexample:Clickheretoviewcodeimage
basics/len1.hpp
//numberofelementsinarawarray:
template<typenameT,unsignedN>
std::size_tlen(T(&)[N])
{
returnN;
}
//numberofelementsforatypehavingsize_type:
template<typenameT>
typenameT::size_typelen(Tconst&t)
{
returnt.size();
}
Here,wedefinetwofunctiontemplateslen()takingonegenericargument:5
1.ThefirstfunctiontemplatedeclarestheparameterasT(&)[N],whichmeans
thattheparameterhastobeanarrayofNelementsoftypeT.
2.ThesecondfunctiontemplatedeclarestheparametersimplyasT,which
placesnoconstraintsontheparameterbutreturnstypeT::size_type,
whichrequiresthatthepassedargumenttypehasacorrespondingmember
size_type.
Whenpassingarawarrayorstringliterals,onlythefunctiontemplateforraw
arraysmatches:Clickheretoviewcodeimage
inta[10];
std::cout<<len(a);//OK:onlylen()forarraymatches
std::cout<<len("tmp");//OK:onlylen()forarraymatches
Accordingtoitssignature,thesecondfunctiontemplatealsomatcheswhen
substituting(respectively)int[10]andcharconst[4]forT,butthose
substitutionsleadtopotentialerrorsinthereturntypeT::size_type.The
secondtemplateisthereforeignoredforthesecalls.
Whenpassingastd::vector<>,onlythesecondfunctiontemplate
matches:Clickheretoviewcodeimage
std::vector<int>v;
std::cout<<len(v);//OK:onlylen()foratypewithsize_type
matches
Whenpassingarawpointer,neitherofthetemplatesmatch(withoutafailure).
Asaresult,thecompilerwillcomplainthatnomatchinglen()functionis
found:Clickheretoviewcodeimage
int*p;
std::cout<<len(p);//ERROR:nomatchinglen()functionfound
Notethatthisdiffersfrompassinganobjectofatypehavingasize_type
member,butnosize()memberfunction,asis,forexample,thecasefor
std::allocator<>:Clickheretoviewcodeimage
std::allocator<int>x;
std::cout<<len(x);//ERROR:len()functionfound,butcan’tsize()
Whenpassinganobjectofsuchatype,thecompilerfindsthesecondfunction
templateasmatchingfunctiontemplate.Soinsteadofanerrorthatnomatching
len()functionisfound,thiswillresultinacompile-timeerrorthatcalling
size()forastd::allocator<int>isinvalid.Thistime,thesecond
functiontemplateisnotignored.
Ignoringacandidatewhensubstitutingitsreturntypeismeaninglesscan
causethecompilertoselectanothercandidatewhoseparametersareaworse
match.Forexample:Clickheretoviewcodeimage
basics/len2.hpp
//numberofelementsinarawarray:
template<typenameT,unsignedN>
std::size_tlen(T(&)[N])
{
returnN;
}
//numberofelementsforatypehavingsize_type:
template<typenameT>
typenameT::size_typelen(Tconst&t)
{
returnt.size();
}
//fallbackforallothertypes:
std::size_tlen(…)
{
return0;
}
Here,wealsoprovideagenerallen()functionthatalwaysmatchesbuthasthe
worstmatch(matchwithellipsis(…)inoverloadresolution(seeSectionC.2on
page682).
So,forrawarraysandvectors,wehavetwomatcheswherethespecificmatch
isthebettermatch.Forpointers,onlythefallbackmatchessothatthecompiler
nolongercomplainsaboutamissinglen()forthiscall.6Butfortheallocator,
thesecondandthirdfunctiontemplatesmatch,withthesecondfunctiontemplate
asthebettermatch.So,still,thisresultsinanerrorthatnosize()member
functioncanbecalled:Clickheretoviewcodeimage
inta[10];
std::cout<<len(a);//OK:len()forarrayisbestmatch
std::cout<<len("tmp");//OK:len()forarrayisbestmatch
std::vector<int>v;
std::cout<<len(v);//OK:len()foratypewithsize_typeisbest
match
int*p;
std::cout<<len(p);//OK:onlyfallbacklen()matches
std::allocator<int>x;
std::cout<<len(x);//ERROR:2ndlen()functionmatchesbest,
//butcan’tcallsize()forx
SeeSection15.7onpage284formoredetailsaboutSFINAEandSection19.4
onpage416aboutsomeapplicationsofSFINAE.
SFINAEandOverloadResolution
Overtime,theSFINAEprinciplehasbecomesoimportantandsoprevalent
amongtemplatedesignersthattheabbreviationhasbecomeaverb.Wesay“we
SFINAEoutafunction”ifwemeantoapplytheSFINAEmechanismtoensure
thatfunctiontemplatesareignoredforcertainconstraintsbyinstrumentingthe
templatecodetoresultininvalidcodefortheseconstraints.Andwheneveryou
readintheC++standardthatafunctiontemplate“shallnotparticipatein
overloadresolutionunless…”itmeansthatSFINAEisusedto“SFINAEout”
thatfunctiontemplateforcertaincases.
Forexample,classstd::threaddeclaresaconstructor:Clickheretoview
codeimage
namespacestd{
classthread{
public:
…
template<typenameF,typename…Args>
explicitthread(F&&f,Args&&…args);
…
};
}
withthefollowingremark:
Remarks:Thisconstructorshallnotparticipateinoverloadresolutionif
decay_t<F>isthesametypeasstd::thread.
Thismeansthatthetemplateconstructorisignoredifitiscalledwitha
std::threadasfirstandonlyargument.Thereasonisthatotherwisea
membertemplatelikethissometimesmightbettermatchthananypredefined
copyormoveconstructor(seeSection6.2onpage95andSection16.2.4on
page333fordetails).BySFINAE’ingouttheconstructortemplatewhencalled
forathread,weensurethatthepredefinedcopyormoveconstructorisalways
usedwhenathreadgetsconstructedfromanotherthread.7
Applyingthistechniqueonacase-by-casebasiscanbeunwieldy.Fortunately,
thestandardlibraryprovidestoolstodisabletemplatesmoreeasily.Thebest-
knownsuchfeatureisstd::enable_if<>,whichwasintroducedinSection
6.3onpage98.Itallowsustodisableatemplatejustbyreplacingatypewitha
constructcontainingtheconditiontodisableit.
Asaconsequence,therealdeclarationofstd::threadtypicallyisas
follows:Clickheretoviewcodeimage
namespacestd{
classthread{
public:
…
template<typenameF,typename…Args,
typename=std::enable_if_t<!std::is_same_v<std::decay_t<F>,
thread>>>
explicitthread(F&&f,Args&&…args);
…
};
}
SeeSection20.3onpage469fordetailsabouthowstd::enable_if<>is
implemented,usingpartialspecializationandSFINAE.
8.4.1ExpressionSFINAEwithdecltype
It’snotalwayseasytofindoutandformulatetherightexpressiontoSFINAEout
functiontemplatesforcertainconditions.
Suppose,forexample,thatwewanttoensurethatthefunctiontemplate
len()isignoredforargumentsofatypethathasasize_typememberbut
notasize()memberfunction.Withoutanyformofrequirementsfora
size()memberfunctioninthefunctiondeclaration,thefunctiontemplateis
selectedanditsultimateinstantiationthenresultsinanerror:Clickheretoview
codeimage
template<typenameT>
typenameT::size_typelen(Tconst&t)
{
returnt.size();
}
std::allocator<int>x;
std::cout<<len(x)<<’\n’;//ERROR:len()selected,butxhasno
size()
Thereisacommonpatternoridiomtodealwithsuchasituation:•Specifythe
returntypewiththetrailingreturntypesyntax(useautoatthefrontand->
beforethereturntypeattheend).
•Definethereturntypeusingdecltypeandthecommaoperator.
•Formulateallexpressionsthatmustbevalidatthebeginningofthecomma
operator(convertedtovoidincasethecommaoperatorisoverloaded).
•Defineanobjectoftherealreturntypeattheendofthecommaoperator.
Forexample:
Clickheretoviewcodeimage
template<typenameT>
autolen(Tconst&t)->decltype((void)(t.size()),T::size_type())
{
returnt.size();
}
HerethereturntypeisgivenbyClickheretoviewcodeimage
decltype((void)(t.size)(),T::size_type())Theoperandofthe
decltypeconstructisacomma-separatedlistofexpressions,sothat
thelastexpressionT::size_type()yieldsavalueofthedesired
returntype(whichdecltypeusestoconvertintothereturntype).
Beforethe(last)comma,wehavetheexpressionsthatmustbevalid,
whichinthiscaseisjustt.size().Thecastoftheexpressionto
voidistoavoidthepossibilityofauser-definedcommaoperator
overloadedforthetypeoftheexpressions.
Notethattheargumentofdecltypeisanunevaluatedoperand,which
meansthatyou,forexample,cancreate“dummyobjects”withoutcalling
constructors,whichisdiscussedinSection11.2.3onpage166.
8.5Compile-Timeif
Partialspecialization,SFINAE,andstd::enable_ifallowustoenableor
disabletemplatesasawhole.C++17additionallyintroducesacompile-timeif
statementthatallowsistoenableordisablespecificstatementsbasedon
compile-timeconditions.Withthesyntaxifconstexpr(…),thecompiler
usesacompile-timeexpressiontodecidewhethertoapplythethenpartorthe
elsepart(ifany).
Asafirstexample,considerthevariadicfunctiontemplateprint()
introducedinSection4.1.1onpage55.Itprintsitsarguments(ofarbitrarytypes)
usingrecursion.Insteadofprovidingaseparatefunctiontoendtherecursion,the
constexpriffeatureallowsustodecidelocallywhethertocontinuethe
recursion:8
Clickheretoviewcodeimage
template<typenameT,typename…Types>
voidprint(Tconst&firstArg,Typesconst&…args)
{
std::cout<<firstArg<<’\n’;
ifconstexpr(sizeof…(args)>0){
print(args…);//codeonlyavailableifsizeof…(args)>0(sinceC++17)
}
}
Here,ifprint()iscalledforoneargumentonly,argsbecomesanempty
parameterpacksothatsizeof…(args)becomes0.Asaresult,therecursive
callofprint()becomesadiscardedstatement,forwhichthecodeisnot
instantiated.Thus,acorrespondingfunctionisnotrequiredtoexistandthe
recursionends.
Thefactthatthecodeisnotinstantiatedmeansthatonlythefirsttranslation
phase(thedefinitiontime)isperformed,whichchecksforcorrectsyntaxand
namesthatdon’tdependontemplateparameters(seeSection1.1.3onpage6).
Forexample:Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tt)
{
ifconstexpr(std::is_integral_v<T>){
if(t>0){
foo(t-1);//OK
}
}
else{
undeclared(t);//errorifnotdeclaredandnotdiscarded(i.e.Tis
notintegral)
undeclared();//errorifnotdeclared(evenifdiscarded)
static_assert(false,"nointegral");//alwaysasserts(evenif
discarded)
static_assert(!std::is_integral_v<T>,"nointegral");//OK
}
}
Notethatifconstexprcanbeusedinanyfunction,notonlyintemplates.
Weonlyneedacompile-timeexpressionthatyieldsaBooleanvalue.For
example:Clickheretoviewcodeimage
intmain()
{
ifconstexpr(std::numeric_limits<char>::is_signed{
foo(42);//OK
}
else{
undeclared(42);//errorif
undeclared()notdeclared
static_assert(false,"unsigned");//alwaysasserts(evenif
discarded)
static_assert(!std::numeric_limits<char>::is_signed,
"charisunsigned");//OK
}
}
Withthisfeature,wecan,forexample,useourisPrime()compile-time
function,introducedinSection8.2onpage125,toperformadditionalcodeifa
givensizeisnotaprimenumber:Clickheretoviewcodeimage
template<typenameT,std::size_tSZ>
voidfoo(std::array<T,SZ>const&coll)
{
ifconstexpr(!isPrime(SZ)){
…//specialadditionalhandlingifthepassedarrayhasnoprime
numberassize
}
…
}
SeeSection14.6onpage263forfurtherdetails.
8.6Summary
•Templatesprovidetheabilitytocomputeatcompiletime(usingrecursionto
iterateandpartialspecializationoroperator?:forselections).
•Withconstexprfunctions,wecanreplacemostcompile-timecomputations
with“ordinaryfunctions”thatarecallableincompile-timecontexts.
•Withpartialspecialization,wecanchoosebetweendifferentimplementations
ofclasstemplatesbasedoncertaincompile-timeconstraints.
•Templatesareusedonlyifneededandsubstitutionsinfunctiontemplate
declarationsdonotresultininvalidcode.ThisprincipleiscalledSFINAE
(substitutionfailureisnotanerror).
•SFINAEcanbeusedtoprovidefunctiontemplatesonlyforcertaintypes
and/orconstraints.
•SinceC++17,acompile-timeifallowsustoenableordiscardstatements
accordingtocompile-timeconditions(evenoutsidetemplates).
1Infact,itwasErwinUnruhwhofirstfounditoutbypresentingaprogram
computingprimenumbersatcompiletime.SeeSection23.7onpage545for
details.
2BeforeC++11,itwascommontodeclarethevaluemembersasenumerator
constantsinsteadofstaticdatamemberstoavoidtheneedtohaveanout-of-
classdefinitionofthestaticdatamember(seeSection23.6onpage543for
details).Forexample:Clickheretoviewcodeimage
enum{value=(p%d!=0)&&DoIsPrime<p,d-1>::value};3Atthetime
ofwritingthisbookin2012"fn">4Theoretically,evenwith
constexpr,thecompilercandecidetocomputetheinitialvalueofb
atruntime.
5Wedon’tnamethisfunctionsize()becausewewanttoavoidanaming
conflictwiththeC++standardlibrary,whichdefinesastandardfunction
templatestd::size()sinceC++17.
6Inpractice,suchafallbackfunctionwouldusuallyprovideamoreuseful
default,throwanexception,orcontainastaticassertiontoresultinauseful
errormessage.
7Sincethecopyconstructorforclassthreadisdeleted,thisalsoensuresthat
copyingisforbidden.
8Althoughthecodereadsifconstexpr,thefeatureiscalledconstexprif,
becauseitisthe“constexpr”formofif(andforhistoricalreasons).
Chapter9
UsingTemplatesinPractice
Templatecodeisalittledifferentfromordinarycode.Insomewaystemplateslie
somewherebetweenmacrosandordinary(nontemplate)declarations.Although
thismaybeanoversimplification,ithasconsequencesnotonlyforthewaywe
writealgorithmsanddatastructuresusingtemplatesbutalsofortheday-to-day
logisticsofexpressingandanalyzingprogramsinvolvingtemplates.
Inthischapterweaddresssomeofthesepracticalitieswithoutnecessarily
delvingintothetechnicaldetailsthatunderliethem.Manyofthesedetailsare
exploredinChapter14.Tokeepthediscussionsimple,weassumethatourC++
compilationsystemsconsistoffairlytraditionalcompilersandlinkers(C++
systemsthatdon’tfallinthiscategoryarerare).
9.1TheInclusionModel
Thereareseveralwaystoorganizetemplatesourcecode.Thissectionpresents
themostpopularapproach:theinclusionmodel.
9.1.1LinkerErrors
MostCandC++programmersorganizetheirnontemplatecodelargelyas
follows:•Classesandothertypesareentirelyplacedinheaderfiles.Typically,
thisisafilewitha.hpp(or.H,.h,.hh,.hxx)filenameextension.
•Forglobal(noninline)variablesand(noninline)functions,onlyadeclarationis
putinaheaderfile,andthedefinitiongoesintoafilecompiledasitsown
translationunit.SuchaCPPfiletypicallyisafilewitha.cpp(or.C,.c,
.cc,or.cxx)filenameextension.
Thisworkswell:Itmakestheneededtypedefinitioneasilyavailablethroughout
theprogramandavoidsduplicatedefinitionerrorsonvariablesandfunctions
fromthelinker.
Withtheseconventionsinmind,acommonerroraboutwhichbeginning
templateprogrammerscomplainisillustratedbythefollowing(erroneous)little
program.Asusualfor“ordinarycode,”wedeclarethetemplateinaheaderfile:
Clickheretoviewcodeimage
basics/myfirst.hpp
#ifndefMYFIRST_HPP
#defineMYFIRST_HPP
//declarationoftemplate
template<typenameT>
voidprintTypeof(Tconst&);
#endif//MYFIRST_HPP
printTypeof()isthedeclarationofasimpleauxiliaryfunctionthatprints
sometypeinformation.TheimplementationofthefunctionisplacedinaCPP
file:Clickheretoviewcodeimage
basics/myfirst.cpp
#include<iostream>
#include<typeinfo>
#include"myfirst.hpp"
//implementation/definitionoftemplate
template<typenameT>
voidprintTypeof(Tconst&x)
{
std::cout<<typeid(x).name()<<’\n’;
}
Theexampleusesthetypeidoperatortoprintastringthatdescribesthetype
oftheexpressionpassedtoit.Itreturnsanlvalueofthestatictype
std::type_info,whichprovidesamemberfunctionname()thatshows
thetypesofsomeexpressions.TheC++standarddoesn’tactuallysaythat
name()mustreturnsomethingmeaningful,butongoodC++implementations,
youshouldgetastringthatgivesagooddescriptionofthetypeoftheexpression
passedtotypeid.1
Finally,weusethetemplateinanotherCPPfile,intowhichourtemplate
declarationis#included:Clickheretoviewcodeimage
basics/myfirstmain.cpp
#include"myfirst.hpp"
//useofthetemplate
intmain()
{
doubleice=3.0;
printTypeof(ice);//callfunctiontemplatefortypedouble
}
AC++compilerwillmostlikelyacceptthisprogramwithoutanyproblems,but
thelinkerwillprobablyreportanerror,implyingthatthereisnodefinitionofthe
functionprintTypeof().
Thereasonforthiserroristhatthedefinitionofthefunctiontemplate
printTypeof()hasnotbeeninstantiated.Inorderforatemplatetobe
instantiated,thecompilermustknowwhichdefinitionshouldbeinstantiatedand
forwhattemplateargumentsitshouldbeinstantiated.Unfortunately,inthe
previousexample,thesetwopiecesofinformationareinfilesthatarecompiled
separately.Therefore,whenourcompilerseesthecalltoprintTypeof()but
hasnodefinitioninsighttoinstantiatethisfunctionfordouble,itjustassumes
thatsuchadefinitionisprovidedelsewhereandcreatesareference(forthe
linkertoresolve)tothatdefinition.Ontheotherhand,whenthecompiler
processesthefilemyfirst.cpp,ithasnoindicationatthatpointthatitmust
instantiatethetemplatedefinitionitcontainsforspecificarguments.
9.1.2TemplatesinHeaderFiles
Thecommonsolutiontothepreviousproblemistousethesameapproachthat
wewouldtakewithmacrosorwithinlinefunctions:Weincludethedefinitions
ofatemplateintheheaderfilethatdeclaresthattemplate.
Thatis,insteadofprovidingafilemyfirst.cpp,werewrite
myfirst.hppsothatitcontainsalltemplatedeclarationsandtemplate
definitions:Clickheretoviewcodeimage
basics/myfirst2.hpp
#ifndefMYFIRST_HPP
#defineMYFIRST_HPP
#include<iostream>
#include<typeinfo>
//declarationoftemplate
template<typenameT>
voidprintTypeof(Tconst&);
//implementation/definitionoftemplate
template<typenameT>
voidprintTypeof(Tconst&x)
{
std::cout<<typeid(x).name()<<’\n’;}
#endif//MYFIRST_HPP
Thiswayoforganizingtemplatesiscalledtheinclusionmodel.Withthisin
place,youshouldfindthatourprogramnowcorrectlycompiles,links,and
executes.
Thereareafewobservationswecanmakeatthispoint.Themostnotableis
thatthisapproachhasconsiderablyincreasedthecostofincludingtheheaderfile
myfirst.hpp.Inthisexample,thecostisnottheresultofthesizeofthe
templatedefinitionitselfbuttheresultofthefactthatwemustalsoincludethe
headersusedbythedefinitionofourtemplate—inthiscase<iostream>and
<typeinfo>.Youmayfindthatthisamountstotensofthousandsoflinesof
codebecauseheaderslike<iostream>containmanytemplatedefinitionsof
theirown.
Thisisarealprobleminpracticebecauseitconsiderablyincreasesthetime
neededbythecompilertocompilesignificantprograms.Wewilltherefore
examinesomepossiblewaystoapproachthisproblem,includingprecompiled
headers(seeSection9.3onpage141)andtheuseofexplicittemplate
instantiation(seeSection14.5onpage260).
Despitethisbuild-timeissue,wedorecommendfollowingthisinclusion
modeltoorganizeyourtemplateswhenpossibleuntilabettermechanism
becomesavailable.Atthetimeofwritingthisbookin2017,suchamechanism
isintheworks:modules,whichisintroducedinSection17.11onpage366.
Theyarealanguagemechanismthatallowstheprogrammertomorelogically
organizecodeinsuchawaythatacompilercanseparatelycompileall
declarationsandthenefficientlyandselectivelyimporttheprocessed
declarationswheneverneeded.
Another(moresubtle)observationabouttheinclusionapproachisthat
noninlinefunctiontemplatesaredistinctfrominlinefunctionsandmacrosinan
importantway:Theyarenotexpandedatthecallsite.Instead,whentheyare
instantiated,theycreateanewcopyofafunction.Becausethisisanautomatic
process,acompilercouldendupcreatingtwocopiesintwodifferentfiles,and
somelinkerscouldissueerrorswhentheyfindtwodistinctdefinitionsforthe
samefunction.Intheory,thisshouldnotbeaconcernofours:Itisaproblemfor
theC++compilationsystemtoaccommodate.Inpractice,thingsworkwellmost
ofthetime,andwedon’tneedtodealwiththisissueatall.Forlargeprojects
thatcreatetheirownlibraryofcode,however,problemsoccasionallyshowup.A
discussionofinstantiationschemesinChapter14andaclosestudyofthe
documentationthatcamewiththeC++translationsystem(compiler)shouldhelp
addresstheseproblems.
Finally,weneedtopointoutthatwhatappliestotheordinaryfunction
templateinourexamplealsoappliestomemberfunctionsandstaticdata
membersofclasstemplates,aswellastomemberfunctiontemplates.
9.2Templatesandinline
Declaringfunctionstobeinlineisacommontooltoimprovetherunningtimeof
programs.Theinlinespecifierwasmeanttobeahintfortheimplementation
thatinlinesubstitutionofthefunctionbodyatthepointofcallispreferredover
theusualfunctioncallmechanism.
However,animplementationmayignorethehint.Hence,theonlyguaranteed
effectofinlineistoallowafunctiondefinitiontoappearmultipletimesina
program(usuallybecauseitappearsinaheaderfilethatisincludedinmultiple
places).
Likeinlinefunctions,functiontemplatescanbedefinedinmultipletranslation
units.Thisisusuallyachievedbyplacingthedefinitioninaheaderfilethatis
includedbymultipleCPPfiles.
Thisdoesn’tmean,however,thatfunctiontemplatesuseinlinesubstitutions
bydefault.Itisentirelyuptothecompilerwhetherandwheninlinesubstitution
ofafunctiontemplatebodyatthepointofcallispreferredovertheusual
functioncallmechanism.Perhapssurprisingly,compilersareoftenbetterthan
programmersatestimatingwhetherinliningacallwouldleadtoanet
performanceimprovement.Asaresult,theprecisepolicyofacompilerwith
respecttoinlinevariesfromcompilertocompiler,andevendependsonthe
optionsselectedforaspecificcompilation.
Nevertheless,withappropriateperformancemonitoringtools,aprogrammer
mayhavebetterinformationthanacompilerandmaythereforewishtooverride
compilerdecisions(e.g.,whentuningsoftwareforparticularplatforms,suchas
mobilesphones,orparticularinputs).Sometimesthisisonlypossiblewith
compiler-specificattributessuchasnoinlineoralways_inline.
It’sworthpointingoutatthispointthatfullspecializationsoffunction
templatesactlikeordinaryfunctionsinthisregard:Theirdefinitioncanappear
onlyonceunlessthey’redefinedinline(seeSection16.3onpage338).See
alsoAppendixAforabroader,detailedoverviewofthistopic.
9.3PrecompiledHeaders
Evenwithouttemplates,C++headerfilescanbecomeverylargeandtherefore
takealongtimetocompile.Templatesaddtothistendency,andtheoutcryof
waitingprogrammershasinmanycasesdrivenvendorstoimplementascheme
usuallyknownasprecompiledheaders(PCH).Thisschemeoperatesoutsidethe
scopeofthestandardandreliesonvendor-specificoptions.Althoughweleave
thedetailsonhowtocreateanduseprecompiledheaderfilestothe
documentationofthevariousC++compilationsystemsthathavethisfeature,it
isusefultogainsomeunderstandingofhowitworks.
Whenacompilertranslatesafile,itdoessostartingfromthebeginningofthe
fileandworkingthroughtotheend.Asitprocesseseachtokenfromthefile
(whichmaycomefrom#includedfiles),itadaptsitsinternalstate,including
suchthingsasaddingentriestoatableofsymbolssotheymaybelookedup
later.Whiledoingso,thecompilermayalsogeneratecodeinobjectfiles.
Theprecompiledheaderschemereliesonthefactthatcodecanbeorganized
insuchamannerthatmanyfilesstartwiththesamelinesofcode.Let’sassume
forthesakeofargumentthateveryfiletobecompiledstartswiththesameN
linesofcode.WecouldcompiletheseNlinesandsavethecompletestateofthe
compileratthatpointinaprecompiledheader.Then,foreveryfileinour
program,wecouldreloadthesavedstateandstartcompilationatlineN+1.At
thispointitisworthwhiletonotethatreloadingthesavedstateisanoperation
thatcanbeordersofmagnitudefasterthanactuallycompilingthefirstNlines.
However,savingthestateinthefirstplaceistypicallymoreexpensivethanjust
compilingtheNlines.Theincreaseincostvariesroughlyfrom20to200
percent.
#include<iostream>Thekeytomakingeffectiveuseofprecompiled
headersistoensurethat—asmuchaspossible—filesstartwitha
maximumnumberofcommonlinesofcode.Inpracticethismeansthe
filesmuststartwiththesame#includedirectives,which(as
mentionedearlier)consumeasubstantialportionofourbuildtime.
Hence,itcanbeveryadvantageoustopayattentiontotheorderin
whichheadersareincluded.Forexample,thefollowingtwofiles:
#include<vector>
#include<list>
…
and
#include<list>
#include<vector>
…
inhibittheuseofprecompiledheadersbecausethereisnocommoninitialstate
inthesources.
Someprogrammersdecidethatitisbetterto#includesomeextra
unnecessaryheadersthantopassonanopportunitytoacceleratethetranslation
ofafileusingaprecompiledheader.Thisdecisioncanconsiderablyeasethe
managementoftheinclusionpolicy.Forexample,itisusuallyrelatively
straightforwardtocreateaheaderfilenamedstd.hppthatincludesallthe
standardheaders:2
#include<iostream>
#include<string>
#include<vector>
#include<deque>
#include<list>
…
Thisfilecanthenbeprecompiled,andeveryprogramfilethatmakesuseofthe
standardlibrarycanthensimplybestartedasfollows:#include"std.hpp"
…
Normallythiswouldtakeawhiletocompile,butgivenasystemwithsufficient
memory,thepre-compiledheaderschemeallowsittobeprocessedsignificantly
fasterthanalmostanysinglestandardheaderwouldrequirewithout
precompilation.Thestandardheadersareparticularlyconvenientinthisway
becausetheyrarelychange,andhencetheprecompiledheaderforourstd.hpp
filecanbebuiltonce.Otherwise,precompiledheadersaretypicallypartofthe
dependencyconfigurationofaproject(e.g.,theyareupdatedasneededbythe
popularmaketooloranintegrateddevelopmentenvironment’s(IDE)project
buildtool).
Oneattractiveapproachtomanageprecompiledheadersistocreatelayersof
precompiledheadersthatgofromthemostwidelyusedandstableheaders(e.g.,
ourstd.hppheader)toheadersthataren’texpectedtochangeallthetimeand
thereforearestillworthprecompiling.However,ifheadersareunderheavy
development,creatingprecompiledheadersforthemcantakemoretimethan
whatissavedbyreusingthem.Akeyconcepttothisapproachisthata
precompiledheaderforamorestablelayercanbereusedtoimprovethe
precompilationtimeofalessstableheader.Forexample,supposethatin
additiontoourstd.hppheader(whichwehaveprecompiled),wealsodefinea
core.hppheaderthatincludesadditionalfacilitiesthatarespecifictoour
projectbutnonethelessachieveacertainlevelofstability:#include"std.hpp"
#include"core_data.hpp
#include"core_algos.hpp"
…
Becausethisfilestartswith#include"std.hpp",thecompilercanload
theassociatedprecom-piledheaderandcontinuewiththenextlinewithout
recompilingallthestandardheaders.Whenthefileiscompletelyprocessed,a
newprecompiledheadercanbeproduced.Applicationscanthenuse#include
"core.hpp"toprovideaccessquicklytolargeamountsoffunctionality
becausethecompilercanloadthelatterprecompiledheader.
9.4DecodingtheErrorNovel
Ordinarycompilationerrorsarenormallyquitesuccinctandtothepoint.For
example,whenacompilersays“classXhasnomember’fun’,”it
usuallyisn’ttoohardtofigureoutwhatiswronginourcode(e.g.,wemight
havemistypedrunasfun).Notsowithtemplates.Let’slookatsome
examples.
SimpleTypeMismatch
ConsiderthefollowingrelativelysimpleexampleusingtheC++standard
library:Clickheretoviewcodeimage
basics/errornovel1.cpp
#include<string>
#include<map>
#include<algorithm>
intmain()
{
std::map<std::string,double>coll;
…
//findthefirstnonemptystringincoll:
autopos=std::find_if(coll.begin(),coll.end(),
[](std::stringconst&s){
returns!="";
});
}
Itcontainsafairlysmallmistake:Inthelambdausedtofindthefirstmatching
stringinthecollection,wecheckagainstagivenstring.However,theelements
inamaparekey/valuepairs,sothatweshouldexpecta
std::pair<std::stringconst,double>.
AversionofthepopularGNUC++compilerreportsthefollowingerror:Click
heretoviewcodeimage
1Infileincludedfrom/cygdrive/p/gcc/gcc61-
include/bits/stl_algobase.h:71:0,
2from/cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
3from/cygdrive/p/gcc/gcc61-include/string:40,
4fromerrornovel1.cpp:1:
5/cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:In
instantiationof'bool__gnu_cxx
::__ops::_Iter_pred<_Predicate>::operator()(_Iterator)[with
_Iterator=std::_Rb_tree_i
terator<std::pair<conststd::__cxx11::basic_string<char>,double>
>;_Predicate=main()
::<lambda(conststring&)>]':
6/cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:104:42:required
from'_InputIterator
std::__find_if(_InputIterator,_InputIterator,_Predicate,
std::input_iterator_tag)
[with_InputIterator=std::_Rb_tree_iterator<std::pair<const
std::__cxx11::basic_string
<char>,double>>;_Predicate=
__gnu_cxx::__ops::_Iter_pred<main()::<lambda(const
string&)>>]'
7/cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:161:23:required
from'_Iteratorstd::__
find_if(_Iterator,_Iterator,_Predicate)[with_Iterator=
std::_Rb_tree_iterator<std::
pair<conststd::__cxx11::basic_string<char>,double>>;_Predicate
=__gnu_cxx::__ops::_
Iter_pred<main()::<lambda(conststring&)>>]'
8/cygdrive/p/gcc/gcc61-include/bits/stl_algo.h:3824:28:required
from'_IIterstd::find
_if(_IIter,_IIter,_Predicate)[with_IIter=
std::_Rb_tree_iterator<std::pair<const
std::__cxx11::basic_string<char>,double>>;_Predicate=main()::
<lambda(conststring&)
>]'
9errornovel1.cpp:13:29:requiredfromhere
10/cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11:
error:nomatchforcallto
'(main()::<lambda(conststring&)>)(std::pair<const
std::__cxx11::basic_string<char>,
double>&)'
11{returnbool(_M_pred(*__it));}
12^~~~~~~~~~~~~~~~~~~~
13/cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11:
note:candidate:bool(*)(
conststring&){akabool(*)(const
std::__cxx11::basic_string<char>&)}<conversion>
14/cygdrive/p/gcc/gcc61-include/bits/predefined_ops.h:234:11:
note:candidateexpects2
arguments,2provided
15errornovel1.cpp:11:52:note:candidate:main()::<lambda(const
string&)>
16[](std::stringconst&s){
17^
18errornovel1.cpp:11:52:note:noknownconversionforargument1
from'std::pair<const
std::__cxx11::basic_string<char>,double>'to'conststring&{aka
conststd::__cxx11::
basic_string<char>&}'
Amessagelikethisstartslookingmorelikeanovelthanadiagnostic.Itcanalso
beoverwhelmingtothepointofdiscouragingnovicetemplateusers.However,
withsomepractice,messageslikethisbecomemanageable,andtheerrorsareat
leastrelativelyeasilylocated.
Thefirstpartofthiserrormessagesaysthatanerroroccurredinafunction
templateinstancedeepinsideaninternalpredefined_ops.hheader,
includedfromerrornovel1.cppviavariousotherheaders.Hereandinthe
followinglines,thecompilerreportswhatwasinstantiatedwithwhich
arguments.Inthiscase,itallstartedwiththestatementendingonline13of
errornovel1.cpp,whichis:Clickheretoviewcodeimage
autopos=std::find_if(coll.begin(),coll.end(),[](std::string
const&s){
returns!="";
});
Thiscausedtheinstantiationofafind_iftemplateonline115ofthe
stl_algo.hheader,wherethecodeClickheretoviewcodeimage
_IIterstd::find_if(_IIter,_IIter,_Predicate)
isinstantiatedwith
Clickheretoviewcodeimage
_IIter=std::_Rb_tree_iterator<std::pair<const
std::__cxx11::basic_string<char>,
double>>
_Predicate=main()::<lambda(conststring&)>
Thecompilerreportsallthisincasewesimplywerenotexpectingallthese
templatestobeinstantiated.Itallowsustodeterminethechainofeventsthat
causedtheinstantiations.
However,inourexample,we’rewillingtobelievethatallkindsoftemplates
neededtobeinstantiated,andwejustwonderwhyitdidn’twork.This
informationcomesinthelastpartofthemessage:Thepartthatsays“no
matchforcall”impliesthatafunctioncallcouldnotberesolvedbecause
thetypesoftheargumentsandtheparametertypesdidn’tmatch.Itlistswhatis
calledClickheretoviewcodeimage
(main()::<lambda(conststring&)>)(std::pair<const
std::__cxx11::basic_string<char>,
double>&)
andcodethatcausedthiscall:
Clickheretoviewcodeimage
{returnbool(_M_pred(*__it));}
Furthermore,justafterthis,thelinecontaining“note:candidate:”
explainsthattherewasasinglecandidatetypeexpectingaconststring&
andthatthiscandidateisdefinedinline11oferrornovel1.cppaslambda
[](std::stringconst&s)combinedwithareasonwhyapossible
candidatedidn’tfit:Clickheretoviewcodeimage
noknownconversionforargument1
from’std::pair<conststd::__cxx11::basic_string<char>,double>’
to’conststring&{akaconststd::__cxx11::basic_string<char>&}’
whichdescribestheproblemwehave.
Thereisnodoubtthattheerrormessagecouldbebetter.Theactualproblem
couldbeemittedbeforethehistoryoftheinstantiation,andinsteadofusingfully
expandedtemplateinstantiationnameslike
std::__cxx11::basic_string<char>,usingjuststd::string
mightbeenough.However,itisalsotruethatalltheinformationinthis
diagnosticcouldbeusefulinsomesituations.Itisthereforenotsurprisingthat
othercompilersprovidesimilarinformation(althoughsomeusethestructuring
techniquesmentioned).
Forexample,theVisualC++compileroutputssomethinglike:Clickhereto
viewcodeimage
1c:\tools_root\cl\inc\algorithm(166):errorC2664:'boolmain::
<lambda_b863c1c7cd07048816
f454330789acb4>::operator()(conststd::string&)const':cannot
convertargument1from
'std::pair<const_Kty,_Ty>'to'conststd::string&'
2with
3[
4_Kty=std::string,
5_Ty=double
6]
7c:\tools_root\cl\inc\algorithm(166):note:Reason:cannot
convertfrom'std::pair<const
_Kty,_Ty>'to'conststd::string'
8with
9[
10_Kty=std::string,
11_Ty=double
12]
13c:\tools_root\cl\inc\algorithm(166):note:Nouser-defined-
conversionoperatoravailable
thatcanperformthisconversion,ortheoperatorcannotbecalled
14c:\tools_root\cl\inc\algorithm(177):note:seereferenceto
functiontemplateinstantiat
ion'_InIt
std::_Find_if_unchecked<std::_Tree_unchecked_iterator<_Mytree>,_Pr>
(_InIt,_In
It,_Pr&)'beingcompiled
15with
16[
17
_InIt=std::_Tree_unchecked_iterator<std::_Tree_val<std::_Tree_simple_types
<std::pair<conststd::string,double>>>>,
18_Mytree=std::_Tree_val<std::_Tree_simple_types<std::pair<const
std::string,
double>>>,
19_Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
20]
21main.cpp(13):note:seereferencetofunctiontemplate
instantiation'_InItstd::find_if
<std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<const
_Kty,_Ty>>>>
,main::<lambda_b863c1c7cd07048816f454330789acb4>>
(_InIt,_InIt,_Pr)'beingcompiled
22with
23[
24
_InIt=std::_Tree_iterator<std::_Tree_val<std::_Tree_simple_types<std::pair<
conststd::string,double>>>>,
25_Kty=std::string,
26_Ty=double,
27_Pr=main::<lambda_b863c1c7cd07048816f454330789acb4>
28]
Here,again,weprovidethechainofinstantiationswiththeinformationtelling
uswhatwasinstantiatedbywhichargumentsandwhereinthecode,andwesee
twicethatweClickheretoviewcodeimage
cannotconvertfrom’std::pair<const_Kty,_Ty>’to’const
std::string’
with
[
_Kty=std::string,
_Ty=double
]
MissingconstonSomeCompilers
Unfortunately,itsometimeshappensthatgenericcodeisaproblemonlywith
somecompilers.Considerthefollowingexample:Clickheretoviewcodeimage
basics/errornovel2.cpp
#include<string>
#include<unordered_set>
classCustomer
{
private:
std::stringname;
public:
Customer(std::stringconst&n)
:name(n){
}
std::stringgetName()const{
returnname;
}
};
intmain()
{
//provideourownhashfunction:
structMyCustomerHash{
//NOTE:missingconstisonlyanerrorwithg++andclang:
std::size_toperator()(Customerconst&c){
returnstd::hash<std::string>()(c.getName());
}
};
//anduseitforahashtableofCustomers:
std::unordered_set<Customer,MyCustomerHash>coll;…
}
WithVisualStudio2013or2015,thiscodecompilesasexpected.However,with
g++orclang,thecodecausessignificanterrormessages.Ong++6.1,for
example,thefirsterrormessageisasfollows:Clickheretoviewcodeimage
1Infileincludedfrom/cygdrive/p/gcc/gcc61-
include/bits/hashtable.h:35:0,
2from/cygdrive/p/gcc/gcc61-include/unordered_set:47,
3fromerrornovel2.cpp:2:
4/cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h:In
instantiationof'structstd::
__detail::__is_noexcept_hash<Customer,main()::MyCustomerHash>':
5/cygdrive/p/gcc/gcc61-include/type_traits:143:12:requiredfrom
'structstd::__and_<
std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::__is_noexcept_hash<Customer,
main()::MyCustomerHash>>'
6/cygdrive/p/gcc/gcc61-include/type_traits:154:38:requiredfrom
'structstd::__not_<
std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::__is_noexcept_
hash<Customer,main()::MyCustomerHash>>>'
7/cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63:
requiredfrom'classstd::
unordered_set<Customer,main()::MyCustomerHash>'
8errornovel2.cpp:28:47:requiredfromhere
9/cygdrive/p/gcc/gcc61-include/bits/hashtable_policy.h:85:34:
error:nomatchforcallto
'(constmain()::MyCustomerHash)(constCustomer&)'
10noexcept(declval<const_Hash&>()(declval<const_Key&>()))>
11~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
12errornovel2.cpp:22:17:note:candidate:std::size_t
main()::MyCustomerHash::operator()(
constCustomer&)<nearmatch>
13std::size_toperator()(constCustomer&c){
14^~~~~~~~
15errornovel2.cpp:22:17:note:passing'const
main()::MyCustomerHash*'as'this'argument
discardsqualifiers
immediatelyfollowedbymorethan20othererrormessages:16Infileincluded
from/cygdrive/p/gcc/gcc61-include/bits/move.h:57:0,
18from/cygdrive/p/gcc/gcc61-include/bits/stl_pair.h:59,
19from/cygdrive/p/gcc/gcc61-include/bits/stl_algobase.h:64,
20from/cygdrive/p/gcc/gcc61-include/bits/char_traits.h:39,
21from/cygdrive/p/gcc/gcc61-include/string:40,
22fromerrornovel2.cpp:1:
23/cygdrive/p/gcc/gcc61-include/type_traits:Ininstantiationof'struct
std::__not_<std::
__and_<std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::__is_noexcept_hash<
Customer,main()::MyCustomerHash>>>':
24/cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63:requiredfrom
'classstd::
unordered_set<Customer,main()::MyCustomerHash>'
25errornovel2.cpp:28:47:requiredfromhere
26/cygdrive/p/gcc/gcc61-include/type_traits:154:38:error:'value'isnota
memberof'std
::__and_<std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::__is_noexcept_hash<
Customer,main()::MyCustomerHash>>'
27:publicintegral_constant<bool,!_Pp::value>
28^~~~
29Infileincludedfrom/cygdrive/p/gcc/gcc61-include/unordered_set:48:0,
30fromerrornovel2.cpp:2:
31/cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:Ininstantiationof'class
std::
unordered_set<Customer,main()::MyCustomerHash>':
32errornovel2.cpp:28:47:requiredfromhere
33/cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:95:63:error:'value'isnot
amember
of'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::
__is_noexcept_hash<Customer,main()::MyCustomerHash>>>'
34typedef__uset_hashtable<_Value,_Hash,_Pred,_Alloc>_Hashtable;
35^~~~~~~~~~
36/cygdrive/p/gcc/gcc61-include/bits/unordered_set.h:102:45:error:'value'is
notamember
of'std::__not_<std::__and_<std::__is_fast_hash<main()::MyCustomerHash>,
std::__detail::
__is_noexcept_hash<Customer,main()::MyCustomerHash>>>'
37typedeftypename_Hashtable::key_typekey_type;
38^~~~~~~~
…
Again,it’shardtoreadtheerrormessage(evenfindingthebeginningandendof
eachmessageisachore).Theessenceisthatdeepinheaderfile
hashtable_policy.hintheinstantiationofstd::unordered_set<>
requiredbyClickheretoviewcodeimage
std::unordered_set<Customer,MyCustomerHash>coll;thereisnomatch
forthecallto
Clickheretoviewcodeimage
constmain()::MyCustomerHash(constCustomer&)intheinstantiation
of
Clickheretoviewcodeimage
noexcept(declval<const_Hash&>()(declval<const_Key&>()))>
~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
(declval<const_Hash&>()isanexpressionoftype
main()::MyCustomerHash).Apossible“nearmatch”candidateisClick
heretoviewcodeimage
std::size_tmain()::MyCustomerHash::operator()(constCustomer&)
whichisdeclaredas
Clickheretoviewcodeimage
std::size_toperator()(constCustomer&c){
^~~~~~~~
andthelastnotesayssomethingabouttheproblem:Clickheretoviewcode
image
passing’constmain()::MyCustomerHash*’as’this’argumentdiscards
qualifiers
Canyouseewhattheproblemis?Thisimplementationofthe
std::unordered_setclasstemplaterequiresthatthefunctioncalloperator
forthehashobjectbeaconstmemberfunction(seealsoSection11.1.1on
page159).Whenthat’snotthecase,anerrorarisesdeepinthegutsofthe
algorithm.
Allothererrormessagescascadefromthefirstandgoawaywhenaconst
qualifierissimplyaddedtothehashfunctionoperator:Clickheretoviewcode
image
std::size_toperator()(constCustomer&c)const{
…
}
Clang3.9givestheslightlybetterhintattheendofthefirsterrormessagethat
operator()ofthehashfunctorisnotmarkedconst:Clickheretoview
codeimage
…
errornovel2.cpp:28:47:note:ininstantiationoftemplateclass
’std::unordered_set<Customer
,MyCustomerHash,std::equal_to<Customer>,std::allocator<Customer>
>’requestedhere
std::unordered_set<Customer,MyCustomerHash>coll;
^
errornovel2.cpp:22:17:note:candidatefunctionnotviable:’this’
argumenthastype’const
MyCustomerHash’,butmethodisnotmarkedconst
std::size_toperator()(constCustomer&c){
^
Notethatclangherementionsdefaulttemplateparameterssuchas
std::allocator<Customer>,whilegccskipsthem.
Asyoucansee,itisoftenhelpfultohavemorethanonecompileravailableto
testyourcode.Notonlydoesithelpyouwritemoreportablecode,butwhere
onecompilerproducesaparticularlyinscrutableerrormessage,anothermight
providemoreinsight.
9.5Afternotes
TheorganizationofsourcecodeinheaderfilesandCPPfilesisapractical
consequenceofvariousincarnationsoftheone-definitionruleorODR.An
extensivediscussionofthisruleispresentedinAppendixA.
Theinclusionmodelisapragmaticanswerdictatedlargelybyexisting
practiceinC++compilerimplementations.However,thefirstC++
implementationwasdifferent:Theinclusionoftemplatedefinitionswasimplicit,
whichcreatedacertainillusionofseparation(seeChapter14fordetailsonthis
originalmodel).
ThefirstC++standard([C++98])providedexplicitsupportfortheseparation
modeloftemplatecompilationviaexportedtemplates.Theseparationmodel
allowedtemplatedeclarationsmarkedasexporttobedeclaredinheaders,
whiletheircorrespondingdefinitionswereplacedinCPPfiles,muchlike
declarationsanddefinitionsfornontemplatecode.Unliketheinclusionmodel,
thismodelwasatheoreticalmodelnotbasedonanyexistingimplementation,
andtheimplementationitselfprovedfarmorecomplicatedthantheC++
standardizationcommitteehadanticipated.Ittookmorethanfiveyearstoseeits
firstimplementationpublished(May2002),andnootherimplementations
appearedintheyearssince.TobetteraligntheC++standardwithexisting
practice,theC++standardizationcommitteeremovedexportedtemplatesfrom
C++11.Readersinterestedinlearningmoreaboutthedetails(andpitfalls)ofthe
separationmodelareencouragedtoreadSections6.3and10.3ofthefirstedition
ofthisbook([VandevoordeJosuttisTemplates1st]).
Itissometimestemptingtoimaginewaysofextendingtheconceptof
precompiledheaderssothatmorethanoneheadercouldbeloadedforasingle
compilation.Thiswouldinprincipleallowforafinergrainedapproachto
precompilation.Theobstaclehereismainlythepreprocessor:Macrosinone
headerfilecanentirelychangethemeaningofsubsequentheaderfiles.However,
onceafilehasbeenprecompiled,macroprocessingiscompleted,anditishardly
practicaltoattempttopatchaprecompiledheaderforthepreprocessoreffects
inducedbyotherheaders.Anewlanguagefeatureknownasmodules(see
Section17.11onpage366)isexpectedtobeaddedtoC++inthenottoodistant
futuretoaddressthisissue(macrodefinitionscannotleakintomodule
interfaces).
9.6Summary
•Theinclusionmodeloftemplatesisthemostwidelyusedmodelfororganizing
templatecode.AlternativesarediscussedinChapter14.
•Onlyfullspecializationsoffunctiontemplatesneedinlinewhendefinedin
headerfilesoutsideclassesorstructures.
•Totakeadvantageofprecompiledheaders,besuretokeepthesameorderfor
#includedirectives.
•Debuggingcodewithtemplatescanbechallenging.
1Withsomeimplementationsthisstringismangled(encodedwithtypesof
argumentsandnamesofsurroundingscopestodistinguishitfromother
names),butademanglerisavailabletoturnitintohuman-readabletext.
2Intheory,thestandardheadersdonotactuallyneedtocorrespondtophysical
files.Inpractice,however,theydo,andthefilesareverylarge.
Chapter10
BasicTemplateTerminology
SofarwehaveintroducedthebasicconceptoftemplatesinC++.Beforewego
intodetails,let’slookattheterminologyweuse.Thisisnecessarybecause,
insidetheC++community(andeveninanearlyversionofthestandard),thereis
sometimesalackofprecisionregardingterminology.
10.1“ClassTemplate”or“TemplateClass”?
InC++,structs,classes,andunionsarecollectivelycalledclasstypes.Without
additionalqualification,theword“class”inplaintexttypeismeanttoinclude
classtypesintroducedwitheitherthekeywordclassorthekeyword
struct.1Notespecificallythat“classtype”includesunions,but“class”does
not.
Thereissomeconfusionabouthowaclassthatisatemplateiscalled:•The
termclasstemplatestatesthattheclassisatemplate.Thatis,itisa
parameterizeddescriptionofafamilyofclasses.
•Thetermtemplateclass,ontheotherhand,hasbeenused–asasynonymfor
classtemplate.
–torefertoclassesgeneratedfromtemplates.
–torefertoclasseswithanamethatisatemplate-id(thecombinationofa
templatenamefollowedbythetemplateargumentsspecifiedbetween<and
>).
Thedifferencebetweenthesecondandthirdmeaningsissomewhatsubtle
andunimportantfortheremainderofthetext.
Becauseofthisimprecision,weavoidthetermtemplateclassinthisbook.
Similarly,weusefunctiontemplate,membertemplate,memberfunction
template,andvariabletemplatebutavoidtemplatefunction,templatemember,
templatememberfunction,andtemplatevariable.
10.2Substitution,Instantiation,andSpecialization
Whenprocessingsourcecodethatusestemplates,aC++compilermustat
varioustimessubstituteconcretetemplateargumentsforthetemplateparameters
inthetemplate.Sometimes,thissubstitutionisjusttentative:Thecompilermay
needtocheckifthesubstitutioncouldbevalid(seeSection8.4onpage129and
Section15.7onpage284).
Theprocessofactuallycreatingadefinitionforaregularclass,typealias,
function,memberfunction,orvariablefromatemplatebysubstitutingconcrete
argumentsforthetemplateparametersiscalledtemplateinstantiation.
Surprisingly,thereiscurrentlynostandardorgenerallyagreedupontermto
denotetheprocessofcreatingadeclarationthatisnotadefinitionthrough
templateparametersubstitution.Wehaveseenthephrasespartialinstantiation
orinstantiationofadeclarationusedbysometeams,butthosearebynomeans
universal.Perhapsamoreintuitivetermisincompleteinstantiation(which,in
thecaseofaclasstemplate,producesanincompleteclass).
Theentityresultingfromaninstantiationoranincompleteinstantiation(i.e.,a
class,function,memberfunction,orvariable)isgenericallycalleda
specialization.
However,inC++theinstantiationprocessisnottheonlywaytoproducea
specialization.Alternativemechanismsallowtheprogrammertospecify
explicitlyadeclarationthatistiedtoaspecialsubstitutionoftemplate
parameters.AsweshowedinSection2.5onpage31,suchaspecializationis
introducedwiththeprefixtemplate<>:Clickheretoviewcodeimage
template<typenameT1,typenameT2>//primaryclasstemplate
classMyClass{
…
};
template<>//explicitspecialization
classMyClass<std::string,float>{
…
};
Strictlyspeaking,thisiscalledanexplicitspecialization(asopposedtoan
instantiatedorgeneratedspecialization).
AsdescribedinSection2.6onpage33,specializationsthatstillhavetemplate
parametersarecalledpartialspecializations:Clickheretoviewcodeimage
template<typenameT>//partialspecialization
classMyClass<T,T>{
…
};
template<typenameT>//partialspecialization
classMyClass<bool,T>{
…
};
Whentalkingabout(explicitorpartial)specializations,thegeneraltemplateis
alsocalledtheprimarytemplate.
10.3DeclarationsversusDefinitions
Sofar,thewordsdeclarationanddefinitionhavebeenusedonlyafewtimesin
thisbook.However,thesewordscarrywiththemaratherprecisemeaningin
standardC++,andthatisthemeaningthatweuse.
AdeclarationisaC++constructthatintroducesorreintroducesanameintoa
C++scope.Thisintroductionalwaysincludesapartialclassificationofthat
name,butthedetailsarenotrequiredtomakeavaliddeclaration.Forexample:
Clickheretoviewcodeimage
classC;//adeclarationofCasaclass
voidf(intp);//adeclarationoff()asafunctionandpasanamed
parameter
externintv;//adeclarationofvasavariable
Notethateventhoughtheyhavea“name,”macrodefinitionsandgotolabels
arenotconsidereddeclarationsinC++.
Declarationsbecomedefinitionswhenthedetailsoftheirstructurearemade
knownor,inthecaseofvariables,whenstoragespacemustbeallocated.For
classtypedefinitions,thismeansabrace-enclosedbodymustbeprovided.For
functiondefinitions,thismeansabrace-enclosedbodymustbeprovided(inthe
commoncase),orthefunctionmustbedesignatedas=default2or=
delete.Foravariable,initializationortheabsenceofanexternspecifier
causesadeclarationtobecomeadefinition.Hereareexamplesthatcomplement
theprecedingnondefinitiondeclarations:Clickheretoviewcodeimage
classC{};//definition(anddeclaration)ofclassC
voidf(intp){//definition(anddeclaration)offunctionf()
std::cout<<p<<’\n’;
}
externintv=1;//aninitializermakesthisadefinitionforv
intw;//globalvariabledeclarationsnotprecededby
//externarealsodefinitions
Byextension,thedeclarationofaclasstemplateorfunctiontemplateiscalleda
definitionifithasabody.Hence,Clickheretoviewcodeimage
template<typenameT>
voidfunc(T);isadeclarationthatisnotadefinition,whereas
Clickheretoviewcodeimage
template<typenameT>
classS{};isinfactadefinition.
10.3.1CompleteversusIncompleteTypes
Typescanbecompleteorincomplete,whichisanotioncloselyrelatedtothe
distinctionbetweenadeclarationandadefinition.Somelanguageconstructs
requirecompletetypes,whereasothersarevalidwithincompletetypestoo.
Incompletetypesareoneofthefollowing:•Aclasstypethathasbeen
declaredbutnotyetdefined.
•Anarraytypewithanunspecifiedbound.
•Anarraytypewithanincompleteelementtype.
•void
•Anenumerationtypeaslongastheunderlyingtypeortheenumerationvalues
arenotdefined.
•Anytypeabovetowhichconstand/orvolatileareapplied.Allother
typesarecomplete.Forexample:Clickheretoviewcodeimage
classC;//Cisanincompletetype
Cconst*cp;//cpisapointertoanincompletetype
externCelems[10];//elemshasanincompletetype
externintarr[];//arrhasanincompletetype
…
classC{};//Cnowisacompletetype(andthereforecpandelems
//nolongerrefertoanincompletetype)
intarr[10];//arrnowhasacompletetype
SeeSection11.5onpage171forhintsabouthowtodealwithincompletetypes
intemplates.
10.4TheOne-DefinitionRule
TheC++languagedefinitionplacessomeconstraintsontheredeclarationof
variousentities.Thetotalityoftheseconstraintsisknownastheone-definition
ruleorODR.Thedetailsofthisrulearealittlecomplexandspanalargevariety
ofsituations.Laterchaptersillustratethevariousresultingfacetsineach
applicablecontext,andyoucanfindacompletedescriptionoftheODRin
AppendixA.Fornow,itsufficestorememberthefollowingODRbasics:•
Ordinary(i.e.,nottemplates)noninlinefunctionsandmemberfunctions,aswell
as(noninline)globalvariablesandstaticdatamembersshouldbedefinedonly
onceacrossthewholeprogram.3
•Classtypes(includingstructsandunions),templates(includingpartial
specializationsbutnotfullspecializations),andinlinefunctionsandvariables
shouldbedefinedatmostoncepertranslationunit,andallthesedefinitions
shouldbeidentical.
Atranslationunitiswhatresultsfrompreprocessingasourcefile;thatis,it
includesthecontentsnamedby#includedirectivesandproducedbymacro
expansions.
Intheremainderofthisbook,linkableentityreferstoanyofthefollowing:a
functionormemberfunction,aglobalvariableorastaticdatamember,including
anysuchthingsgeneratedfromatemplate,asvisibletothelinker.
10.5TemplateArgumentsversusTemplate
Parameters
Comparethefollowingclasstemplate:Clickheretoviewcodeimage
template<typenameT,intN>
classArrayInClass{
public:
Tarray[N];
};
withasimilarplainclass:Clickheretoviewcodeimage
classDoubleArrayInClass{
public:
doublearray[10];
};
Thelatterbecomesessentiallyequivalenttotheformerifwereplacethe
parametersTandNbydoubleand10respectively.InC++,thenameofthis
replacementisdenotedasArrayInClass<double,10>
Notehowthenameofthetemplateisfollowedbytemplateargumentsinangle
brackets.
Regardlessofwhethertheseargumentsarethemselvesdependentontemplate
parameters,thecombinationofthetemplatename,followedbytheargumentsin
anglebrackets,iscalledatemplate-id.
Thisnamecanbeusedmuchlikeacorrespondingnontemplateentitywould
beused.Forexample:Clickheretoviewcodeimage
intmain()
{
ArrayInClass<double,10>
ad;ad.array[0]=1.0;
}
Itisessentialtodistinguishbetweentemplateparametersandtemplate
arguments.Inshort,youcansaythat“parametersareinitializedbyarguments.”4
Ormoreprecisely:•Templateparametersarethosenamesthatarelistedafter
thekeywordtemplateinthetemplatedeclarationordefinition(TandNinour
example).
•Templateargumentsaretheitemsthataresubstitutedfortemplateparameters
(doubleand10inourexample).Unliketemplateparameters,template
argumentscanbemorethanjust“names.”
Thesubstitutionoftemplateparametersbytemplateargumentsisexplicitwhen
indicatedwithatemplate-id,buttherearevarioussituationswhenthe
substitutionisimplicit(e.g.,iftemplateparametersaresubstitutedbytheir
defaultarguments).
Afundamentalprincipleisthatanytemplateargumentmustbeaquantityor
valuethatcanbedeterminedatcompiletime.Asbecomesclearlater,this
requirementtranslatesintodramaticbenefitsfortherun-timecostsoftemplate
entities.Becausetemplateparametersareeventuallysubstitutedbycompile-time
values,theycanthemselvesbeusedtoformcompile-timeexpressions.Thiswas
exploitedintheArrayInClasstemplatetosizethememberarrayarray.
Thesizeofanarraymustbeaconstant-expression,andthetemplateparameterN
qualifiesassuch.
Wecanpushthisreasoningalittlefurther:Becausetemplateparametersare
compile-timeentities,theycanalsobeusedtocreatevalidtemplatearguments.
Hereisanexample:Clickheretoviewcodeimage
template<typenameT>
classDozen{
public:
ArrayInClass<T,12>contents;
};
NotehowinthisexamplethenameTisbothatemplateparameteranda
templateargument.Thus,amechanismisavailabletoenabletheconstructionof
morecomplextemplatesfromsimplerones.Ofcourse,thisisnotfundamentally
differentfromthemechanismsthatallowustoassembletypesandfunctions.
10.6Summary
•Useclasstemplate,functiontemplate,andvariabletemplateforclasses,
functions,andvariables,respectively,thataretemplates.
•Templateinstantiationistheprocessofcreatingregularclassesorfunctionsby
replacingtemplateparameterswithconcretearguments.Theresultingentityis
aspecialization.
•Typescanbecompleteorincomplete.
•Accordingtotheone-definitionrule(ODR),noninlinefunctions,member
functions,globalvariables,andstaticdatamembersshouldbedefinedonly
onceacrossthewholeprogram.
1InC++,theonlydifferencebetweenclassandstructisthatthedefault
accessforclassisprivate,whereasthedefaultaccessforstructis
public.However,weprefertouseclassfortypesthatusenewC++
features,andweusestructforordinaryCdatastructurethatcanbeusedas
“plainolddata”(POD).
2Defaultedfunctionsarespecialmemberfunctionsthatwillbegivenadefault
implementationbythecompiler,suchasthedefaultcopyconstructor.
3Globalandstaticvariablesanddatamemberscanbedefinedasinlinesince
C++17.Thisremovestherequirementthattheybedefinedinexactlyone
translationunit.
4Intheacademicworld,argumentsaresometimescalledactualparameters,
whereasparametersarecalledformalparameters.
Chapter11
GenericLibraries
Sofar,ourdiscussionoftemplateshasfocusedontheirspecificfeatures,
capabilities,andconstraints,withimmediatetasksandapplicationsinmind(the
kindofthingswerunintoasapplicationprogrammers).However,templatesare
mosteffectivewhenusedtowritegenericlibrariesandframeworks,whereour
designshavetoconsiderpotentialusesthatareaprioribroadlyunconstrained.
Whilejustaboutallthecontentinthisbookcanbeapplicabletosuchdesigns,
herearesomegeneralissuesyoushouldconsiderwhenwritingportable
componentsthatyouintendtobeusableforas-yetunimaginedtypes.
Thelistofissuesraisedhereisnotcompleteinanysense,butitsummarizes
someofthefeaturesintroducedsofar,introducessomeadditionalfeatures,and
referstosomefeaturescoveredlaterinthisbook.Wehopeitwillalsobeagreat
motivatortoreadthroughthemanychaptersthatfollow.
11.1Callables
Manylibrariesincludeinterfacestowhichclientcodepassesanentitythatmust
be“called.”Examplesincludeanoperationthatmustbescheduledonanother
thread,afunctionthatdescribeshowtohashvaluestostoretheminahashtable,
anobjectthatdescribesanorderinwhichtosortelementsinacollection,anda
genericwrapperthatprovidessomedefaultargumentvalues.Thestandard
libraryisnoexceptionhere:Itdefinesmanycomponentsthattakesuchcallable
entities.
Onetermusedinthiscontextiscallback.Traditionallythattermhasbeen
reservedforentitiesthatarepassedasfunctioncallarguments(asopposedto,
e.g.,templatearguments),andwemaintainthistradition.Forexample,asort
functionmayincludeacallbackparameteras“sortingcriterion,”whichiscalled
todeterminewhetheroneelementprecedesanotherinthedesiredsortedorder.
InC++,thereareseveraltypesthatworkwellforcallbacksbecausetheycan
bothbepassedasfunctioncallargumentsandcanbedirectlycalledwiththe
syntaxf(…):•Pointer-to-functiontypes
•Classtypeswithanoverloadedoperator()(sometimescalledfunctors),
includinglambdas•Classtypeswithaconversionfunctionyieldingapointer-
to-functionorreference-to-functionCollectively,thesetypesarecalled
functionobjecttypes,andavalueofsuchatypeisafunctionobject.157
TheC++standardlibraryintroducestheslightlybroadernotionofacallable
type,whichiseitherafunctionobjecttypeorapointertomember.Anobjectof
callabletypeisacallableobject,whichwerefertoasacallablefor
convenience.
Genericcodeoftenbenefitsfrombeingabletoacceptanykindofcallable,
andtemplatesmakeitpossibletodoso.
11.1.1SupportingFunctionObjects
Let’slookhowthefor_each()algorithmofthestandardlibraryis
implemented(usingourownname“foreach”toavoidnameconflictsandfor
simplicityskippingreturninganything):Clickheretoviewcodeimage
basics/foreach.hpp
template<typenameIter,typenameCallable>
voidforeach(Itercurrent,Iterend,Callableop)
{
while(current!=end){//aslongasnotreachedtheend
op(*current);//callpassedoperatorforcurrentelement
++current;//andmoveiteratortonextelement
}
}
Thefollowingprogramdemonstratestheuseofthistemplatewithvarious
functionobjects:Clickheretoviewcodeimage
basics/foreach.cpp
#include<iostream>
#include<vector>
#include"foreach.hpp"
//afunctiontocall:
voidfunc(inti)
{std::cout<<"func()calledfor:"<<i<<’\n’;
}
//afunctionobjecttype(forobjectsthatcanbeusedasfunctions):
classFuncObj{
public:
voidoperator()(inti)const{//Note:constmemberfunction
std::cout<<"FuncObj::op()calledfor:"<<i<<’\n’;
}
};
intmain()
{
std::vector<int>primes={2,3,5,7,11,13,17,19};
foreach(primes.begin(),primes.end(),//range
func);//functionascallable(decaystopointer)
foreach(primes.begin(),primes.end(),//range
&func);//functionpointerascallable
foreach(primes.begin(),primes.end(),//range
FuncObj());//functionobjectascallable
foreach(primes.begin(),primes.end(),//range
[](inti){//lambdaascallable
std::cout<<"lambdacalledfor:"<<i<<’\n’;
});
}
Let’slookateachcaseindetail:•Whenwepassthenameofafunctionasa
functionargument,wedon’treallypassthefunctionitselfbutapointeror
referencetoit.Aswitharrays(seeSection7.4onpage115),functionarguments
decaytoapointerwhenpassedbyvalue,andinthecaseofaparameterwhose
typeisatemplateparameter,apointer-to-functiontypewillbededuced.
Justlikearrays,functionscanbepassedbyreferencewithoutdecay.
However,functiontypescannotreallybequalifiedwithconst.Ifwewereto
declarethelastparameterofforeach()withtypeCallableconst&,
theconstwouldjustbeignored.(Generallyspeaking,referencestofunctions
arerarelyusedinmainstreamC++code.)•Oursecondcallexplicitlytakesa
functionpointerbypassingtheaddressofafunctionname.Thisisequivalent
tothefirstcall(wherethefunctionnameimplicitlydecayedtoapointervalue)
butisperhapsalittleclearer.
•Whenpassingafunctor,wepassaclasstypeobjectasacallable.Calling
throughaclasstypeusuallyamountstoinvokingitsoperator().Sothecall
op(*current);
isusuallytransformedinto
Clickheretoviewcodeimage
op.operator()(*current);//calloperator()withparameter
*currentforop
Notethatwhendefiningoperator(),youshouldusuallydefineitasa
constantmemberfunction.Otherwise,subtleerrormessagescanoccurwhen
frameworksorlibrariesexpectthiscallnottochangethestateofthepassed
object(seeSection9.4onpage146fordetails).
Itisalsopossibleforaclasstypeobjecttobeimplicitlyconvertibletoa
pointerorreferencetoasurrogatecallfunction(discussedinSectionC.3.5on
page694).Insuchacase,thecallop(*current);
wouldbetransformedinto
(op.operatorF())(*current);
whereFisthetypeofthepointer-to-functionorreference-to-functionthatthe
classtypeobjectcanbeconvertedto.Thisisrelativelyunusual.
•Lambdaexpressionsproducefunctors(calledclosures),andthereforethiscase
isnotdifferentfromthefunctorcase.Lambdasare,however,avery
convenientshorthandnotationtointroducefunctors,andsotheyappear
commonlyinC++codesinceC++11.
Interestingly,lambdasthatstartwith[](nocaptures)produceaconversion
operatortoafunctionpointer.However,thatisneverselectedasasurrogate
callfunctionbecauseitisalwaysaworsematchthanthenormal
operator()oftheclosure.
11.1.2DealingwithMemberFunctionsandAdditional
Arguments
Onepossibleentitytocallwasnotusedinthepreviousexample:member
functions.That’sbecausecallinganonstaticmemberfunctionnormallyinvolves
specifyinganobjecttowhichthecallisappliedusingsyntaxlike
object.memfunc(…)orptr->memfunc(…)andthatdoesn’tmatch
theusualpatternfunction-object(…).
Fortunately,sinceC++17,theC++standardlibraryprovidesautility
std::invoke()thatconvenientlyunifiesthiscasewiththeordinary
function-callsyntaxcases,therebyenablingcallstoanycallableobjectwitha
singleform.Thefollowingimplementationofourforeach()templateuses
std::invoke():Clickheretoviewcodeimage
basics/foreachinvoke.hpp
#include<utility>
#include<functional>
template<typenameIter,typenameCallable,typename…Args>
voidforeach(Itercurrent,Iterend,Callableop,Argsconst&…args)
{
while(current!=end){//aslongasnotreachedtheendofthe
elements
std::invoke(op,//callpassedcallablewith
args…,//anyadditionalargs
*current);//andthecurrentelement
++current;
}
}
Here,besidesthecallableparameter,wealsoacceptanarbitrarynumberof
additionalparameters.Theforeach()templatethencallsstd::invoke()
withthegivencallablefollowedbytheadditionalgivenparametersalongwith
thereferencedelement.std::invoke()handlesthisasfollows:•Ifthe
callableisapointertomember,itusesthefirstadditionalargumentasthethis
object.Allremainingadditionalparametersarejustpassedasargumentstothe
callable.
•Otherwise,alladditionalparametersarejustpassedasargumentstothe
callable.
Notethatwecan’tuseperfectforwardinghereforthecallableoradditional
parameters:Thefirstcallmight“steal”theirvalues,leadingtounexpected
behaviorcallingopinsubsequentiterations.
Withthisimplementation,wecanstillcompileouroriginalcallsto
foreach()above.Now,inaddition,wecanalsopassadditionalargumentsto
thecallableandthecallablecanbeamemberfunction.1Thefollowingclient
codeillustratesthis:Clickheretoviewcodeimage
basics/foreachinvoke.cpp
#include<iostream>
#include<vector>
#include<string>
#include"foreachinvoke.hpp"
//aclasswithamemberfunctionthatshallbecalled
classMyClass{
public:
voidmemfunc(inti)const{
std::cout<<"MyClass::memfunc()calledfor:"<<i<<’\n’;
}
};
intmain()
{
std::vector<int>primes={2,3,5,7,11,13,17,19};
//passlambdaascallableandanadditionalargument:
foreach(primes.begin(),primes.end(),//elementsfor2ndargoflambda
[](std::stringconst&prefix,inti){//lambdatocall
std::cout<<prefix<<i<<’\n’;
},
"-value:");//1stargoflambda
//callobj.memfunc()for/witheachelementsinprimespassedas
argument
MyClassobj;
foreach(primes.begin(),primes.end(),//elementsusedasargs
&MyClass::memfunc,//memberfunctiontocall
obj);//objecttocallmemfunc()for
}
Thefirstcallofforeach()passesitsfourthargument(thestringliteral"-
value:")tothefirstparameterofthelambda,whilethecurrentelementin
thevectorbindstothesecondparameterofthelambda.Thesecondcallpasses
thememberfunctionmemfunc()asthethirdargumenttobecalledforobj
passedasthefourthargument.
SeeSectionD.3.1onpage716fortypetraitsthatyieldwhetheracallablecan
beusedbystd::invoke().
11.1.3WrappingFunctionCalls
Acommonapplicationofstd::invoke()istowrapsinglefunctioncalls
(e.g.,tologthecalls,measuretheirduration,orpreparesomecontextsuchas
startinganewthreadforthem).Now,wecansupportmovesemanticsbyperfect
forwardingboththecallableandallpassedarguments:Clickheretoviewcode
image
basics/invoke.hpp
#include<utility>//forstd::invoke()
#include<functional>//forstd::forward()
template<typenameCallable,typename…Args>
decltype(auto)call(Callable&&op,Args&&…args)
{
returnstd::invoke(std::forward<Callable>(op),//passedcallablewith
std::forward<Args>(args)…);//anyadditionalargs
}
Theotherinterestingaspectishowtodealwiththereturnvalueofacalled
functionto“perfectlyforward”itbacktothecaller.Tosupportreturning
references(suchasastd::ostream&)youhavetousedecltype(auto)
insteadofjustauto:Clickheretoviewcodeimage
template<typenameCallable,typename…Args>
decltype(auto)call(Callable&&op,Args&&…args)decltype(auto)
(availablesinceC++14)isaplaceholdertypethatdeterminesthe
typeofvariable,returntype,ortemplateargumentfromthetypeof
theassociatedexpression(initializer,returnvalue,ortemplate
argument).SeeSection15.10.3onpage301fordetails.
Ifyouwanttotemporarilystorethevaluereturnedbystd::invoke()ina
variabletoreturnitafterdoingsomethingelse(e.g.,todealwiththereturnvalue
orlogtheendofthecall),youalsohavetodeclarethetemporaryvariablewith
decltype(auto):Clickheretoviewcodeimage
decltype(auto)ret{std::invoke(std::forward<Callable>(op),
std::forward<Args>(args)…)};
…
returnret;Notethatdeclaringretwithauto&&isnotcorrect.Asa
reference,auto&&extendsthelifetimeofthereturnedvalueuntil
theendofitsscope(seeSection11.3onpage167)butnotbeyond
thereturnstatementtothecallerofthefunction.
However,thereisalsoaproblemwithusingdecltype(auto):Ifthe
callablehasreturntypevoid,theinitializationofretasdecltype(auto)
isnotallowed,becausevoidisanincompletetype.Youhavethefollowing
options:•Declareanobjectinthelinebeforethatstatement,whosedestructor
performstheobservablebehaviorthatyouwanttorealize.Forexample:2
Clickheretoviewcodeimage
structcleanup{
~cleanup(){
…//codetoperformonreturn
}
}dummy;
returnstd::invoke(std::forward<Callable>(op),
std::forward<Args>(args)…);
•Implementthevoidandnon-voidcasesdifferently:Clickheretoviewcode
image
basics/invokeret.hpp
#include<utility>//forstd::invoke()
#include<functional>//forstd::forward()
#include<type_traits>//forstd::is_same<>andinvoke_result<>
template<typenameCallable,typename…Args>
decltype(auto)call(Callable&&op,Args&&…args)
{
ifconstexpr(std::is_same_v<std::invoke_result_t<Callable,Args…>,
void>){
//returntypeisvoid:
std::invoke(std::forward<Callable>(op),
std::forward<Args>(args)…);
…
return;
}
else{
//returntypeisnotvoid:
decltype(auto)ret{std::invoke(std::forward<Callable>(op),
std::forward<Args>(args)…)};
…
returnret;
}
}
With
Clickheretoviewcodeimage
ifconstexpr(std::is_same_v<std::invoke_result_t<Callable,Args…>,
void>)wetestatcompiletimewhetherthereturntypeofcalling
callablewithArgs…isvoid.SeeSectionD.3.1onpage717for
detailsaboutstd::invoke_result<>.3
FutureC++versionsmighthopefullyavoidtheneedforsuchasspecialhandling
ofvoid(seeSection17.7onpage361).
11.2OtherUtilitiestoImplementGenericLibraries
std::invoke()isjustoneexampleofusefulutilitiesprovidedbytheC++
standardlibraryforimplementinggenericlibraries.Inwhatfollows,wesurvey
someotherimportantones.
11.2.1TypeTraits
Thestandardlibraryprovidesavarietyofutilitiescalledtypetraitsthatallowus
toevaluateandmodifytypes.Thissupportsvariouscaseswheregenericcode
hastoadapttoorreactonthecapabilitiesofthetypesforwhichtheyare
instantiated.Forexample:Clickheretoviewcodeimage
#include<type_traits>
template<typenameT>
classC
{
//ensurethatTisnotvoid(ignoringconstorvolatile):
static_assert(!std::is_same_v<std::remove_cv_t<T>,void>,
"invalidinstantiationofclassCforvoidtype");
public:
template<typenameV>
voidf(V&&v){
ifconstexpr(std::is_reference_v<T>){
…//specialcodeifTisareferencetype
}
ifconstexpr(std::is_convertible_v<std::decay_t<V>,T>){
…//specialcodeifVisconvertibletoT
}
ifconstexpr(std::has_virtual_destructor_v<V>){
…//specialcodeifVhasvirtualdestructor
}
}
};
Asthisexampledemonstrates,bycheckingcertainconditionswecanchoose
betweendifferentimplementationsofthetemplate.Here,weusethecompile-
timeiffeature,whichisavailablesinceC++17(seeSection8.5onpage134),
butwecouldhaveusedstd::enable_if,partialspecialization,orSFINAE
toenableordisablehelpertemplatesinstead(seeChapter8fordetails).
However,notethattypetraitsmustbeusedwithparticularcare:Theymight
behavedifferentlythanthe(naive)programmermightexpect.Forexample:
Clickheretoviewcodeimage
std::remove_const_t<intconst&>//yieldsintconst&
Here,becauseareferenceisnotconst(althoughyoucan’tmodifyit),thecall
hasnoeffectandyieldsthepassedtype.
Asaconsequence,theorderofremovingreferencesandconstmatters:
Clickheretoviewcodeimage
std::remove_const_t<std::remove_reference_t<intconst&>>//int
std::remove_reference_t<std::remove_const_t<intconst&>>//intconst
Instead,youmightcalljustClickheretoviewcodeimage
std::decay_t<intconst&>//yieldsint
which,however,wouldalsoconvertrawarraysandfunctionstothe
correspondingpointertypes.
Alsotherearecaseswheretypetraitshaverequirements.Notsatisfyingthose
requirementsresultsinundefinedbehavior.4Forexample:Clickheretoview
codeimage
make_unsigned_t<int>//unsignedint
make_unsigned_t<intconst&>//undefinedbehavior(hopefullyerror)
Sometimestheresultmaybesurprising.Forexample:Clickheretoviewcode
image
add_rvalue_reference_t<int>//int&&
add_rvalue_reference_t<intconst>//intconst&&
add_rvalue_reference_t<intconst&>//intconst&(lvalue-refremains
lvalue-ref)
Herewemightexpectthatadd_rvalue_referencealwaysresultsinan
rvaluereference,butthereference-collapsingrulesofC++(seeSection15.6.1
onpage277)causethecombinationofanlvaluereferenceandrvaluereference
toproduceanlvaluereference.
Asanotherexample:
Clickheretoviewcodeimage
is_copy_assignable_v<int>//yieldstrue(generally,youcanassign
aninttoanint)
is_assignable_v<int,int>//yieldsfalse(can’tcall42=42)
Whileis_copy_assignablejustchecksingeneralwhetheryoucanassign
intstoanother(checkingtheoperationforlvalues),is_assignabletakes
thevaluecategory(seeAppendixB)intoaccount(herecheckingwhetheryou
canassignaprvaluetoaprvalue).Thatis,thefirstexpressionisequivalentto
Clickheretoviewcodeimage
is_assignable_v<int&,int&>//yieldstrue
Forthesamereason:
Clickheretoviewcodeimage
is_swappable_v<int>//yieldstrue(assuminglvalues)
is_swappable_v<int&,int&>//yieldstrue(equivalenttotheprevious
check)
is_swappable_with_v<int,int>//yieldsfalse(takingvaluecategory
intoaccount)
Forallthesereasons,carefullynotetheexactdefinitionoftypetraits.We
describethestandardonesindetailinAppendixD.
11.2.2std::addressof()
Thestd::addressof<>()functiontemplateyieldstheactualaddressofan
objectorfunction.Itworkseveniftheobjecttypehasanoverloadedoperator&.
Eventhoughthelatterissomewhatrare,itmighthappen(e.g.,insmartpointers).
Thus,itisrecommendedtouseaddressof()ifyouneedanaddressofan
objectofarbitrarytype:Clickheretoviewcodeimage
template<typenameT>
voidf(T&&x)
{
autop=&x;//mightfailwithoverloadedoperator&
autoq=std::addressof(x);//worksevenwithoverloadedoperator&
…
}
11.2.3std::declval()
Thestd::declval<>()functiontemplatecanbeusedasaplaceholderfor
anobjectreferenceofaspecifictype.Thefunctiondoesn’thaveadefinitionand
thereforecannotbecalled(anddoesn’tcreateanobject).Hence,itcanonlybe
usedinunevaluatedoperands(suchasthoseofdecltypeandsizeof
constructs).So,insteadoftryingtocreateanobject,youcanassumeyouhavean
objectofthecorrespondingtype.
Forexample,thefollowingdeclarationdeducesthedefaultreturntypeRT
fromthepassedtemplateparametersT1andT2:Clickheretoviewcodeimage
basics/maxdefaultdeclval.hpp
#include<utility>
template<typenameT1,typenameT2,
typenameRT=std::decay_t<decltype(true?std::declval<T1>()
:std::declval<T2>())>>
RTmax(T1a,T2b)
{
returnb<a?a:b;
}
Toavoidthatwehavetocalla(default)constructorforT1andT2tobeableto
calloperator?:intheexpressiontoinitializeRT,weusestd::declvalto
“use”objectsofthecorrespondingtypewithoutcreatingthem.Thisisonly
possibleintheunevaluatedcontextofdecltype,though.
Don’tforgettousethestd::decay<>typetraittoensurethedefaultreturn
typecan’tbeareference,becausestd::declval()itselfyieldsrvalue
references.Otherwise,callssuchasmax(1,2)willgetareturntypeof
int&&.5SeeSection19.3.4onpage415fordetails.
11.3PerfectForwardingTemporaries
AsshowninSection6.1onpage91,wecanuseforwardingreferencesand
std::forward<>to“perfectlyforward”genericparameters:Clickhereto
viewcodeimage
template<typenameT>
voidf(T&&t)//tisforwardingreference
{
g(std::forward<T>(t));//perfectlyforwardpassedargumentttog()
}
However,sometimeswehavetoperfectlyforwarddataingenericcodethatdoes
notcomethroughaparameter.Inthatcase,wecanuseauto&&tocreatea
variablethatcanbeforwarded.Assume,forexample,thatwehavechainedcalls
tofunctionsget()andset()wherethereturnvalueofget()shouldbe
perfectlyforwardedtoset():Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tx)
{
set(get(x));
}
Supposefurtherthatweneedtoupdateourcodetoperformsomeoperationon
theintermediatevalueproducedbyget().Wedothisbyholdingthevalueina
variabledeclaredwithauto&&:Clickheretoviewcodeimage
template<typenameT>
voidfoo(Tx)
{
auto&&val=get(x);
…
//perfectlyforwardthereturnvalueofget()toset():
set(std::forward<decltype(val)>(val));
}
Thisavoidsextraneouscopiesoftheintermediatevalue.
11.4ReferencesasTemplateParameters
Althoughitisnotcommon,templatetypeparameterscanbecomereference
types.Forexample:Clickheretoviewcodeimage
basics/tmplparamref.cpp
#include<iostream>
template<typenameT>
voidtmplParamIsReference(T){
std::cout<<"Tisreference:"<<std::is_reference_v<T><<’\n’;
}
intmain()
{
std::cout<<std::boolalpha;
inti;
int&r=i;
tmplParamIsReference(i);//false
tmplParamIsReference(r);//false
tmplParamIsReference<int&>(i);//true
tmplParamIsReference<int&>(r);//true
}
EvenifareferencevariableispassedtotmplParamIsReference(),the
templateparameterTisdeducedtothetypeofthereferencedtype(because,for
areferencevariablev,theexpressionvhasthereferencedtype;thetypeofan
expressionisneverareference).However,wecanforcethereferencecaseby
explicitlyspecifyingthetypeofT:tmplParamIsReference<int&>(r);
tmplParamIsReference<int&>(i);Doingthiscanfundamentallychangethe
behaviorofatemplate,and,aslikelyasnot,atemplatemaynothavebeen
designedwiththispossibilityinmind,therebytriggeringerrorsorunexpected
behavior.Considerthefollowingexample:Clickheretoviewcodeimage
basics/referror1.cpp
template<typenameT,TZ=T{}>
classRefMem{
private:
Tzero;
public:
RefMem():zero{Z}{
}
};
intnull=0;
intmain()
{
RefMem<int>rm1,rm2;
rm1=rm2;//OK
RefMem<int&>rm3;//ERROR:invaliddefaultvalueforN
RefMem<int&,0>rm4;//ERROR:invaliddefaultvalueforN
externintnull;
RefMem<int&,null>rm5,rm6;
rm5=rm6;//ERROR:operator=isdeletedduetoreferencemember
}
HerewehaveaclasswithamemberoftemplateparametertypeT,initialized
withanontypetemplateparameterZthathasazero-initializeddefaultvalue.
Instantiatingtheclasswithtypeintworksasexpected.However,whentrying
toinstantiateitwithareference,thingsbecometricky:•Thedefault
initializationnolongerworks.
•Youcannolongerpassjust0asinitializerforanint.
•And,perhapsmostsurprising,theassignmentoperatorisnolongeravailable
becauseclasseswithnonstaticreferencemembershavedeleteddefault
assignmentoperators.
Also,usingreferencetypesfornontypetemplateparametersistrickyandcanbe
dangerous.Considerthisexample:Clickheretoviewcodeimage
basics/referror2.cpp
#include<vector>
#include<iostream>
template<typenameT,int&SZ>//Note:sizeisreference
classArr{
private:
std::vector<T>elems;
public:
Arr():elems(SZ){//usecurrentSZasinitialvectorsize
}
voidprint()const{
for(inti=0;i<SZ;++i){//loopoverSZelements
std::cout<<elems[i]<<’’;
}
}
};
intsize=10;
intmain()
{
Arr<int&,size>y;//compile-timeERRORdeepinthecodeofclass
std::vector<>
Arr<int,size>x;//initializesinternalvectorwith10elements
x.print();//OK
size+=100;//OOPS:modifiesSZinArr<>
x.print();//run-timeERROR:invalidmemoryaccess:loopsover120
elements
}
Here,theattempttoinstantiateArrforelementsofareferencetyperesultsinan
errordeepinthecodeofclassstd::vector<>,becauseitcan’tbe
instantiatedwithreferencesaselements:Clickheretoviewcodeimage
Arr<int&,size>y;//compile-timeERRORdeepinthecodeofclass
std::vector<>
Theerroroftenleadstothe“errornovel”describedinSection9.4onpage143,
wherethecompilerprovidestheentiretemplateinstantiationhistoryfromthe
initialcauseoftheinstantiationdowntotheactualtemplatedefinitioninwhich
theerrorwasdetected.
Perhapsworseistherun-timeerrorresultingfrommakingthesizeparametera
reference:Itallowstherecordedsizevaluetochangewithoutthecontainerbeing
awareofit(i.e.,thesizevaluecanbecomeinvalid).Thus,operationsusingthe
size(liketheprint()member)areboundtorunintoundefinedbehavior
(causingtheprogramtocrash,orworse):Clickheretoviewcodeimage
intintsize=10;
…
Arr<int,size>x;//initializesinternalvectorwith10elements
size+=100;//OOPS:modifiesSZinArr<>
x.print();//run-timeERROR:invalidmemoryaccess:loopsover120
elements
NotethatchangingthetemplateparameterSZtobeoftypeintconst&does
notaddressthisissue,becausesizeitselfisstillmodifiable.
Arguably,thisexampleisfar-fetched.However,inmorecomplexsituations,
issueslikethesedooccur.Also,inC++17nontypeparameterscanbededuced;
forexample:Clickheretoviewcodeimage
template<typenameT,decltype(auto)SZ>
classArr;Usingdecltype(auto)caneasilyproducereferencetypes
andisthereforegenerallyavoidedinthiscontext(useautoby
default).SeeSection15.10.3onpage302fordetails.
TheC++standardlibraryforthisreasonsometimeshassurprising
specificationsandconstraints.Forexample:•Inordertostillhaveanassignment
operatorevenifthetemplateparametersareinstantiatedforreferences,classes
std::pair<>andstd::tuple<>implementtheassignmentoperator
insteadofusingthedefaultbehavior.Forexample:Clickheretoviewcode
image
namespacestd{
template<typenameT1,typenameT2>
structpair{
T1first;
T2second;
…
//defaultcopy/moveconstructorsareOKevenwithreferences:
pair(pairconst&)=default;
pair(pair&&)=default;
…
//butassignmentoperatorhavetobedefinedtobeavailablewith
references:
pair&operator=(pairconst&p);
pair&operator=(pair&&p)noexcept(…);
…
};
}
•Becauseofthecomplexityofpossiblesideeffects,instantiationoftheC++17
standardlibraryclasstemplatesstd::optional<>and
std::variant<>forreferencetypesisill-formed(atleastinC++17).
Todisablereferences,asimplestaticassertionisenough:Clickheretoview
codeimage
template<typenameT>
classoptional
{
static_assert(!std::is_reference<T>::value,
"Invalidinstantiationofoptional<T>forreferences");
…
};
Referencetypesingeneralarequiteunlikeothertypesandaresubjecttoseveral
uniquelanguagerules.Thisimpacts,forexample,thedeclarationofcall
parameters(seeSection7onpage105)andalsothewaywedefinetypetraits
(seeSection19.6.1onpage432).
11.5DeferEvaluations
Whenimplementingtemplates,sometimesthequestioncomesupwhetherthe
codecandealwithincompletetypes(seeSection10.3.1onpage154).Consider
thefollowingclasstemplate:Clickheretoviewcodeimage
template<typenameT>
classCont{
private:
T*elems;
public:
…
};
Sofar,thisclasscanbeusedwithincompletetypes.Thisisuseful,forexample,
withclassesthatrefertoelementsoftheirowntype:Clickheretoviewcode
image
structNode
{
std::stringvalue;
Cont<Node>next;//onlypossibleifContacceptsincompletetypes
};
However,forexample,justbyusingsometraits,youmightlosetheabilityto
dealwithincompletetypes.Forexample:Clickheretoviewcodeimage
template<typenameT>
classCont{
private:
T*elems;
public:
…
typenamestd::conditional<std::is_move_constructible<T>::value,
T&&,
T&
>::type
foo();
};
Here,weusethetraitstd::conditional(seeSectionD.5onpage732)to
decidewhetherthereturntypeofthememberfunctionfoo()isT&&orT&.
ThedecisiondependsonwhetherthetemplateparametertypeTsupportsmove
semantics.
Theproblemisthatthetraitstd::is_move_constructiblerequires
thatitsargumentisacompletetype(andisnotvoidoranarrayofunknown
bound;seeSectionD.3.2onpage721).Thus,withthisdeclarationoffoo(),
thedeclarationofstructnodefails.6
Wecandealwiththisproblembyreplacingfoo()byamembertemplateso
thattheevaluationofstd::is_move_constructibleisdeferredtothe
pointofinstantiationoffoo():Clickheretoviewcodeimage
template<typenameT>
classCont{
private:
T*elems;
public:
template<typenameD=T>
typenamestd::conditional<std::is_move_constructible<D>::value,
T&&,
T&
>::type
foo();
};
Now,thetraitsdependsonthetemplateparameterD(defaultedtoT,thevalue
wewantanyway)andthecompilerhastowaituntilfoo()iscalledfora
concretetypelikeNodebeforeevaluatingthetraits(bythenNodeisacomplete
type;itwasonlyincompletewhilebeingdefined).
11.6ThingstoConsiderWhenWritingGeneric
Libraries
Let’slistsomethingstorememberwhenimplementinggenericlibraries(note
thatsomeofthemmightbeintroducedlaterinthisbook):•Useforwarding
referencestoforwardvaluesintemplates(seeSection6.1onpage91).Ifthe
valuesdonotdependontemplateparameters,useauto&&(seeSection11.3on
page167).
•Whenparametersaredeclaredasforwardingreferences,bepreparedthata
templateparameterhasareferencetypewhenpassinglvalues(seeSection
15.6.2onpage279).
•Usestd::addressof()whenyouneedtheaddressofanobjectdepending
onatemplateparametertoavoidsurpriseswhenitbindstoatypewith
overloadedoperator&(Section11.2.2onpage166).
•Formemberfunctiontemplates,ensurethattheydon’tmatchbetterthanthe
predefinedcopy/moveconstructororassignmentoperator(Section6.4onpage
99).
•Considerusingstd::decaywhentemplateparametersmightbestring
literalsandarenotpassedbyvalue(Section7.4onpage116andSectionD.4
onpage731).
•Ifyouhaveoutorinoutparametersdependingontemplateparameters,be
preparedtodealwiththesituationthatconsttemplateargumentsmaybe
specified(see,e.g.,Section7.2.2onpage110).
•Bepreparedtodealwiththesideeffectsoftemplateparametersbeing
references(seeSection11.4onpage167fordetailsandSection19.6.1onpage
432foranexample).Inparticular,youmightwanttoensurethatthereturn
typecan’tbecomeareference(seeSection7.5onpage117).
•Bepreparedtodealwithincompletetypestosupport,forexample,recursive
datastructures(seeSection11.5onpage171).
•OverloadforallarraytypesandnotjustT[SZ](seeSection5.4onpage71).
11.7Summary
•Templatesallowyoutopassfunctions,functionpointers,functionobjects,
functors,andlambdasascallables.
•Whendefiningclasseswithanoverloadedoperator(),declareitasconst
(unlessthecallchangesitsstate).
•Withstd::invoke(),youcanimplementcodethatcanhandleallcallables,
includingmemberfunctions.
•Usedecltype(auto)toforwardareturnvalueperfectly.
•Typetraitsaretypefunctionsthatcheckforpropertiesandcapabilitiesoftypes.
•Usestd::addressof()whenyouneedtheaddressofanobjectina
template.
•Usestd::declval()tocreatevaluesofspecifictypesinunevaluated
expressions.
•Useauto&&toperfectlyforwardobjectsingenericcodeiftheirtypedoesnot
dependontemplateparameters.
•Bepreparedtodealwiththesideeffectsoftemplateparametersbeing
references.
•Youcanusetemplatestodefertheevaluationofexpressions(e.g.,tosupport
usingincompletetypesinclasstemplates).
1std::invoke()alsoallowsapointertodatamemberasacallbacktype.
Insteadofcallingafunction,itreturnsthevalueofthecorrespondingdata
memberintheobjectreferredtobytheadditionalargument.
2ThankstoDanielKrüglerforpointingthatout.
3std::invoke_result<>isavailablesinceC++17.SinceC++11,toget
thereturntypeyoucouldcall:typename
std::result_of<Callable(Args…)>::type4TherewasaproposalforC++17to
requirethatviolationsofpreconditionsoftypetraitsmustalwaysresultina
compile-timeerror.However,becausesometypetraitshaveover-constraining
requirements,suchasalwaysrequiringcompletetypes,thischangewas
postponed.
5ThankstoDietmarKühlforpointingthatout.
6Notallcompilersyieldanerrorifstd::is_move_constructibleis
notanincompletetype.Thisisallowed,becauseforthiskindoferror,no
diagnosticsisrequired.Thus,thisisatleastaportabilityproblem.
PartII
TemplatesinDepth
Thefirstpartofthisbookprovidedatutorialformostofthelanguageconcepts
underlyingC++templates.Thatpresentationissufficienttoanswermost
questionsthatmayariseineverydayC++programming.Thesecondpartofthis
bookprovidesareferencethatanswerseventhemoreunusualquestionsthat
arisewhenpushingtheenvelopeofthelanguagetoachievesomeadvanced
softwareeffects.Ifdesired,youcanskipthispartonafirstreadandreturnto
specifictopicsaspromptedbyreferencesinlaterchaptersorafterlookingupa
conceptintheindex.
Ourgoalistobeclearandcompletebutalsotokeepthediscussionconcise.
Tothisend,ourexamplesareshortandoftensomewhatartificial.Thisalso
ensuresthatwedon’tstrayfromthetopicathandtounrelatedissues.
Inaddition,welookatpossiblefuturechangesandextensionsforthe
templateslanguagefeatureinC++.
Thetopicsofthispartinclude:•Fundamentaltemplatedeclarationissues•
Themeaningofnamesintemplates•TheC++templateinstantiation
mechanisms•Thetemplateargumentdeductionrules•Specializationand
overloading•Futurepossibilities
Chapter12
FundamentalsinDepth
Inthischapterwereviewsomeofthefundamentalsintroducedinthefirstpartof
thisbookindepth:thedeclarationoftemplates,therestrictionsontemplate
parameters,theconstraintsontemplatearguments,andsoforth.
12.1ParameterizedDeclarations
C++currentlysupportsfourfundamentalkindsoftemplates:classtemplates,
functiontemplates,variabletemplates,andaliastemplates.Eachofthese
templatekindscanappearinnamespacescope,butalsoinclassscope.Inclass
scopetheybecomenestedclasstemplates,memberfunctiontemplates,static
datamembertemplates,andmemberaliastemplates.Suchtemplatesare
declaredmuchlikeordinaryclasses,functions,variables,andtypealiases(or
theirclassmembercounterparts)exceptforbeingintroducedbya
parameterizationclauseoftheformtemplate<parametershere>Notethat
C++17introducedanotherconstructthatisintroducedwithsucha
parameterizationclause:deductionguides(seeSection2.9onpage42and
Section15.12.1onpage314).Thosearen’tcalledtemplatesinthisbook(e.g.,
theyarenotinstantiated),butthesyntaxwaschosentobereminiscentof
functiontemplates.
We’llcomebacktotheactualtemplateparameterdeclarationsinalater
section.First,someexamplesillustratethefourkindsoftemplates.Theycan
occurinnamespacescope(globallyorinanamespace)asfollows:Clickhereto
viewcodeimage
details/definitions1.hpp
template<typenameT>//anamespacescopeclasstemplate
classData{
public:
staticconstexprboolcopyable=true;
…
};
template<typenameT>//anamespacescopefunctiontemplate
voidlog(Tx){
…
}
template<typenameT>//anamespacescopevariabletemplate(since
C++14)
Tzero=0;
template<typenameT>//anamespacescopevariabletemplate(since
C++14)
booldataCopyable=Data<T>::copyable;
template<typenameT>//anamespacescopealiastemplate
usingDataList=Data<T*>;Notethatinthisexample,thestaticdata
memberData<T>::copyableisnotavariabletemplate,eventhoughitis
indirectlyparameterizedthroughtheparameterizationoftheclass
templateData.However,avariabletemplatecanappearinclassscope
(asthenextexamplewillillustrate),andinthatcaseitisastatic
datamembertemplate.
Thefollowingexampleshowsthefourkindsoftemplatesasclassmembers
thataredefinedwithintheirparentclass:Clickheretoviewcodeimage
details/definitions2.hpp
classCollection{
public:
template<typenameT>//anin-classmemberclasstemplatedefinition
classNode{
…
};
template<typenameT>//anin-class(andthereforeimplicitlyinline)
T*alloc(){//memberfunctiontemplatedefinition
…
}
template<typenameT>//amembervariabletemplate(sinceC++14)
staticTzero=0;
template<typenameT>//amemberaliastemplate
usingNodePtr=Node<T>*;
};
NotethatinC++17,variables—includingstaticdatamembers—andvariable
templatescanbe“in-line,”whichmeansthattheirdefinitioncanberepeated
acrosstranslationunits.Thisisredundantforvariabletemplates,whichcan
alwaysbedefinedinmultipletranslationunits.Unlikememberfunctions,
however,astaticdatamemberbeingdefinedinitsenclosingclassdoesnotmake
itinline:Thekeywordinlinemustbespecifiedinallcases.
Finally,thefollowingcodedemonstrateshowmembertemplatesthatarenot
aliastemplatescanbedefinedout-of-class:Clickheretoviewcodeimage
details/definitions3.hpp
template<typenameT>//anamespacescopeclasstemplate
classList{
public:
List()=default;//becauseatemplateconstructorisdefined
template<typenameU>//anothermemberclasstemplate,
classHandle;//withoutitsdefinition
template<typenameU>//amemberfunctiontemplate
List(List<U>const&);//(constructor)
template<typenameU>//amembervariabletemplate(sinceC++14)
staticUzero;
};
template<typenameT>//out-of-classmemberclasstemplatedefinition
template<typenameU>
classList<T>::Handle{
…
};
template<typenameT>//out-of-classmemberfunctiontemplate
definition
template<typenameT2>
List<T>::List(List<T2>const&b)
{
…
}
template<typenameT>//out-of-classstaticdatamembertemplate
definition
template<typenameU>
UList<T>::zero=0;
Membertemplatesdefinedoutsidetheirenclosingclassmayneedmultiple
template<…>parameterizationclauses:oneforeveryenclosingclass
templateandoneforthemembertemplateitself.Theclausesarelistedstarting
fromtheoutermostclasstemplate.
Notealsothataconstructortemplate(aspecialkindofmemberfunction
template)disablestheimplicitdeclarationofthedefaultconstructor(becauseit
isonlyimplicitlydeclaredifnootherconstructorisdeclared).Addinga
defaulteddeclarationList()=default;ensuresaninstanceofList<T>is
default-constructiblewiththesemanticsofanimplicitlydeclaredconstructor.
UnionTemplates
Uniontemplatesarepossibletoo(andtheyareconsideredakindofclass
template):Clickheretoviewcodeimage
template<typenameT>
unionAllocChunk{
Tobject;
unsignedcharbytes[sizeof(T)];
};
DefaultCallArguments
Functiontemplatescanhavedefaultcallargumentsjustlikeordinaryfunction
declarations:Clickheretoviewcodeimage
template<typenameT>
voidreport_top(Stack<T>const&,intnumber=10);
template<typenameT>
voidfill(Array<T>&,Tconst&=T{});//T{}iszeroforbuilt-in
types
Thelatterdeclarationshowsthatadefaultcallargumentcoulddependona
templateparameter.Italsocanbedefinedas(theonlywaypossiblebefore
C++11,seeSection5.2onpage68)Clickheretoviewcodeimage
template<typenameT>
voidfill(Array<T>&,Tconst&=T());//T()iszeroforbuilt-in
types
Whenthefill()functioniscalled,thedefaultargumentisnotinstantiatedifa
secondfunctioncallargumentissupplied.Thisensuresthatnoerrorisissuedif
thedefaultcallargumentcannotbeinstantiatedforaparticularT.Forexample:
Clickheretoviewcodeimage
classValue{
public:
explicitValue(int);//nodefaultconstructor
};
voidinit(Array<Value>&array)
{
Valuezero(0);
fill(array,zero);//OK:defaultconstructornotused
fill(array);//ERROR:undefineddefaultconstructorforValueis
used
}
NontemplateMembersofClassTemplates
Inadditiontothefourfundamentalkindsoftemplatesdeclaredinsideaclass,
youcanalsohaveordinaryclassmembersparameterizedbybeingpartofaclass
templateTheyareoccasionally(erroneously)alsoreferredtoasmember
templates.Althoughtheycanbeparameterized,suchdefinitionsaren’tquite
first-classtemplates.Theirparametersareentirelydeterminedbythetemplateof
whichtheyaremembers.Forexample:Clickheretoviewcodeimage
template<intI>
classCupBoard
{
classShelf;//ordinaryclassinclasstemplate
voidopen();//ordinaryfunctioninclasstemplate
enumWood:unsignedchar;//ordinaryenumerationtypeinclass
template
staticdoubletotalWeight;//ordinarystaticdatamemberinclass
template
};
Thecorrespondingdefinitionsonlyspecifyaparameterizationclauseforthe
parentclasstemplates,butnotforthememberitself,becauseitisnotatemplate
(i.e.,noparameterizationclauseisassociatedwiththenameappearingafterthe
last::):Clickheretoviewcodeimage
template<intI>//definitionofordinaryclassinclasstemplate
classCupBoard<I>::Shelf{
…
};
template<intI>//definitionofordinaryfunctioninclasstemplate
voidCupBoard<I>::open()
{
…
}
template<intI>//definitionofordinaryenumerationtypeclassin
classtemplate
enumCupBoard<I>::Wood{
Maple,Cherry,Oak
};
template<intI>//definitionofordinarystaticmemberinclass
template
doubleCupBoard<I>::totalWeight=0.0;SinceC++17,thestatic
totalWeightmembercanbeinitializedinsidetheclasstemplateusing
inline:Clickheretoviewcodeimage
template<intI>
classCupBoard
…
inlinestaticdoubletotalWeight=0.0;
};
Althoughsuchparameterizeddefinitionsarecommonlycalledtemplates,the
termdoesn’tquiteapplytothem.Atermthathasbeenoccasionallysuggested
fortheseentitiesistemploid.SinceC++17,theC++standarddoesdefinethe
notionofatemplatedentity,whichincludestemplatesandtemploidsaswellas,
recursively,anyentitydefinedorcreatedintemplatedentities(thatincludes,
e.g.,afriendfunctiondefinedinsideaclasstemplate(seeSection2.4onpage
30)ortheclosuretypeofalambdaexpressionappearinginatemplate).Neither
temploidnortemplatedentityhasgainedmuchtractionsofar,buttheymaybe
usefultermstocommunicatemorepreciselyaboutC++templatesinthefuture.
12.1.1VirtualMemberFunctions
Memberfunctiontemplatescannotbedeclaredvirtual.Thisconstraintis
imposedbecausetheusualimplementationofthevirtualfunctioncall
mechanismusesafixed-sizetablewithoneentrypervirtualfunction.However,
thenumberofinstantiationsofamemberfunctiontemplateisnotfixeduntilthe
entireprogramhasbeentranslated.Hence,supportingvirtualmemberfunction
templateswouldrequiresupportforawholenewkindofmechanisminC++
compilersandlinkers.
Incontrast,theordinarymembersofclasstemplatescanbevirtualbecause
theirnumberisfixedwhenaclassisinstantiated:Clickheretoviewcodeimage
template<typenameT>
classDynamic{
public:
virtual~Dynamic();//OK:onedestructorperinstanceofDynamic<T>
template<typenameT2>
virtualvoidcopy(T2const&);
//ERROR:unknownnumberofinstancesofcopy()
//givenaninstanceofDynamic<T>
};
12.1.2LinkageofTemplates
Everytemplatemusthaveaname,andthatnamemustbeuniquewithinits
scope,exceptthatfunctiontemplatescanbeoverloaded(seeChapter16).Note
especiallythat,unlikeclasstypes,classtemplatescannotshareanamewitha
differentkindofentity:Clickheretoviewcodeimage
intC;
…
classC;//OK:classnamesandnonclassnamesareinadifferent
“space”
intX;
…
template<typenameT>
classX;//ERROR:conflictwithvariableX
structS;
…
template<typenameT>
classS;//ERROR:conflictwithstructS
Templatenameshavelinkage,buttheycannothaveClinkage.Nonstandard
linkagesmayhaveanimplementation-dependentmeaning(however,wedon’t
knowofanimplementationthatsupportsnonstandardnamelinkagesfor
templates):Clickheretoviewcodeimage
extern"C++"template<typenameT>
voidnormal();//thisisthedefault:thelinkagespecificationcould
beleftout
extern"C"template<typenameT>
voidinvalid();//ERROR:templatescannothaveClinkage
extern"Java"template<typenameT>
voidjavaLink();//nonstandard,butmaybesomecompilerwillsomeday
//supportlinkagecompatiblewithJavagenerics
Templatesusuallyhaveexternallinkage.Theonlyexceptionsarenamespace
scopefunctiontemplateswiththestaticspecifier,templatesthataredirector
indirectmembersofanunnamedname-space(whichhaveinternallinkage),and
membertemplatesofunnamedclasses(whichhavenolinkage).Forexample:
Clickheretoviewcodeimage
template<typenameT>//referstothesameentityasadeclarationof
the
voidexternal();//samename(andscope)inanotherfile
template<typenameT>//unrelatedtoatemplatewiththesamenamein
staticvoidinternal();//anotherfile
template<typenameT>//redeclarationofthepreviousdeclaration
staticvoidinternal();
namespace{
template<typename>//alsounrelatedtoatemplatewiththesamename
voidotherInternal();//inanotherfile,evenonethatsimilarly
appears
}//inanunnamednamespace
namespace{
template<typename>//redeclarationoftheprevioustemplate
declaration
voidotherInternal();
}
struct{
template<typenameT>voidf(T){}//nolinkage:cannotberedeclared
}x;
Notethatsincethelattermembertemplatehasnolinkage,itmustbedefined
withintheunnamedclassbecausethereisnowaytoprovideadefinitionoutside
theclass.
Currentlytemplatescannotbedeclaredinfunctionscopeorlocalclassscope,
butgenericlambdas(seeSection15.10.6onpage309),whichhaveassociated
closuretypesthatcontainmemberfunctiontemplates,canappearinlocal
scopes,whicheffectivelyimpliesakindoflocalmemberfunctiontemplate.
Thelinkageofaninstanceofatemplateisthatofthetemplate.Forexample,a
functioninternal<void>()instantiatedfromthetemplateinternal
declaredabovewillhaveinternallinkage.Thishasaninterestingconsequencein
thecaseofvariabletemplates.Indeed,considerthefollowingexample:Click
heretoviewcodeimage
template<typenameT>Tzero=T{};Allinstantiationsofzerohave
externallinkage,evensomethinglikezero<intconst>.That’sperhaps
counterintuitivegiventhatintconstzero_int=int{};hasinternal
linkagebecauseitisdeclaredwithaconsttype.Similarly,all
instantiationsofthetemplateClickheretoviewcodeimage
template<typenameT>intconstmax_volume=11;haveexternal
linkage,despiteallthoseinstantiationsalsohavingtypeintconst.
12.1.3PrimaryTemplates
Normaldeclarationsoftemplatesdeclareprimarytemplates.Suchtemplate
declarationsaredeclaredwithoutaddingtemplateargumentsinanglebrackets
afterthetemplatename:Clickheretoviewcodeimage
template<typenameT>classBox;//OK:primarytemplate
template<typenameT>classBox<T>;//ERROR:doesnotspecialize
template<typenameT>voidtranslate(T);//OK:primarytemplate
template<typenameT>voidtranslate<T>(T);//ERROR:notallowedfor
functions
template<typenameT>constexprTzero=T{};//OK:primarytemplate
template<typenameT>constexprTzero<T>=T{};//ERROR:doesnot
specialize
Nonprimarytemplatesoccurwhendeclaringpartialspecializationsofclassor
variabletemplates.ThosearediscussedinChapter16.Functiontemplatesmust
alwaysbeprimarytemplates(seeSection17.3onpage356foradiscussionofa
potentialfuturelanguagechange).
12.2TemplateParameters
Therearethreebasickindsoftemplateparameters:
1.Typeparameters(thesearebyfarthemostcommon)2.Nontypeparameters3.
TemplatetemplateparametersAnyofthesebasickindsoftemplate
parameterscanbeusedasthebasisofatemplateparameterpack(seeSection
12.2.4onpage188).
Templateparametersaredeclaredintheintroductoryparameterizationclause
ofatemplatedeclaration.1Suchdeclarationsdonotnecessarilyneedtobe
named:Clickheretoviewcodeimage
template<typename,int>
classX;//X<>isparameterizedbyatypeandaninteger
Aparameternameis,ofcourse,requirediftheparameterisreferredtolaterin
thetemplate.Notealsothatatemplateparameternamecanbereferredtoina
subsequentparameterdeclaration(butnotbefore):Clickheretoviewcode
image
template<typenameT,//thefirstparameterisused
TRoot,//inthedeclarationofthesecondoneand
template<T>classBuf>//inthedeclarationofthethirdone
classStructure;
12.2.1TypeParameters
Typeparametersareintroducedwitheitherthekeywordtypenameorthe
keywordclass:Thetwoareentirelyequivalent.2Thekeywordmustbe
followedbyasimpleidentifier,andthatidentifiermustbefollowedbyacomma
todenotethestartofthenextparameterdeclaration,aclosinganglebracket(>)
todenotetheendoftheparameterizationclause,oranequalsign(=)todenote
thebeginningofadefaulttemplateargument.
Withinatemplatedeclaration,atypeparameteractsmuchlikeatypealias(see
Section2.8onpage38).Forexample,itisnotpossibletouseanelaborated
nameoftheformclassTwhenTisatemplateparameter,evenifTwereto
besubstitutedbyaclasstype:Clickheretoviewcodeimage
template<typenameAllocator>
classList{
classAllocator*allocptr;//ERROR:use“Allocator*allocptr”
friendclassAllocator;//ERROR:use“friendAllocator”
…
};
12.2.2NontypeParameters
Nontypetemplateparametersstandforconstantvaluesthatcanbedeterminedat
compileorlinktime.3Thetypeofsuchaparameter(inotherwords,thetypeof
thevalueforwhichitstands)mustbeoneofthefollowing:•Anintegertypeor
anenumerationtype
•Apointertype4
•Apointer-to-membertype
•Anlvaluereferencetype(bothreferencestoobjectsandreferencestofunctions
areacceptable)•std::nullptr_t
•Atypecontainingautoordecltype(auto)(sinceC++17only;see
Section15.10.1onpage296)Allothertypesarecurrentlyexcluded(although
floating-pointtypesmaybeaddedinthefuture;seeSection17.2onpage356).
Perhapssurprisingly,thedeclarationofanontypetemplateparametercanin
somecasesalsostartwiththekeywordtypename:Clickheretoviewcode
image
template<typenameT,//atypeparameter
typenameT::Allocator*Allocator>//anontypeparameter
classList;orwiththekeywordclass:Clickheretoviewcodeimage
template<classX*>//anontypeparameterofpointertype
classY;Thetwocasesareeasilydistinguishedbecausethefirstis
followedbyasimpleidentifierandthenoneofasmallsetoftokens
(’=’foradefaultargument,’,’toindicatethatanothertemplate
parameterfollows,oraclosing>toendthetemplateparameter
list).Section5.1onpage67andSection13.3.2onpage229explain
theneedforthekeywordtypenameinthefirstnontypeparameter.
Functionandarraytypescanbespecified,buttheyareimplicitlyadjustedto
thepointertypetowhichtheydecay:Clickheretoviewcodeimage
template<intbuf[5]>classLexer;//bufisreallyanint*
template<int*buf>classLexer;//OK:thisisaredeclaration
template<intfun()>structFuncWrap;//funreallyhaspointerto
//functiontype
template<int(*)()>structFuncWrap;//OK:thisisaredeclaration
Nontypetemplateparametersaredeclaredmuchlikevariables,buttheycannot
havenontypespecifierslikestatic,mutable,andsoforth.Theycanhave
constandvolatilequalifiers,butifsuchaqualifierappearsatthe
outermostleveloftheparametertype,itissimplyignored:Clickheretoview
codeimage
template<intconstlength>classBuffer;//constisuselesshere
template<intlength>classBuffer;//sameaspreviousdeclaration
Finally,nonreferencenontypeparametersarealwaysprvalues5whenusedin
expressions.Theiraddresscannotbetaken,andtheycannotbeassignedto.A
nontypeparameteroflvaluereferencetype,ontheotherhand,canbeusedto
denoteanlvalue:Clickheretoviewcodeimage
template<int&Counter>
structLocalIncrement{
LocalIncrement(){Counter=Counter+1;}//OK:referencetoan
integer
~LocalIncrement(){Counter=Counter-1;}
};
Rvaluereferencesarenotpermitted.
12.2.3TemplateTemplateParameters
Templatetemplateparametersareplaceholdersforclassoraliastemplates.They
aredeclaredmuchlikeclasstemplates,butthekeywordsstructandunion
cannotbeused:Clickheretoviewcodeimage
template<template<typenameX>classC>//OK
voidf(C<int>*p);
template<template<typenameX>structC>//ERROR:structnotvalid
here
voidf(C<int>*p);
template<template<typenameX>unionC>//ERROR:unionnotvalidhere
voidf(C<int>*p);C++17allowstheuseoftypenameinsteadofclass:
Thatchangewasmotivatedbythefactthattemplatetemplate
parameterscanbesubstitutednotonlybyclasstemplatesbutalsoby
aliastemplates(whichinstantiatetoarbitrarytypes).So,inC++17,
ourexampleabovecanbewritteninsteadasClickheretoviewcode
image
template<template<typenameX>typenameC>//OKsinceC++17
voidf(C<int>*p);Inthescopeoftheirdeclaration,template
templateparametersareusedjustlikeotherclassoralias
templates.
Theparametersoftemplatetemplateparameterscanhavedefaulttemplate
arguments.Thesedefaultargumentsapplywhenthecorrespondingparameters
arenotspecifiedinusesofthetemplatetemplateparameter:Clickheretoview
codeimage
template<template<typenameT,
typenameA=MyAllocator>classContainer>
classAdaptation{
Container<int>storage;//implicitlyequivalentto
Container<int,MyAllocator>
…
};
TandAarethenamesofthetemplateparameterofthetemplatetemplate
parameterContainer.Thesenamesbeusedonlyinthedeclarationofother
parametersofthattemplatetemplateparameter.Thefollowingcontrived
templateillustratesthisconcept:Clickheretoviewcodeimage
template<template<typenameT,T*>classBuf>//OK
classLexer{
staticT*storage;//ERROR:atemplatetemplateparametercannotbe
usedhere
…
};
Usuallyhowever,thenamesofthetemplateparametersofatemplatetemplate
parameterarenotneededinthedeclarationofothertemplateparametersandare
thereforeoftenleftunnamedaltogether.Forexample,ourearlierAdaptation
templatecouldbedeclaredasfollows:Clickheretoviewcodeimage
template<template<typename,
typename=MyAllocator>classContainer>
classAdaptation{
Container<int>storage;//implicitlyequivalentto
Container<int,MyAllocator>
…
};
12.2.4TemplateParameterPacks
SinceC++11,anykindoftemplateparametercanbeturnedintoatemplate
parameterpackbyintroducinganellipsis(…)priortothetemplateparameter
nameor,ifthetemplateparameterisunnamed,wherethetemplateparameter
namewouldoccur:Clickheretoviewcodeimage
template<typename…Types>//declaresatemplateparameterpacknamed
Types
classTuple;Atemplateparameterpackbehaveslikeitsunderlying
templateparameter,butwithacrucialdifference:Whileanormal
templateparametermatchesexactlyonetemplateargument,atemplate
parameterpackcanmatchanynumberoftemplatearguments.Thismeans
thattheTupleclasstemplatedeclaredaboveacceptsanynumberof
(possiblydistinct)typesastemplatearguments:Clickheretoview
codeimage
usingIntTuple=Tuple<int>;//OK:onetemplateargument
usingIntCharTuple=Tuple<int,char>;//OK:twotemplatearguments
usingIntTriple=Tuple<int,int,int>;//OK:threetemplate
arguments
usingEmptyTuple=Tuple<>;//OK:zerotemplatearguments
Similarly,templateparameterpacksofnontypeandtemplatetemplate
parameterscanacceptanynumberofnontypeortemplatetemplatearguments,
respectively:Clickheretoviewcodeimage
template<typenameT,unsigned…Dimensions>
classMultiArray;//OK:declaresanontypetemplateparameterpack
usingTransformMatrix=MultiArray<double,3,3>;//OK:3x3matrix
template<typenameT,template<typename,typename>…Containers>
voidtestContainers();//OK:declaresatemplatetemplateparameter
pack
TheMultiArrayexamplerequiresallnontypetemplateargumentstobeofthe
sametypeunsigned.C++17introducedthepossibilityofdeducednontype
templatearguments,whichallowsustoworkaroundthatrestrictiontosome
extent—seeSection15.10.1onpage298fordetails.
Primaryclasstemplates,variabletemplates,andaliastemplatesmayhaveat
mostonetemplateparameterpackand,ifpresent,thetemplateparameterpack
mustbethelasttemplateparameter.Functiontemplateshaveaweaker
restriction:Multipletemplateparameterpacksarepermitted,aslongaseach
templateparametersubsequenttoatemplateparameterpackeitherhasadefault
value(seethenextsection)orcanbededuced(seeChapter15):Clickhereto
viewcodeimage
template<typename…Types,typenameLast>
classLastType;//ERROR:templateparameterpackisnotthelast
templateparameter
template<typename…TestTypes,typenameT>
voidrunTests(Tvalue);//OK:templateparameterpackisfollowed
//byadeducibletemplateparameter
template<unsigned…>structTensor;
template<unsigned…Dims1,unsigned…Dims2>
autocompose(Tensor<Dims1…>,Tensor<Dims2…>);
//OK:thetensordimensionscanbededuced
Thelastexampleisthedeclarationofafunctionwithadeducedreturntype—
aC++14feature.SeealsoSection15.10.1onpage296.
Declarationsofpartialspecializationsofclassandvariabletemplates(see
Chapter16)canhavemultipleparameterpacks,unliketheirprimarytemplate
counterparts.Thatisbecausepartialspecializationareselectedthrougha
deductionprocessthatisnearlyidenticaltothatusedforfunctiontemplates.
Clickheretoviewcodeimage
template<typename…>Typelist;
template<typenameX,typenameY>structZip;
template<typename…Xs,typename…Ys>
structZip<Typelist<Xs…>,Typelist<Ys…>>;
//OK:partialspecializationusesdeductiontodetermine
//theXsandYssubstitutions
Perhapsnotsurprisingly,atypeparameterpackcannotbeexpandedinitsown
parameterclause.Forexample:Clickheretoviewcodeimage
template<typename…Ts,Ts…vals>structStaticValues{};
//ERROR:Tscannotbeexpandedinitsownparameterlist
However,nestedtemplatescancreatesimilarvalidsituations:Clickheretoview
codeimage
template<typename…Ts>structArgList{
template<Ts…vals>structVals{};
};
ArgList<int,char,char>::Vals<3,’x’,’y’>tada;Atemplatethat
containsatemplateparameterpackiscalledavariadictemplate
becauseitacceptsavariablenumberoftemplatearguments.Chapter4
andSection12.4onpage200describetheuseofvariadictemplates.
12.2.5DefaultTemplateArguments
Anykindoftemplateparameterthatisnotatemplateparameterpackcanbe
equippedwithadefaultargument,althoughitmustmatchthecorresponding
parameterinkind(e.g.,atypeparametercannothaveanontypedefault
argument).Adefaultargumentcannotdependonitsownparameter,becausethe
nameoftheparameterisnotinscopeuntilafterthedefaultargument.However,
itmaydependonpreviousparameters:Clickheretoviewcodeimage
template<typenameT,typenameAllocator=allocator<T>>
classList;Atemplateparameterforaclasstemplate,variable
template,oraliastemplatecanhaveadefaulttemplateargumentonly
ifdefaultargumentswerealsosuppliedforthesubsequent
parameters.(Asimilarconstraintexistsfordefaultfunctioncall
arguments.)Thesubsequentdefaultvaluesareusuallyprovidedinthe
sametemplatedeclaration,buttheycouldalsohavebeendeclaredin
apreviousdeclarationofthattemplate.Thefollowingexamplemakes
thisclear:Clickheretoviewcodeimage
template<typenameT1,typenameT2,typenameT3,
typenameT4=char,typenameT5=char>
classQuintuple;//OK
template<typenameT1,typenameT2,typenameT3=char,
typenameT4,typenameT5>
classQuintuple;//OK:T4andT5alreadyhavedefaults
template<typenameT1=char,typenameT2,typenameT3,
typenameT4,typenameT5>
classQuintuple;//ERROR:T1cannothaveadefaultargument
//becauseT2doesn’thaveadefault
Defaulttemplateargumentsfortemplateparametersoffunctiontemplatesdonot
requiresubsequenttemplateparameterstohaveadefaulttemplateargument:6
Clickheretoviewcodeimage
template<typenameR=void,typenameT>
R*addressof(T&value);//OK:ifnotexplicitlyspecified,Rwillbe
void
Defaulttemplateargumentscannotberepeated:Clickheretoviewcodeimage
template<typenameT=void>
classValue;
template<typenameT=void>
classValue;//ERROR:repeateddefaultargument
Anumberofcontextsdonotpermitdefaulttemplatearguments:•Partial
specializations:
Clickheretoviewcodeimage
template<typenameT>
classC;
…
template<typenameT=int>
classC<T*>;//ERROR
•Parameterpacks:
Clickheretoviewcodeimage
template<typename…Ts=int>structX;//ERROR
•Theout-of-classdefinitionofamemberofaclasstemplate:Clickheretoview
codeimage
template<typenameT>structX
{
Tf();
};
template<typenameT=int>TX<T>::f(){//ERROR
…
}
•Afriendclasstemplatedeclaration:
Clickheretoviewcodeimage
structS{
template<typename=void>friendstructF;
};
•Afriendfunctiontemplatedeclarationunlessitisadefinitionandno
declarationofitappearsanywhereelseinthetranslationunit:Clickhereto
viewcodeimage
structS{
template<typename=void>friendvoidf();//ERROR:notadefinition
template<typename=void>friendvoidg(){//OKsofar
}
};
template<typename>voidg();//ERROR:g()wasgivenadefault
templateargument
//whendefined;nootherdeclarationmayexisthere
12.3TemplateArguments
Wheninstantiatingatemplate,templateparametersaresubstitutedbytemplate
arguments.Theargumentscanbedeterminedusingseveraldifferent
mechanisms:•Explicittemplatearguments:Atemplatenamecanbefollowed
byexplicittemplateargumentsenclosedinanglebrackets.Theresultingnameis
calledatemplate-id.
•Injectedclassname:WithinthescopeofaclasstemplateXwithtemplate
parametersP1,P2,…,thenameofthattemplate(X)canbeequivalenttothe
template-idX<P1,P2,…>.SeeSection13.2.3onpage221fordetails.
•Defaulttemplatearguments:Explicittemplateargumentscanbeomittedfrom
templateinstancesifdefaulttemplateargumentsareavailable.However,fora
classoraliastemplate,evenifalltemplateparametershaveadefaultvalue,the
(possiblyempty)anglebracketsmustbeprovided.
•Argumentdeduction:Functiontemplateargumentsthatarenotexplicitly
specifiedmaybededucedfromthetypesofthefunctioncallargumentsina
call.ThisisdescribedindetailinChapter15.Deductionisalsodoneinafew
othersituations.Ifallthetemplateargumentscanbededuced,noangle
bracketsneedtobespecifiedafterthenameofthefunctiontemplate.C++17
alsointroducestheabilitytodeduceclasstemplateargumentsfromthe
initializerofavariabledeclarationorfunctional-notationtypeconversion;see
Section15.12onpage313foradiscussion.
12.3.1FunctionTemplateArguments
Templateargumentsforafunctiontemplatecanbespecifiedexplicitly,deduced
fromthewaythetemplateisused,orprovidedasadefaulttemplateargument.
Forexample:Clickheretoviewcodeimage
details/max.cpp
template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
intmain()
{
::max<double>(1.0,-3.0);//explicitlyspecifytemplateargument
::max(1.0,-3.0);//templateargumentisimplicitlydeducedtobe
double
::max<int>(1.0,3.0);//theexplicit<int>inhibitsthededuction;
//hencetheresulthastypeint
}
Sometemplateargumentscanneverbededucedbecausetheircorresponding
templateparameterdoesnotappearinafunctionparametertypeorforsome
otherreason(seeSection15.2onpage271).Thecorrespondingparametersare
typicallyplacedatthebeginningofthelistoftemplateparameterssotheycanbe
specifiedexplicitlywhileallowingtheotherargumentstobededuced.For
example:Clickheretoviewcodeimage
details/implicit.cpp
template<typenameDstT,typenameSrcT>
DstTimplicit_cast(SrcTconst&x)//SrcTcanbededuced,butDstT
cannot
{
returnx;
}
intmain()
{
doublevalue=implicit_cast<double>(-1);
}
Ifwehadreversedtheorderofthetemplateparametersinthisexample(inother
words,ifwehadwrittentemplate<typenameSrcT,typename
DstT>),acallofimplicit_castwouldhavetospecifybothtemplate
argumentsexplicitly.
Moreover,suchparameterscan’tusefullybeplacedafteratemplateparameter
packorappearinapartialspecialization,becausetherewouldbenowayto
explicitlyspecifyordeducethem.
Clickheretoviewcodeimage
template<typename…Ts,intN>
voidf(double(&)[N+1],Ts…ps);//uselessdeclarationbecauseN
//cannotbespecifiedordeduced
Becausefunctiontemplatescanbeoverloaded,explicitlyprovidingallthe
argumentsforafunctiontemplatemaynotbesufficienttoidentifyasingle
function:Insomecases,itidentifiesasetoffunctions.Thefollowingexample
illustratesaconsequenceofthisobservation:Clickheretoviewcodeimage
template<typenameFunc,typenameT>
voidapply(FuncfuncPtr,Tx)
{
funcPtr(x);
}
template<typenameT>voidsingle(T);
template<typenameT>voidmulti(T);
template<typenameT>voidmulti(T*);
intmain()
{
apply(&single<int>,3);//OK
apply(&multi<int>,7);//ERROR:nosinglemulti<int>
}
Inthisexample,thefirstcalltoapply()worksbecausethetypeofthe
expression&single<int>isunambiguous.Asaresult,thetemplateargument
valuefortheFuncparameteriseasilydeduced.Inthesecondcall,however,
&multi<int>couldbeoneoftwodifferenttypesandthereforeFunccannot
bededucedinthiscase.
Furthermore,itispossiblethatsubstitutingtemplateargumentsinafunction
templateresultsinanattempttoconstructaninvalidC++typeorexpression.
Considerthefollowingoverloadedfunctiontemplate(RT1andRT2are
unspecifiedtypes):Clickheretoviewcodeimage
template<typenameT>RT1test(typenameT::Xconst*);
template<typenameT>RT2test(…);Theexpressiontest<int>makesno
senseforthefirstofthetwofunctiontemplatesbecausetypeint
hasnomembertypeX.However,thesecondtemplatehasnosuch
problem.Therefore,theexpression&test<int>identifiestheaddress
ofasinglefunction.Thefactthatthesubstitutionofintintothe
firsttemplatefailsdoesnotmaketheexpressioninvalid.This
SFINAE(substitutionfailureisnotanerror)principleisan
importantingredienttomaketheoverloadingoffunctiontemplates
practicalandisdiscussedinSection8.4onpage129andSection
15.7onpage284.
12.3.2TypeArguments
Templatetypeargumentsarethe“values”specifiedfortemplatetype
parameters.Anytype(includingvoid,functiontypes,referencetypes,etc.)can,
ingeneral,beusedasatemplateargument,buttheirsubstitutionforthetemplate
parametersmustleadtovalidconstructs:Clickheretoviewcodeimage
template<typenameT>
voidclear(Tp)
{
*p=0;//requiresthattheunary*beapplicabletoT
}
intmain()
{
inta;
clear(a);//ERROR:intdoesn’tsupporttheunary*
}
12.3.3NontypeArguments
Nontypetemplateargumentsarethevaluessubstitutedfornontypeparameters.
Suchavaluemustbeoneofthefollowingthings:•Anothernontypetemplate
parameterthathastherighttype.
•Acompile-timeconstantvalueofinteger(orenumeration)type.Thisis
acceptableonlyifthecorrespondingparameterhasatypethatmatchesthatof
thevalueoratypetowhichthevaluecanbeimplicitlyconvertedwithout
narrowing.Forexample,acharvaluecanbeprovidedforanintparameter,
but500isnotvalidforan8-bitcharparameter.
•Thenameofanexternalvariableorfunctionprecededbythebuilt-inunary&
(“addressof”)operator.Forfunctionsandarrayvariables,&canbeleftout.
Suchtemplateargumentsmatchnontypeparametersofapointertype.C++17
relaxedthisrequirementtopermitanyconstant-expressionthatproducesa
pointertoafunctionorvariable.
•Thepreviouskindofargumentbutwithoutaleading&operatorisavalid
argumentforanontypeparameterofreferencetype.Heretoo,C++17relaxed
theconstrainttopermitanyconstant-expressionglvalueforafunctionor
variable.
•Apointer-to-memberconstant;inotherwords,anexpressionoftheform
&C::mwhereCisaclasstypeandmisanonstaticmember(dataorfunction).
Thismatchesnontypeparametersofpointer-to-membertypeonly.Andagain,
inC++17,theactualsyntacticformisnolongerconstrained:Anyconstant-
expressionevaluatingtoamatchingpointer-to-memberconstantispermitted.
•Anullpointerconstantisavalidargumentforanontypeparameterofpointer
orpointer-to-membertype.
Fornontypeparametersofintegraltype—probablythemostcommonkindof
nontypeparameter—implicitconversionstotheparametertypeareconsidered.
WiththeintroductionofconstexprconversionfunctionsinC++11,this
meansthattheargumentbeforeconversioncanhaveaclasstype.
PriortoC++17,whenmatchinganargumenttoaparameterthatisapointeror
reference,user-definedconversions(constructorsforoneargumentand
conversionoperators)andderived-to-baseconversionsarenotconsidered,even
thoughinothercircumstancestheywouldbevalidimplicitconversions.Implicit
conversionsthatmakeanargumentmoreconstand/ormorevolatileare
fine.
Herearesomevalidexamplesofnontypetemplatearguments:Clickhereto
viewcodeimage
template<typenameT,TnontypeParam>
classC;
C<int,33>*c1;//integertype
inta;
C<int*,&a>*c2;//addressofanexternalvariable
voidf();
voidf(int);
C<void(*)(int),f>*c3;//nameofafunction:overloadresolution
selects
//f(int)inthiscase;the&isimplied
template<typenameT>voidtempl_func();
C<void(),&templ_func<double>>*c4;//functiontemplate
instantiationsarefunctions
structX{
staticboolb;
intn;
constexproperatorint()const{return42;}
};
C<bool&,X::b>*c5;//staticclassmembersareacceptable
variable/functionnames
C<intX::*,&X::n>*c6;//anexampleofapointer-to-memberconstant
C<long,X{}>*c7;//OK:Xisfirstconvertedtointviaaconstexpr
conversion
//functionandthentolongviaastandardintegerconversion
Ageneralconstraintoftemplateargumentsisthatacompileroralinkermustbe
abletoexpresstheirvaluewhentheprogramisbeingbuilt.Valuesthataren’t
knownuntilaprogramisrun(e.g.,theaddressoflocalvariables)aren’t
compatiblewiththenotionthattemplatesareinstantiatedwhentheprogramis
built.
Evenso,therearesomeconstantvaluesthatare,perhapssurprisingly,not
currentlyvalid:•Floating-pointnumbers
•Stringliterals(PriortoC++11,nullpointerconstantswerenotpermitted
either.)Oneoftheproblemswithstringliteralsisthattwoidenticalliteralscan
bestoredattwodistinctaddresses.Analternative(butcumbersome)wayto
expresstemplatesinstantiatedoverconstantstringsinvolvesintroducingan
additionalvariabletoholdthestring:Clickheretoviewcodeimage
template<charconst*str>
classMessage{
…
};
externcharconsthello[]="HelloWorld!";
charconsthello11[]="HelloWorld!";
voidfoo()
{
staticcharconsthello17[]="HelloWorld!";
Message<hello>msg03;//OKinallversions
Message<hello11>msg11;//OKsinceC++11
Message<hello17>msg17;//OKsinceC++17
}
Therequirementisthatanontypetemplateparameterdeclaredasreferenceor
pointercanbeaconstantexpressionwithexternallinkageinallC++versions,
internallinkagesinceC++11,oranylinkagesinceC++17.
SeeSection17.2onpage354foradiscussionofpossiblefuturechangesin
thisareaHereareafewother(lesssurprising)invalidexamples:Clickhereto
viewcodeimage
template<typenameT,TnontypeParam>
classC;
structBase{
inti;
}base;
structDerived:publicBase{
}derived;
C<Base*,&derived>*err1;//ERROR:derived-to-baseconversionsare
notconsidered
C<int&,base.i>*err2;//ERROR:fieldsofvariablesaren’t
consideredtobevariables
inta[10];
C<int*,&a[0]>*err3;//ERROR:addressesofarrayelementsaren’t
acceptableeither
12.3.4TemplateTemplateArguments
Atemplatetemplateargumentmustgenerallybeaclasstemplateoralias
templatewithparametersthatexactlymatchtheparametersofthetemplate
templateparameteritsubstitutes.PriortoC++17,defaulttemplateargumentsof
atemplatetemplateargumentwereignored(butifthetemplatetemplate
parameterhasdefaultarguments,theyareconsideredduringtheinstantiationof
thetemplate).C++17relaxedthematchingruletojustrequirethatthetemplate
templateparameterbeatleastasspecialized(seeSection16.2.2onpage330)as
thecorrespondingtemplatetemplateargument.
ThismakesthefollowingexampleinvalidpriortoC++17:Clickheretoview
codeimage
#include<list>
//declaresinnamespacestd:
//template<typenameT,typenameAllocator=allocator<T>>
//classlist;
template<typenameT1,typenameT2,
template<typename>classCont>//Contexpectsoneparameter
classRel{
…
};
Rel<int,double,std::list>rel;//ERRORbeforeC++17:std::listhas
morethan
//onetemplateparameter
Theprobleminthisexampleisthatthestd::listtemplateofthestandard
libraryhasmorethanoneparameter.Thesecondparameter(whichdescribesan
allocator)hasadefaultvalue,butpriortoC++17,thatisnotconsideredwhen
matchingstd::listtotheContainerparameter.
Variadictemplatetemplateparametersareanexceptiontothepre-C++17
“exactmatch”ruledescribedaboveandofferasolutiontothislimitation:They
enablemoregeneralmatchingagainsttemplatetemplatearguments.Atemplate
templateparameterpackcanmatchzeroormoretemplateparametersofthe
samekindinthetemplatetemplateargument:Clickheretoviewcodeimage
#include<list>
template<typenameT1,typenameT2,
template<typename…>classCont>//Contexpectsanynumberof
classRel{//typeparameters
…
};
Rel<int,double,std::list>rel;//OK:std::listhastwotemplate
parameters
//butcanbeusedwithoneargument
Templateparameterpackscanonlymatchtemplateargumentsofthesamekind.
Forexample,thefollowingclasstemplatecanbeinstantiatedwithanyclass
templateoraliastemplatehavingonlytemplatetypeparameters,becausethe
templatetypeparameterpackpassedthereasTTcanmatchzeroormore
templatetypeparameters:Clickheretoviewcodeimage
#include<list>
#include<map>
//declaresinnamespacestd:
//template<typenameKey,typenameT,
//typenameCompare=less<Key>,
//typenameAllocator=allocator<pair<Keyconst,T>>>
//classmap;
#include<array>
//declaresinnamespacestd:
//template<typenameT,size_tN>
//classarray;
template<template<typename…>classTT>
classAlmostAnyTmpl{
};
AlmostAnyTmpl<std::vector>withVector;//twotypeparameters
AlmostAnyTmpl<std::map>withMap;//fourtypeparameters
AlmostAnyTmpl<std::array>withArray;//ERROR:atemplatetype
parameterpack
//doesn’tmatchanontypetemplateparameter
Thefactthat,priortoC++17,onlythekeywordclasscouldbeusedtodeclare
atemplatetemplateparameterdoesnotindicatethatonlyclasstemplates
declaredwiththekeywordclasswereallowedassubstitutingarguments.
Indeed,struct,union,andaliastemplatesareallvalidargumentsfora
templatetemplateparameter(aliastemplatessinceC++11,whentheywere
introduced).Thisissimilartotheobservationthatanytypecanbeusedasan
argumentforatemplatetypeparameterdeclaredwiththekeywordclass.
12.3.5Equivalence
Twosetsoftemplateargumentsareequivalentwhenvaluesoftheargumentsare
identicalone-forone.Fortypearguments,typealiasesdon’tmatter:Itisthetype
ultimatelyunderlyingthetypealiasdeclarationthatiscompared.Forinteger
nontypearguments,thevalueoftheargumentiscompared;howthatvalueis
expresseddoesn’tmatter.Thefollowingexampleillustratesthisconcept:Click
heretoviewcodeimage
template<typenameT,intI>
classMix;
usingInt=int;
Mix<int,3*3>*p1;
Mix<Int,4+5>*p2;//p2hasthesametypeasp1
(Asisclearfromthisexample,notemplatedefinitionisneededtoestablishthe
equivalenceofthetemplateargumentlists.)Intemplate-dependentcontexts,
however,the“value”ofatemplateargumentcannotalwaysbeestablished
definitely,andtherulesforequivalencebecomealittlemorecomplicated.
Considerthefollowingexample:Clickheretoviewcodeimage
template<intN>structI{};
template<intM,intN>voidf(I<M+N>);//#1
template<intN,intM>voidf(I<N+M>);//#2
template<intM,intN>voidf(I<N+M>);//#3ERROR
Studydeclarations#1and#2carefully,andyou’llnoticethatbyjustrenamingM
andNto,respectively,NandM,youobtainthesamedeclaration:Thetwoare
thereforeequivalentanddeclarethesamefunctiontemplatef.Theexpressions
M+NandN+Minthosetwodeclarationsarecalledequivalent.
Declaration#3is,however,subtlydifferent:Theorderoftheoperandsis
inverted.ThatmakestheexpressionN+Min#3notequivalenttoeitherofthe
othertwoexpressions.However,becausetheexpressionwillproducethesame
resultforanyvaluesofthetemplateparametersinvolved,thoseexpressionsare
calledfunctionallyequivalent.Itisanerrorfortemplatestobedeclaredinways
thatdifferonlybecausethedeclarationsincludefunctionallyequivalent
expressionsthatarenotactuallyequivalent.However,suchanerrorneednotbe
diagnosedbyyourcompiler.That’sbecausesomecompilersmay,forexample,
internallyrepresentN+1+1inexactlythesamewayasN+2,whereasother
compilersmaynot.Ratherthanimposeaspecificimplementationapproach,the
standardallowseitheroneandrequiresprogrammerstobecarefulinthisarea.
Afunctiongeneratedfromafunctiontemplateisneverequivalenttoan
ordinaryfunctioneventhoughtheymayhavethesametypeandthesamename.
Thishastwoimportantconsequencesforclassmembers:1.Afunctiongenerated
fromamemberfunctiontemplateneveroverridesavirtualfunction.
2.Aconstructorgeneratedfromaconstructortemplateisneveracopyormove
constructor.7Similarly,anassignmentgeneratedfromanassignmenttemplate
isneveracopy-assignmentormove-assignmentoperator.(However,thisis
lesspronetoproblemsbecauseimplicitcallsofcopy-assignmentormove-
assignmentoperatorsarelesscommon.)Thiscanbegoodandbad.See
Section6.2onpage95andSection6.4onpage102fordetails.
12.4VariadicTemplates
Variadictemplates,introducedinSection4.1onpage55,aretemplatesthat
containatleastonetemplateparameterpack(seeSection12.2.4onpage188).8
Variadictemplatesareusefulwhenatemplate’sbehaviorcanbegeneralizedto
anynumberofarguments.TheTupleclasstemplateintroducedinSection
12.2.4onpage188isonesuchtype,becauseatuplecanhaveanynumberof
elements,allofwhicharetreatedthesameway.Wecanalsoimagineasimple
print()functionthattakesanynumberofargumentsanddisplayseachof
theminsequence.
Whentemplateargumentsaredeterminedforavariadictemplate,each
templateparameterpackinthevariadictemplatewillmatchasequenceofzero
ormoretemplatearguments.Werefertothissequenceoftemplateargumentsas
anargumentpack.Thefollowingexampleillustrateshowthetemplateparameter
packTypesmatchestodifferentargumentpacksdependingonthetemplate
argumentsprovidedforTuple:Clickheretoviewcodeimage
template<typename…Types>
classTuple{
//providesoperationsonthelistoftypesinTypes
};
intmain(){
Tuple<>t0;//Typescontainsanemptylist
Tuple<int>t1;//Typescontainsint
Tuple<int,float>t2;//Typescontainsintandfloat
}
Becauseatemplateparameterpackrepresentsalistoftemplateargumentsrather
thanasingletemplateargument,itmustbeusedinacontextwherethesame
languageconstructappliestoalloftheargumentsintheargumentpack.One
suchconstructisthesizeof…operation,whichcountsthenumberof
argumentsintheargumentpack:Clickheretoviewcodeimage
template<typename…Types>
classTuple{
public:
staticconstexprstd::size_tlength=sizeof…(Types);
};
inta1[Tuple<int>::length];//arrayofoneinteger
inta3[Tuple<short,int,long>::length];//arrayofthreeintegers
12.4.1PackExpansions
Thesizeof…expressionisanexampleofapackexpansion.Apackexpansion
isaconstructthatexpandsanargumentpackintoseparatearguments.While
sizeof…performsthisexpansionjusttocountthenumberofseparate
arguments,otherformsofparameterpacks—thosethatoccurwhereC++expects
alist—canexpandtomultipleelementswithinthatlist.Suchpackexpansions
areidentifiedbyanellipsis(…)totherightofanelementinthelist.Hereisa
simpleexamplewherewecreateanewclasstemplateMyTuplethatderives
fromTuple,passingalongitsarguments:Clickheretoviewcodeimage
template<typename…Types>
classMyTuple:publicTuple<Types…>{
//extraoperationsprovidedonlyforMyTuple
};
MyTuple<int,float>t2;//inheritsfromTuple<int,float>
ThetemplateargumentTypes…isapackexpansionthatproducesasequenceof
templatearguments,oneforeachargumentwithintheargumentpacksubstituted
forTypes.Asillustratedintheexample,theinstantiationoftype
MyTuple<int,float>substitutestheargumentpackint,floatforthe
templatetypeparameterpackTypes.Whenthisoccursinthepackexpansion
Types…,wegetonetemplateargumentforintandoneforfloat,so
MyTuple<int,float>inheritsfromTuple<int,float>.
Anintuitivewaytounderstandpackexpansionsistothinkofthemintermsof
asyntacticexpansion,wheretemplateparameterpacksarereplacedwithexactly
therightnumberof(non-pack)templateparametersandpackexpansionsare
writtenoutasseparatearguments,onceforeachofthenon-packtemplate
parameters.Forexample,hereishowMyTuplewouldlookifitwereexpanded
fortwoparameters:9
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
classMyTuple:publicTuple<T1,T2>{
//extraoperationsprovidedonlyforMyTuple
};
andforthreeparameters:
Clickheretoviewcodeimage
template<typenameT1,typenameT2,typenameT3>
classMyTuple:publicTuple<T1,T2,T3>{
//extraoperationsprovidedonlyforMyTuple
};
However,notethatyoucan’taccesstheindividualelementsofaparameterpack
directlybyname,becausenamessuchasT1,T2,andsoon,arenotdefinedina
variadictemplate.Ifyouneedthetypes,theonlythingyoucandoistopass
them(recursively)toanotherclassorfunction.
Eachpackexpansionhasapattern,whichisthetypeorexpressionthatwill
berepeatedforeachargumentintheargumentpackandtypicallycomesbefore
theellipsisthatdenotesthepackexpansion.Ourpriorexampleshavehadonly
trivialpatterns—thenameoftheparameterpack—butpatternscanbearbitrarily
complex.Forexample,wecandefineanewtypePtrTuplethatderivesfroma
Tupleofpointerstoitsargumenttypes:Clickheretoviewcodeimage
template<typename…Types>
classPtrTuple:publicTuple<Types*…>{
//extraoperationsprovidedonlyforPtrTuple
};
PtrTuple<int,float>t3;//InheritsfromTuple<int*,float*>
ThepatternforthepackexpansionTypes*…intheexampleaboveisTypes*.
Repeatedsubstitutionintothispatternproducesasequenceoftemplatetype
arguments,allofwhicharepointerstothetypesintheargumentpacksubstituted
forTypes.Underthesyntacticinterpretationofpackexpansions,hereishow
PtrTuplewouldlookifitwereexpandedforthreeparameters:Clickhereto
viewcodeimage
template<typenameT1,typenameT2,typenameT3>
classPtrTuple:publicTuple<T1*,T2*,T3*>{
//extraoperationsprovidedonlyforPtrTuple
};
12.4.2WhereCanPackExpansionsOccur?
Ourexamplesthusfarhavefocusedontheuseofpackexpansionstoproducea
sequenceoftemplatearguments.Infact,packexpansionscanbeusedessentially
anywhereinthelanguagewherethegrammarprovidesacomma-separatedlist,
including:•Inthelistofbaseclasses.
•Inthelistofbaseclassinitializersinaconstructor.
•Inalistofcallarguments(thepatternistheargumentexpression).
•Inalistofinitializers(e.g.,inabracedinitializerlist).
•Inthetemplateparameterlistofaclass,function,oraliastemplate.
•Inthelistofexceptionsthatcanbethrownbyafunction(deprecatedinC++11
andC++14,anddisallowedinC++17).
•Withinanattribute,iftheattributeitselfsupportspackexpansions(althoughno
suchattributeisdefinedbytheC++standard).
•Whenspecifyingthealignmentofadeclaration.
•Whenspecifyingthecapturelistofalambda.
•Intheparameterlistofafunctiontype.
•Inusingdeclarations(sinceC++17;seeSection4.4.5onpage65).We’ve
alreadymentionedsizeof…asapack-expansionmechanismthatdoesnot
actuallyproducealist.C++17alsoaddsfoldexpressions,whichareanother
mechanismthatdoesnotproduceacomma-separatedlist(seeSection12.4.6
onpage207.
Someofthesepack-expansioncontextsareincludedmerelyforthesakeof
completeness,sowewillfocusourattentionononlythosepack-expansion
contextsthattendtobeusefulinpractice.Sincepackexpansionsinallcontexts
followthesameprinciplesandsyntax,youshouldbeabletoextrapolatefrom
theexamplesgivenhereshouldyoufindaneedforthemoreesotericpack-
expansioncontexts.
Apackexpansioninalistofbaseclassesexpandstosomenumberofdirect
baseclasses.Suchexpansionscanbeusefultoaggregateexternallysupplieddata
andfunctionalityviamixins,whichareclassesintendedtobe“mixedinto”a
classhierarchytoprovidenewbehaviors.Forexample,thefollowingPoint
classusespackexpansionsinseveraldifferentcontextstoallowarbitrary
mixins:10
Clickheretoviewcodeimage
template<typename…Mixins>
classPoint:publicMixins…{//baseclasspackexpansion
doublex,y,z;
public:
Point():Mixins()…{}//baseclassinitializerpackexpansion
template<typenameVisitor>
voidvisitMixins(Visitorvisitor){
visitor(static_cast<Mixins&>(*this)…);//callargumentpack
expansion
}
};
structColor{charred,green,blue;};
structLabel{std::stringname;};
Point<Color,Label>p;//inheritsfrombothColorandLabel
ThePointclassusesapackexpansiontotakeeachofthesuppliedmixinsand
expanditintoapublicbaseclass.ThedefaultconstructorofPointthenapplies
apackexpansioninthebaseinitializerlisttovalue-initializeeachofthebase
classesintroducedviathemixinmechanism.
ThememberfunctiontemplatevisitMixinsisthemostinterestinginthat
itusestheresultsofapackexpansionasargumentstoacall.Bycasting*this
toeachofthemixintypes,thepackexpansionproducescallargumentsthatrefer
toeachofthebaseclassescorrespondingtothemixins.Actuallywritingavisitor
forusewithvisitMixins,whichcanmakeuseofsuchanarbitrarynumber
offunctioncallarguments,iscoveredinSection12.4.3onpage204.
Apackexpansioncanalsobeusedwithinatemplateparameterlisttocreatea
nontypeortemplateparameterpack:Clickheretoviewcodeimage
template<typename…Ts>
structValues{
template<Ts…Vs>
structHolder{
};
};
inti;
Values<char,int,int*>::Holder<’a’,17,&i>valueHolder;Notethat
oncethetypeargumentsforValues<…>havebeenspecified,the
nontypeargumentlistforValues<…>::Holderhasafixedlength;the
parameterpackVsisthusnotavariable-lengthparameterpack.
Valuesisanontypetemplateparameterpackforwhicheachoftheactual
templateargumentscanhaveadifferenttype,asspecifiedbythetypesprovided
forthetemplatetypeparameterpackTypes.Notethattheellipsisinthe
declarationofValuesplaysadualrole,bothdeclaringthetemplateparameter
asatemplateparameterpackanddeclaringthetypeofthattemplateparameter
packasapackexpansion.Whilesuchtemplateparameterpacksarerarein
practice,thesameprincipleappliesinamuchmoregeneralcontext:function
parameters.
12.4.3FunctionParameterPacks
Afunctionparameterpackisafunctionparameterthatmatcheszeroormore
functioncallarguments.Likeatemplateparameterpack,afunctionparameter
packisintroducedusinganellipsis(…)priorto(orintheplaceof)thefunction
parameternameand,alsolikeatemplateparameterpack,afunctionparameter
packmustbeexpandedbyapackexpansionwheneveritisused.Template
parameterpacksandfunctionparameterpackstogetherarereferredtoas
parameterpacks.
Unliketemplateparameterpacks,functionparameterpacksarealwayspack
expansions,sotheirdeclaredtypesmustincludeatleastoneparameterpack.In
thefollowingexample,weintroduceanewPointconstructorthatcopy-
initializeseachofthemixinsfromsuppliedconstructorarguments:Clickhereto
viewcodeimage
template<typename…Mixins>
classPoint:publicMixins…
{
doublex,y,z;
public:
//defaultconstructor,visitorfunction,etc.elided
Point(Mixins…mixins)//mixinsisafunctionparameterpack
:Mixins(mixins)…{}//initializeeachbasewiththesuppliedmixin
value
};
structColor{charred,green,blue;};
structLabel{std::stringname;};
Point<Color,Label>p({0x7F,0,0x7F},{"center"});Afunction
parameterpackforafunctiontemplatemaydependontemplate
parameterpacksdeclaredinthattemplate,whichallowsthefunction
templatetoacceptanarbitrarynumberofcallargumentswithout
losingtypeinformation:Clickheretoviewcodeimage
template<typename…Types>
voidprint(Types…values);
intmain
{
std::stringwelcome("Welcometo");
print(welcome,"C++",2011,’\n’);//callsprint<std::string,char
const*,
}//int,char>
Whencallingthefunctiontemplateprint()withsomenumberofarguments,
thetypesoftheargumentswillbeplacedintheargumentpacktobesubstituted
forthetemplatetypeparameterpackTypes,whiletheactualargumentvalues
willbeplacedintoanargumentpacktobesubstitutedforthefunctionparameter
packvalues.Theprocessbywhichtheargumentsaredeterminedfromthecall
isdescribedindetailinChapter15.Fornow,itsufficestonotethattheithtypein
TypesisthetypeoftheithvalueinValuesandthatbothoftheseparameter
packsareavailablewithinthebodyofthefunctiontemplateprint().
Theactualimplementationofprint()usesrecursivetemplateinstantiation,
atemplatemetaprogrammingtechniquedescribedinSection8.1onpage123
andChapter23.
Thereisasyntacticambiguitybetweenanunnamedfunctionparameterpack
appearingattheendofaparameterlistandaC-style“vararg”parameter.For
example:Clickheretoviewcodeimage
template<typenameT>voidc_style(int,T…);
template<typename…T>voidpack(int,T…);Inthefirstcase,the“T…”
istreatedas“T,…”:anunnamedparameteroftypeTfollowedbyaC-
stylevarargparameter.Inthesecondcase,the“T…”constructis
treatedasafunctionparameterpackbecauseTisavalidexpansion
pattern.Thedisambiguationcanbeforcedbyaddingacommabefore
theellipsis(whichensurestheellipsisistreatedasaC-style
“vararg”parameter)orbyfollowingthe…byanidentifier,which
makesitanamedfunctionparameterpack.Notethatingeneric
lambdas,atrailing…willbetreatedasdenotingaparameterpackif
thetypethatimmediatelyprecedesit(withnointerveningcomma)
containsauto.
12.4.4MultipleandNestedPackExpansions
Thepatternofapackexpansioncanbearbitrarilycomplexandmayinclude
multiple,distinctparameterpacks.Wheninstantiatingapackexpansion
containingmultipleparameterpacks,alloftheparameterpacksmusthavethe
samelength.Theresultingsequenceoftypesorvalueswillbeformedelement-
wisebysubstitutingthefirstargumentofeachparameterpackintothepattern,
followedbythesecondargumentofeachparameterpack,andsoon.For
example,thefollowingfunctioncopiesallofitsargumentsbeforeforwarding
themontothefunctionobjectf:Clickheretoviewcodeimage
template<typenameF,typename…Types>
voidforwardCopy(Ff,Typesconst&…values){
f(Types(values)…);
}
Thecallargumentpackexpansionnamestwoparameterspacks,Typesand
values.Wheninstantiatingthistemplate,theelement-wiseexpansionofthe
Typesandvaluesparameterpacksproducesaseriesofobjectconstructions,
whichbuildsacopyoftheithvalueinvaluesbycastingittotheithtypein
Types.Underthesyntacticinterpretationofpackexpansions,athree-argument
forwardCopywouldlooklikethis:Clickheretoviewcodeimage
template<typenameF,typenameT1,typenameT2,typenameT3>
voidforwardCopy(Ff,T1const&v1,T2const&v2,T3const&v3){
f(T1(v1),T2(v2),T3(v3));
}
Packexpansionsthemselvesmayalsobenested.Insuchcases,eachoccurrence
ofaparameterpackis“expanded”byitsnearestenclosingpackexpansion(and
onlythatpackexpansion).Thefollowingexamplesillustratesanestedpack
expansioninvolvingthreedifferentparameterpacks:Clickheretoviewcode
image
template<typename…OuterTypes>
classNested{
template<typename…InnerTypes>
voidf(InnerTypesconst&…innerValues){
g(OuterTypes(InnerTypes(innerValues)…)…);
}
};
Inthecalltog(),thepackexpansionwithpattern
InnerTypes(innerValues)istheinnermostpackexpansion,which
expandsbothInnerTypesandinnerValuesandproducesasequenceof
functioncallargumentsfortheinitializationofanobjectdenotedby
OuterTypes.Theouterpackexpansion’spatternincludestheinnerpack
expansion,producingasetofcallargumentsforthefunctiong(),createdfrom
theinitializationofeachofthetypesinOuterTypesfromthesequenceof
functioncallargumentsproducedbytheinnerexpansion.Underthesyntactic
interpretationofthispackexpansion,whereOuterTypeshastwoarguments
andbothInnerTypesandinnerValueshavethreearguments,thenesting
becomesmoreapparent:Clickheretoviewcodeimage
template<typenameO1,typenameO2>
classNested{
template<typenameI1,typenameI2,typenameI3>
voidf(I1const&iv1,I2const&iv2,I3const&iv3){
g(O1(I1(iv1),I2(iv2),I3(iv3)),
O2(I1(iv1),I2(iv2),I3(iv3)),
O3(I1(iv1),I2(iv2),I3(iv3)));
}
};
Multipleandnestedpackexpansionsareapowerfultool(e.g.,seeSection26.2
onpage608).
12.4.5Zero-LengthPackExpansions
Thesyntacticinterpretationofpackexpansionscanbeausefultoolfor
understandinghowaninstantiationofavariadictemplatewillbehavewith
differentnumbersofarguments.However,thesyntacticinterpretationoftenfails
inthepresenceofzero-lengthargumentpacks.Toillustratethis,considerthe
PointclasstemplatefromSection12.4.2onpage202,syntacticallysubstituted
withzeroarguments:template<>
classPoint:{
Point():{}
};
Thecodeaswrittenaboveisill-formed,sincethetemplateparameterlistisnow
emptyandtheemptybaseclassandbaseclassinitializerlistseachhaveastray
coloncharacter.
Packexpansionsareactuallysemanticconstructs,andthesubstitutionofan
argumentpackofanysizedoesnotaffecthowthepackexpansion(orits
enclosingvariadictemplate)isparsed.Rather,whenapackexpansionexpands
toanemptylist,theprogrambehaves(semantically)asifthelistwerenot
present.TheinstantiationPoint<>endsuphavingnobaseclasses,andits
defaultconstructorhasnobaseclassinitializersbutisotherwisewell-formed.
Thissemanticrulesholdsevenwhenthesyntacticinterpretationofzero-length
packexpansionwouldbewell-defined(butdifferent)code.Forexample:Click
heretoviewcodeimage
template<typenameT,typename…Types>
voidg(Types…values){
Tv(values…);
}
Thevariadicfunctiontemplateg()createsavaluevthatisdirect-initialized
fromthesequenceofvaluesitisgiven.Ifthatsequenceofvaluesisempty,the
declarationofvlooks,syntactically,likeafunctiondeclarationTv().
However,sincesubstitutionintoapackexpansionissemanticandcannotaffect
thekindofentityproducedbyparsing,visinitializedwithzeroarguments,that
is,value-initialization.11
12.4.6FoldExpressions
Arecurringpatterninprogrammingisthefoldofanoperationonasequenceof
values.Forexample,arightfoldofafunctionfnoverasequencex[1],
x[2],…,x[n-1],x[n]isgivenbyfn(x[1],fn(x[2],fn(…,
fn(x[n-1],x[n])…)))Whileexploringanewlanguagefeature,theC++
committeeranintotheneedtodealwithsuchconstructsforthespecialcaseofa
logicalbinaryoperator(i.e.,&&or||)appliedtoapackexpansion.Withoutan
extrafeature,wemightwritethefollowingcodetoachievethatforthe&&
operator:Clickheretoviewcodeimage
booland_all(){returntrue;}
template<typenameT>
booland_all(Tcond){returncond;}
template<typenameT,typename…Ts>
booland_all(Tcond,Ts…conds){
returncond&&and_all(conds…);
}
WithC++17,anewfeaturecalledfoldexpressionswasadded(seeSection4.2
onpage58foranintroduction).Itappliestoallbinaryoperatorsexcept.,->,
and[].
Givenanunexpandedexpressionpatternpackandanonpatternexpression
value,C++17allowsustowriteforanysuchoperatorop,either(packop…op
value)forarightfoldoftheoperator(calledabinaryrightfold),or(valueop…
oppack)foraleftfold(calledabinaryleftfold).Notethattheparenthesesare
requiredhere.SeeSection4.2onpage58forsomebasicexamples.
Thefoldoperationappliestothesequencethatresultsfromexpandingthe
packandaddingvalueaseitherthelastelementofthesequence(foraright
fold)orthefirstelementofthesequence(foraleftfold).
Withthisfeatureavailable,codelike
Clickheretoviewcodeimage
template<typename…T>boolg(){
returnand_all(trait<T>()…);
}
(whereand_allisasdefinedabove),caninsteadbewrittenasClickhereto
viewcodeimage
template<typename…T>boolg(){
return(trait<T>()&&…&&true);
}
Asyou’dexpect,foldexpressionsarepackexpansions.Notethatifthepackis
empty,thetypeofthefoldexpressioncanstillbedeterminedfromthenon-pack
operand(valueintheformsabove).
However,thedesignersofthisfeaturealsowantedanoptiontoleaveoutthe
valueoperand.TwootherformsarethereforeavailableinC++17:Theunary
rightfold
(packop…)andtheunaryleftfold
(…oppack)Again,theparenthesesarerequired.Clearlythiscreates
aproblemforemptyexpansions:Howdowedeterminetheirtypeand
value?Theansweristhatanemptyexpansionofaunaryfoldis
generallyanerror,withthreeexceptions:•Anemptyexpansionofa
unaryfoldof&&producesthevaluetrue,.
•Anemptyexpansionofaunaryfoldof||producesthevaluefalse.
•Anemptyexpansionofaunaryfoldofthecommaoperator(,)producesa
voidexpression.
Notethatthiswillcreatesurprisesifyouoverloadoneofthesespecialoperators
inasomewhatunusualway.Forexample:Clickheretoviewcodeimage
structBooleanSymbol{
…
};
BooleanSymboloperator||(BooleanSymbol,BooleanSymbol);
template<typename…BTs>voidsymbolic(BTs…ps){
BooleanSymbolresult=(ps||…);
…
}
Supposewecallsymbolicwithtypesthatarederivedfrom
BooleanSymbol.Forallexpansions,theresultwillproducea
BooleanSymbolvalueexceptfortheemptyexpansion,whichwillproducea
boolvalue.12Wethereforegenerallycautionagainsttheuseofunaryfold
expressions,andrecommendusingbinaryfoldexpressionsinstead(withan
explicitlyspecifiedemptyexpansionvalue).
12.5Friends
Thebasicideaoffrienddeclarationsisasimpleone:Identifyclassesor
functionsthathaveaprivilegedconnectionwiththeclassinwhichthefriend
declarationappears.Mattersaresomewhatcomplicated,however,bytwofacts:
1.Afrienddeclarationmaybetheonlydeclarationofanentity.
2.Afriendfunctiondeclarationcanbeadefinition.
12.5.1FriendClassesofClassTemplates
Friendclassdeclarationscannotbedefinitionsandthereforearerarely
problematic.Inthecontextoftemplates,theonlynewfacetoffriendclass
declarationsistheabilitytonameaparticularinstanceofaclasstemplateasa
friend:template<typenameT>
classNode;
template<typenameT>
classTree{
friendclassNode<T>;
…
};
Notethattheclasstemplatemustbevisibleatthepointwhereoneofits
instancesismadeafriendofaclassorclasstemplate.Withanordinaryclass,
thereisnosuchrequirement:Clickheretoviewcodeimage
template<typenameT>
classTree{
friendclassFactory;//OKeveniffirstdeclarationofFactory
friendclassNode<T>;//errorifNodeisn’tvisible
};
Section13.2.2onpage220hasmoretosayaboutthis.
Oneapplication,introducedinSection5.5onpage75,isthedeclarationof
otherclasstemplateinstantiationstobefriends:Clickheretoviewcodeimage
template<typenameT>
classStack{
public:
…
//assignstackofelementsoftypeT2
template<typenameT2>
Stack<T>&operator=(Stack<T2>const&);
//togetaccesstoprivatemembersofStack<T2>foranytypeT2:
template<typename>friendclassStack;
…
};
C++11alsoaddedsyntaxtomakeatemplateparameterafriend:
template<typenameT>
classWrap{
friendT;
…
};
ThisisvalidforanytypeTbutisignoredifTisnotactuallyaclasstype.13
12.5.2FriendFunctionsofClassTemplates
Aninstanceofafunctiontemplatecanbemadeafriendbymakingsurethe
nameofthefriendfunctionisfollowedbyanglebrackets.Theanglebrackets
cancontainthetemplatearguments,butiftheargumentscanbededuced,the
anglebracketscanbeleftempty:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
voidcombine(T1,T2);
classMixer{
friendvoidcombine<>(int&,int&);
//OK:T1=int&,T2=int&
friendvoidcombine<int,int>(int,int);
//OK:T1=int,T2=int
friendvoidcombine<char>(char,int);
//OK:T1=charT2=int
friendvoidcombine<char>(char&,int);
//ERROR:doesn’tmatchcombine()template
friendvoidcombine<>(long,long){…}
//ERROR:definitionnotallowed!
};
Notethatwecannotdefineatemplateinstance(atmost,wecandefinea
specialization),andhenceafrienddeclarationthatnamesaninstancecannotbea
definition.
Ifthenameisnotfollowedbyanglebrackets,therearetwopossibilities:1.If
thenameisn’tqualified(inotherwords,itdoesn’tcontain::),itneverrefersto
atemplateinstance.Ifnomatchingnontemplatefunctionisvisibleatthepointof
thefrienddeclaration,thefrienddeclarationisthefirstdeclarationofthat
function.Thedeclarationcouldalsobeadefinition.
2.Ifthenameisqualified(itcontains::),thenamemustrefertoapreviously
declaredfunctionorfunctiontemplate.Amatchingfunctionispreferredover
amatchingfunctiontemplate.However,suchafrienddeclarationcannotbea
definition.Anexamplemayhelpclarifythevariouspossibilities:Clickhereto
viewcodeimage
voidmultiply(void*);//ordinaryfunction
template<typenameT>
voidmultiply(T);//functiontemplate
classComrades{
friendvoidmultiply(int){}
//definesanewfunction::multiply(int)
friendvoid::multiply(void*);
//referstotheordinaryfunctionabove,
//nottothemultiply<void*>instance
friendvoid::multiply(int);
//referstoaninstanceofthetemplate
friendvoid::multiply<double*>(double*);
//qualifiednamescanalsohaveanglebrackets,
//butatemplatemustbevisible
friendvoid::error(){}
//ERROR:aqualifiedfriendcannotbeadefinition
};
Inourpreviousexamples,wedeclaredthefriendfunctionsinanordinaryclass.
Thesamerulesapplywhenwedeclaretheminclasstemplates,butthetemplate
parametersmayparticipateinidentifyingthefunctionthatistobeafriend:Click
heretoviewcodeimage
template<typenameT>
classNode{
Node<T>*allocate();
…
};
template<typenameT>
classList{
friendNode<T>*Node<T>::allocate();
…
};
Afriendfunctionmayalsobedefinedwithinaclasstemplate,inwhichcaseitis
onlyinstantiatedwhenitisactuallyused.Thistypicallyrequiresthefriend
functiontousetheclasstemplateitselfinthetypeofthefriendfunction,which
makesiteasiertoexpressfunctionsontheclasstemplatethatcanbecalledasif
theywerevisibleinnamespacescope:Clickheretoviewcodeimage
template<typenameT>
classCreator{
friendvoidfeed(Creator<T>){//everyTinstantiatesadifferent
function::feed()
…
}
};
intmain()
{
Creator<void>one;
feed(one);//instantiates::feed(Creator<void>)
Creator<double>two;
feed(two);//instantiates::feed(Creator<double>)
}
Inthisexample,everyinstantiationofCreatorgeneratesadifferentfunction.
Notethateventhoughthesefunctionsaregeneratedaspartoftheinstantiationof
atemplate,thefunctionsthemselvesareordinaryfunctions,notinstancesofa
template.However,theyareconsideredtemplatedentities(seeSection12.1on
page181)andtheirdefinitionisinstantiatedonlywhenused.Alsonotethat
becausethebodyofthesefunctionsisdefinedinsideaclassdefinition,theyare
implicitlyinline.Hence,itisnotanerrorforthesamefunctiontobegenerated
intwodifferenttranslationunits.Section13.2.2onpage220andSection21.2.1
onpage497havemoretosayaboutthistopic.
12.5.3FriendTemplates
Usuallywhendeclaringafriendthatisaninstanceofafunctionoraclass
template,wecanexpressexactlywhichentityistobethefriend.Sometimesitis
nonethelessusefultoexpressthatallinstancesofatemplatearefriendsofa
class.Thisrequiresafriendtemplate.Forexample:Clickheretoviewcode
image
classManager{
template<typenameT>
friendclassTask;
template<typenameT>
friendvoidSchedule<T>::dispatch(Task<T>*);
template<typenameT>
friendintticket(){
return++Manager::counter;
}
staticintcounter;
};
Justaswithordinaryfrienddeclarations,afriendtemplatecanbeadefinition
onlyifitnamesanunqualifiedfunctionnamethatisnotfollowedbyangle
brackets.
Afriendtemplatecandeclareonlyprimarytemplatesandmembersofprimary
templates.Anypartialspecializationsandexplicitspecializationsassociatedwith
aprimarytemplateareautomaticallyconsideredfriendstoo.
12.6Afternotes
ThegeneralconceptandsyntaxofC++templateshaveremainedrelatively
stablesincetheirinceptioninthelate1980s.Classtemplatesandfunction
templateswerepartoftheinitialtemplatefacility.Soweretypeparametersand
nontypeparameters.
However,therewerealsosomesignificantadditionstotheoriginaldesign,
mostlydrivenbytheneedsoftheC++standardlibrary.Membertemplatesmay
wellbethemostfundamentalofthoseadditions.Curiously,onlymember
functiontemplateswereformallyvotedintotheC++standard.Memberclass
templatesbecamepartofthestandardbyaneditorialoversight.
Friendtemplates,defaulttemplatearguments,andtemplatetemplate
parameterscameafterwardduringthestandardizationofC++98.Theabilityto
declaretemplatetemplateparametersissometimescalledhigher-order
genericity.Theywereoriginallyintroducedtosupportacertainallocatormodel
intheC++standardlibrary,butthatallocatormodelwaslaterreplacedbyone
thatdoesnotrelyontemplatetemplateparameters.Later,templatetemplate
parameterscameclosetobeingremovedfromthelanguagebecausetheir
specificationhadremainedincompleteuntilverylateinthestandardization
processforthe1998standard.Eventuallyamajorityofcommitteemembers
votedtokeepthemandtheirspecificationswerecompleted.
Aliastemplateswereintroducedaspartofthe2011standard.Aliastemplates
servethesameneedsastheoft-requested“typedeftemplates”featurebymaking
iteasytowriteatemplatethatismerelyadifferentspellingofanexistingclass
template.Thespecification(N2258)thatmadeitintothestandardwasauthored
byGabrielDosReisandBjarneStroustrup;MatMarcusalsocontributedto
someoftheearlydraftsofthatproposal.Gabyalsoworkedoutthedetailsofthe
variabletemplateproposalforC++14(N3651).Originally,theproposalonly
intendedtosupportconstexprvariables,butthatrestrictionwasliftedbythe
timeitwasadoptedinthedraftstandard.
VariadictemplatesweredrivenbytheneedsoftheC++11standardlibraryand
theBoostlibraries(see[Boost]),whereC++templatelibrarieswereusing
increasinglyadvanced(andconvoluted)techniquestoprovidetemplatesthat
acceptanarbitrarynumberoftemplatearguments.DougGregor,JaakkoJ¨arvi,
GaryPowell,JensMaurer,andJasonMerrillprovidedtheinitialspecification
forthestandard(N2242).Dougalsodevelopedtheoriginalimplementationof
thefeature(inGNU’sGCC)whilethespecificationwasbeingdeveloped,which
muchhelpedtheabilitytousethefeatureinthestandardlibrary.
FoldexpressionsweretheworkofAndrewSuttonandRichardSmith:They
wereaddedtoC++17throughtheirpaperN4191.
1AnexceptionsinceC++14aretheimplicittemplatetypeparametersfora
genericlambda;seeSection15.10.6onpage309.
2Thekeywordclassdoesnotimplythatthesubstitutingargumentshouldbe
aclasstype.Itcouldbeanyaccessibletype.
3Templatetemplateparametersdonotdenotetypeseither;however,theyare
distinctfromnontypeparameters.Thisoddityishistorical:Templatetemplate
parameterswereaddedtothelanguageaftertypeparametersandnontype
parameters.
4Atthetimeofthiswriting,only“pointertoobject”and“pointertofunction”
typesarepermitted,whichexcludestypeslikevoid*.However,all
compilersappeartoacceptvoid*also.
5SeeAppendixBforadiscussionofvaluecategoriessuchasrvaluesand
lvalues.
6Templateargumentsforsubsequenttemplateparameterscanstillbe
determinedbytemplateargumentdeduction;seeChapter15.
7However,aconstructortemplatecanbeadefaultconstructor.
8ThetermvariadicisborrowedfromC’svariadicfunctions,whichaccepta
variablenumberoffunctionarguments.Variadictemplatesalsoborrowed
fromCtheuseoftheellipsistodenotezeroormoreargumentsandare
intendedasatype-safereplacementforC’svariadicfunctionsforsome
applications.
9Thissyntacticunderstandingofpackexpansionsisausefultool,butitbreaks
downwhenthetemplateparameterpackshavelengthzero.Section12.4.5on
page207providesmoredetailsabouttheinterpretationofzero-lengthpack
expansions.
10MixinsarediscussedinfurtherdetailinSection21.3onpage508.
11Thereisasimilarrestrictiononmembersofclasstemplatesandnestedclasses
withinclasstemplates:Ifamemberisdeclaredwithatypethatdoesnot
appeartobeafunctiontype,butafterinstantiationthetypeofthatmemberisa
functiontype,theprogramisill-formedbecausethesemanticinterpretationof
thememberhaschangedfromadatamembertoamemberfunction.
12Becauseoverloadingthesethreespecialoperatorsisunusual,thisproblemis
fortunatelyrare(butsubtle).Theoriginalproposalforfoldexpressions
includedemptyexpansionvaluesformorecommonoperatorslike+and*,
whichwouldhavecausedmoreseriousproblems.
13ThiswastheveryfirstextensionaddedtoC++11,thankstoaproposalby
WilliamM.“Mike”Miller.
Chapter13
NamesinTemplates
Namesareafundamentalconceptinmostprogramminglanguages.Theyarethe
meansbywhichaprogrammercanrefertopreviouslyconstructedentities.
WhenaC++compilerencountersaname,itmust“lookitup”toidentifythe
entitybeingreferred.Fromanimplementer’spointofview,C++isahard
languageinthisrespect.ConsidertheC++statementx*y;.Ifxandyarethe
namesofvariables,thisstatementisamultiplication,butifxisthenameofa
type,thenthestatementdeclaresyasapointertoanentityoftypex.
ThissmallexampledemonstratesthatC++(likeC)isacontext-sensitive
language:Aconstructcannotalwaysbeunderstoodwithoutknowingitswider
context.Howdoesthisrelatetotemplates?Well,templatesareconstructsthat
mustdealwithmultiplewidercontexts:(1)thecontextinwhichthetemplate
appears,(2)thecontextinwhichthetemplateisinstantiated,and(3)thecontexts
associatedwiththetemplateargumentsforwhichthetemplateisinstantiated.
Henceitshouldnotbetotallysurprisingthat“names”mustbedealtwithquite
carefullyinC++.
13.1NameTaxonomy
C++classifiesnamesinavarietyofways—alargevarietyofways,infact.To
helpcopewiththisabundanceofterminology,weprovideTable13.1andTable
13.2,whichdescribetheseclassifications.Fortunately,youcangaingoodinsight
intomostC++templateissuesbyfamiliarizingyourselfwithtwomajornaming
concepts:1.Anameisaqualifiednameifthescopetowhichitbelongsis
explicitlydenotedusingascope-resolutionoperator(::)oramemberaccess
operator(.or->).Forexample,this->countisaqualifiedname,but
countisnot(eventhoughtheplaincountmightactuallyrefertoaclass
member).
2.Anameisadependentnameifitdependsinsomewayonatemplate
parameter.Forexample,std::vector<T>::iteratorisusuallya
dependentnameifTisatemplateparameter,butitisanondependentnameif
Tisaknowntypealias(suchastheTfromusingT=int).
Classification ExplanationandNotes
Identifier Anamethatconsistssolelyofanuninterruptedsequencesof
letters,underscores(_),anddigits.Itcannotstartwithadigit,
andsomeidentifiersarereservedfortheimplementation:You
shouldnotintroducetheminyourprograms(asaruleofthumb,
avoidleadingunderscoresanddoubleunderscores).Theconcept
of“letter”shouldbetakenbroadlyandincludesspecial
universalcharacternames(UCNs)thatencodeglyphsfrom
nonalphabeticallanguages.
Operator-
function-id
Thekeywordoperatorfollowedbythesymbolforan
operator—forexample,operatornewandoperator[
].1
Conversion-
function-id
Usedtodenoteauser-definedimplicitconversionoperator—for
example,operatorint&,whichcouldalsobeobfuscatedas
operatorintbitand.
Literal-
operator-id
Usedtodenoteauser-definedliteraloperator—forexample,
operator""_km,whichwillbeusedwhenwritingaliteral
suchas100_km(introducedinC++11).
Template-id Thenameofatemplatefollowedbytemplatearguments
enclosedinanglebrackets;forexample,List<T,int,0>.
Atemplate-idmayalsobeanoperator-function-idoraliteral-
operator-idfollowedbytemplateargumentsenclosedinangle
brackets;forexample,operator+<X<int>>.
Unqualified-
id
Thegeneralizationofanidentifier.Itcanbeanyoftheabove
(identifier,operator-function-id,conversion-function-id,literal-
operator-id,ortemplate-id)ora“destructorname”(e.g.,
notationslike~Dataor~List<T,T,N>).
Qualified-id Anunqualified-idthatisqualifiedwiththenameofaclass,
enum,ornamespace,orjustwiththeglobalscoperesolution
operator.Notethatsuchanameitselfcanbequalified.Examples
are::X,S::x,Array<T>::y,and::N::A<T>::z.
Qualified
name
Thistermisnotdefinedinthestandard,butweuseittoreferto
namesthatundergoqualifiedlookup.Specifically,thisisa
qualifiedidoranunqualified-idthatisusedafteranexplicit
memberaccessoperator(.or->).ExamplesareS::x,this-
>f,andp->A::m.However,justclass_meminacontextthat
isimplicitlyequivalenttothis->class_memisnota
qualifiedname:Thememberaccessmustbeexplicit.
Unqualified
name
Anunqualified-idthatisnotaqualifiedname.Thisisnota
standardtermbutcorrespondstonamesthatundergowhatthe
standardcallsunqualifiedlookup.
Name Eitheraqualifiedoranunqualifiedname.
Table13.1.NameTaxonomy(Part1)
Classification ExplanationandNotes
Dependent
name
Anamethatdependsinsomewayonatemplateparameter.
Typically,aqualifiedorunqualifiednamethatexplicitly
containsatemplateparameterisdependent.Furthermore,a
qualifiednamethatisqualifiedbyamemberaccessoperator(.
or->)istypicallydependentifthetypeoftheexpressiononthe
leftoftheaccessoperatoristype-dependent,aconceptthatis
discussedinSection13.3.6onpage233.Inparticular,bin
this->bisgenerallyadependentnamewhenitappearsina
template.Finally,anamethatissubjecttoargument-dependent
lookup(describedinSection13.2onpage217),suchasident
inacalloftheformident(x,y)or+intheexpressionx+
y,isadependentnameifandonlyifanyoftheargument
expressionsistype-dependent.
Nondependent
name
Anamethatisnotadependentnamebytheabovedescription.
Table13.2.NameTaxonomy(Part2)
Itisusefultoreadthroughthetablestogainsomefamiliaritywiththetermsthat
aresometimesusedtodescribeC++templateissues,butitisnotessentialto
remembertheexactmeaningofeveryterm.Shouldtheneedarise,theycanbe
foundeasilyintheindex.
13.2LookingUpNames
TherearemanysmalldetailstolookingupnamesinC++,butwewillfocusonly
onafewmajorconcepts.Thedetailsarenecessarytoensureonlythat(1)normal
casesaretreatedintuitively,and(2)pathologicalcasesarecoveredinsomeway
bythestandard.
Qualifiednamesarelookedupinthescopeimpliedbythequalifying
construct.Ifthatscopeisaclass,thenbaseclassesmayalsobesearched.
However,enclosingscopesarenotconsideredwhenlookingupqualifiednames.
Thefollowingillustratesthisbasicprinciple:Clickheretoviewcodeimage
intx;
classB{
public:
inti;
};
classD:publicB{
};
voidf(D*pd)
{
pd->i=3;//findsB::i
D::x=2;//ERROR:doesnotfind::xintheenclosingscope
}
Incontrast,unqualifiednamesaretypicallylookedupinsuccessivelymore
enclosingscopes(al-thoughinmemberfunctiondefinitions,thescopeofthe
classanditsbaseclassesissearchedbeforeanyotherenclosingscopes).Thisis
calledordinarylookup.Hereisabasicexampleshowingthemainidea
underlyingordinarylookup:Clickheretoviewcodeimage
externintcount;//#1
intlookup_example(intcount)//#2
{
if(count<0){
intcount=1;//#3
lookup_example(count);//unqualifiedcountrefersto#3
}
returncount+::count;//thefirst(unqualified)countrefersto
#2;
}//thesecond(qualified)countrefersto#1
Amorerecenttwisttothelookupofunqualifiednamesisthat—inadditionto
ordinarylookup—theymaysometimesundergoargument-dependentlookup
(ADL).2BeforeproceedingwiththedetailsofADL,let’smotivatethe
mechanismwithourperennialmax()template:template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
Supposenowthatweneedtoapplythistemplatetoatypedefinedinanother
namespace:Clickheretoviewcodeimage
namespaceBigMath{
classBigNumber{
…
};
booloperator<(BigNumberconst&,BigNumberconst&);
…
}
usingBigMath::BigNumber;
voidg(BigNumberconst&a,BigNumberconst&b)
{
…
BigNumberx=::max(a,b);
…
}
Theproblemhereisthatthemax()templateisunawareoftheBigMath
namespace,butordinarylookupwouldnotfindtheoperator<applicableto
valuesoftypeBigNumber.Withoutsomespecialrules,thisgreatlyreducesthe
applicabilityoftemplatesinthepresenceofC++namespaces.ADListheC++
answertothose“specialrules.”
13.2.1Argument-DependentLookup
ADLappliesprimarilytounqualifiednamesthatlookliketheynamea
nonmemberfunctioninafunctioncalloroperatorinvocation.ADLdoesnot
happenifordinarylookupfinds•thenameofamemberfunction,
•thenameofavariable,
•thenameofatype,or
•thenameofablock-scopefunctiondeclaration.
ADLisalsoinhibitedifthenameofthefunctiontobecalledisenclosedin
parentheses.
Otherwise,ifthenameisfollowedbyalistofargumentexpressionsenclosed
inparentheses,ADLproceedsbylookingupthenameinnamespacesandclasses
“associatedwith”thetypesofthecallarguments.Theprecisedefinitionofthese
associatednamespacesandassociatedclassesisgivenlater,butintuitivelythey
canbethoughtofasbeingallthenamespacesandclassesthatarefairlydirectly
connectedtoagiventype.Forexample,ifthetypeisapointertoaclassX,then
theassociatedclassesandnamespacewouldincludeXaswellasany
namespacesorclassestowhichXbelongs.
Theprecisedefinitionofthesetofassociatednamespacesandassociated
classesforagiventypeisdeterminedbythefollowingrules:•Forbuilt-intypes,
thisistheemptyset.
•Forpointerandarraytypes,thesetofassociatednamespacesandclassesisthat
oftheunderlyingtype.
•Forenumerationtypes,theassociatednamespaceisthenamespaceinwhich
theenumerationisdeclared.
•Forclassmembers,theenclosingclassistheassociatedclass.
•Forclasstypes(includinguniontypes),thesetofassociatedclassesisthetype
itself,theenclosingclass,andanydirectandindirectbaseclasses.Thesetof
associatednamespacesisthenamespacesinwhichtheassociatedclassesare
declared.Iftheclassisaclasstemplateinstance,thenthetypesofthetemplate
typeargumentsandtheclassesandnamespacesinwhichthetemplatetemplate
argumentsaredeclaredarealsoincluded.
•Forfunctiontypes,thesetsofassociatednamespacesandclassescomprisethe
namespacesandclassesassociatedwithalltheparametertypesandthose
associatedwiththereturntype.
•Forpointer-to-member-of-class-Xtypes,thesetsofassociatednamespacesand
classesincludethoseassociatedwithXinadditiontothoseassociatedwiththe
typeofthemember.(Ifitisapointer-to-member-functiontype,thenthe
parameterandreturntypescancontributetoo.)ADLthenlooksupthenamein
alltheassociatednamespacesasifthenamehadbeenqualifiedwitheachof
thesenamespacesinturn,exceptthatusingdirectivesareignored.The
followingexampleillustratesthis:Clickheretoviewcodeimage
details/adl.cpp
#include<iostream>
namespaceX{
template<typenameT>voidf(T);
}
namespaceN{
usingnamespaceX;
enumE{e1};
voidf(E){
std::cout<<"N::f(N::E)called\n";
}
}
voidf(int)
{
std::cout<<"::f(int)called\n";
}
intmain()
{
::f(N::e1);//qualifiedfunctionname:noADL
f(N::e1);//ordinarylookupfinds::f()andADLfindsN::f(),
}//thelatterispreferred
Notethatinthisexample,theusingdirectiveinnamespaceNisignoredwhen
ADLisperformed.HenceX::f()isneverevenacandidateforthecallin
main().
13.2.2Argument-DependentLookupofFriend
Declarations
Afriendfunctiondeclarationcanbethefirstdeclarationofthenominated
function.Ifthisisthecase,thenthefunctionisassumedtobedeclaredinthe
nearestnamespacescope(whichmaybetheglobalscope)enclosingtheclass
containingthefrienddeclaration.However,suchafrienddeclarationisnot
directlyvisibleinthatscope.Considerthefollowingexample:Clickhereto
viewcodeimage
template<typenameT>
classC{
…
friendvoidf();
friendvoidf(C<T>const&);
…
};
voidg(C<int>*p)
}
{f();//isf()visiblehere?
f(*p);//isf(C<int>const&)visiblehere?
}
Iffrienddeclarationswerevisibleintheenclosingnamespace,theninstantiating
aclasstemplatemaymakevisiblethedeclarationofordinaryfunctions.This
wouldleadtosurprisingbehavior:Thecallf()wouldresultinacompilation
errorunlessaninstantiationoftheclassCoccurredearlierintheprogram!
Ontheotherhand,itcanbeusefultodeclare(anddefine)afunctionina
frienddeclarationonly(seeSection21.2.1onpage497foratechniquethat
reliesonthisbehavior).Suchafunctioncanbefoundwhentheclassofwhich
theyareafriendisamongtheassociatedclassesconsideredbyADL.
Reconsiderourlastexample.Thecallf()hasnoassociatedclassesor
namespacesbecausetherearenoarguments:Itisaninvalidcallinourexample.
However,thecallf(*p)doeshavetheassociatedclassC<int>(becausethis
isthetypeof*p),andtheglobalnamespaceisalsoassociated(becausethisis
thenamespaceinwhichthetypeof*pisdeclared).Therefore,thesecondfriend
functiondeclarationcouldbefoundprovidedtheclassC<int>wasactually
fullyinstantiatedpriortothecall.Toensurethis,itisassumedthatacall
involvingalookupforfriendsinassociatedclassesactuallycausestheclassto
beinstantiated(ifnotdonealready).3
Theabilityofargument-dependentlookuptofindfrienddeclarationsand
definitionissometimesreferredtoasfriendnameinjection.However,thisterm
issomewhatmisleading,becauseitisthenameofapre-standardC++feature
thatdidinfact“inject”thenamesoffrienddeclarationsintotheenclosingscope,
makingthemvisibletonormalnamelookup.Inourexampleabove,thiswould
meanthatbothcallswouldbewell-formed.Thischapter’safternotesfurther
detailthehistoryoffriendnameinjection.
13.2.3InjectedClassNames
Thenameofaclassisinjectedinsidethescopeofthatclassitselfandis
thereforeaccessibleasanunqualifiednameinthatscope.(However,itisnot
accessibleasaqualifiednamebecausethisisthenotationusedtodenotethe
constructors.)Forexample:Clickheretoviewcodeimage
details/inject.cpp
#include<iostream>
intC;
classC{
private:
inti[2];
public:
staticintf(){
returnsizeof(C);
}
};
intf()
{
returnsizeof(C);
}
intmain()
{
std::cout<<"C::f()="<<C::f()<<’,’
<<"::f()="<<::f()<<’\n’;
}
ThememberfunctionC::f()returnsthesizeoftypeC,whereasthefunction
::f()returnsthesizeofthevariableC(inotherwords,thesizeofanint
object).
Classtemplatesalsohaveinjectedclassnames.However,they’restranger
thanordinaryinjectedclassnames:Theycanbefollowedbytemplatearguments
(inwhichcasetheyareinjectedclasstemplatenames),butiftheyarenot
followedbytemplateargumentstheyrepresenttheclasswithitsparametersas
itsarguments(or,forapartialspecialization,itsspecializationarguments)ifthe
contextexpectsatype,oratemplateifthecontextexpectsatemplate.This
explainsthefollowingsituation:Clickheretoviewcodeimage
template<template<typename>classTT>classX{
};
template<typenameT>classC{
C*a;//OK:sameas“C<T>*a;”
C<void>&b;//OK
X<C>c;//OK:Cwithoutatemplateargumentlistdenotesthe
templateC
X<::C>d;//OK:::Cisnottheinjectedclassnameandtherefore
always
//denotesthetemplate
};
Notehowtheunqualifiednamereferstotheinjectednameandisnotconsidered
thenameofthetemplateifitisnotfollowedbyalistoftemplatearguments.To
compensate,wecanforcethenameofthetemplatetobefoundbyusingthefile
scopequalifier::.
Theinjectedclassnameforavariadictemplatehasanadditionalwrinkle:If
theinjectedclassnameweredirectlyformedbyusingthevariadictemplate’s
templateparametersasthetemplatearguments,theinjectedclassnamewould
containtemplateparameterpacksthathavenotbeenexpanded(seeSection
12.4.1onpage201fordetailsofpackexpansion).Therefore,whenformingthe
injectedclassnameforavariadictemplate,thetemplateargumentthat
correspondstoatemplateparameterpackisapackexpansionwhosepatternis
thattemplateparameterpack:Clickheretoviewcodeimage
template<intI,typename…T>classV{
V*a;//OK:sameas“V<I,T…>*a;”
V<0,void>b;//OK
};
13.2.4CurrentInstantiations
Theinjectedclassnameofaclassorclasstemplateiseffectivelyanaliasforthe
typebeingdefined.Foranontemplateclass,thispropertyisobvious,becausethe
classitselfistheonlytypewiththatnameandinthatscope.However,insidea
classtemplateoranestedclasswithinaclasstemplate,eachtemplate
instantiationproducesadifferenttype.Thispropertyisparticularlyinterestingin
thatcontext,becauseitmeansthattheinjectedclassnamereferstothesame
instantiationoftheclasstemplateratherthansomeotherspecializationofthat
classtemplate(thesameholdsfornestedclassesofclasstemplates).
Withinaclasstemplate,theinjectedclassnameoranytypethatisequivalent
totheinjectedclassname(includinglookingthroughtypealiasdeclarations)of
anyenclosingclassorclasstemplateissaidtorefertoacurrentinstantiation.
Typesthatdependonatemplateparameter(i.e.,dependenttypes)butdonot
refertoacurrentinstantiationaresaidtorefertoanunknownspecialization,
whichmaybeinstantiatedfromthesameclasstemplateorsomeentirely
differentclasstemplate.Thefollowingexampleillustratesthedifference:Click
heretoviewcodeimage
template<typenameT>classNode{
usingType=T;
Node*next;//Nodereferstoacurrentinstantiation
Node<Type>*previous;//Node<Type>referstoacurrentinstantiation
Node<T*>*parent;//Node<T*>referstoanunknownspecialization
};
Identifyingwhetheratypereferstoacurrentinstantiationcanbeconfusingin
thepresenceofnestedclassesandclasstemplates.Theinjectedclassnamesof
enclosingclassesandclasstemplates(ortypesequivalenttothem)dorefertoa
currentinstantiation,whilethenamesofothernestedclassesorclasstemplates
donot:Clickheretoviewcodeimage
template<typenameT>classC{
usingType=T;
structI{
C*c;//Creferstoacurrentinstantiation
C<Type>*c2;//C<Type>referstoacurrentinstantiation
I*i;//Ireferstoacurrentinstantiation
};
structJ{
C*c;//Creferstoacurrentinstantiation
C<Type>*c2;//C<Type>referstoacurrentinstantiation
I*i;//Ireferstoanunknownspecialization,
//becauseIdoesnotenclose
JJ*j;//Jreferstoacurrentinstantiation
};
};
Whenatypereferstoacurrentinstantiation,thecontentsofthatinstantiated
classareguaranteedtobeinstantiatedfromtheclasstemplateornestedclass
thereofthatiscurrentlybeingdefined.Thishasimplicationsfornamelookup
whenparsingtemplates—thesubjectofournextsection—butitalsoleadstoan
alternative,moregame-likewaytodeterminewhetheratypeXwithinthe
definitionofaclasstemplatereferstoacurrentinstantiationoranunknown
specialization:Ifanotherprogrammercanwriteanexplicitspecialization
(describedindetailinChapter16)suchthatXreferstothatspecialization,thenX
referstoanunknownspecialization.Forexample,considertheinstantiationof
thetypeC<int>::Jinthecontextoftheaboveexample:Weknowthe
definitionofC<T>::Jusedtoinstantiatetheconcretetype(sincethat’sthetype
we’reinstantiating).Moreover,becauseanexplicitspecializationcannot
specializeatemplateormemberofatemplatewithoutalsospecializingallofthe
enclosingtemplatesormembers,C<int>willbeinstantiatedfromthe
enclosingclassdefinition.Hence,thereferencestoJandC<int>(whereType
isint)withinJrefertoacurrentinstantiation.Ontheotherhand,onecould
writeanexplicitspecializationforC<int>::Iasfollows:Clickheretoview
codeimage
template<>structC<int>::I{
//definitionofthespecialization
};
Here,thespecializationofC<int>::Iprovidesacompletelydifferent
definitionthantheonethatwasvisiblefromthedefinitionofC<T>::J,sothe
IinsidethedefinitionofC<T>::Jreferstoanunknownspecialization.
13.3ParsingTemplates
Twofundamentalactivitiesofcompilersformostprogramminglanguagesare
tokenization—alsocalledscanningorlexing—andparsing.Thetokenization
processreadsthesourcecodeasasequenceofcharactersandgeneratesa
sequenceoftokensfromit.Forexample,onseeingthesequenceofcharacters
int*p=0;,the“tokenizer”willgeneratetokendescriptionsforakeyword
int,asymbol/operator*,anidentifierp,asymbol/operator=,anintegerliteral
0,andasymbol/operator;.
Aparserwillthenfindknownpatternsinthetokensequencebyrecursively
reducingtokensorpreviouslyfoundpatternsintohigherlevelconstructs.For
example,thetoken0isavalidexpression,thecombination*followedbyan
identifierpisavaliddeclarator,andthatdeclaratorfollowedby“=”followed
bytheexpression“0”isavalidinit-declarator.Finally,thekeywordintisa
knowntypename,and,whenfollowedbytheinit-declarator*p=0,youget
theinitializingdeclarationofp.
13.3.1ContextSensitivityinNontemplates
Asyoumayknoworexpect,tokenizingiseasierthanparsing.Fortunately,
parsingisasubjectforwhichasolidtheoryhasbeendeveloped,andmany
usefullanguagesarenothardtoparseusingthistheory.However,thetheory
worksbestforcontext-freelanguages,andwehavealreadynotedthatC++is
contextsensitive.Tohandlethis,aC++compilerwillcoupleasymboltableto
thetokenizerandparser:Whenadeclarationisparsed,itisenteredinthe
symboltable.Whenthetokenizerfindsanidentifier,itlooksitupandannotates
theresultingtokenifitfindsatype.
Forexample,iftheC++compilersees
x*
thetokenizerlooksupx.Ifitfindsatype,theparserseesidentifier,
type,x
symbol,*
andconcludesthatadeclarationhasstarted.However,ifxisnotfoundtobea
type,thentheparserreceivesfromthetokenizeridentifier,nontype,
x
symbol,*
andtheconstructcanbeparsedvalidlyonlyasamultiplication.Thedetailsof
theseprinciplesaredependentontheparticularimplementationstrategy,butthe
gistshouldbethere.
Anotherexampleofcontextsensitivityisillustratedinthefollowing
expression:X<1>(0)
IfXisthenameofaclasstemplate,thenthepreviousexpressioncaststhe
integer0tothetypeX<1>generatedfromthattemplate.IfXisnotatemplate,
thenthepreviousexpressionisequivalentto(X<1)>0
Inotherwords,Xiscomparedwith1,andtheresultofthatcomparison—trueor
false,implicitlyconvertedto1or0inthiscase—iscomparedwith0.Although
codelikethisisrarelyused,itisvalidC++(andvalidC,forthatmatter).AC++
parserwillthereforelookupnamesappearingbeforea<andtreatthe<asan
anglebracketonlyifthenameisknowntobethatofatemplate;otherwise,the<
istreatedasanordinaryless-thanoperator.
Thisformofcontextsensitivityisanunfortunateconsequenceofhaving
chosenanglebracketstodelimittemplateargumentlists.Hereisanothersuch
consequence:Clickheretoviewcodeimage
template<boolB>
classInvert{
public:
staticboolconstresult=!B;
};
voidg()
{
booltest=Invert<(1>0)>::result;//parenthesesrequired!
}
IftheparenthesesinInvert<(1>0)>wereomitted,thegreater-thansymbol
wouldbemistakenfortheclosingofthetemplateargumentlist.Thiswould
makethecodeinvalidbecausethecompilerwouldreadittobeequivalentto
((Invert<1>))0>::result.4
Thetokenizerisn’tsparedproblemswiththeangle-bracketnotationeither.For
example,inClickheretoviewcodeimage
List<List<int>>a;
//^--nospacebetweenrightanglebrackets
thetwo>characterscombineintoaright-shifttoken>>andhencearenever
treatedastwoseparatetokensbythetokenizer.Thisisaconsequenceofthe
maximummunchtokenizationprinciple:AC++implementationmustcollectas
manyconsecutivecharactersaspossibleintoatoken.5
AsmentionedinSection2.2onpage28,sinceC++11,theC++standard
specificallycallsoutthiscase—whereanestedtemplate-idisclosedbyaright-
shifttoken>>—and,withintheparser,treatstherightshiftasbeingequivalent
totwoseparaterightanglebrackets>and>toclosetwotemplate-idsatonce.6
It’sinterestingtonotethatthischangesilentlychangesthemeaningofsome—
admittedlycontrived—programs.Considerthefollowingexample:Clickhereto
viewcodeimage
names/anglebrackethack.cpp
#include<iostream>
template<intI>structX{
staticintconstc=2;
};
template<>structX<0>{
typedefintc;
};
template<typenameT>structY{
staticintconstc=3;
};
staticintconstc=4;
intmain()
{
std::cout<<(Y<X<1>>::c>::c>::c)<<’’;
std::cout<<(Y<X<1>>::c>::c>::c)<<’\n’;
}
ThisisavalidC++98programthatoutputs03.ItisalsoavalidC++11
program,buttheretheanglebrackethackmakesthetwoparenthesized
expressionsequivalent,andtheoutputis00.7
Asimilarproblemexistedbecauseoftheexistenceofthedigraph<:asan
alternativeforthesourcecharacter[(whichisnotavailableonsometraditional
keyboards).Considerthefollowingexample:Clickheretoviewcodeimage
template<typenameT>structG{};
structS;
G<::S>gs;//validsinceC++11,butanerrorbeforethat
BeforeC++11,thatlastlineofcodewasequivalenttoG[:S>gs;,whichis
clearlyinvalid.Another“lexicalhack”wasaddedtoaddressthatproblem:When
acompilerseesthecharacters<::notimmediatelyfollowedby:or>,the
leadingpairofcharacters<:isnottreatedasadigraphtokenequivalentto[.8
Thisdigraphhackcanmakepreviouslyvalid(butsomewhatcontrived)
programsinvalid:9
Clickheretoviewcodeimage
#defineF(X)X##:
inta[]={1,2,3},i=1;
intn=aF(<::)i];//validinC++98/C++03,butnotinC++11
Tounderstandthis,notethatthe“digraphhack”appliestopreprocessingtokens,
whicharethekindsoftokensacceptabletothepreprocessor(theymaynotbe
acceptableafterpreprocessinghascompleted),andtheyaredecidedbefore
macroexpansioncompletes.Withthatinmind,C++98/C++03unconditionally
transforms<:into[inthemacroinvocationF(<::),andthedefinitionofn
expandstointn=a[::i];whichisperfectlyfine.C++11,however,doesnot
performthedigraphtransformationbecause,beforemacroexpansion,the
sequence<::isnotfollowedby:or>,butby).Withoutthedigraph
transformation,theconcatenationoperator##mustattempttoglue::and:
intoanewpreprocessingtoken,butthatdoesn’tworkbecause:::isnotavalid
concatenationtoken.Thatstandardmakesthisundefinedbehavior,whichallows
thecompilertodoanything.Somecompilerswilldiagnosethisproblem,while
otherswon’tandwilljustkeepthetwopreprocessingtokensseparate,whichisa
syntaxerrorbecauseitleadstoadefinitionofnthatexpandstointn=a<:::i];
13.3.2DependentNamesofTypes
Theproblemwithnamesintemplatesisthattheycannotalwaysbesufficiently
classified.Inparticular,onetemplatecannotlookintoanothertemplatebecause
thecontentsofthatothertemplatecanbemadeinvalidbyanexplicit
specialization.Thefollowingcontrivedexampleillustratesthis:Clickhereto
viewcodeimage
template<typenameT>
classTrap{
public:
enum{x};//#1xisnotatypehere
};
template<typenameT>
classVictim{
public:
inty;
voidpoof(){
Trap<T>::x*y;//#2declarationormultiplication?
}
};
template<>
classTrap<void>{//evilspecialization!
public:
usingx=int;//#3xisatypehere
};
voidboom(Victim<void>&bomb)
{
bomb.poof();
}
Asthecompilerisparsingline#2,itmustdecidewhetheritisseeinga
declarationoramultiplication.Thisdecisioninturndependsonwhetherthe
dependentqualifiednameTrap<T>::xisatypename.Itmaybetemptingto
lookinthetemplateTrapatthispointandfindthat,accordingtoline#1,
Trap<T>::xisnotatype,whichwouldleaveustobelievethatline#2isa
multiplication.However,alittlelaterthesourcecorruptsthisideabyoverriding
thegenericTrap<T>::xforthecasewhereTisvoid.Inthiscase,
Trap<T>::xisinfacttypeint.
Inthisexample,thetypeTrap<T>isadependenttypebecausethetype
dependsonthetemplateparameterT.Moreover,Trap<T>referstoan
unknownspecialization(describedinSection13.2.4onpage223),whichmeans
thatthecompilercannotsafelylookinsidethetemplatetodeterminewhetherthe
nameTrap<T>::xisatypeornot.Hadthetypeprecedingthe::referredtoa
currentinstantiation—forexample,withVictim<T>::y—thecompilercould
havelookedintothetemplatedefinitionbecauseitiscertainthatnoother
specializationcouldintervene.Thus,whenthetypepreceding::referstothe
currentinstantiation,qualifiednamelookupinatemplatebehavesverysimilarly
toqualifiednamelookupfornondependenttypes.
However,asillustratedbytheexample,namelookupintoanunknown
specializationisstillaproblem.Thelanguagedefinitionresolvesthisproblem
byspecifyingthatingeneraladependentqualifiednamedoesnotdenoteatype
unlessthatnameisprefixedwiththekeywordtypename.Ifitturnsout,after
substitutingtemplatearguments,thatthenameisnotthenameofatype,the
programisinvalidandyourC++compilershouldcomplainatinstantiationtime.
Notethatthisuseoftypenamediffersfromtheusetodenotetemplatetype
parameters.Unliketypeparameters,youcannotequivalentlyreplace
typenamewithclass.
Thetypenameprefixtoanameisrequiredwhenthenamesatisfiesallofthe
followingconditions:10
1.Itisqualifiedandnotitselffollowedby::toformamorequalifiedname.
2.Itisnotpartofanelaborated-type-specifier(i.e.,atypenamethatstartswith
oneofthekeywordsclass,struct,union,orenum).
3.Itisnotusedinalistofbaseclassspecificationsorinalistofmember
initializersintroducingaconstructordefinition.11
4.Itisdependentonatemplateparameter.
5.Itisamemberofanunknownspecialization,meaningthatthetypenamedby
thequalifierreferstoanunknownspecialization.
Furthermore,thetypenameprefixisnotallowedunlessatleastthefirsttwo
previousconditionshold.Toillustratethis,considerthefollowingerroneous
example:12
Clickheretoviewcodeimage
template<typename1T>
structS:typename2X<T>::Base{
S():typename3X<T>::Base(typename4X<T>::Base(0)){
}
typename5X<T>f(){
typename6X<T>::C*p;//declarationofpointerp
X<T>::D*q;//multiplication!
}
typename7X<int>::C*s;
usingType=T;
usingOtherType=typename8S<T>::Type;
};
Eachoccurrenceoftypename—correctornot—isnumberedwithasubscript
foreasyreference.Thefirst,typename1,indicatesatemplateparameter.The
previousrulesdonotapplytothisfirstuse.Thesecondandthirdtypenames
aredisallowedbytheseconditeminthepreviousrules.Namesofbaseclassesin
thesetwocontextscannotbeprecededbytypename.However,typename4is
required.Here,thenameofthebaseclassisnotusedtodenotewhatisbeing
initializedorderivedfrom.Instead,thenameispartofanexpressionto
constructatemporaryX<T>::Basefromitsargument0(asortofconversion,
ifyouwill).Thefifthtypenameisprohibitedbecausethenamethatfollowsit,
X<T>,isnotaqualifiedname.Thesixthoccurrenceisrequiredifthisstatement
istodeclareapointer.Thenextlineomitsthetypenamekeywordandis
thereforeinterpretedbythecompilerasamultiplication.Theseventh
typenameisoptionalbecauseitsatisfiesthefirsttworulesbutnotthelasttwo.
Theeighthtypenameisalsooptional,becauseitreferstoamemberofa
currentinstantiation(andthereforedoesnotsatisfythelastrule).
Thelastoftherulesfordeterminingwhetherthetypenameprefixis
requiredcanoccasionallybetrickytoevaluate,becauseitdependsontherules
fordeterminingwhetheratypereferstoacurrentinstantiationoranunknown
specialization.Insuchcases,itissafesttosimplyaddthetypenamekeyword
toindicatethatyouintendthequalifiednamethatfollowstobeatype.The
typenamekeyword,evenifit’soptional,willprovidedocumentationofyour
intent.
13.3.3DependentNamesofTemplates
Aproblemverysimilartotheoneencounteredintheprevioussectionoccurs
whenanameofatemplateisdependent.Ingeneral,aC++compilerisrequired
totreata<followingthenameofatemplateasthebeginningofatemplate
argumentlist;otherwise,itisaless-thanoperator.Asisthecasewithtype
names,acompilerhastoassumethatadependentnamedoesnotrefertoa
templateunlesstheprogrammerprovidesextrainformationusingthekeyword
template:Clickheretoviewcodeimage
template<typenameT>
classShell{
public:
template<intN>
classIn{
public:
template<intM>
classDeep{
public:
virtualvoidf();
};
};
};
template<typenameT,intN>
classWeird{
public:
voidcase1(
typenameShell<T>::templateIn<N>::templateDeep<N>*p){
p->templateDeep<N>::f();//inhibitvirtualcall
}
voidcase2(
typenameShell<T>::templateIn<N>::templateDeep<N>&p){
p.templateDeep<N>::f();//inhibitvirtualcall
}
};
Thissomewhatintricateexampleshowshowalltheoperatorsthatcanqualifya
name(::,->,and.)mayneedtobefollowedbythekeywordtemplate.
Specifically,thisisthecasewheneverthetypeofthenameorexpression
precedingthequalifyingoperatorisdependentonatemplateparameterand
referstoanunknownspecialization,andthenamethatfollowstheoperatorisa
template-id(inotherwords,atemplatenamefollowedbytemplateargumentsin
anglebrackets).Forexample,intheexpressionp.template
Deep<N>::f()
thetypeofpdependsonthetemplateparameterT.Consequently,aC++
compilercannotlookupDeeptoseeifitisatemplate,andwemustexplicitly
indicatethatDeepisthenameofatemplatebyinsertingtheprefixtemplate.
Withoutthisprefix,p.Deep<N>::f()isparsedas((p.Deep)<N)>f().
Notealsothatthismayneedtohappenmultipletimeswithinaqualifiedname
becausequalifiersthemselvesmaybequalifiedwithadependentqualifier.(This
isillustratedbythedeclarationoftheparametersofcase1andcase2inthe
previousexample.)Ifthekeywordtemplateisomittedincasessuchasthese,
theopeningandclosinganglebracketsareparsedasless-thanandgreater-than
operators.Aswiththetypenamekeyword,onecansafelyaddthetemplate
prefixtoindicatethatthefollowingnameisatemplate-id,evenifthe
templateprefixisnotstrictlyneeded.
13.3.4DependentNamesinUsingDeclarations
Usingdeclarationscanbringinnamesfromtwoplaces:namespacesandclasses.
Thenamespacecaseisnotrelevantinthiscontextbecausetherearenosuch
thingsasnamespacetemplates.Usingdeclarationsthatbringinnamesfrom
classes,ontheotherhand,canbringinnamesonlyfromabaseclasstoaderived
class.Suchusingdeclarationsbehavelike“symboliclinks”or“shortcuts”inthe
derivedclasstothebasedeclaration,therebyallowingthemembersofthe
derivedclasstoaccessthenominatednameasifitwereactuallyamember
declaredinthatderivedclass.Ashortnontemplateexampleillustratestheidea
betterthanmerewords:classBX{
public:
voidf(int);
voidf(charconst*);
voidg();
};
classDX:privateBX{
public:
usingBX::f;
};
TheprevioususingdeclarationbringsinthenamefofthebaseclassBXintothe
derivedclassDX.Inthiscase,thisnameisassociatedwithtwodifferent
declarations,thusemphasizingthatwearedealingwithamechanismfornames
andnotindividualdeclarationsofsuchnames.Notealsothatthiskindofusing
declarationcanmakeaccessibleanotherwiseinaccessiblemember.ThebaseBX
(andthusitsmembers)areprivatetotheclassDX,exceptthatthefunctions
BX::fhavebeenintroducedinthepublicinterfaceofDXandaretherefore
availabletotheclientsofDX.
Bynowyoucanprobablyperceivetheproblemwhenausingdeclaration
bringsinanamefromadependentclass.Althoughweknowaboutthename,we
don’tknowwhetherit’sthenameofatype,atemplate,orsomethingelse:Click
heretoviewcodeimage
template<typenameT>
classBXT{
public:
usingMystery=T;
template<typenameU>
structMagic;
};
template<typenameT>
classDXTT:privateBXT<T>{
public:
usingtypenameBXT<T>::Mystery;
Mystery*p;//wouldbeasyntaxerrorwithouttheearliertypename
};
Again,ifwewantadependentnametobebroughtinbyausingdeclarationto
denoteatype,wemustexplicitlysaysobyinsertingthekeywordtypename.
Strangely,theC++standarddoesnotprovideforasimilarmechanismtomark
suchdependentnamesastemplates.Thefollowingsnippetillustratesthe
problem:Clickheretoviewcodeimage
template<typenameT>
classDXTM:privateBXT<T>{
public:
usingBXT<T>::templateMagic;//ERROR:notstandard
Magic<T>*plink;//SYNTAXERROR:Magicisnota
};//knowntemplate
Thestandardizationcommitteehasnotbeeninclinedtoaddressthisissue.
However,C++11aliastemplatesdoprovideapartialworkaround:Clickhereto
viewcodeimage
template<typenameT>
classDXTM:privateBXT<T>{
public:
template<typenameU>
usingMagic=typenameBXT<T>::templateMagic<T>;//Aliastemplate
Magic<T>*plink;//OK
};
Thisisalittleunwieldy,butitachievesthedesiredeffectforthecaseofclass
templates.Thecaseoffunctiontemplates(arguablylesscommon)remains
unaddressed,unfortunately.
13.3.5ADLandExplicitTemplateArguments
Considerthefollowingexample:
Clickheretoviewcodeimage
namespaceN{
classX{
…
};
template<intI>voidselect(X*);
}
voidg(N::X*xp)
{
select<3>(xp);//ERROR:noADL!
}
Inthisexample,wemayexpectthatthetemplateselect()isfoundthrough
ADLinthecallselect<3>(xp).However,thisisnotthecasebecausea
compilercannotdecidethatxpisafunctioncallargumentuntilithasdecided
that<3>isatemplateargumentlist.Furthermore,acompilercannotdecidethat
<3>isatemplateargumentlistuntilithasfoundselect()tobeatemplate.
Becausethischicken-and-eggproblemcannotberesolved,theexpressionis
parsedas(select<3)>(xp),whichmakesnosense.
ThisexamplemaygivetheimpressionthatADLisdisabledfortemplate-ids,
butitisnot.Thecodecanbefixedbyintroducingafunctiontemplatenamed
selectthatisvisibleatthecall:Clickheretoviewcodeimage
template<typenameT>voidselect();Eventhoughitdoesn’tmakeany
senseforthecallselect<3>(xp),thepresenceofthisfunction
templateensuresthatselect<3>willbeparsedasatemplate-id.ADL
willthenfindthefunctiontemplateN::select,andthecallwill
succeed.
13.3.6DependentExpressions
Likenames,expressionsthemselvescanbedependentontemplateparameters.
Anexpressionthatdependsonatemplateparametercanbehavedifferentlyfrom
oneinstantiationtothenext—forexample,selectingadifferentoverloaded
functionorproducingadifferenttypeorconstantvalue.Expressionsthatdonot
dependonatemplateparameter,incontrast,providethesamebehaviorinall
instantiations.
Anexpressioncanbedependentonatemplateparameterinseveraldifferent
ways.Themostcommonformofdependentexpressionisatype-dependent
expression,wherethetypeoftheexpressionitselfcanvaryfromone
instantiationtothenext—forexample,anexpressionthatreferstoafunction
parameterwhosetypeisthatofatemplateparameter:Clickheretoviewcode
image
template<typenameT>voidtypeDependent1(Tx)
{
x;//theexpressiontype-dependent,becausethetypeofxcanvary
}
Expressionsthathavetype-dependentsubexpressionsaregenerallytype-
dependentthemselves—forexample,callingafunctionf()withtheargument
x:Clickheretoviewcodeimage
template<typenameT>voidtypeDependent2(Tx)
{
f(x);//theexpressionistype-dependent,becausexistype-
dependent
}
Here,notethattypeoff(x)canvaryfromoneinstantiationtothenextboth
becausefmightresolvetoatemplatewhoseresulttypedependsonthe
argumenttypeandbecausetwo-phaselookup(discussedinSection14.3.1on
page249)mightfindcompletelydifferentfunctionsnamedfindifferent
instantiations.
Notallexpressionsthatinvolvetemplateparametersaretype-dependent.For
example,anexpressionthatinvolvestemplateparameterscanproducedifferent
constantvaluesfromoneinstantiationtothenext.Suchexpressionsarecalled
value-dependentexpressions,thesimplestofwhicharethosethatrefertoa
nontypetemplateparameterofnondependenttype.Forexample:Clickhereto
viewcodeimage
template<intN>voidvalueDependent1()
{
N;//theexpressionisvalue-dependentbutnottype-dependent,
//becauseNhasafixedtypebutavaryingconstantvalue
}
Liketype-dependentexpressions,anexpressionisgenerallyvalue-dependentif
itiscomposedofothervalue-dependentexpressions,soN+Norf(N)are
alsovalue-dependentexpressions.
Interestingly,someoperations,suchassizeof,haveaknownresulttype,so
theycanturnatype-dependentoperandintoavalue-dependentexpressionthatis
nottype-dependent.Forexample:Clickheretoviewcodeimage
template<typenameT>voidvalueDependent2(Tx)
{
sizeof(x);//theexpressionisvalue-dependentbutnottype-
dependent
}
Thesizeofoperationalwaysproducesavalueoftypestd::size_t,
regardlessofitsinput,soasizeofexpressionisnevertype-dependent,evenif
—asinthiscase—itssubexpressionistype-dependent.However,theresulting
constantvaluewillvaryfromoneinstantiationtothenext,sosizeof(x)isa
value-dependentexpression.
Whatifweapplysizeofonavalue-dependentexpression?
Clickheretoviewcodeimage
template<typenameT>voidmaybeDependent(Tconst&x)
{
sizeof(sizeof(x));
}
Here,theinnersizeofexpressionisvalue-dependent,asnotedabove.
However,theoutersizeofexpressionalwayscomputesthesizeofa
std::size_t,sobothitstypeandconstantvalueareconsistentacrossall
instantiationsofthetemplate,despitetheinnermostexpression(x)beingtype-
dependent.Anyexpressionthatinvolvesatemplateparameterisan
instantiation-dependentexpression,13evenifbothitstypeandconstantvalueare
invariantacrossvalidinstantiations.However,aninstantiation-dependent
expressionmayturnouttobeinvalidwheninstantiated.Forexample,
instantiatingmaybeDependent()withanincompleteclasstypewilltrigger
anerror,becausesizeofcannotbeappliedtosuchtypes.
Type-,value-,andinstantiation-dependencecanbethoughtofasaseriesof
increasinglymoreinclusiveclassificationsofexpressions.Anytype-dependent
expressionisalsoconsideredtobevalue-dependent,becauseanexpression
whosetypethatvariesfromoneinstantiationtothenextwillnaturallyhaveits
constantvaluevaryfromoneinstantiationtothenext.Similarly,anexpression
whosetypeorvaluevariesfromoneinstantiationtothenextdependsona
templateparameterinsomeway,sobothtype-dependentexpressionsandvalue-
dependentexpressionsareinstantiation-dependent.Thiscontainment
relationshipisillustratedbyFigure13.1.
Clickheretoviewcodeimage
Figure13.1.Containmentrelationshipamongtype-,value-,andinstantiation-
dependentexpressions
Asoneproceedsfromtheinnermostcontext(type-dependentexpressions)to
theoutermostcontext,moreofthebehaviorofthetemplateisdeterminedwhen
thetemplateisparsedandthereforecannotvaryfromoneinstantiationtothe
next.Forexample,considerthecallf(x):Ifxistype-dependent,thenfisa
dependentnamethatissubjecttotwo-phaselookup(Section14.3.1onpage
249),whereasifxisvalue-dependentbutnottype-dependent,fisa
nondependentnameforwhichnamelookupcanbecompletelydeterminedatthe
timethatthetemplateisparsed.
13.3.7CompilerErrors
AC++compilerispermitted(butnotrequired!)todiagnoseerrorsatthetimethe
templateisparsedwhenalloftheinstantiationsofthetemplatewouldproduce
thaterror.Let’sexpandonthef(x)examplefromtheprevioussectionto
explorethisfurther:Clickheretoviewcodeimage
voidf(){}
template<intx>voidnondependentCall()
{
f(x);//xisvalue-dependent,sof()isnondependent;
//thiscallwillneversucceed
}
Here,thecallf(x)willproduceanerrorineveryinstantiationbecausefisa
nondependentnameandtheonlyvisiblefacceptszeroarguments,notone.A
C++compilercanproduceanerrorwhenparsingthetemplateormaywaituntil
thefirsttemplateinstantiation:Commonlyusedcompilersdifferevenonthis
simpleexample.Onecanconstructsimilarexampleswithexpressionsthatare
instantiation-dependentbutnotvalue-dependent:Clickheretoviewcodeimage
template<intN>voidinstantiationDependentBound()
{
constexprintx=sizeof(N);
constexprinty=sizeof(N)+1;
intarray[x-y];//arraywillhaveanegativesizeinall
instantiations
}
13.4InheritanceandClassTemplates
Classtemplatescaninheritorbeinheritedfrom.Formanypurposes,thereis
nothingsignificantlydifferentbetweenthetemplateandnontemplatescenarios.
However,thereisoneimportantsubtletywhenderivingaclasstemplatefroma
baseclassreferredtobyadependentname.Let’sfirstlookatthesomewhat
simplercaseofnondependentbaseclasses.
13.4.1NondependentBaseClasses
Inaclasstemplate,anondependentbaseclassisonewithacompletetypethat
canbedeterminedwithoutknowingthetemplatearguments.Inotherwords,the
nameofthisbaseisdenotedusinganondependentname.Forexample:Click
heretoviewcodeimage
template<typenameX>
classBase{
public:
intbasefield;
usingT=int;
};
classD1:publicBase<Base<void>>{//notatemplatecasereally
public:
voidf(){basefield=3;}//usualaccesstoinheritedmember
};
template<typenameT>
classD2:publicBase<double>{//nondependentbase
public:
voidf(){basefield=7;}//usualaccesstoinheritedmember
Tstrange;//TisBase<double>::T,notthetemplateparameter!
};
Nondependentbasesintemplatesbehaveverymuchlikebasesinordinary
nontemplateclasses,butthereisaslightlyunfortunatesurprise:Whenan
unqualifiednameislookedupinthetemplatedderivation,thenondependent
basesareconsideredbeforethelistoftemplateparameters.Thismeansthatin
thepreviousexample,thememberstrangeoftheclasstemplateD2always
hasthetypeTcorrespondingtoBase<double>::T(inotherwords,int).
Forexample,thefollowingfunctionisnotvalidC++(assumingtheprevious
declarations):Clickheretoviewcodeimage
voidg(D2<int*>&d2,int*p)
{
d2.strange=p;//ERROR:typemismatch!
}
Thisiscounterintuitiveandrequiresthewriterofthederivedtemplatetobe
awareofnamesinthenondependentbasesfromwhichitderives—evenwhen
thatderivationisindirectorthenamesareprivate.Itwouldprobablyhavebeen
preferabletoplacetemplateparametersinthescopeoftheentitythey
“templatize.”
13.4.2DependentBaseClasses
Inthepreviousexample,thebaseclassisfullydetermined.Itdoesnotdependon
atemplateparameter.ThisimpliesthataC++compilercanlookup
nondependentnamesinthosebaseclassesassoonasthetemplatedefinitionis
seen.Analternative—notallowedbytheC++standard—wouldconsistin
delayingthelookupofsuchnamesuntilthetemplateisinstantiated.The
disadvantageofthisalternativeapproachisthatitalsodelaysanyerrormessages
resultingfrommissingsymbolsuntilinstantiation.Hence,theC++standard
specifiesthatanondependentnameappearinginatemplateislookedupassoon
asitisencountered.Keepingthisinmind,considerthefollowingexample:Click
heretoviewcodeimage
template<typenameT>
classDD:publicBase<T>{//dependentbase
public:
voidf(){basefield=0;}//#1problem…
};
template<>//explicitspecialization
classBase<bool>{
public:
enum{basefield=42};//#2tricky!
};
voidg(DD<bool>&d)
{
d.f();//#3oops?
}
Atpoint#1wefindourreferencetoanondependentnamebasefield:Itmust
belookeduprightaway.SupposewelookitupinthetemplateBaseandbindit
totheintmemberthatwefindtherein.However,shortlyafterthisweoverride
thegenericdefinitionofBasewithanexplicitspecialization.Asithappens,this
specializationchangesthemeaningofthebasefieldmembertowhichwe
alreadycommitted!So,whenweinstantiatethedefinitionofDD::fatpoint#3
,wefindthatwetooeagerlyboundthenondependentnameatpoint#1.Thereis
nomodifiablebasefieldinDD<bool>thatwasspecializedatpoint#2,and
anerrormessageshouldhavebeenissued.
Tocircumventthisproblem,standardC++saysthatnondependentnamesare
notlookedupindependentbaseclasses14(buttheyarestilllookedupassoonas
theyareencountered).So,astandardC++compilerwillemitadiagnosticat
point#1.Tocorrectthecode,itsufficestomakethenamebasefield
dependentbecausedependentnamescanbelookeduponlyatthetimeof
instantiation,andatthattimetheconcretebaseinstancethatmustbeexplored
willbeknown.Forexample,atpoint#3,thecompilerwillknowthatthebase
classofDD<bool>isBase<bool>andthatthishasbeenexplicitly
specializedbytheprogrammer.Inthiscase,ourpreferredwaytomakethename
dependentisasfollows:Clickheretoviewcodeimage
//Variation1:
template<typenameT>
classDD1:publicBase<T>{
public:
voidf(){this->basefield=0;}//lookupdelayed
};
Analternativeconsistsinintroducingadependencyusingaqualifiedname:
Clickheretoviewcodeimage
//Variation2:
template<typenameT>
classDD2:publicBase<T>{
public:
voidf(){Base<T>::basefield=0;}
};
Caremustbetakenwiththissolution,becauseiftheunqualifiednondependent
nameisusedtoformavirtualfunctioncall,thenthequalificationinhibitsthe
virtualcallmechanismandthemeaningoftheprogramchanges.Nonetheless,
therearesituationswhenthefirstvariationcannotbeusedandthisalternativeis
appropriate:Clickheretoviewcodeimage
template<typenameT>
classB{
public:
enumE{e1=6,e2=28,e3=496};
virtualvoidzero(Ee=e1);
virtualvoidone(E&);
};
template<typenameT>
classD:publicB<T>{
public:
voidf(){
typenameD<T>::Ee;//this->Ewouldnotbevalidsyntax
this->zero();//D<T>::zero()wouldinhibitvirtuality
one(e);//oneisdependentbecauseitsargument
}//isdependent
};
NotehowweusedD<T>::EinsteadofB<T>::Einthisexample.Inthiscase,
eitheroneworks.Inmultiple-inheritancecases,however,wemaynotknow
whichbaseclassprovidesthedesiredmember(inwhichcaseusingthederived
classforqualificationworks)ormultiplebaseclassesmaydeclarethesame
name(inwhichcasewemayhavetouseaspecificbaseclassnamefor
disambiguation).
Notethatthenameoneinthecallone(e)isdependentonthetemplate
parametersimplybecausethetypeofoneofthecall’sexplicitargumentsis
dependent.Implicitlyuseddefaultargumentswithatypethatdependsona
templateparameterdonotcountbecausethecompilercannotverifythisuntilit
alreadyhasdecidedthelookup—achicken-and-eggproblem.Toavoidsubtlety,
weprefertousethethis->prefixinallsituationsthatallowit—evenfor
nontemplatecode.
Ifyoufindthattherepeatedqualificationsareclutteringupyourcode,youcan
bringanamefromadependentbaseclassinthederivedclassonceandforall:
Clickheretoviewcodeimage
//Variation3:
template<typenameT>
classDD3:publicBase<T>{
public:
usingBase<T>::basefield;//#1dependentnamenowinscope
voidf(){basefield=0;}//#2fine
};
Thelookupatpoint#2succeedsandfindstheusingdeclarationofpoint#1.
However,theusingdeclarationisnotverifieduntilinstantiationtimeandour
goalisachieved.Therearesomesubtlelimitationstothisscheme.Forexample,
ifmultiplebasesarederivedfrom,theprogrammermustselectexactlywhich
onecontainsthedesiredmember.
Whensearchingforaqualifiednamewithinthecurrentinstantiation,theC++
standardspecifiesthatnamelookupfirstsearchinthecurrentinstantiationandin
allnondependentbases,similartothewayitperformsunqualifiedlookupfor
thatname.Ifanynameisfound,thenthequalifiednamereferstoamemberofa
currentinstantiationandwillnotbeadependentname.15Ifnosuchnameis
found,andtheclasshasanydependentbases,thenthequalifiednamereferstoa
memberofanunknownspecialization.Forexample:Clickheretoviewcode
image
classNonDep{
public:
usingType=int;
};
template<typenameT>
classDep{
public:
usingOtherType=T;
};
template<typenameT>
classDepBase:publicNonDep,publicDep<T>{
public:
voidf(){
typenameDepBase<T>::Typet;//findsNonDep::Type;
//typenamekeywordisoptional
typenameDepBase<T>::OtherType*ot;//findsnothing;
DepBase<T>::OtherType
//isamemberofanunknownspecialization
}
};
13.5Afternotes
Thefirstcompilerreallytoparsetemplatedefinitionswasdevelopedbya
companycalledTaligentinthemid-1990s.Beforethat—andevenseveralyears
afterthat—mostcompilerstreatedtemplatesasasequenceoftokenstobe
playedbackthroughtheparseratinstantiationtime.Hencenoparsingwasdone,
exceptforaminimalamountsufficienttofindtheendofatemplatedefinition.
Atthetimeofthiswriting,theMicrosoftVisualC++compilerstillworksthis
way.TheEdisonDesignGroup’s(EDG’s)compilerfrontendusesahybrid
techniquewheretemplatesaretreatedinternallyasasequenceofannotated
tokens,buta“genericparsing”isperformedtovalidatethesyntaxinmodes
wherethatisdesirable(EDG’sproductemulatesmultipleothercompilers;in
particular,itcancloselyemulatethebehaviorofMicrosoft’scompiler).
BillGibbonswasTaligent’srepresentativetotheC++committeeandwasthe
principaladvocateformakingtemplatesunambiguouslyparsable.TheTaligent
effortwasnotreleaseduntilthecompilerwasacquiredandcompletedby
Hewlett-Packard(HP),tobecometheaC++compiler.Amongitscompetitive
advantages,theaC++compilerwasquicklyrecognizedforitshigh-quality
diagnostics.Thefactthattemplatediagnosticswerenotalwaysdelayeduntil
instantiationtimeundoubtedlycontributedtothisperception.
Relativelyearlyduringthedevelopmentoftemplates,TomPennello—a
widelyrecognizedparsingexpertworkingforMetaware—notedsomeofthe
problemsassociatedwithanglebrackets.Stroustrupalsocommentsonthattopic
in[StroustrupDnE]andarguesthathumansprefertoreadanglebracketsrather
thanparentheses.However,otherpossibilitiesexist,andPennellospecifically
proposedbraces(e.g.,List{::X})ataC++standardsmeetingin1991(heldin
Dallas).16Atthattimetheextentoftheproblemwasmorelimitedbecause
templatesnestedinsideothertemplates—calledmembertemplates—werenot
valid,andthusthediscussionofSection13.3.3onpage230waslargely
irrelevant.Asaresult,thecommitteedeclinedtheproposaltoreplacetheangle
brackets.
Thenamelookuprulefornondependentnamesanddependentbaseclasses
thatisdescribedinSection13.4.2onpage237wasintroducedintheC++
standardin1993.Itwasdescribedtothe“generalpublic”inBjarneStroustrup’s
[StroustrupDnE]inearly1994.Yetthefirstgenerallyavailableimplementation
ofthisruledidnotappearuntilearly1997whenHPincorporateditintotheir
aC++compiler,andbythenlargeamountsofcodederivedclasstemplatesfrom
dependentbases.Indeed,whentheHPengineersstartedtestingtheir
implementation,theyfoundthatmostoftheprogramsthatusedtemplatesin
nontrivialwaysnolongercompiled.17Inparticular,allimplementationsofthe
StandardTemplateLibrary(STL)broketheruleinmanyhundreds—and
sometimesthousands—ofplaces.18Toeasethetransitionprocessfortheir
customers,HPsoftenedthediagnosticassociatedwithcodethatassumedthat
nondependentnamescouldbefoundindependentbaseclassesasfollows:When
anondependentnameusedinthescopeofaclasstemplateisnotfoundusingthe
standardrules,aC++peeksinsidethedependentbases.Ifthenameisstillnot
found,aharderrorisissuedandcompilationfails.However,ifthenameisfound
inadependentbase,awarningisissued,andthenameismarkedtobetreatedas
ifitweredependent,sothatlookupwillbereattemptedatinstantiationtime.
Thelookuprulethatcausesanameinnondependentbasestohidean
identicallynamedtemplateparameter(Section13.4.1onpage236)isan
oversight,butsuggestionstochangetherulehavenotgarneredsupportfromthe
C++standardizationcommittee.Itisbesttoavoidcodewithtemplateparameter
namesthatarealsousedinnondependentbaseclasses.Goodnaming
conventionsarehelpfulforsuchproblems.
Friendnameinjectionwasconsideredharmfulbecauseitmadethevalidityof
programsmoresensitivetotheorderingofinstantiations.BillGibbons(whoat
thetimewasworkingontheTaligentcompiler)wasamongthemostvocal
supportersofaddressingtheproblem,becauseeliminatinginstantiationorder
dependenciesenablednewandinterestingC++developmentenvironments(on
whichTaligentwasrumoredtobeworking).However,theBarton-Nackman
trick(Section21.2.1onpage497)requiredaformoffriendnameinjection,and
itisthisparticulartechniquethatcausedittoremaininthelanguageinits
current(weakened)formbasedonADL.
AndrewKoenigfirstproposedADLforoperatorfunctionsonly(whichiswhy
ADLissometimescalledKoeniglookup).Themotivationwasprimarily
aesthetics:Explicitlyqualifyingoperatornameswiththeirenclosingnamespace
looksawkwardatbest(e.g.,insteadofa+bwemayneedtowrite
N::operator+(a,b)),andhavingtowriteusingdeclarationsforevery
operatorcanleadtounwieldycode.Hence,itwasdecidedthatoperatorswould
belookedupinthenamespacesassociatedwitharguments.ADLwaslater
extendedtoordinaryfunctionnamestoaccommodatealimitedkindoffriend
nameinjectionandtosupportatwo-phaselookupmodelfortemplatesandtheir
instantiations(Chapter14).ThegeneralizedADLrulesarealsocalledextended
Koeniglookup.
ThespecificationfortheanglebrackethackwasaddedtoC++11byDavid
VandevoordethroughhispaperN1757.Healsoaddedthedigraphhackviathe
resolutionofCoreissue1104,toaddressarequestoftheUnitedStates’review
ofadraftoftheC++11standard.
2InC++98/C++03,thiswasalsocalledKoeniglookup(orextendedKoenig
lookup)afterAndrewKoenig,whofirstproposedavariationofthis
mechanism.
3AlthoughthiswasclearlyintendedbythosewhowrotetheC++standard,itis
notclearlyspelledoutinthestandard.
4Notethedoubleparenthesestoavoidparsing(Invert<1>)0asacast
operation—yetanothersourceofsyntacticambiguity.
5Specificexceptionswereintroducedtoaddresstokenizationissuesdescribed
inthissection.
6The1998and2003versionsoftheC++standarddidnotsupportthis“angle
brackethack.”However,theneedtointroduceaspacebetweenthetwo
consecutiverightanglebracketswassuchacommonstumblingblockfor
beginningtemplateusersthatthecommitteedecidedtocodifythishackinthe
2011standard.
7SomecompilersthatprovideaC++98orC++03modekeeptheC++11
behaviorinthosemodesandthusprint00evenwhenformallycompiling
C++98/C++03code.
8Thisisthereforeanexceptiontotheaforementionedmaximummunch
principle.
9ThankstoRichardSmithforpointingthatout.
10NotethatC++20willprobablyremovetheneedfortypenameinmostcases
(seeSection17.1onpage354fordetails).
11Syntactically,onlytypenamesarepermittedwithinthesecontexts,soa
qualifiednameisalwaysassumedtonameatype.
12Adaptedfrom[VandevoordeSolutions],provingonceandforallthatC++
promotescodereuse.
13Thetermstype-dependentexpressionandvalue-dependentexpressionareused
intheC++standardtodescribethesemanticsoftemplates,andtheyhavean
effectonseveralaspectsoftemplateinstantiation(Chapter14).Ontheother
hand,theterminstantiation-dependentexpressionismainlyonlyusedbythe
authorsofC++compilers.Ourdefinitionofainstantiation-dependent
expressioncomesfromtheItaniumC++ABI[ItaniumABI],whichprovides
thebasisforbinaryinteroperabilityamonganumberofdifferentC++
compilers.
14Thisispartofthetwo-phaselookuprulesthatdistinguishbetweenafirstphase
whentemplatedefinitionsarefirstseenandasecondphasewhentemplatesare
instantiated(seeSection14.3.1onpage249).
15However,thelookupisnonethelessrepeatedwhenthetemplateisinstantiated,
andifadifferentresultisproducedinthatcontext,theprogramisill-formed.
16Bracesarenotentirelywithoutproblemseither.Specifically,thesyntaxto
specializeclasstemplateswouldrequirenontrivialadaptation.
17Fortunately,theyfoundoutbeforetheyreleasedthenewfunctionality.
18Ironically,thefirstoftheseimplementationshadbeendevelopedbyHPas
well.
Chapter14
Instantiation
Templateinstantiationistheprocessthatgeneratestypes,functions,and
variablesfromgenerictemplatedefinitions.1TheconceptofinstantiationofC++
templatesisfundamentalbutalsosomewhatintricate.Oneoftheunderlying
reasonsforthisintricacyisthatthedefinitionsofentitiesgeneratedbya
templatearenolongerlimitedtoasinglelocationinthesourcecode.The
locationofthetemplate,thelocationwherethetemplateisused,andthe
locationswherethetemplateargumentsaredefinedallplayaroleinthemeaning
oftheentity.
Inthischapterweexplainhowwecanorganizeoursourcecodetoenable
propertemplateuse.Inaddition,wesurveythevariousmethodsthatareusedby
themostpopularC++compilerstohandletemplateinstantiation.Althoughall
thesemethodsshouldbesemanticallyequivalent,itisusefultounderstandbasic
principlesofthecompiler’sinstantiationstrategy.Eachmechanismcomeswith
itssetoflittlequirkswhenbuildingreal-lifesoftwareand,conversely,each
influencedthefinalspecificationsofstandardC++.
14.1On-DemandInstantiation
WhenaC++compilerencounterstheuseofatemplatespecialization,itwill
createthatspecializationbysubstitutingtherequiredargumentsforthetemplate
parameters.2Thisisdoneautomaticallyandrequiresnodirectionfromtheclient
code(orfromthetemplatedefinition,forthatmatter).Thison-demand
instantiationfeaturesetsC++templatesapartfromsimilarfacilitiesinother
earlycompiledlanguages(likeAdaorEiffel;someoftheselanguagesrequire
explicitinstantiationdirectives,whereasothersuserun-timedispatch
mechanismstoavoidtheinstantiationprocessaltogether).Itissometimesalso
calledimplicitorautomaticinstantiation.
On-demandinstantiationimpliesthatthecompileroftenneedsaccesstothe
fulldefinition(inotherwords,notjustthedeclaration)ofthetemplateandsome
ofitsmembersatthepointofuse.Considerthefollowingtinysourcecodefile:
Clickheretoviewcodeimage
template<typenameT>classC;//#1declarationonly
C<int>*p=0;//#2fine:definitionofC<int>notneeded
template<typenameT>
classC{
public:
voidf();//#3memberdeclaration
};//#4classtemplatedefinitioncompleted
voidg(C<int>&c)//#5useclasstemplatedeclarationonly
{
c.f();//#6useclasstemplatedefinition;
}//willneeddefinitionofC::f()
//inthistranslationunit
template<typenameT>
voidC<T>::f()//requireddefinitiondueto#6
{
}
Atpoint#1inthesourcecode,onlythedeclarationofthetemplateisavailable,
notthedefinition(suchadeclarationissometimescalledaforwarddeclaration).
Asisthecasewithordinaryclasses,wedonotneedthedefinitionofaclass
templatetobevisibletodeclarepointersorreferencestothistype,aswasdone
atpoint#2.Forexample,thetypeoftheparameteroffunctiong()doesnot
requirethefulldefinitionofthetemplateC.However,assoonasacomponent
needstoknowthesizeofatemplatespecializationorifitaccessesamemberof
suchaspecialization,theentireclasstemplatedefinitionisrequiredtobevisible.
Thisexplainswhyatpoint#6inthesourcecode,theclasstemplatedefinition
mustbeseen;otherwise,thecompilercannotverifythatthememberexistsand
isaccessible(notprivateorprotected).Furthermore,thememberfunction
definitionisneededtoo,sincethecallatpoint#6requiresC<int>::f()to
exist.
Hereisanotherexpressionthatneedstheinstantiationofthepreviousclass
templatebecausethesizeofC<void>isneeded:C<void>*p=new
C<void>;
Inthiscase,instantiationisneededsothatthecompilercandeterminethesizeof
C<void>,whichthenew-expressionneedstodeterminehowmuchstorageto
allocate.Youmightobservethatforthisparticulartemplate,thetypeofthe
argumentXsubstitutedforTwillnotinfluencethesizeofthetemplatebecause
inanycase,C<X>isanemptyclass.However,acompilerisnotrequiredto
avoidinstantiationbyanalyzingthetemplatedefinition(andallcompilersdo
performtheinstantiationinpractice).Furthermore,instantiationisalsoneededin
thisexampletodeterminewhetherC<void>hasanaccessibledefault
constructorandtoensureC<void>doesnotdeclarememberoperatorsnewor
delete.
Theneedtoaccessamemberofaclasstemplateisnotalwaysveryexplicitly
visibleinthesourcecode.Forexample,C++overloadresolutionrequires
visibilityintoclasstypesforparametersofcandidatefunctions:Clickhereto
viewcodeimage
template<typenameT>
classC{
public:
C(int);//aconstructorthatcanbecalledwithasingleparameter
};//maybeusedforimplicitconversions
voidcandidate(C<double>);//#1
voidcandidate(int){}//#2
intmain()
{
candidate(42);//bothpreviousfunctiondeclarationscanbecalled
}
Thecallcandidate(42)willresolvetotheoverloadeddeclarationatpoint
#2.However,thedeclarationatpoint#1couldalsobeinstantiatedtocheck
whetheritisaviablecandidateforthecall(itisinthiscasebecausetheone-
argumentconstructorcanimplicitlyconvert42toanrvalueoftype
C<double>).Notethatthecompilerisallowed(butnotrequired)toperform
thisinstantiationifitcanresolvethecallwithoutit(ascouldbethecaseinthis
examplebecauseanimplicitconversionwouldnotbeselectedoveranexact
match).NotealsothattheinstantiationofC<double>couldtriggeranerror,
whichmaybesurprising.
14.2LazyInstantiation
Theexamplessofarillustraterequirementsthatarenotfundamentallydifferent
fromtherequirementswhenusingnontemplateclasses.Manyusesrequirea
classtypetobecomplete(seeSection10.3.1onpage154).Forthetemplate
case,thecompilerwillgeneratethiscompletedefinitionfromtheclasstemplate
definition.
Apertinentquestionnowarises:Howmuchofthetemplateisinstantiated?A
vagueansweristhefollowing:Onlyasmuchasisreallyneeded.Inotherwords,
acompilershouldbe“lazy”wheninstantiatingtemplates.Let’slookatexactly
whatthislazinessentails.
14.2.1PartialandFullInstantiation
Aswehaveseen,thecompilersometimesdoesn’tneedtosubstitutethe
completedefinitionofaclassorfunctiontemplate.Forexample:Clickhereto
viewcodeimage
template<typenameT>Tf(Tp){return2*p;}
decltype(f(2))x=2;Inthisexample,thetypeindicatedby
decltype(f(2))doesnotrequirethecompleteinstantiationofthe
functiontemplatef().Acompileristhereforeonlypermittedto
substitutethedeclarationoff(),butnotits“body.”Thisis
sometimescalledpartialinstantiation.
Similarly,ifaninstanceofaclasstemplateisreferredtowithouttheneedfor
thatinstancetobeacompletetype,thecompilershouldnotperformacomplete
instantiationofthatclasstemplateinstance.Considerthefollowingexample:
Clickheretoviewcodeimage
template<typenameT>classQ{
usingType=typenameT::Type;
};
Q<int>*p=0;//OK:thebodyofQ<int>isnotsubstituted
Here,thefullinstantiationofQ<int>wouldtriggeranerror,because
T::Typedoesn’tmakesensewhenTisint.ButbecauseQ<int>neednot
becompleteinthisexample,nofullinstantiationisperformedandthecodeis
okay(albeitsuspicious).
Variabletemplatesalsohavea“full”vs.“partial”instantiationdistinction.The
followingexampleillustratesit:Clickheretoviewcodeimage
template<typenameT>Tv=T::default_value();
decltype(v<int>)s;//OK:initializerofv<int>notinstantiated
Afullinstantiationofv<int>wouldelicitanerror,butthatisnotneededifwe
onlyneedthetypeofthevariabletemplateinstance.
Interestingly,aliastemplatesdonothavethisdistinction:Therearenotwo
waysofsubstitutingthem.
InC++,whenspeakingabout“templateinstantiation”withoutbeingspecific
aboutfullorpartialinstantiation,theformerisintended.Thatis,instantiationis
fullinstantiationbydefault.
14.2.2InstantiatedComponents
Whenaclasstemplateisimplicitly(fully)instantiated,eachdeclarationofits
membersisinstantiatedaswell,butthecorrespondingdefinitionsarenot(i.e.,
thememberarepartiallyinstantiated).Thereareafewexceptionstothis.First,if
theclasstemplatecontainsananonymousunion,themembersofthatunion’s
definitionarealsoinstantiated.3Theotherexceptionoccurswithvirtualmember
functions.Theirdefinitionsmayormaynotbeinstantiatedasaresultof
instantiatingaclasstemplate.Manyimplementationswill,infact,instantiatethe
definitionbecausetheinternalstructurethatenablesthevirtualcallmechanism
requiresthevirtualfunctionsactuallytoexistaslinkableentities.
Defaultfunctioncallargumentsareconsideredseparatelywheninstantiating
templates.Specifically,theyarenotinstantiatedunlessthereisacalltothat
function(ormemberfunction)thatactuallymakesuseofthedefaultargument.
If,ontheotherhand,thefunctioniscalledwithexplicitargumentsthatoverride
thedefault,thenthedefaultargumentsarenotinstantiated.
Similarly,exceptionspecificationsanddefaultmemberinitializersarenot
instantiatedunlesstheyareneeded.
Let’sputtogethersomeexamplesthatillustratesomeoftheseprinciples:
Clickheretoviewcodeimage
details/lazy1.hpp
template<typenameT>
classSafe{
};
template<intN>
classDanger{
intarr[N];//OKhere,althoughwouldfailforN<=0
};
template<typenameT,intN>
classTricky{
public:
voidnoBodyHere(Safe<T>=3);//OKuntilusageofdefaultvalue
resultsinanerror
voidinclass(){
Danger<N>noBoomYet;//OKuntilinclass()isusedwithN<=0
}
structNested{
Danger<N>pfew;//OKuntilNestedisusedwithN<=0
};
union{//dueanonymousunion:
Danger<N>anonymous;//OKuntilTrickyisinstantiatedwithN<=0
intalign;
};
voidunsafe(T(*p)[N]);//OKuntilTrickyisinstantiatedwithN<=0
voiderror(){
Danger<-1>boom;//alwaysERROR(whichnotallcompilersdetect)
}
};
AstandardC++compilerwillexaminethesetemplatedefinitionstocheckthe
syntaxandgeneralsemanticconstraints.Whiledoingso,itwill“assumethe
best”whencheckingconstraintsinvolvingtemplateparameters.Forexample,
theparameterNinthememberDanger::arrcouldbezeroornegative
(whichwouldbeinvalid),butitisassumedthatthisisn’tthecase.4The
definitionsofinclass(),structNested,andtheanonymousunionare
thusnotaproblem.
Forthesamereason,thedeclarationofthememberunsafe(T(*p)[N])
isnotaproblem,aslongasNisanunsubstitutedtemplateparameter.
Thedefaultargumentspecification(=3)onthedeclarationofthemember
noBodyHere()issuspiciousbecausethetemplateSafe<>isn’tinitializable
withaninteger,buttheassumptionisthateitherthedefaultargumentwon’t
actuallybeneededforthegenericdefinitionofSafe<T>orthatSafe<T>will
bespecialized(seeChapter16)toenableinitializationwithanintegervalue.
However,thedefinitionofthememberfunctionerror()isanerrorevenwhen
thetemplateisnotinstantiated,becausetheuseofDanger<-1>requiresa
completedefinitionoftheclassDanger<-1>,andgeneratingthatclassruns
intoanattempttodefineanarraywithnegativesize.Interestingly,whilethe
standardclearlystatesthatthiscodeisinvalid,italsoallowscompilersnotto
diagnosetheerrorwhenthetemplateinstanceisnotactuallyused.Thatis,since
Tricky<T,N>::error()isnotusedforanyconcreteTandN,acompileris
notrequiredtoissueanerrorforthiscase.Forexample,GCCandVisualC++do
notdiagnosethiserroratthetimeofthiswriting.
Nowlet’sanalyzewhathappenswhenweaddthefollowingdefinition:
Tricky<int,-1>inst;Thiscausesthecompilerto(fully)instantiate
Tricky<int,-1>bysubstitutingintforTand-1forNinthedefinition
oftemplateTricky<>.Notallthememberdefinitionswillbeneeded,butthe
defaultconstructorandthedestructor(bothimplicitlydeclaredinthiscase)are
definitelycalled,andhencetheirdefinitionsmustbeavailablesomehow(which
isthecaseinourexample,sincetheyareimplicitlygenerated).Asexplained
above,themembersofTricky<int,-1>arepartiallyinstantiated(i.e.,their
declarationsaresubstituted):Thatprocesscanpotentiallyresultinerrors.For
example,thedeclarationofunsafe(T(*p)[N])createsanarraytypewith
anegativeofnumberelements,andthatisanerror.Similarly,themember
anonymousnowtriggersanerror,becausetypeDanger<-1>cannotbe
completed.Incontrast,thedefinitionsofthemembersinclass()and
structNestedarenotyetinstantiated,andthusnoerrorsoccurfromtheir
needforthecompletetypeDanger<-1>(whichcontainsaninvalidarray
definitionaswediscussedearlier).
Aswritten,wheninstantiatingatemplate,inpractice,thedefinitionsofvirtual
membersshouldalsobeprovided.Otherwise,linkererrorsarelikelytooccur.
Forexample:Clickheretoviewcodeimage
details/lazy2.cpp
template<typenameT>
classVirtualClass{
public:
virtual~VirtualClass(){}
virtualTvmem();//LikelyERRORifinstantiatedwithoutdefinition
};
intmain()
{
VirtualClass<int>inst;
}
Finally,anoteaboutoperator->.Consider:template<typenameT>
classC{
public:
Toperator->();
};
Normally,operator->mustreturnapointertypeoranotherclasstypeto
whichoperator->applies.ThissuggeststhatthecompletionofC<int>
triggersanerror,becauseitdeclaresareturntypeofintforoperator->.
However,becausecertainnaturalclasstemplatedefinitionstriggerthesekindsof
definitions,5thelanguageruleismoreflexible.Auser-definedoperator->is
onlyrequiredtoreturnatypetowhichanother(e.g.,built-in)operator->
appliesifthatoperatorisactuallyselectedbyoverloadresolution.Thisistrue
evenoutsidetemplates(althoughtherelaxedbehaviorislessusefulinthose
contexts).Hence,thedeclarationheretriggersnoerror,eventhoughintis
substitutedforthereturntype.
14.3TheC++InstantiationModel
Templateinstantiationistheprocessofobtainingaregulartype,function,or
variablefromacorrespondingtemplateentitybyappropriatelysubstitutingthe
templateparameters.Thismaysoundfairlystraightforward,butinpracticemany
detailsneedtobeformallyestablished.
14.3.1Two-PhaseLookup
InChapter13wesawthatdependentnamescannotberesolvedwhenparsing
templates.Instead,theyarelookedupagainatthepointofinstantiation.
Nondependentnames,however,arelookedupearlysothatmanyerrorscanbe
diagnosedwhenthetemplateisfirstseen.Thisleadstotheconceptoftwo-phase
lookup:6Thefirstphaseistheparsingofatemplate,andthesecondphaseisits
instantiation:1.Duringthefirstphase,whileparsingatemplate,nondependent
namesarelookedupusingboththeordinarylookuprulesand,ifapplicable,the
rulesforargument-dependentlookup(ADL).Unqualifieddependentnames
(whicharedependentbecausetheylooklikethenameofafunctioninafunction
callwithdependentarguments)arelookedupusingtheordinarylookuprules,
buttheresultofthelookupisnotconsideredcompleteuntilanadditionallookup
isperformedinthesecondphase(whenthetemplateisinstantiated).
2.Duringthesecondphase,whileinstantiatingatemplateatapointcalledthe
pointofinstantiation(POI),dependentqualifiednamesarelookedup(with
thetemplateparametersreplacedwiththetemplateargumentsforthatspecific
instantiation),andanadditionalADLisperformedfortheunqualified
dependentnamesthatwerelookedupusingordinarylookupinthefirstphase.
Forunqualifieddependentnames,theinitialordinarylookup—whilenot
complete—isusedtodecidewhetherthenameisatemplate.Considerthe
followingexample:Clickheretoviewcodeimage
namespaceN{
template<typename>voidg(){}
enumE{e};
}
template<typename>voidf(){}
template<typenameT>voidh(TP){
f<int>(p);//#1
g<int>(p);//#2ERROR
}
intmain(){
h(N::e);//callstemplatehwithT=N::E
}
Inline#1,whenseeingthenameffollowedbya<,thecompilerhastodecide
whetherthat<isananglebracketoraless-thansign.Thatdependsonwhether
fisknowntobethenameofatemplateornot;inthiscase,ordinarylookup
findsthedeclarationoff,whichisindeedatemplate,andsoparsingsucceeds
withanglebrackets.
Line#2,however,producesanerrorbecausenotemplategisfoundusing
ordinarylookup;the<isthustreatedasaless-thansign,whichisasyntaxerror
inthisexample.Ifwecouldgetpastthisissue,we’deventuallyfindthetemplate
N::gusingADLwheninstantiatinghforT=N::E(sinceNisanamespace
associatedwithE),butwecannotgetthatfaruntilwesuccessfullyparsethe
genericdefinitionofh.
14.3.2PointsofInstantiation
Wehavealreadyillustratedthattherearepointsinthesourceoftemplateclients
whereaC++compilermusthaveaccesstothedeclarationorthedefinitionofa
templateentity.Apointofinstantiation(POI)iscreatedwhenacodeconstruct
referstoatemplatespecializationinsuchawaythatthedefinitionofthe
correspondingtemplateneedstobeinstantiatedtocreatethatspecialization.The
POIisapointinthesourcewherethesubstitutedtemplatecouldbeinserted.For
example:Clickheretoviewcodeimage
classMyInt{
public:
MyInt(inti);
};
MyIntoperator-(MyIntconst&);
booloperator>(MyIntconst&,MyIntconst&);
usingInt=MyInt;
template<typenameT>
voidf(Ti)
{
if(i>0){
g(-i);
}
}
//#1
voidg(Int)
{
//#2
f<Int>(42);//pointofcall
//#3
}
//#4
WhenaC++compilerseesthecallf<Int>(42),itknowsthetemplatefwill
needtobeinstantiatedforTsubstitutedwithMyInt:APOIiscreated.Points#2
and#3areveryclosetothepointofcall,buttheycannotbePOIsbecauseC++
doesnotallowustoinsertthedefinitionof::f<Int>(Int)there.The
essentialdifferencebetweenpoint#1andpoint#4isthatatpoint#4thefunction
g(Int)isvisible,andhencethetemplate-dependentcallg(-i)canbe
resolved.However,ifpoint#1werethePOI,thenthatcallcouldnotberesolved
becauseg(Int)isnotyetvisible.Fortunately,C++definesthePOIfora
referencetoafunctiontemplatespecializationtobeimmediatelyafterthe
nearestnamespacescopedeclarationordefinitionthatcontainsthatreference.In
ourexample,thisispoint#4.
YoumaywonderwhythisexampleinvolvedthetypeMyIntratherthan
simpleint.Theanswerliesinthefactthatthesecondlookupperformedatthe
POIisonlyanADL.Becauseinthasnoassociatednamespace,thePOIlookup
wouldthereforenottakeplaceandwouldnotfindfunctiong.Hence,ifwewere
toreplacethetypealiasdeclarationforIntwithusingInt=int;theprevious
examplewouldnolongercompile.Thefollowingexamplesuffersfromasimilar
problem:Clickheretoviewcodeimage
template<typenameT>
voidf1(Tx)
{
g1(x);//#1
}
voidg1(int)
{
}
intmain()
{
f1(7);//ERROR:g1notfound!
}
//#2POIforf1<int>(int)
Thecallf1(7)createsaPOIforf1<int>(int)justoutsideofmain()at
point#2.Inthisinstantiation,thekeyissueisthelookupoffunctiong1.When
thedefinitionofthetemplatef1isfirstencountered,itisnotedthatthe
unqualifiednameg1isdependentbecauseitisthenameofafunctionina
functioncallwithdependentarguments(thetypeoftheargumentxdependson
thetemplateparameterT).Therefore,g1islookedupatpoint#1usingordinary
lookuprules;however,nog1isvisibleatthispoint.Atpoint#2,thePOI,the
functionislookedupagaininassociatednamespacesandclasses,buttheonly
argumenttypeisint,andithasnoassociatednamespacesandclasses.
Therefore,g1isneverfoundeventhoughordinarylookupatthePOIwould
havefoundg1.
Thepointofinstantiationforvariabletemplatesishandledsimilarlytothatof
functiontemplates.7Forclasstemplatespecializations,thesituationisdifferent,
asthefollowingexampleillustrates:Clickheretoviewcodeimage
template<typenameT>
classS{
public:
Tm;
};
//#1
unsignedlongh()
{
//#2
return(unsignedlong)sizeof(S<int>);
//#3
}
//#4
Again,thefunctionscopepoints#2and#3cannotbePOIsbecauseadefinition
ofanamespacescopeclassS<int>cannotappearthere(andtemplatescan
generallynotappearinfunctionscope8).Ifweweretofollowtherulefor
functiontemplateinstances,thePOIwouldbeatpoint#4,butthenthe
expressionsizeof(S<int>)isinvalidbecausethesizeofS<int>cannot
bedetermineduntilpoint#4isreached.Therefore,thePOIforareferencetoa
generatedclassinstanceisdefinedtobethepointimmediatelybeforethenearest
namespacescopedeclarationordefinitionthatcontainsthereferencetothat
instance.Inourexample,thisispoint#1.
Whenatemplateisactuallyinstantiated,theneedforadditionalinstantiations
mayappear.Considerashortexample:Clickheretoviewcodeimage
template<typenameT>
classS{
public:
usingI=int;
};
//#1
template<typenameT>
voidf()
{
S<char>::Ivar1=41;
typenameS<T>::Ivar2=42;
}
intmain()
{
f<double>();
}
//#2:#2a,#2b
OurprecedingdiscussionalreadyestablishedthatthePOIforf<double>()is
atpoint#2.Thefunctiontemplatef()alsoreferstotheclassspecialization
S<char>withaPOIthatisthereforeatpoint#1.ItreferencesS<T>too,but
becausethisisstilldependent,wecannotreallyinstantiateitatthispoint.
However,ifweinstantiatef<double>()atpoint#2,wenoticethatwealso
needtoinstantiatethedefinitionofS<double>.Suchsecondaryortransitive
POIsaredefinedslightlydifferently.Forfunctiontemplates,thesecondaryPOI
isexactlythesameastheprimaryPOI.Forclassentities,thesecondaryPOI
immediatelyprecedes(inthenearestenclosingnamespacescope)theprimary
POI.Inourexample,thismeansthatthePOIoff<double>()canbeplaced
atpoint#2b,andjustbeforeit—atpoint#2a—isthesecondaryPOIfor
S<double>.NotehowthisdiffersfromthePOIforS<char>.
AtranslationunitoftencontainsmultiplePOIsforthesameinstance.Forclass
templateinstances,onlythefirstPOIineachtranslationunitisretained,andthe
subsequentonesareignored(theyarenotreallyconsideredPOIs).Forinstances
offunctionandvariabletemplates,allPOIsareretained.Ineithercase,theODR
requiresthattheinstantiationsoccurringatanyoftheretainedPOIsbe
equivalent,butaC++compilerdoesnotneedtoverifyanddiagnoseviolations
ofthisrule.ThisallowsaC++compilertopickjustonenonclassPOItoperform
theactualinstantiationwithoutworryingthatanotherPOImightresultina
differentinstantiation.
Inpractice,mostcompilersdelaytheactualinstantiationofmostfunction
templatestotheendofthetranslationunit.Someinstantiationscannotbe
delayed,includingcaseswhereinstantiationisneededtodetermineadeduced
returntype(seeSection15.10.1onpage296andSection15.10.4onpage303)
andcaseswherethefunctionisconstexprandmustbeevaluatedtoproducea
constantresult.Somecompilersinstantiateinlinefunctionswhenthey’refirst
usedtopotentiallyinlinethecallrightaway.9ThiseffectivelymovesthePOIsof
thecorrespondingtemplatespecializationstotheendofthetranslationunit,
whichispermittedbytheC++standardasanalternativePOI.
14.3.3TheInclusionModel
WheneveraPOIisencountered,thedefinitionofthecorrespondingtemplate
mustsomehowbeaccessible.Forclassspecializationsthismeansthattheclass
templatedefinitionmusthavebeenseenearlierinthetranslationunit.Forthe
POIsoffunctionandvariabletemplates(andmemberfunctionsandstaticdata
membersofclasstemplates)thisisalsoneeded,andtypicallytemplate
definitionsaresimplyaddedtoheaderfilesthatare#includedintothe
translationunit,evenwhenthey’renontypetemplates.Thissourcemodelfor
templatedefinitionsiscalledtheinclusionmodel,anditistheonlyautomatic
sourcemodelfortemplatessupportedbythecurrentC++standard.10
Althoughtheinclusionmodelencouragesprogrammerstoplacealltheir
templatedefinitionsinheaderfilessothattheyareavailabletosatisfyanyPOIs
thatmayarise,itisalsopossibletoexplicitlymanageinstantiationsusing
explicitinstantiationdeclarationsandexplicitinstantiationdefinitions(see
Section14.5onpage260).Doingsoislogisticallynottrivialandmostofthe
timeprogrammerswillprefertorelyontheautomaticinstantiationmechanism
instead.Onechallengeforanimplementationwiththeautomaticschemeisto
dealwiththepossibilityofhavingPOIsforthesamespecializationofafunction
orvariabletemplates(orthesamememberfunctionorstaticdatamemberofa
classtemplateinstance)acrossdifferenttranslationunits.Wediscussapproaches
tothisproblemnext.
14.4ImplementationSchemes
InthissectionwereviewsomewaysinwhichC++implementationssupportthe
inclusionmodel.Alltheseimplementationsrelyontwoclassiccomponents:a
compilerandalinker.Thecompilertranslatessourcecodetoobjectfiles,which
containmachinecodewithsymbolicannotations(cross-referencingotherobject
filesandlibraries).Thelinkercreatesexecutableprogramsorlibrariesby
combiningtheobjectfilesandresolvingthesymboliccross-referencesthey
contain.Inwhatfollows,weassumesuchamodeleventhoughitisentirely
possible(butnotpopular)toimplementC++inotherways.Forexample,one
mightimagineaC++interpreter.
Whenaclasstemplatespecializationisusedinmultipletranslationunits,a
compilerwillrepeattheinstantiationprocessineverytranslationunit.Thisposes
veryfewproblemsbecauseclassdefinitionsdonotdirectlycreatelow-level
code.TheyareusedonlyinternallybyaC++implementationtoverifyand
interpretvariousotherexpressionsanddeclarations.Inthisregard,themultiple
instantiationsofaclassdefinitionarenotmateriallydifferentfromthemultiple
inclusionsofaclassdefinition—typicallythroughheaderfileinclusion—in
varioustranslationunits.
However,ifyouinstantiatea(noninline)functiontemplate,thesituationmay
bedifferent.Ifyouweretoprovidemultipledefinitionsofanordinarynoninline
function,youwouldviolatetheODR.Assume,forexample,thatyoucompile
andlinkaprogramconsistingofthefollowingtwofiles://====a.cpp:
intmain()
{
}
//====b.cpp:
intmain()
{
}
C++compilerswillcompileeachmoduleseparatelywithoutanyproblems
becauseindeedtheyarevalidC++translationunits.However,yourlinkerwill
mostlikelyprotestifyoutrytolinkthetwotogether:Duplicatedefinitionsare
notallowed.
Incontrast,considerthetemplatecase:
Clickheretoviewcodeimage
//====t.hpp:
//commonheader(inclusionmodel)
template<typenameT>
classS{
public:
voidf();
};
template<typenameT>
voidS::f()//memberdefinition
{
}
voidhelper(S<int>*);
//====a.cpp:
#include"t.hpp"
voidhelper(S<int>*s)
{
s->f();//#1firstpointofinstantiationofS::f
}
//====b.cpp:
#include"t.hpp"
intmain()
{
S<int>s;
helper(&s);
s.f();//#2secondpointofinstantiationofS::f
}
Ifthelinkertreatsinstantiatedmemberfunctionsofclasstemplatesjustlikeit
doesforordinaryfunctionsormemberfunctions,thecompilerneedstoensure
thatitgeneratescodeatonlyoneofthetwoPOIs:atpoints#1or#2,butnot
both.Toachievethis,acompilerhastocarryinformationfromonetranslation
unittotheother,andthisissomethingC++compilerswereneverrequiredtodo
priortotheintroductionoftemplates.Inwhatfollows,wediscussthethree
broadclassesofsolutionsthathavebeenusedbyC++implementers.
Notethatthesameproblemoccurswithalllinkableentitiesproducedby
templateinstantiation:instantiatedfunctiontemplatesandmemberfunction
templates,aswellasinstantiatedstaticdatamembersandinstantiatedvariable
templates.
14.4.1GreedyInstantiation
ThefirstC++compilersthatpopularizedgreedyinstantiationwereproducedby
acompanycalledBorland.Ithasgrowntobebyfarthemostcommonlyused
techniqueamongthevariousC++systems.
Greedyinstantiationassumesthatthelinkerisawarethatcertainentities—
linkabletemplateinstantiationsinparticular—mayinfactappearinduplicate
acrossthevariousobjectfilesandlibraries.Thecompilerwilltypicallymark
theseentitiesinaspecialway.Whenthelinkerfindsmultipleinstances,itkeeps
oneanddiscardsalltheothers.Thereisnotmuchmoretoitthanthat.
Intheory,greedyinstantiationhassomeseriousdrawbacks:•Thecompiler
maybewastingtimeongeneratingandoptimizingNinstantiations,ofwhich
onlyonewillbekept.
•Linkerstypicallydonotcheckthattwoinstantiationsareidenticalbecause
someinsignificantdifferencesingeneratedcodecanvalidlyoccurformultiple
instancesofonetemplatespecialization.Thesesmalldifferencesshouldnot
causethelinkertofail.(Thesedifferencescouldresultfromtinydifferencesin
thestateofthecompilerattheinstantiationtimes.)However,thisoftenalso
resultsinthelinkernotnoticingmoresubstantialdifferences,suchaswhenone
instantiationwascompiledwithstrictfloating-pointmathruleswhereasthe
otherwascompiledwithrelaxed,higher-performancefloating-pointmath
rules.11
•Thesumofalltheobjectfilescouldpotentiallybemuchlargerthanwith
alternativesbecausethesamecodemaybeduplicatedmanytimes.
Inpractice,theseshortcomingsdonotseemtohavecausedmajorproblems.
Perhapsthisisbecausegreedyinstantiationcontrastsveryfavorablywiththe
alternativesinoneimportantaspect:Thetraditionalsource-objectdependencyis
preserved.Inparticular,onetranslationunitgeneratesbutoneobjectfile,and
eachobjectfilecontainscompiledcodeforallthelinkabledefinitionsinthe
correspondingsourcefile(whichincludestheinstantiateddefinitions).Another
importantbenefitisthatallfunctiontemplateinstancesarecandidatesfor
inliningwithoutresortingtoexpensive“link-time”optimizationmechanisms
(and,inpractice,functiontemplateinstancesareoftensmallfunctionsthat
benefitfrominlining).Theotherinstantiationmechanismstreatinlinefunction
templateinstancesspeciallytoensuretheycanbeexpandedinline.However,
greedyinstantiationallowsevennoninlinefunctiontemplateinstancestobe
expandedinline.
Finally,itmaybeworthnotingthatthelinkermechanismthatallowsduplicate
definitionsoflinkableentitiesisalsotypicallyusedtohandleduplicatespilled
inlinedfunctions12andvirtualfunctiondispatchtables.13Ifthismechanismis
notavailable,thealternativeisusuallytoemittheseitemswithinternallinkage,
attheexpenseofgeneratinglargercode.Therequirementthataninlinefunction
haveasingleaddressmakesitdifficulttoimplementthatalternativeina
standard-conformingway.
14.4.2QueriedInstantiation
Inthemid-1990s,acompanycalledSunMicrosystems14releaseda
reimplementationofitsC++compiler(version4.0)withanewandinteresting
solutionoftheinstantiationproblem,whichwecallqueriedinstantiation.
Queriedinstantiationisconceptuallyremarkablysimpleandelegant,andyetitis
chronologicallythemostrecentclassofinstantiationschemesthatwereview
here.Inthisscheme,adatabasesharedbythecompilationsofalltranslation
unitsparticipatinginaprogramismaintained.Thisdatabasekeepstrackof
whichspecializationshavebeeninstantiatedandonwhatsourcecodethey
depend.Thegeneratedspecializationsthemselvesaretypicallystoredwiththis
informationinthedatabase.Wheneverapointofinstantiationforalinkable
entityisencountered,oneofthreethingscanhappen:1.Nospecializationis
available:Inthiscase,instantiationoccurs,andtheresultingspecializationis
enteredinthedatabase.
2.Aspecializationisavailablebutisoutofdatebecausesourcechangeshave
occurredsinceitwasgenerated.Here,too,instantiationoccurs,butthe
resultingspecializationreplacestheonepreviouslystoredinthedatabase.
3.Anup-to-datespecializationisavailableinthedatabase.Nothingneedstobe
done.Althoughconceptuallysimple,thisdesignpresentsafew
implementationchallenges:•Itisnottrivialtomaintaincorrectlythe
dependenciesofthedatabasecontentswithrespecttothestateofthesource
code.Althoughitisnotincorrecttomistakethethirdcaseforthesecond,
doingsoincreasestheamountofworkdonebythecompiler(andhence
overallbuildtime).
•Itisquitecommontocompilemultiplesourcefilesconcurrently.Hence,an
industrial-strengthimplementationneedstoprovidetheappropriateamountof
concurrencycontrolinthedatabase.
Despitethesechallenges,theschemecanbeimplementedquiteefficiently.
Furthermore,therearenoobviouspathologicalcasesthatwouldmakethis
solutionscalepoorly,incontrast,forexample,withgreedyinstantiation,which
mayleadtoalotofwastedwork.
Theuseofadatabasemayalsopresentsomeproblemstotheprogrammer,
unfortunately.Theoriginofmostoftheseproblemsliesinthatfactthatthe
traditionalcompilationmodelinheritedfrommostCcompilersnolonger
applies:Asingletranslationunitnolongerproducesasinglestandaloneobject
file.Assume,forexample,thatyouwishtolinkyourfinalprogram.Thislink
operationneedsnotonlythecontentsofeachoftheobjectfilesassociatedwith
yourvarioustranslationunits,butalsotheobjectfilesstoredinthedatabase.
Similarly,ifyoucreateabinarylibrary,youneedtoensurethatthetoolthat
createsthatlibrary(typicallyalinkeroranarchiver)isawareofthedatabase
contents.Moregenerally,anytoolthatoperatesonobjectfilesmayneedtobe
madeawareofthecontentsofthedatabase.Manyoftheseproblemscanbe
alleviatedbynotstoringtheinstantiationsinthedatabase,butinsteadby
emittingtheobjectcodeintheobjectfilethatcausedtheinstantiationinthefirst
place.
Librariespresentyetanotherchallenge.Anumberofgeneratedspecializations
maybepackagedinalibrary.Whenthelibraryisaddedtoanotherproject,that
project’sdatabasemayneedtobemadeawareoftheinstantiationsthatare
alreadyavailable.Ifnot,andiftheprojectcreatessomeofitsownpointsof
instantiationforthespecializationspresentinthelibrary,duplicateinstantiation
mayoccur.Apossiblestrategytodealwithsuchsituationsistousethesame
linkertechnologythatenablesgreedyinstantiation:Makethelinkerawareof
generatedspecializationsandhaveitweedoutduplicates(whichshould
nonethelessoccurmuchlessfrequentlythanwithgreedyinstantiation).Various
othersubtlearrangementsofsources,objectfiles,andlibrariescanleadto
frustratingproblemssuchasmissinginstantiationsbecausetheobjectcode
containingtherequiredinstantiationwasnotlinkedinthefinalexecutable
program.
Ultimately,queriedinstantiationdidnotsurviveinthemarketplace,andeven
Sun’scompilernowusesgreedyinstantiation.
14.4.3IteratedInstantiation
ThefirstcompilertosupportC++templateswasCfront3.0—adirectdescendant
ofthecompilerthatBjarneStroustrupwrotetodevelopthelanguage.15An
inflexibleconstraintonCfrontwasthatithadtobeveryportablefromplatform
toplatform,andthismeantthatit(1)usedtheClanguageasacommontarget
representationacrossalltargetplatformsand(2)usedthelocaltargetlinker.In
particular,thisimpliedthatthelinkerwasnotawareoftemplates.Infact,Cfront
emittedtemplateinstantiationsasordinaryCfunctions,andthereforeithadto
avoidduplicateinstantiations.AlthoughtheCfrontsourcemodelwasdifferent
fromthestandardinclusionmodel,itsinstantiationstrategycanbeadaptedtofit
theinclusionmodel.Assuch,italsomeritsrecognitionasthefirstincarnationof
iteratedinstantiation.TheCfrontiterationcanbedescribedasfollows:1.
Compilethesourceswithoutinstantiatinganyrequiredlinkablespecializations.
2.Linktheobjectfilesusingaprelinker.
3.Theprelinkerinvokesthelinkerandparsesitserrormessagestodetermine
whetheranyaretheresultofmissinginstantiations.Ifso,theprelinker
invokesthecompileronsourcesthatcontaintheneededtemplatedefinitions,
withoptionstogeneratethemissinginstantiations.
4.Repeatstep3ifanydefinitionsaregenerated.
Theneedtoiteratestep3ispromptedbytheobservationthattheinstantiationof
onelinkableentitymayleadtotheneedforanothersuchentitythatwasnotyet
instantiated.Eventuallytheiterationwill“converge,”andthelinkerwillsucceed
inbuildingacompleteprogram.
ThedrawbacksoftheoriginalCfrontschemearequitesevere:•Theperceived
timetolinkisaugmentednotonlybytheprelinkeroverheadbutalsobythecost
ofeveryrequiredrecompilationandrelinking.SomeusersofCfront-based
systemsreportedlinktimesof“afewdays”comparedwith“aboutanhour”with
thealternativeschemesreportedearlier.
•Diagnostics(errors,warnings)aredelayeduntillinktime.Thisisespecially
painfulwhenlinkingbecomesexpensiveandthedevelopermustwaithours
justtofindoutaboutatypoinatemplatedefinition.
•Specialcaremustbetakentorememberwherethesourcecontaininga
particulardefinitionislocated(step1).Cfrontinparticularusedacentral
repository,whichhadtodealwithsomeofthechallengesofthecentral
databaseinthequeriedinstantiationapproach.Inparticular,theoriginalCfront
implementationwasnotengineeredtosupportconcurrentcompilations.
TheiterationprinciplewassubsequentlyrefinedbothbytheEdisonDesign
Group’s(EDG)implementationandbyHP’saC++,16eliminatingsomeofthe
drawbacksoftheoriginalCfrontimplementation.Inpractice,these
implementationsworkquitewell,and,althoughabuild“fromscratch”is
typicallymoretimeconsumingthanthealternativeschemes,subsequentbuild
timesarequitecompetitive.Still,relativelyfewC++compilersuseiterated
instantiationanymore.
14.5ExplicitInstantiation
Itispossibletocreateexplicitlyapointofinstantiationforatemplate
specialization.Theconstructthatachievesthisiscalledanexplicitinstantiation
directive.Syntactically,itconsistsofthekeywordtemplatefollowedbya
declarationofthespecializationtobeinstantiated.Forexample:Clickhereto
viewcodeimage
template<typenameT>
voidf(T)
{
}
//fourvalidexplicitinstantiations:
templatevoidf<int>(int);
templatevoidf<>(float);
templatevoidf(long);
templatevoidf(char);Notethateveryinstantiationdirectiveis
valid.Templateargumentscanbededuced(seeChapter15).
Membersofclasstemplatescanalsobeexplicitlyinstantiatedinthisway:
Clickheretoviewcodeimage
template<typenameT>
classS{
public:
voidf(){
}
};
templatevoidS<int>::f();
templateclassS<void>;Furthermore,allthemembersofaclass
templatespecializationcanbeexplicitlyinstantiatedbyexplicitly
instantiatingtheclasstemplatespecialization.Becausethese
explicitinstantiationdirectivesensurethatadefinitionofthe
namedtemplatespecialization(ormemberthereof)iscreated,the
explicitinstantiationdirectivesabovearemoreaccuratelyreferred
toasexplicitinstantiationdefinitions.Atemplatespecialization
thatisexplicitlyinstantiatedshouldnotbeexplicitlyspecialized,
andviceversa,becausethatwouldimplythatthetwodefinitions
couldbedifferent(thusviolatingtheODR).
14.5.1ManualInstantiation
ManyC++programmershaveobservedthatautomatictemplateinstantiationhas
anontrivialnegativeimpactonbuildtimes.Thisisparticularlytruewith
compilersthatimplementgreedyinstantiation(Section14.4.1onpage256),
becausethesametemplatespecializationsmaybeinstantiatedandoptimizedin
manydifferenttranslationunits.
Atechniquetoimprovebuildtimesconsistsinmanuallyinstantiatingthose
templatespecializationsthattheprogramrequiresinasinglelocationand
inhibitingtheinstantiationinallothertranslationunits.Oneportablewayto
ensurethisinhibitionistonotprovidethetemplatedefinitionexceptinthe
translationunitwhereitisexplicitlyinstantiated.17Forexample:Clickhereto
viewcodeimage
//=====translationunit1:
template<typenameT>voidf();//nodefinition:prevents
instantiation
//inthistranslationunit
voidg()
{
f<int>();
}
//=====translationunit2:
template<typenameT>voidf()
{
//implementation
}
templatevoidf<int>();//manualinstantiation
voidg();
intmain()
{
g();
}
Inthefirsttranslationunit,thecompilercannotseethedefinitionofthefunction
templatef,soitwillnot(cannot)produceaninstantiationoff<int>.The
secondtranslationunitprovidesthedefinitionoff<int>viaanexplicit
instantiationdefinition;withoutit,theprogramwouldfailtolink.
Manualinstantiationhasacleardisadvantage:Wemustcarefullykeeptrackof
whichentitiestoinstantiate.Forlargeprojectsthisquicklybecomesanexcessive
burden;hencewedonotrecommendit.Wehaveworkedonseveralprojectsthat
initiallyunderestimatedthisburden,andwecametoregretourdecisionasthe
codematured.
However,manualinstantiationalsohasafewadvantagesbecausethe
instantiationcanbetunedtotheneedsoftheprogram.Clearly,theoverheadof
largeheadersisavoided,asistheoverheadofrepeatedlyinstantiatingthesame
templateswiththesameargumentsinmultipletranslationunits.Moreover,the
sourcecodeoftemplatedefinitioncanbekepthidden,butthennoadditional
instantiationscanbecreatedbyaclientprogram.
Someoftheburdenofmanualinstantiationcanbealleviatedbyplacingthe
templatedefinitionintoathirdsourcefile,conventionallywiththeextension
.tpp.Forourfunctionf,thisbreaksdowninto:Clickheretoviewcodeimage
//=====f.hpp:
template<typenameT>voidf();//nodefinition:prevents
instantiation
//=====t.hpp:
#include"f.hpp"
template<typenameT>voidf()//definition
{
//implementation
}
//=====f.cpp:
#include"f.tpp"
templatevoidf<int>();//manualinstantiation
Thisstructureprovidessomeflexibility.Onecanincludeonlyf.hpptogetthe
declarationoff,withnoautomaticinstantiation.Explicitinstantiationscanbe
manuallyaddedtof.cppasneeded.Or,ifmanualinstantiationsbecometoo
onerous,onecanalsoincludef.tpptoenableautomaticinstantiation.
14.5.2ExplicitInstantiationDeclarations
Amoretargetedapproachtotheeliminationofredundantautomatic
instantiationsistheuseofanexplicitinstantiationdeclaration,whichisan
explicitinstantiationdirectiveprefixedbythekeywordextern.Anexplicit
instantiationdeclarationgenerallysuppressesautomaticinstantiationofthe
namedtemplatespecialization,becauseitdeclaresthatthenamedtemplate
specializationwillbedefinedsomewhereintheprogram(byanexplicit
instantiationdefinition).Wesaygenerally,becausetherearemanyexceptionsto
this:•Inlinefunctionscanstillbeinstantiatedforthepurposeofexpandingthem
inline(butnoseparateobjectcodeisgenerated).
•Variableswithdeducedautoordecltype(auto)typesandfunctionswith
deducedreturntypescanstillbeinstantiatedtodeterminetheirtypes.
•Variableswhosevaluesareusableasconstant-expressionscanstillbe
instantiatedsotheirvaluescanbeevaluated.
•Variablesofreferencetypescanstillbeinstantiatedsotheentitytheyreference
canberesolved.
•Classtemplatesandaliastemplatescanstillbeinstantiatedtocheckthe
resultingtypes.
Usingexplicitinstantiationdeclarations,wecanprovidethetemplatedefinition
forfintheheader(t.hpp),thensuppressautomaticinstantiationfor
commonlyusedspecializations,asfollows:Clickheretoviewcodeimage
//=====t.hpp:
template<typenameT>voidf()
{
}
externtemplatevoidf<int>();//declaredbutnotdefined
externtemplatevoidf<float>();//declaredbutnotdefined
//=====t.cpp:
templatevoidf<int>();//definition
templatevoidf<float>();//definition
Eachexplicitinstantiationdeclarationmustbepairedwithacorresponding
explicitinstantiationdefinition,whichmustfollowtheexplicitinstantiation
declaration.Omittingthedefinitionwillresultinalinkererror.
Explicitinstantiationdeclarationscanbeusedtoimprovecompileorlink
timeswhencertainspecializationsareusedinmanydifferenttranslationunits.
Unlikewithmanualinstantiation,whichrequiresmanuallyupdatingthelistof
explicitinstantiationdefinitionseachtimeanewspecializationisrequired,
explicitinstantiationdeclarationscanbeintroducedasanoptimizationatany
point.However,thecompile-timebenefitsmaynotbeassignificantaswith
manualinstantiation,bothbecausesomeredundantautomaticinstantiationis
likelytooccur18andbecausethetemplatedefinitionsarestillparsedaspartof
theheader.
14.6Compile-TimeifStatements
AsintroducedinSection8.5onpage134,C++17addedanewstatementkind
thatturnsouttoberemarkablyusefulwhenwritingtemplates:compile-timeif.
Italsointroducesanewwrinkleintheinstantiationprocess.
Thefollowingexampleillustratesitsbasicoperation:Clickheretoviewcode
image
template<typenameT>boolf(Tp){
ifconstexpr(sizeof(T)<=sizeof(longlong)){
returnp>0;
}else{
returnp.compare(0)>0;
}
}
boolg(intn){
returnf(n);//OK
}
Thecompile-timeifisanifstatement,wheretheifkeywordisimmediately
followedbytheconstexprkeyword(asinthisexample).19Theparenthesized
conditionthatfollowsmusthaveaconstantBooleanvalue(implicitconversions
toboolareincludedinthatconsideration).Thecompilerthereforeknows
whichbranchwillbeselected;theotherbranchiscalledthediscardedbranch.
Ofparticularinterestisthatduringtheinstantiationoftemplates(including
genericlambdas),thediscardedbranchisnotinstantiated.Thatisnecessaryfor
ourexampletobevalid:Weareinstantiatingf(T)withT=int,which
meansthattheelsebranchisdiscarded.Ifitweren’tdiscarded,itwouldbe
instantiatedandwe’drunintoanerrorfortheexpressionp.compare(0)
(whichisn’tvalidwhenpisasimpleinteger).
PriortoC++17anditsconstexprifstatements,avoidingsucherrorsrequired
explicittemplatespecializationoroverloading(seeChapter16)toachieve
similareffects.
Theexampleabove,inC++14,mightbeimplementedasfollows:Clickhere
toviewcodeimage
template<boolb>structDispatch{//onlytobeinstantiatedwhenb
isfalse
staticboolf(Tp){//(duetonextspecializationfortrue)
returnp.compare(0)>0;
}
};
template<>structDispatch<true>{
staticboolf(Tp){
returnp>0;
}
};
template<typenameT>boolf(Tp){
returnDispatch<sizeof(T)<=sizeof(longlong)>::f(p);
}
boolg(intn){
returnf(n);//OK
}
Clearly,theconstexprifalternativeexpressesourintentionfarmoreclearlyand
concisely.However,itrequiresimplementationstorefinetheunitof
instantiation:Whereaspreviouslyfunctiondefinitionswerealwaysinstantiated
asawhole,nowitmustbepossibletoinhibittheinstantiationofpartsofthem.
Anotherveryhandyuseofconstexprifisexpressingtherecursionneededto
handlefunctionparameterpacks.Togeneralizetheexample,introducedin
Section8.5onpage134:Clickheretoviewcodeimage
template<typenameHead,typename…Remainder>
voidf(Head&&h,Remainder&&…r){
doSomething(std::forward<Head>(h));
ifconstexpr(sizeof…(r)!=0){
//handletheremainderrecursively(perfectlyforwardingthe
arguments):
f(std::forward<Remainder>(r)…);
}
}
Withoutconstexprifstatements,thisrequiresanadditionaloverloadofthef()
templatetoensurethatrecursionterminates.
Eveninnontemplatecontexts,constexprifstatementshaveasomewhat
uniqueeffect:Clickheretoviewcodeimage
voidh();
voidg(){
ifconstexpr(sizeof(int)==1){
h();
}
}
Onmostplatforms,theconditioning()isfalseandthecalltoh()is
thereforediscarded.Asaconsequence,h()neednotnecessarilybedefinedat
all(unlessitisusedelsewhere,ofcourse).Hadthekeywordconstexprbeen
omittedinthisexample,alackofadefinitionforh()wouldoftenelicitanerror
atlinktime.20
14.7IntheStandardLibrary
TheC++standardlibraryincludesanumberoftemplatesthatareonly
commonlyusedwithafewbasictypes.Forexample,the
std::basic_stringclasstemplateismostcommonlyusedwithchar
(becausestd::stringisatypealiasofstd::basic_string<char>)
orwchar_t,althoughitispossibletoinstantiateitwithothercharacter-like
types.Therefore,itiscommonforstandardlibraryimplementationstointroduce
explicitinstantiationdeclarationsforthesecommoncases.Forexample:Click
heretoviewcodeimage
namespacestd{
template<typenamecharT,typenametraits=char_traits<charT>,
typenameAllocator=allocator<charT>>
classbasic_string{
…
};
externtemplateclassbasic_string<char>;
externtemplateclassbasic_string<wchar_t>;
}
Thesourcefilesimplementingthestandardlibrarywillthencontainthe
correspondingexplicitinstantiationdefinitions,sothatthesecommon
implementationscanbesharedamongallusersofthestandardlibrary.Similar
explicitinstantiationsoftenexistforthevariousstreamclasses,suchas
basic_iostream,basic_istream,andsoon.
14.8Afternotes
Thischapterdealswithtworelatedbutdifferentissues:theC++template
compilationmodelandvariousC++templateinstantiationmechanisms.
Thecompilationmodeldeterminesthemeaningofatemplateatvariousstages
ofthetranslationofaprogram.Inparticular,itdetermineswhatthevarious
constructsinatemplatemeanwhenitisinstantiated.Namelookupisan
essentialingredientofthecompilationmodel.
StandardC++onlysupportsasinglecompilationmodel,theinclusionmodel.
However,the1998and2003standardsalsosupportedaseparationmodelof
templatecompilation,whichallowedatemplatedefinitiontobewrittenina
differenttranslationunitfromitsinstantiations.Theseexportedtemplateswere
onlyeverimplementedonce,bytheEdisonDesignGroup(EDG).21Their
implementationeffortdeterminedthat(1)implementingtheseparationmodelof
C++templateswasvastlymoredifficultandtimeconsumingthanhadbeen
anticipated,and(2)thepresumedbenefitsoftheseparationmodel,suchas
improvedcompiletimes,didnotmaterializeduetocomplexitiesofthemodel.
Asthedevelopmentofthe2011standardwaswrappingup,itbecameclearthat
otherimplementerswerenotgoingtosupportthefeature,andtheC++standards
committeevotedtoremoveexportedtemplatesfromthelanguage.Werefer
readersinterestedinthedetailsoftheseparationmodeltothefirsteditionofthis
book([VandevoordeJosuttisTemplates1st]),whichdescribesthebehaviorof
exportedtemplates.
TheinstantiationmechanismsaretheexternalmechanismsthatallowC++
implementationstocreateinstantiationscorrectly.Thesemechanismsmaybe
constrainedbyrequirementsofthelinkerandothersoftwarebuildingtools.
Whileinstantiationmechanismsdifferfromoneimplementationtothenext(and
eachhasitstrade-offs),theygenerallydonothaveasignificantimpactonday-
to-dayprogramminginC++.
ShortlyafterC++11wascompleted,WalterBright,HerbSutter,andAndrei
Alexandrescuproposeda“staticif”featurenotunlikeconstexprif(viapaper
N3329).Itwas,however,amoregeneralfeaturethatcouldappearevenoutside
offunctiondefinitions.(WalterBrightistheprincipaldesignerandimplementer
oftheDprogramminglanguage,whichhasasimilarfeature.)Forexample:
Clickheretoviewcodeimage
template<unsignedlongN>
structFact{
staticif(N<=1){
constexprunsignedlongvalue=1;
}else{
constexprunsignedlongvalue=N*Fact<N-1>::value;
}
};
Notehowclass-scopedeclarationsaremadeconditionalinthisexample.This
powerfulabilitywascontroversial,however,withsomecommitteemembers
fearingthatitmightbeabusedandothersnotlikingsometechnicalaspectsof
theproposal(suchasthefactthatnoscopeisintroducedbythebracesandthe
discardedbranchisnotparsedatall).
Afewyearslater,VilleVoutilainencamebackwithaproposal(P0128)that
wasmostlywhatwouldbecomeconstexprifstatements.Itwentthroughafew
minordesigniterations(involvingtentativekeywordsstatic_ifand
constexpr_if)and,withthehelpofJensMaurer,Villeeventually
shepherdedtheproposalintothelanguage(viapaperP0292r2).
1Theterminstantiationissometimesalsousedtorefertothecreationof
objectsfromtypes.Inthisbook,however,italwaysreferstotemplate
instantiation.
2Thetermspecializationisusedinthegeneralsenseofanentitythatisa
specificinstanceofatemplate(seeChapter10).Itdoesnotrefertothe
explicitspecializationmechanismdescribedinChapter16.
3Anonymousunionsarealwaysspecialinthisway:Theirmemberscanbe
consideredtobemembersoftheenclosingclass.Ananonymousunionis
primarilyaconstructthatsaysthatsomeclassmemberssharethesame
storage.
4Somecompilers,suchasGCC,allowzero-lengtharraysasextensionsand
maythereforeacceptthiscodeevenwhenNendsupbeing0.
5Typicalexamplesaresmartpointertemplates(e.g.,thestandard
std::unique_ptr<T>).
6Besidestwo-phaselookup,termssuchastwo-stagelookuportwo-phasename
lookuparealsoused.
7Surprisingly,thisisnotclearlyspecifiedinthestandardatthetimeofthis
writing.However,itisnotexpectedtobeacontroversialissue.
8Thecalloperatorofgenericlambdasareasubtleexceptiontothat
observation.
9Inmoderncompilerstheinliningofcallsistypicallyhandledbyamostly
language-independentcomponentofthecompilerdedicatedtooptimizations
(a“backend”or“middleend”).However,C++“frontends”(theC++-specific
partoftheC++compiler)thatweredesignedintheearlierdaysofC++may
alsohavetheabilitytoexpandcallsinlinebecauseolderbackendsweretoo
conservativewhenconsideringcallsforinlineexpansion.
10TheoriginalC++98standardalsoprovidedaseparationmodel.Itnevergained
popularityandwasremovedjustbeforepublishingtheC++11standard.
11Currentsystemshavegrowntodetectcertainotherdifferences,however.For
example,theymightreportifoneinstantiationhasassociateddebugging
informationandanotherdoesnot.
12Whenacompilerisunableto“inline”everycalltoafunctionthatyoumarked
withthekeywordinline,aseparatecopyofthefunctionisemittedinthe
objectfile.Thismayhappeninmultipleobjectfiles.
13Virtualfunctioncallsareusuallyimplementedasindirectcallsthroughatable
ofpointerstofunctions.See[LippmanObjMod]forathoroughstudyofsuch
implementationaspectsofC++.
14SunMicrosystemswaslateracquiredbyOracle.
15DonotletthisphrasemisleadyouintothinkingthatCfrontwasanacademic
prototype:Itwasusedinindustrialcontextsandformedthebasisofmany
commercialC++compilerofferings.Release3.0appearedin1991butwas
plaguedwithbugs.Version3.0.1followedsoonthereafterandmadetemplates
usable.
16HP’saC++wasgrownoutoftechnologyfromacompanycalledTaligent(later
absorbedbyInternationalBusinessMachines,orIBM).HPalsoaddedgreedy
instantiationtoaC++andmadethatthedefaultmechanism.
17Inthe1998and2003C++standards,thiswastheonlyportablewaytoinhibit
instantiationinothertranslationunits.
18Aninterestingpartofthisoptimizationproblemistodetermineexactlywhich
specializationsaregoodcandidatesforexplicitinstantiationdeclarations.Low-
levelutilitiessuchasthecommonUnixtoolnmcanbeusefulinidentifying
whichautomaticinstantiationsactuallymadeitintotheobjectfilesthat
compriseaprogram.
19Althoughthecodereadsifconstexpr,thefeatureiscalledconstexprif,
becauseitisthe“constexpr”formofif.
20Optimizationmaynonethelessmasktheerror.Withconstexpriftheproblemis
guaranteednottoexist.
21Ironically,EDGwasthemostvocalopponentofthefeaturewhenitwasadded
totheworkingpaperfortheoriginalstandard.
Chapter15
TemplateArgumentDeduction
Explicitlyspecifyingtemplateargumentsoneverycalltoafunctiontemplate
(e.g.,concat<std::string,int>(s,3))canquicklyleadtounwieldy
code.Fortunately,aC++compilercanoftenautomaticallydeterminethe
intendedtemplateargumentsusingapowerfulprocesscalledtemplateargument
deduction.
Inthischapterweexplainthedetailsofthetemplateargumentdeduction
process.AsisoftenthecaseinC++,therearemanyrulesthatusuallyproduce
anintuitiveresult.Asolidunderstandingofthischapterallowsustoavoidthe
moresurprisingsituations.
Althoughtemplateargumentdeductionwasfirstdevelopedtoeasethe
invocationoffunctiontemplates,ithassincebeenbroadenedtoapplytoseveral
otheruses,includingdeterminingthetypesofvariablesfromtheirinitializers.
15.1TheDeductionProcess
Thebasicdeductionprocesscomparesthetypesofanargumentofafunction
callwiththecorrespondingparameterizedtypeofafunctiontemplateand
attemptstoconcludethecorrectsubstitutionforoneormoreofthededuced
parameters.Eachargument-parameterpairisanalyzedindependently,andifthe
conclusionsdifferintheend,thedeductionprocessfails.Considerthefollowing
example:Clickheretoviewcodeimage
template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
}
autog=max(1,1.0);Herethefirstcallargumentisoftypeint,so
theparameterTofouroriginalmax()templateistentativelydeduced
tobeint.Thesecondcallargumentisadouble,however,andsoT
shouldbedoubleforthisargument:Thisconflictswiththeprevious
conclusion.Notethatwesaythat“thedeductionprocessfails,”not
that“theprogramisinvalid.”Afterall,itispossiblethatthe
deductionprocesswouldsucceedforanothertemplatenamedmax
(functiontemplatescanbeoverloadedmuchlikeordinaryfunctions;
seeSection1.5onpage15andChapter16).
Ifallthededucedtemplateparametersareconsistentlydetermined,the
deductionprocesscanstillfailifsubstitutingtheargumentsintherestofthe
functiondeclarationresultsinaninvalidconstruct.Forexample:Clickhereto
viewcodeimage
template<typenameT>
typenameT::ElementTat(Ta,inti)
{
returna[i];
}
voidf(int*p)
{
intx=at(p,7);
}
HereTisconcludedtobeint*(thereisonlyoneparametertypewhereT
appears,sothereareobviouslynoanalysisconflicts).However,substituting
int*forTinthereturntypeT::ElementTisclearlyinvalidC++,andthe
deductionprocessfails.1
Westillneedtoexplorehowargument-parametermatchingproceeds.We
describeitintermsofmatchingatypeA(derivedfromthecallargumenttype)to
aparameterizedtypeP(derivedfromthecallparameterdeclaration).Ifthecall
parameterisdeclaredwithareferencedeclarator,Pistakentobethetype
referenced,andAisthetypeoftheargument.Otherwise,however,Pisthe
declaredparametertype,andAisobtainedfromthetypeoftheargumentby
decaying2arrayandfunctiontypestopointertypes,ignoringtop-levelconst
andvolatilequalifiers.Forexample:Clickheretoviewcodeimage
template<typenameT>voidf(T);//parameterizedtypePisT
template<typenameT>voidg(T&);//parameterizedtypePisalsoT
doublearr[20];
intconstseven=7;
f(arr);//nonreferenceparameter:Tisdouble*
g(arr);//referenceparameter:Tisdouble[20]
f(seven);//nonreferenceparameter:Tisint
g(seven);//referenceparameter:Tisintconst
f(7);//nonreferenceparameter:Tisint
g(7);//referenceparameter:Tisint=>ERROR:can’tpass7toint&
Foracallf(arr),thearraytypeofarrdecaystotypedouble*,whichis
thetypededucedforT.Inf(seven)theconstqualificationisstrippedand
henceTisdeducedtobeint.Incontrast,callingg(x)deducesTtobetype
double[20](nodecayoccurs).Similarly,g(seven)hasanlvalueargument
oftypeintconst,andbecauseconstandvolatilequalifiersarenot
droppedwhenmatchingreferenceparameters,Tisdeducedtobeintconst.
However,notethatg(7)woulddeduceTtobeint(becausenonclassrvalue
expressionsneverhaveconstorvolatilequalifiedtypes),andthecall
wouldfailbecauseanargument7cannotbepassedtoaparameteroftypeint&.
Thefactthatnodecayoccursforargumentsboundtoreferenceparameters
canbesurprisingwhentheargumentsarestringliterals.Reconsiderourmax()
templatedeclaredwithreferences:Clickheretoviewcodeimage
template<typenameT>
Tconst&max(Tconst&a,Tconst&b);Itwouldbereasonableto
expectthatfortheexpressionmax("Apple","Pie")Tisdeducedtobe
charconst*.However,thetypeof"Apple"ischarconst[6],andthe
typeof"Pie"ischarconst[4].Noarray-to-pointerdecayoccurs
(becausethedeductioninvolvesreferenceparameters),andtherefore
Twouldhavetobebothchar[6]andchar[4]fordeductiontosucceed.
Thatis,ofcourse,impossible.SeeSection7.4onpage115fora
discussionabouthowtodealwiththissituation.
15.2DeducedContexts
Parameterizedtypesthatareconsiderablymorecomplexthanjust“T”canbe
matchedtoagivenargumenttype.Hereareafewexamplesthatarestillfairly
basic:Clickheretoviewcodeimage
template<typenameT>
voidf1(T*);
template<typenameE,
intN>
voidf2(E(&)[N]);
template<typename
T1,typenameT2,typenameT3>
voidf3(T1(T2::*)(T3*));
classS{
public:
voidf(double*);
};
voidg(int***ppp)
{
boolb[42];
f1(ppp);//deducesTtobeint**
f2(b);//deducesEtobeboolandNtobe42
f3(&S::f);//deducesT1=void,T2=S,andT3=double}
Complextypedeclarationsarebuiltfrommoreelementaryconstructs(pointer,
reference,array,andfunctiondeclarators;pointer-to-memberdeclarators;
template-ids;andsoforth),andthematchingprocessproceedsfromthetop-level
constructandrecursesthroughthecomposingelements.Itisfairtosaythatmost
typedeclarationconstructscanbematchedinthisway,andthesearecalled
deducedcontexts.However,afewconstructsarenotdeducedcontexts.For
example:•Qualifiedtypenames.Forexample,atypenamelikeQ<T>::Xwill
neverbeusedtodeduceatemplateparameterT.
•Nontypeexpressionsthatarenotjustanontypeparameter.Forexample,atype
namelikeS<I+1>willneverbeusedtodeduceI.NeitherwillTbededuced
bymatchingagainstaparameteroftypeint(&)[sizeof(S<T>)].These
limitationsshouldcomeasnosurprisebecausethedeductionwould,in
general,notbeunique(orevenfinite),althoughthislimitationofqualified
typenamesissometimeseasilyoverlooked.Anondeducedcontextdoesnot
automaticallyimplythattheprogramisinerrororeventhattheparameter
beinganalyzedcannotparticipateintypededuction.Toillustratethis,consider
thefollowing,moreintricateexample:Clickheretoviewcodeimage
details/fppm.cpp
template<intN>
classX{
public:
usingI=int;
voidf(int){
}
};
template<intN>
voidfppm(void(X<N>::*p)(typenameX<N>::I));;
intmain()
{
fppm(&X<33>::f);//fine:Ndeducedtobe33
}
Inthefunctiontemplatefppm(),thesubconstructX<N>::Iisanondeduced
context.However,themember-classcomponentX<N>ofthepointer-to-member
typeisadeduciblecontext,andwhentheparameterN,whichisdeducedfromit,
ispluggedinthenondeducedcontext,atypecompatiblewiththatoftheactual
argument&X<33>::fisobtained.Thedeductionthereforesucceedsonthat
argument-parameterpair.
Conversely,itispossibletodeducecontradictionsforaparametertype
entirelybuiltfromdeducedcontexts.Forexample,assumingsuitablydeclared
classtemplatesXandY:Clickheretoviewcodeimage
template<typenameT>
voidf(X<Y<T>,Y<T>>);
voidg()
{
f(X<Y<int>,Y<int>>());//OK
f(X<Y<int>,Y<char>>());//ERROR:deductionfails
}
Theproblemwiththesecondcalltothefunctiontemplatef()isthatthetwo
argumentsdeducedifferentargumentsfortheparameterT,whichisnotvalid.
(Inbothcases,thefunctioncallargumentisatemporaryobjectobtainedby
callingthedefaultconstructoroftheclasstemplateX.)
15.3SpecialDeductionSituations
Thereareseveralsituationsinwhichthepair(A,P)usedfordeductionisnot
obtainedfromtheargumentstoafunctioncallandtheparametersofafunction
template.Thefirstsituationoccurswhentheaddressofafunctiontemplateis
taken.Inthiscase,Pistheparameterizedtypeofthefunctiontemplate
declaration,andAisthefunctiontypeunderlyingthepointerthatisinitializedor
assignedto.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidf(T,T);
void(*pf)(char,char)=&f;Inthisexample,Pisvoid(T,T)andA
isvoid(char,char).DeductionsucceedswithTsubstitutedwithchar,
andpfisinitializedtotheaddressofthespecializationf<char>.
Similarly,functiontypesareusedforPandAforafewotherspecial
situations:•Determiningapartialorderingbetweenoverloadedfunction
templates•Matchinganexplicitspecializationtoafunctiontemplate•Matching
anexplicitinstantiationtoatemplate
•Matchingafriendfunctiontemplatespecializationtoatemplate•Matchinga
placementoperatordeleteoroperatordelete[]toa
correspondingplacementoperatorneworoperatornew[]template
Someofthesetopics,alongwiththeuseoftemplateargumentdeductionfor
classtemplatepartialspecializations,arefurtherdevelopedinChapter16.
Anotherspecialsituationoccurswithconversionfunctiontemplates.For
example:Clickheretoviewcodeimage
classS{
public:
template<typenameT>operatorT&();
};
Inthiscase,thepair(P,A)isobtainedasifitinvolvedanargumentofthetype
towhichweareattemptingtoconvertandaparametertypethatisthereturn
typeoftheconversionfunction.Thefollowingcodeillustratesonevariation:
voidf(int(&)[20]);
voidg(Ss)
{
f(s);
}
HereweareattemptingtoconvertStoint(&)[20].TypeAistherefore
int[20]andtypePisT.ThedeductionsucceedswithTsubstitutedwith
int[20].
Finally,somespecialtreatmentisalsoneededforthedeductionoftheauto
placeholdertype.ThatisdiscussedinSection15.10.4onpage303.
15.4InitializerLists
Whentheargumentofafunctioncallisaninitializerlist,thatargumentdoesn’t
haveaspecifictype,soingeneralnodeductionwillbeperformedfromthat
givenpair(A,P)becausethereisnoA.Forexample:Clickheretoviewcode
image
#include<initializer_list>
template<typenameT>voidf(Tp);
intmain(){
f({1,2,3});//ERROR:cannotdeduceTfromabracedlist
}
However,iftheparametertypeP,afterremovingreferencesandtop-level
constandvolatilequalifiers,isequivalentto
std::initializer_list<P′>forsometypeP′thathasadeducible
pattern,deductionproceedsbycomparingP′tothetypeofeachelementinthe
initializerlist,succeedingonlyifalloftheelementshavethesametype:Click
heretoviewcodeimage
deduce/initlist.cpp
#include<initializer_list>
template<typenameT>voidf(std::initializer_list<T>);
intmain()
{
f({2,3,5,7,9});//OK:Tisdeducedtoint
f({’a’,’e’,’i’,’o’,’u’,42});//ERROR:Tdeducedtobothcharand
int
}
Similarly,iftheparametertypePisareferencetoanarraytypewithelement
typeP′forsometypeP′thathasadeduciblepattern,deductionproceedsby
comparingP′tothetypeofeachelementintheinitializerlist,succeedingonlyif
alloftheelementshavethesametype.Furthermore,iftheboundhasadeducible
pattern(i.e.,justnamesanontypetemplateparameter),thenthatboundis
deducedtothenumberofelementsinthelist.
15.5ParameterPacks
Thedeductionprocessmatcheseachargumenttoeachparametertodetermine
thevaluesoftemplatearguments.Whenperformingtemplateargument
deductionforvariadictemplates,however,the1:1relationshipbetween
parametersandargumentsnolongerholds,becauseaparameterpackcanmatch
multiplearguments.Inthiscase,thesameparameterpack(P)ismatchedto
multiplearguments(A),andeachmatchingproducesadditionalvaluesforany
templateparameterpacksinP:Clickheretoviewcodeimage
template<typenameFirst,typename…Rest>
voidf(Firstfirst,Rest…rest);
voidg(inti,doublej,int*k)
{
f(i,j,k);//deducesFirsttoint,Restto{double,int*}
}
Here,thedeductionforthefirstfunctionparameterissimple,sinceitdoesnot
involveanyparameterpacks.Thesecondfunctionparameter,rest,isa
functionparameterpack.Itstypeisapackexpansion(Rest…)whosepatternis
thetypeRest:ThispatternservesasP,tobecomparedagainstthetypesAof
thesecondandthirdcallarguments.WhencomparedagainstthefirstsuchA(the
typedouble),thefirstvalueinthetemplateparameterpackRestisdeduced
todouble.Similarly,whencomparedagainstthesecondsuchA(thetype
int*),thesecondvalueinthetemplateparameterpackRestisdeducedto
int*.Thus,deductiondeterminesthevalueoftheparameterpackResttobe
thesequence{double,int*}.Substitutingtheresultsofthatdeductionand
thedeductionforthefirstfunctionparameteryieldsthefunctiontype
void(int,double,int*),whichmatchestheargumenttypesatthecall
site.
Becausedeductionforfunctionparameterpacksusesthepatternofthe
expansionforitscomparison,thepatterncanbearbitrarilycomplex,andvalues
formultipletemplateparametersandparameterpackscanbedeterminedfrom
eachoftheargumenttypes.Considerthedeductionbehaviorofthefunctions
h1()andh2(),below:Clickheretoviewcodeimage
template<typenameT,typenameU>classpair{};
template<typenameT,typename…Rest>
voidh1(pair<T,Rest>const&…);
template<typename…Ts,typename…Rest>
voidh2(pair<Ts,Rest>const&…);
voidfoo(pair<int,float>pif,pair<int,double>pid,
pair<double,double>pdd)
{
h1(pif,pid);//OK:deducesTtoint,Restto{float,double}
h2(pif,pid);//OK:deducesTsto{int,int},Restto{float,
double}
h1(pif,pdd);//ERROR:Tdeducedtointfromthe1starg,butto
doublefromthe2nd
h2(pif,pdd);//OK:deducesTsto{int,double},Restto{float,
double}
}
Forbothh1()andh2(),Pisareferencetypethatisadjustedtothe
unqualifiedversionofthereference(pair<T,Rest>orpair<Ts,
Rest>,respectively)fordeductionagainsteachargumenttype.Sinceall
parametersandargumentsarespecializationsofclasstemplatepair,the
templateargumentsarecompared.Forh1(),thefirsttemplateargument(T)is
notaparameterpack,soitsvalueisdeducedindependentlyforeachargument.If
thedeductionsdiffer,asinthesecondcalltoh1(),deductionfails.Forthe
secondpairtemplateargumentinbothh1()andh2()(Rest),andforthe
firstpairargumentinh2()(Ts),deductiondeterminessuccessivevaluesfor
thetemplateparameterpacksfromeachoftheargumenttypesinA.
Deductionforparameterpacksisnotlimitedtofunctionparameterpacks
wheretheargument-parameterpairscomefromcallarguments.Infact,this
deductionisusedwhereverapackexpansionisattheendofafunction
parameterlistoratemplateargumentlist.3Forexample,considertwosimilar
operationsonasimpleTupletype:Clickheretoviewcodeimage
template<typename…Types>classTuple{};
template<typename…Types>
boolf1(Tuple<Types…>,Tuple<Types…>);
template<typename…Types1,typename…Types2>
boolf2(Tuple<Types1…>,Tuple<Types2…>);
voidbar(Tuple<short,int,long>sv,
Tuple<unsignedshort,unsigned,unsignedlong>uv)
{
f1(sv,sv);//OK:Typesisdeducedto{short,int,long}
f2(sv,sv);//OK:Types1isdeducedto{short,int,long},
//Types2isdeducedto{short,int,long}
f1(sv,uv);//ERROR:Typesisdeducedto{short,int,long}fromthe
1starg,but
//to{unsignedshort,unsigned,unsignedlong}fromthe2nd
f2(sv,uv);//OK:Types1isdeducedto{short,int,long},
//Types2isdeducedto{unsignedshort,unsigned,unsignedlong}
}
Inbothf1()andf2(),thetemplateparameterpacksarededucedby
comparingthepatternofthepackexpansionembeddedwithintheTupletype
(e.g.,Typesforh1())againsteachofthetemplateargumentsoftheTuple
typeprovidedbythecallargument,deducingsuccessivevaluesforthe
correspondingtemplateparameterpack.Thefunctionf1()usesthesame
templateparameterpackTypesinbothfunctionparameters,ensuringthat
deductiononlysucceedswhenthetwofunctioncallargumentshavethesame
Tuplespecializationastheirtype.Thefunctionf2(),ontheotherhand,uses
differentparameterpacksfortheTupletypesineachofitsfunction
parameters,sothetypesofthefunctioncallargumentscanbedifferent—solong
asbotharespecializationsofTuple.
15.5.1LiteralOperatorTemplates
Literaloperatortemplateshavetheirargumentdeterminedinauniqueway.The
followingexampleillustratesthis:Clickheretoviewcodeimage
template<char…>intoperator""_B7();//#1
…
inta=121_B7;//#2
Here,theinitializerfor#2containsauser-definedliteral,whichisturnedintoa
calltotheliteraloperatortemplate#2withthetemplateargumentlist<’1’,
’2’,’1’>.Thus,animplementationoftheliteraloperatorsuchasClickhere
toviewcodeimage
template<char…cs>
intoperator""_B7()
{
std::array<char,sizeof…(cs)>chars{cs…};//initializearrayofpassed
chars
for(charc:chars){//anduseit(printithere)
std::cout<<"’"<<c<<"’";
}
std::cout<<’\n’;
return…;
}
willoutput’1’’2’’1’’.’’5’for121.5_B7.
Notethatthistechniqueisonlysupportedfornumericliteralsthatarevalid
evenwithoutthesuffix.Forexample:Clickheretoviewcodeimage
autob=01.3_B7;//OK:deduces<’0’,’1’,’.’,’3’>
autoc=0xFF00_B7;//OK:deduces<’0’,’x’,’F’,’F’,’0’,’0’>
autod=0815_B7;//ERROR:8isnovalidoctalliteral
autoe=hello_B7;//ERROR:identifierhello_B7isnotdefined
autof="hello"_B7;//ERROR:literaloperator_B7doesnotmatch
SeeSection25.6onpage599foranapplicationofthefeaturetocompute
integralliteralsatcompiletime.
15.6RvalueReferences
C++11introducedrvaluereferencestoenablenewtechniques,includingmove
semanticsandperfectforwarding.Thissectiondescribestheinteractions
betweenrvaluereferencesanddeduction.
15.6.1ReferenceCollapsingRules
Programmersarenotallowedtodirectlydeclarea“referencetoareference”:
Clickheretoviewcodeimage
intconst&r=42;
intconst&&ref2ref=i;//ERROR:referencetoreferenceisinvalid
However,whencomposingtypesthroughthesubstitutionoftemplate
parameters,typealiases,ordecltypeconstructs,suchsituationsarepermitted.
Forexample:Clickheretoviewcodeimage
usingRI=int&;
inti=42;
RIr=i;
Rconst&rr=r;//OK:rrhastypeint&
Therulesthatdeterminethetyperesultingfromsuchacompositionareknown
asthereferencecollapsingrules.4First,anyconstorvolatilequalifiers
appliedontopoftheinnerreferencearesimplydiscarded(i.e.,onlythe
qualifiersundertheinnerreferenceareretained).Thenthetworeferencesare
reducedtoasinglereferenceaccordingtoTable15.1,whichcanbesummarized
as“ifeitherreferenceisanlvaluereference,soistheresultingtype;otherwise,it
isanrvaluereference.”
Innerreference OuterreferenceResultingreference
& + & → &
& + && → &
&& + & → &
&& + && → &&
Table15.1.ReferenceCollapsingRules
Onemoreexampleshowstheserulesinaction:
Clickheretoviewcodeimage
usingRCI=intconst&;
RCIvolatile&&r=42;//OK:rhastypeintconst&
usingRRI=int&&;
RRIconst&&rr=42;//OK:rrhastypeint&&
HerevolatileisappliedontopofthereferencetypeRCI(analiasforint
const&)andisthereforediscarded.Anrvaluereferenceisthenplacedontop
ofthattype,butsincetheunderlyingtypeisanlvaluereferenceandlvalue
references“takeprecedence”inthereferencecollapsingrule,theoveralltype
remainsintconst&(orRCI,whichisanequivalentalias).Similarly,the
constontopofRRIisdiscarded,andapplyinganrvaluereferenceontopof
theresultingrvaluereferencetype,stillleavesuswithanrvaluereferencetypein
theend(whichisabletobindanrvaluelike42).
15.6.2ForwardingReferences
AsintroducedinSection6.1onpage91,templateargumentdeductionbehaves
inaspecialwaywhenafunctionparameterisaforwardingreference(anrvalue
referencetoatemplateparameterofthatfunctiontemplate).Inthiscase,
templateargumentdeductionconsidersnotjustthetypeofthefunctioncall
argumentbutalsowhetherthatargumentisanlvalueoranrvalue.Inthecases
wheretheargumentisanlvalue,thetypedeterminedbytemplateargument
deductionisanlvaluereferencetotheargumenttype,andthereference
collapsingrules(seeabove)ensurethatthesubstitutedparameterwillbean
lvaluereference.Otherwise,thetypededucedforthetemplateparameteris
simplytheargumenttype(notareferencetype),andthesubstitutedparameteris
anrvaluereferencetothattype.Forexample:Clickheretoviewcodeimage
template<typenameT>voidf(T&&p);//pisaforwardingreference
voidg()
{
inti;
intconstj=0;
f(i);//argumentisanlvalue;deducesTtoint&and
//parameterphastypeint&
f(j);//argumentisanlvalue;deducesTtointconst&
//parameterphastypeintconst&
f(2);//argumentisanrvalue;deducesTtoint
//parameterphastypeint&&
}
Inthecallf(i)thetemplateparameterTisdeducedtoint&,sincethe
expressioniisanlvalueoftypeint.Substitutingint&forTintothe
parametertypeT&&requiresreferencecollapsing,andweapplytherule&+&&
!&toconcludethattheresultingparametertypeisint&,whichisperfectly
suitedtoacceptanlvalueoftypeint.Incontrast,inthecallf(2),the
argument2isanrvalueandthetemplateparameteristhereforededucedto
simplybethetypeofthatrvalue(i.e.,int).Noreferencecollapsingisneeded
fortheresultingfunctionparameter,whichisjustint&&(again,aparameter
suitedforitsargument).
ThedeductionofTasareferencetypecanhavesomeinterestingeffectson
theinstantiationofthetemplate.Forexample,alocalvariabledeclaredwithtype
Twill,afterinstantiationforanlvalue,havereferencetypeandwilltherefore
requireaninitializer:Clickheretoviewcodeimage
template<typenameT>voidf(T&&)//pisaforwardingreference
{
Tx;//forpassedlvalues,xisareference
…
}
Thismeansthatthedefinitionofthefunctionf()aboveneedstobecareful
howitusesthetypeT,orthefunctiontemplateitselfwon’tworkproperlywith
lvaluearguments.Todealwiththissituation,thestd::remove_reference
typetraitisfrequentlyusedtoensurethatxisnotareference:Clickheretoview
codeimage
template<typenameT>voidf(T&&)//pisaforwardingreference
{
std::remove_reference_t<T>x;//xisneverareference
…
}
15.6.3PerfectForwarding
Thecombinationofthespecialdeductionruleforrvaluereferencesandthe
referencecollapsingrulesmakesitpossibletowriteafunctiontemplatewitha
parameterthatacceptsalmostanyargument5andcapturesitssalientproperties
(itstypeandwhetheritisanlvalueoranrvalue).Thefunctiontemplatecanthen
“forward”theargumentalongtoanotherfunctionasfollows:Clickheretoview
codeimage
classC{
…
};
voidg(C&);
voidg(Cconst&);
voidg(C&&);
template<typenameT>
voidforwardToG(T&&x)
{
g(static_cast<T&&>(x));//forwardxtog()
}
voidfoo()
{
Cv;
Cconstc;
forwardToG(v);//eventuallycallsg(C&)
forwardToG(c);//eventuallycallsg(Cconst&)
forwardToG(C());//eventuallycallsg(C&&)
forwardToG(std::move(v));//eventuallycallsg(C&&)
}
Thetechniqueillustratedaboveiscalledperfectforwarding,becausetheresult
ofcallingg()indirectlythroughforwardToG()willbethesameasifthe
codecalledg()directly:Noadditionalcopiesaremade,andthesameoverload
ofg()willbeselected.
Theuseofstatic_castwithinthefunctionforwardToG()requires
someadditionalexplanation.IneachinstantiationofforwardToG(),the
parameterxwilleitherhavelvaluereferencetypeorrvaluereferencetype.
Regardless,theexpressionxwillbeanlvalueofthetypethatthereference
refersto.6Thestatic_castcastsxtoitsoriginaltypeandlvalue-orrvalue-
ness.ThetypeT&&willeithercollapsetoanlvaluereference(iftheoriginal
argumentwasanlvaluecausingTtobeanlvaluereference)orwillbeanrvalue
reference(iftheoriginalargumentwasanrvalue),sotheresultofthe
static_casthasthesametypeandlvalue-orrvalue-nessastheoriginal
argument,therebyachievingperfectforwarding.
AsintroducedinSection6.1onpage91,theC++standardlibraryprovidesa
functiontemplatestd::forward<>()inheader<utility>thatshouldbe
usedinplaceofstatic_castforperfectforwarding.Usingthatutility
templatebetterdocumentstheprogrammer’sintentthanthearguablyopaque
static_castconstructsshownaboveandpreventserrorssuchasomitting
one&.Thatis,theexampleaboveismoreclearlywrittenasfollows:Clickhere
toviewcodeimage
#include<utility>
template<typenameT>voidforwardToG(T&&x)
{
g(std::forward<T>(x));//forwardxtog()
}
PerfectForwardingforVariadicTemplates
Perfectforwardingcombineswellwithvariadictemplates,allowingafunction
templatetoacceptanynumberoffunctioncallargumentsandforwardeachof
themalongtoanotherfunction:Clickheretoviewcodeimage
template<typename…Ts>voidforwardToG(Ts&&…xs)
{
g(std::forward<Ts>(xs)…);//forwardallxstog()
}
TheargumentsinacalltoforwardToG()will(independently)deduce
successivevaluesfortheparameterpackTs(seeSection15.5onpage275),so
thatthetypesandlvalue-orrvalue-nessofeachargumentiscaptured.Thepack
expansion(seeSection12.4.1onpage201)inthecalltog()willthenforward
eachoftheseargumentsusingtheperfectforwardingtechniqueexplainedabove.
Despiteitsname,perfectforwardingisnot,infact,“perfect”inthesensethat
itdoesnotcaptureallinterestingpropertiesofanexpression.Forexample,it
doesnotdistinguishwhetheranlvalueisabit-fieldlvalue,nordoesitcapture
whethertheexpressionhasaspecificconstantvalue.Thelattercausesproblems
particularlywhenwe’redealingwiththenullpointerconstant,whichisavalue
ofintegraltypethatevaluatestotheconstantvaluezero.Sincetheconstantvalue
ofanexpressionisnotcapturedbyperfectforwarding,overloadresolutioninthe
followingexamplewillbehavedifferentlyforthedirectcalltog()thanforthe
forwardedcalltog():Clickheretoviewcodeimage
voidg(int*);
voidg(…);
template<typenameT>voidforwardToG(T&&x)
{
g(std::forward<T>(x));//forwardxtog()
}
voidfoo()
{
g(0);//callsg(int*)
forwardToG(0);//eventuallycallsg(…)
}
Thisisyetanotherreasontousenullptr(introducedinC++11)insteadof
nullpointerconstants:Clickheretoviewcodeimage
g(nullptr);//callsg(int*)
forwardToG(nullptr);//eventuallycallsg(int*)
Allofourexamplesofperfectforwardinghavefocusedonforwardingthe
functionargumentswhilemaintainingtheirprecisetypeandwhetheritisan
lvalueorrvalue.Thesameproblemoccurswhenforwardingthereturnvalueofa
calltoanotherfunction,withpreciselythesametypeandvaluecategory,a
generalizationoflvaluesandrvaluesdiscussedinAppendixB.Thedecltype
facilityintroducedinC++11(anddescribedinSection15.10.2onpage298)
enablesthisuseofasomewhatverboseidiom:Clickheretoviewcodeimage
template<typename…Ts>
autoforwardToG(Ts&&…xs)->decltype(g(std::forward<Ts>(xs)…))
{
returng(std::forward<Ts>(xs)…);//forwardallxstog()
}
Notethattheexpressioninthereturnstatementiscopiedverbatimintothe
decltypetype,sothattheexacttypeofthereturnexpressioniscomputed.
Moreover,thetrailingreturntypefeatureisused(i.e.,theautoplaceholder
beforethefunctionnameandthe->toindicatethereturntype)sothatthe
functionparameterpackxsisinscopeforthedecltypetype.This
forwardingfunction“perfectly”forwardsallargumentstog()andthen
“perfectly”forwardsitsresultbacktothecaller.
C++14introducedadditionalfeaturestofurthersimplifythiscase:Clickhere
toviewcodeimage
template<typename…Ts>
decltype(auto)forwardToG(Ts&&…xs)
{
returng(std::forward<Ts>(xs)…);//forwardallxstog()
}
Theuseofdecltype(auto)asareturntypeindicatesthatthecompiler
shoulddeducethereturntypefromthedefinitionofthefunction.SeeSection
15.10.1onpage296andSection15.10.3onpage301.
15.6.4DeductionSurprises
Theresultsofthespecialdeductionruleforrvaluereferencesareveryusefulfor
perfectforwarding.However,theycancomeasasurprise,becausefunction
templatestypicallygeneralizethetypesinthefunctionsignaturewithout
affectingwhatkindsofarguments(lvalueorrvalue)itallows.Considerthis
example:Clickheretoviewcodeimage
voidint_lvalues(int&);//acceptslvaluesoftypeint
template<typenameT>voidlvalues(T&);//acceptslvaluesofanytype
voidint_rvalues(int&&);//acceptsrvaluesoftypeint
template<typenameT>voidanything(T&&);//SURPRISE:acceptslvalues
and
//rvaluesofanytype
Programmerswhoaresimplyabstractingaconcretefunctionlike
int_rvaluestoitstemplateequivalentwouldlikelybesurprisedbythefact
thatthefunctiontemplateanythingacceptslvalues.Fortunately,this
deductionbehavioronlyapplieswhenthefunctionparameteriswritten
specificallywiththeformtemplate-parameter&&,ispartofafunctiontemplate,
andthenamedtemplateparameterisdeclaredbythatfunctiontemplate.
Therefore,thisdeductionruledoesnotapplyinanyofthefollowingsituations:
Clickheretoviewcodeimage
template<typenameT>
classX
{
public:
X(X&&);//Xisnotatemplateparameter
X(T&&);//thisconstructorisnotafunctiontemplate
template<typenameOther>X(X<U>&&);//X<U>isnotatemplate
parametertemplate<
typenameU>X(U,T&&);//Tisatemplateparameterfrom
//anoutertemplate
};
Despitethesurprisingbehaviorthatthistemplatedeductionrulegives,thecases
wherethisbehaviorcausesproblemsdon’tcomeupallthatofteninpractice.
Whenitoccurs,onecanuseacombinationofSFINAE(seeSection8.4onpage
129andSection15.7onpage284)andtypetraitssuchasstd::enable_if
(seeSection6.3onpage98andSection20.3onpage469)torestrictthe
templatetorvalues:Clickheretoviewcodeimage
template<typenameT>
typenamestd::enable_if<!std::is_lvalue_reference<T>::value>::type
rvalues(T&&);//acceptsrvaluesofanytype
15.7SFINAE(SubstitutionFailureIsNotAnError)
TheSFINAE(substitutionfailureisnotanerror)principle,introducedinSection
8.4onpage129,isanimportantaspectoftemplateargumentdeductionthat
preventsunrelatedfunctiontemplatesfromcausingerrorsduringoverload
resolution.7
Forexample,considerapairoffunctiontemplatesthatextractsthebeginning
iteratorforacontaineroranarray:Clickheretoviewcodeimage
template<typenameT,unsignedN>
T*begin(T(&array)[N])
{
returnarray;
}
template<typenameContainer>
typenameContainer::iteratorbegin(Container&c)
{
returnc.begin();
}
intmain()
{
std::vector<int>v;
inta[10];
::begin(v);//OK:onlycontainerbegin()matches,becausethefirst
deductionfails
::begin(a);//OK:onlyarraybegin()matches,becausethesecond
substitutionfails
}
Thefirstcalltobegin(),inwhichtheargumentisastd::vector<int>,
attemptstemplateargumentdeductionforbothbegin()functiontemplates:•
Templateargumentdeductionforthearraybegin()fails,becausea
std::vectorisnotanarray,soitisignored.
•Templateargumentdeductionforthecontainerbegin()succeedswith
Containerdeducedtostd::vector<int>,sothatthefunctiontemplate
isinstantiatedandcalled.
Thesecondcalltobegin(),inwhichtheargumentisanarray,alsopartially
fails:•Deductionforthearraybegin()succeedswithTdeducedtointand
Ndeducedto10.
•Deductionforthecontainerbegin()determinesthatContainershouldbe
replacedbyint[10].Whileingeneralthissubstitutionisfine,theproduced
returntypeContainer::iteratorisinvalid,becauseanarraytypedoes
nothaveanestedtypenamediterator.Inanyothercontext,tryingto
accessanestedtypethatdoesnotexistwouldcauseanimmediatecompile-
timeerror.Duringthesubstitutionoftemplatearguments,SFINAEturnssuch
errorsintodeductionfailures,andthefunctiontemplateisremovedfrom
consideration.Thus,thesecondbegin()candidateisignoredandthe
specializationofthefirstbegin()functiontemplateiscalled.
15.7.1ImmediateContext
SFINAEprotectsagainstattemptstoforminvalidtypesorexpressions,
includingerrorsduetoambiguitiesoraccesscontrolviolations,thatoccurwithin
theimmediatecontextofthefunctiontemplatesubstitution.Definingthe
immediatecontextofafunctiontemplatesubstitutionismoreeasilydoneby
definingwhatisnotinthatcontext.8Specifically,duringfunctiontemplate
substitutionforthepurposeofdeduction,anythingthathappensduringthe
instantiationof•thedefinitionofaclasstemplate(i.e.,its“body”andlistof
baseclasses),•thedefinitionofafunctiontemplate(“body”and,inthecaseofa
constructor,itsconstructor-initializers),•theinitializerofavariabletemplate,
•adefaultargument,
•adefaultmemberinitializer,or
•anexceptionspecification
isnotpartoftheimmediatecontextofthatfunctiontemplatesubstitution.Any
implicitdefinitionofspecialmemberfunctionstriggeredbythesubstitution
processisnotpartoftheimmediatecontextofthesubstitutioneither.Everything
elseispartofthatcontext.
Soifsubstitutingthetemplateparametersofafunctiontemplatedeclaration
requirestheinstantiationofthebodyofaclasstemplatebecauseamemberof
thatclassisbeingreferredto,anerrorduringthatinstantiationisnotinthe
immediatecontextofthefunctiontemplatesubstitutionandisthereforeareal
error(evenifanotherfunctiontemplatematcheswithouterror).Forexample:
Clickheretoviewcodeimage
template<typenameT>
classArray{
public:
usingiterator=T*;
};
template<typenameT>
voidf(Array<T>::iteratorfirst,Array<T>::iteratorlast);
template<typenameT>
voidf(T*,T*);
intmain()
{
f<int&>(0,0);//ERROR:substitutingint&forTinthefirstfunction
template
}//instantiatesArray<int&>,whichthenfails
Themaindifferencebetweenthisexampleandthepriorexampleiswherethe
failureoccurs.Inthepriorexample,thefailureoccurredwhenformingatype
typenameContainer::iteratorthatwasintheimmediatecontextof
thesubstitutionoffunctiontemplatebegin().Inthisexample,thefailure
occursintheinstantiationofArray<int&>,which—althoughitwastriggered
fromthefunctiontemplate’scontext—actuallyoccursinthecontextoftheclass
templateArray.Therefore,theSFINAEprincipledoesnotapply,andthe
compilerwillproduceanerror.
HereisaC++14example—relyingondeducedreturntypes(seeSection
15.10.1onpage296)—thatinvolvesanerrorduringtheinstantiationofa
functiontemplatedefinition:Clickheretoviewcodeimage
template<typenameT>autof(Tp){
returnp->m;
}
intf(…);
template<typenameT>autog(Tp)->decltype(f(p));
intmain()
{
g(42);
}
Thecallg(42)deducesTtobeint.Makingthatsubstitutioninthe
declarationofg()requiresustodeterminethetypeoff(p)(wherepisnow
knowntobeoftypeint)andthereforetodeterminethereturntypeoff().
Therearetwocandidatesforf().Thenontemplatecandidateisamatch,butnot
averygoodonebecauseitmatcheswithanellipsisparameter.Unfortunately,the
templatecandidatehasadeducedreturntype,andsowemustinstantiateits
definitiontodeterminethatreturntype.Thatinstantiationfailsbecausep->mis
notvalidwhenpisanintandsincethefailureisoutsidetheimmediatecontext
ofthesubstitution(becauseit’sinasubsequentinstantiationofafunction
definition),thefailureproducesanerror.Becauseofthis,werecommend
avoidingdeducedreturntypesiftheycaneasilybespecifiedexplicitly.
SFINAEwasoriginallyintendedtoeliminatesurprisingerrorsdueto
unintendedmatcheswithfunctiontemplateoverloading,aswiththecontainer
begin()example.However,theabilitytodetectaninvalidexpressionortype
enablesremarkablecompile-timetechniques,allowingonetodeterminewhether
aparticularsyntaxisvalid.ThesetechniquesarediscussedinSection19.4on
page416.
SeeespeciallySection19.4.4onpage424foranexampleofmakingatype
traitSFINAE-friendlytoavoidproblemsduetotheimmediatecontextissue.
15.8LimitationsofDeduction
Templateargumentdeductionisapowerfulfeature,eliminatingtheneedto
explicitlyspecifytemplateargumentsinmostcallstofunctiontemplatesand
enablingbothfunctiontemplateoverloading(seeSection1.5onpage15)and
partialclasstemplatespecialization(seeSection16.4onpage347).However,
thereareafewlimitationsthatprogrammersmayencounterwhenusing
templatesandthoselimitationsarediscussedinthissection.
15.8.1AllowableArgumentConversions
Normally,templatedeductionattemptstofindasubstitutionofthefunction
templateparametersthatmaketheparameterizedtypePidenticaltotypeA.
However,whenthisisnotpossible,thefollowingdifferencesaretolerablewhen
Pcontainsatemplateparameterinadeducedcontext:•Iftheoriginalparameter
wasdeclaredwithareferencedeclarator,thesubstitutedPtypemaybemore
const/volatile-qualifiedthantheAtype.
•IftheAtypeisapointerorpointer-to-membertype,itmaybeconvertibleto
thesubstitutedPtypebyaqualificationconversion(inotherwords,a
conversionthataddsconstand/orvolatilequalifiers).
•Unlessdeductionoccursforaconversionoperatortemplate,thesubstitutedP
typemaybeabaseclasstypeoftheAtypeorapointertoabaseclasstypeof
theclasstypeforwhichAisapointertype.Forexample:Clickheretoview
codeimage
template<typenameT>
classB{
};
template<typenameT>
classD:publicB<T>{
};
template<typenameT>voidf(B<T>*);
voidg(D<long>dl)
{
f(&dl);//deductionsucceedswithTsubstitutedwithlong
}
IfPdoesnotcontainatemplateparameterinadeducedcontext,thenallimplicit
conversionarepermissible.Forexample:Clickheretoviewcodeimage
template<typenameT>intf(T,typenameT::X);
structV{
V();
structX{
X(double);
};
}v;
intr=f(v,7.0);//OK:Tisdeducedtointthroughthefirst
parameter,
//whichcausesthesecondparametertohavetypeV::X
//whichcanbeconstructedfromadoublevalue
Therelaxedmatchingrequirementsareconsideredonlyifanexactmatchwas
notpossible.Evenso,deductionsucceedsonlyifexactlyonesubstitutionwas
foundtofittheAtypetothesubstitutedPtypewiththeseaddedconversions.
Notethattheserulesareverynarrowinscope,ignoring(forexample)various
conversionsthatcouldbeappliedtothefunctionargumentstomakeacall
succeed.Forexample,considerthefollowingcalltothemax()function
templateshowninSection15.1onpage269:Clickheretoviewcodeimage
std::stringmaxWithHello(std::strings)
{
return::max(s,"hello");
}
Here,templateargumentdeductionfromthefirstargumentdeducesTto
std::string,whiledeductionfromthesecondargumentdeducesTto
char[6],sotemplateargumentdeductionfails,becausebothparametersuse
thesametemplateparameter.Thisfailuremaycomeasasurprise,becausethe
stringliteral"hello"isimplicitlyconvertibletostd::string,andthecall
::max<std::string>(s,"helloa")Clickheretoviewcodeimage
wouldhavesucceeded.
Perhapsevenmoresurprisingisthatwhenthetwoargumentshavedifferent
classtypesderivedfromacommonbaseclass,deductiondoesnotconsiderthat
commonbaseclassasacandidateforthededucedtype.SeeSection1.2onpage
7foradiscussionofthisissueandpossiblesolutions.
15.8.2ClassTemplateArguments
PriortoC++17,templateargumentdeductionappliedexclusivelytofunction
andmemberfunctiontemplates.Inparticular,theargumentsforaclasstemplate
werenotdeducedfromtheargumentstoacallofoneofitsconstructors.For
example:Clickheretoviewcodeimage
template<typenameT>
classS{
public:
S(Tb):a(b){
}
private:
Ta;
};
Sx(12);//ERRORbeforeC++17:theclasstemplateparameterTwasnot
deducedfrom
//theconstructorcallargument12
ThislimitationisliftedinC++17—seeSection15.12onpage313.
15.8.3DefaultCallArguments
Defaultfunctioncallargumentscanbespecifiedinfunctiontemplatesjustas
theyareinordinaryfunctions:Clickheretoviewcodeimage
template<typenameT>
voidinit(T*loc,Tconst&val=T())
{
*loc=val;
}
Infact,asthisexampleshows,thedefaultfunctioncallargumentcandepend
onatemplateparameter.Suchadependentdefaultargumentisinstantiatedonly
ifnoexplicitargumentisprovided—aprinciplethatmakesthefollowing
examplevalid:Clickheretoviewcodeimage
classS{
public:
S(int,int);
};
Ss(0,0);
intmain()
{
init(&s,S(7,42));//T()isinvalidforT=S,butthedefault
//callargumentT()needsnoinstantiation
//becauseanexplicitargumentisgiven
}
Evenwhenadefaultcallargumentisnotdependent,itcannotbeusedto
deducetemplatearguments.ThismeansthatthefollowingisinvalidC++:Click
heretoviewcodeimage
template<typenameT>
voidf(Tx=42)
{
}
intmain()
{
f<int>();//OK:T=int
f();//ERROR:cannotdeduceTfromdefaultcallargument
}
15.8.4ExceptionSpecifications
Likedefaultcallarguments,exceptionspecificationsareonlyinstantiatedwhen
theyareneeded.Thismeansthattheydonotparticipateintemplateargument
deduction.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidf(T,int)noexcept(nonexistent(T()));//#1
template<typenameT>
voidf(T,…);//#2(C-stylevarargfunction)
voidtest(inti)
{
f(i,i);//ERROR:chooses#1,buttheexpressionnonexistent(T())
isill-formed
}
Thenoexceptspecificationinthefunctionmarked#1triestocalla
nonexistentfunction.Normally,suchanerrordirectlywithinthedeclarationof
thefunctiontemplatewouldtriggeratemplateargumentdeductionfailure
(SFINAE),allowingthecallf(i,i)tosucceedbyselectingthefunction
marked#2thatisanotherwiselessermatch(matchingwithanellipsisparameter
istheworstkindofmatchfromthepointofoverloadresolution;seeAppendix
C).However,becauseexceptionspec-ificationsdonotparticipateintemplate
argumentdeduction,overloadresolutionselects#1andtheprogrambecomesill-
formedwhenthenoexceptspecificationislaterinstantiated.
Thesamerulesapplytoexceptionspecificationsthatlistthepotential
exceptiontypes:Clickheretoviewcodeimage
template<typenameT>
voidg(T,int)throw(typenameT::Nonexistent);//#1
template<typenameT>
voidg(T,…);//#2
voidtest(inti)
{
g(i,i);//ERROR:chooses#1,butthetypeT::Nonexistentisill-
formed
}
However,these“dynamic”exceptionspecificationshavebeendeprecatedsince
C++11andwereremovedinC++17.
15.9ExplicitFunctionTemplateArguments
Whenafunctiontemplateargumentcannotbededuced,itmaybepossibleto
explicitlyspecifyitfollowingthefunctiontemplatename.Forexample:Click
heretoviewcodeimage
template<typenameT>Tdefault_value()
{
returnT{};
}
intmain()
{
returndefault_value<int>();
}
Thismaybedonealsofortemplateparametersthatarededucible:Clickhereto
viewcodeimage
template<typenameT>voidcompute(Tp)
{
…
}
intmain()
{
compute<double>(2);
}
Onceatemplateargumentisexplicitlyspecified,itscorrespondingparameteris
nolongersubjecttodeduction.That,inturn,allowsconversionstotakeplaceon
thefunctioncallparameterthatwouldnotbepossibleinadeducedcall.Inthe
exampleabove,theargument2inthecallcompute<double>(2)willbe
implicitlyconvertedtodouble.
Itispossibletoexplicitlyspecifysometemplateargumentswhilehaving
othersbededuced.However,theexplicitlyspecifiedonesarealwaysmatched
left-to-rightwiththetemplateparameters.Therefore,parametersthatcannotbe
deduced(orthatarelikelytobespecifiedexplicitly)shouldbespecifiedfirst.
Forexample:Clickheretoviewcodeimage
template<typenameOut,typenameIn>
Outconvert(Inp)
{
…
}
intmain(){
autox=convert<double>(42);//thetypeofparameterpisdeduced,
//butthereturntypeisexplicitlyspecified
}
Itisoccasionallyusefultospecifyanemptytemplateargumentlisttoensure
theselectedfunctionisatemplateinstancewhilestillusingdeductionto
determinethetemplatearguments:Clickheretoviewcodeimage
intf(int);//#1
template<typenameT>Tf(T);//#2
intmain(){
autox=f(42);//calls#1
autoy=f<>(42);//calls#2
}
Heref(42)selectsthenontemplatefunctionbecauseoverloadresolution
prefersanordinaryfunctionoverafunctiontemplateifallotherthingsareequal.
However,forf<>(42)thepresenceofatemplateargumentlistrulesoutthe
nontemplatefunction(eventhoughnoactualtemplateargumentsarespecified).
Inthecontextoffriendfunctiondeclarations,thepresenceofanexplicit
templateargumentlisthasaninterestingeffect.Considerthefollowingexample:
Clickheretoviewcodeimage
voidf();
template<typename>voidf();
namespaceN{
classC{
friendintf();//OK
friendintf<>();//ERROR:returntypeconflict
};
}
Whenaplainidentifierisusedtonameafriendfunction,thatfunctionisonly
lookedupwithinthenearestenclosingscope,andifitisnotfoundthere,anew
entityisdeclaredinthatscope(butitremains“invisible”exceptwhenlookedup
viaargument-dependentlookup(ADL);seeSection13.2.2onpage220).Thatis
whathappenswithourfirstfrienddeclarationabove:Nofisdeclaredwithin
namespaceN,andsoanewN::f()is“invisibly”declared.
However,whentheidentifiernamingthefriendisfollowedbyatemplate
argumentlist,atemplatemustbevisiblethroughnormallookupatthatpoint,
andnormallookupwillgoupanynumberofscopesthatmayberequired.So,
ourseconddeclarationabovewillfindtheglobalfunctiontemplatef(),butthe
compilerwillthenissueanerrorbecausethereturntypesdonotmatch(sinceno
ADLisperformedhere,thedeclarationcreatedbytheprecedingfriendfunction
declarationisignored).
ExplicitlyspecifiedtemplateargumentsaresubstitutedusingSFINAE
principles:Ifthesubstitutionleadstoanerrorintheimmediatecontextofthat
substitution,thefunctiontemplateisdiscarded,butothertemplatesmaystill
succeed.Forexample:Clickheretoviewcodeimage
template<typenameT>typenameT::ETypef();//#1
template<typenameT>Tf();//#2
intmain(){
autox=f<int*>();
}
Here,substitutingint*forTincandidate#1causessubstitutiontofail,butin
candidate#2itsucceeds,andthereforethatisthecandidateselected.Infact,if
aftersubstitutionexactlyonecandidateremains,thenthenameofthefunction
templatewiththeexplicittemplateargumentsbehavesprettymuchlikean
ordinaryfunctionname,includingdecayingtoapointer-to-functiontypein
manycontexts.Thatis,replacingmain()abovebyClickheretoviewcode
image
intmain(){
autox=f<int*>;//OK:xisapointertofunction
}
producesavalidtranslationunit.However,thefollowingexample:Clickhereto
viewcodeimage
template<typenameT>voidf(T);
template<typenameT>voidf(T,T);
intmain(){
autox=f<int*>;//ERROR:therearetwopossiblef<int*>here
}
isnotvalidbecausef<int*>doesnotidentifyasinglefunctioninthatcase.
Variadicfunctiontemplatescanbeusedwithexplicittemplateargumentsalso:
Clickheretoviewcodeimage
template<typename…Ts>voidf(Ts…ps);
intmain(){
f<double,double,int>(1,2,3);//OK:1and2areconvertedto
double
}
Interestingly,apackcanbepartiallyexplicitlyspecifiedandpartiallydeduced:
Clickheretoviewcodeimage
template<typename…Ts>voidf(Ts…ps);
intmain(){
f<double,int>(1,2,3);//OK:thetemplateargumentsare<double,
int,int>
}
15.10DeductionfromInitializersandExpressions
C++11includestheabilitytodeclareavariablewhosetypeisdeducedfromits
initializer.Italsoprovidesamechanismtoexpressthetypeofanamedentity(a
variableorfunction)orofanexpression.Thesefacilitiesturnedouttobevery
convenient,andC++14andC++17addedadditionalvariationsonthattheme.
15.10.1TheautoTypeSpecifier
Theautotypespecifiercanbeusedinanumberofplaces(primarily,
namespacescopesandlocalscopes)todeducethetypeofavariablefromits
initializer.Insuchcases,autoiscalledaplaceholdertype(anotherplaceholder
type,decltype(auto),willbedescribedalittlelaterinSection15.10.2on
page298).Forexample:Clickheretoviewcodeimage
template<typenameContainer>
voiduseContainer(Containerconst&container)
{
autopos=container.begin();
while(pos!=container.end()){
auto&element=*pos++;
…//operateontheelement
}
}
Thetwousesofautointheexampleaboveeliminatetheneedtowritetwolong
andpotentiallycomplicatedtypes,thecontainer’siteratortypeandtheiterator’s
valuetype:Clickheretoviewcodeimage
typenameContainer::const_iteratorpos=container.begin();
…
typenamestd::iterator_traits<typename
Container::iterator>::reference
element=*pos++;
Deductionforautousesthesamemechanismastemplateargumentdeduction.
ThetypespecifierautoisreplacedbyaninventedtemplatetypeparameterT,
thendeductionproceedsasifthevariablewereafunctionparameterandits
initializerthecorrespondingfunctionargument.Forthefirstautoexample,that
correspondstothefollowingsituation:Clickheretoviewcodeimage
template<typenameT>voiddeducePos(Tpos);
deducePos(container.begin());
whereTisthetypetobededucedforauto.Oneoftheimmediateconsequences
ofthisisthatavariableoftypeautowillneverbeareferencetype.Theuseof
auto&withinthesecondautoexampleillustrateshowoneproducesa
referencetoadeducedtype.Itsdeductionisequivalenttothefollowingfunction
templateandcall:Clickheretoviewcodeimage
template<typenameT>deduceElement(T&element);
deduceElement(*pos++);
Here,elementwillalwaysbeofreferencetype,anditsinitializercannot
produceatemporary.
Itisalsopossibletocombineautowithrvaluereferences,butdoingso
makesitbehavelikeaforwardingreference,becausethedeductionmodelfor
auto&&fr=…;isbasedonafunctiontemplate:
Clickheretoviewcodeimage
template<typenamet>voidf(T&&fr);//autoreplacedbytemplate
parameterT
Thatexplainsthefollowingexample:
Clickheretoviewcodeimage
intx;
auto&&rr=42;//OK:rvaluereferencebindstoanrvalue(auto=
int)
auto&&lr=x;//AlsoOK:auto=int&andreferencecollapsingmakes
//lranlvaluereference
Thistechniqueisfrequentlyusedingenericcodetobindtheresultofafunction
oroperatorinvocationwhosevaluecategory(lvaluevs.rvalue)isn’tknown,
withouthavingtomakeacopyofthatresult.Forexample,itisoftenthe
preferredwaytodeclaretheiteratingvalueinarange-basedforloop:Click
heretoviewcodeimage
template<typenameContainer>voidg(Containerc){
for(auto&&x:c){
…
}
}
Herewedonotknowthesignaturesofthecontainer’siterationinterfaces,butby
usingauto&&wecanbeconfidentthatnoadditionalcopiesaremadeofthe
valuesweareiteratingthrough.std::forward<T>()canbeinvokedas
usualonthevariableasusual,ifperfectforwardingoftheboundvalueis
desired.Thatenablesakindsof“delayed”perfectforwarding.SeeSection11.3
onpage167foranexample.
Inadditiontoreferences,onecancombinetheautospecifiertomakea
variableconst,apointer,amemberpointer,andsoon,butautohastobethe
“main”typespecifierofthedeclaration.Itcannotbenestedinatemplate
argumentorpartofthedeclaratorthatfollowsthetypespecifier.Thefollowing
exampleillustratesvariouspossibilities:Clickheretoviewcodeimage
template<typenameT>structX{Tconstm;};
autoconstN=400u;//OK:constantoftypeunsignedint
auto*gp=(void*)nullptr;//OK:gphastypevoid*
autoconstS::*pm=&X<int>::m;//OK:pmhastypeintconst
X<int>::*
X<auto>xa=X<int>();//ERROR:autointemplateargument
intconstauto::*pm2=&X<int>::m;//ERROR:autoispartofthe
“declarator”
TherearenotechnicalreasonswhyC++couldnotsupportallthecasesinthis
lastexample,buttheC++committeefeltthatthebenefitswereoutweighedby
boththeadditionalimplementationcostandthepotentialforabuse.
Inordertoavoidconfusingbothprogrammersandcompilers,theolduseof
autoasa“storageclassspecifier”isnolongerpermittedinC++11(andlater
standards):Clickheretoviewcodeimage
intg(){
autointr=24;//validinC++03butinvalidinC++11
returnr;
}
Thisolduseofauto(inheritedfromC)isalwaysredundant.Mostcompilers
canusuallydisambiguatethatusefromthenewuseasaplaceholder(even
thoughtheydon’thaveto),offeringatransitionpathfromolderC++codeto
newerC++code.Theolduseofautoisveryrareinpractice,however.
DeducedReturnTypes
C++14addedanothersituationwhereadeducibleautoplaceholdertypecan
appear:functionreturntypes.Forexample:autof(){return42;}
definesafunctionwithreturntypeint(thetypeof42).Thiscanbeexpressed
usingtrailingreturntypesyntaxalso:autof()->auto{return42;}
Inthelattercase,thefirstautoannouncesthetrailingreturntype,andthe
secondautoistheplaceholdertypetodeduce.Thereislittlereasontofavor
thatmoreverbosesyntax,however.
Thesamemechanismexistsforlambdasbydefault:Ifnoreturntypeis
specifiedexplicitly,thelambda’sreturntypeisdeducedasifitwereauto:9
Clickheretoviewcodeimage
autolm=[](intx){returnf(x);};
//sameas:[](intx)->auto{returnf(x);};
Functionscanbedeclaredseparatelyfromtheirdefinition.Thatistruewith
functionswhosereturntypeisdeducedalso:Clickheretoviewcodeimage
autof();//forwarddeclaration
autof(){return42;}
However,theforwarddeclarationisofverylimiteduseinacaselikethis,since
thedefinitionmustbevisibleatanypointwherethefunctionisused.Perhaps
surprisingly,itisnotvalidtoprovideaforwarddeclarationwitha“resolved”
returntype.Forexample:Clickheretoviewcodeimage
intknown();
autoknown(){return42;}//ERROR:incompatiblereturntype
Mostly,theabilitytoforwarddeclareafunctionwithadeducedreturntypeis
onlyusefultobeabletomoveamemberfunctiondefinitionoutsidetheclass
definitionbecauseofstylisticpreferences:Clickheretoviewcodeimage
structS{
autof();//thedefinitionwillfollowtheclassdefinition
};
autoS::f(){return42;}
DeducibleNontypeParameters
PriortoC++17,nontypetemplateargumentshadtobedeclaredwithaspecific
type.However,thattypecouldbeatemplateparametertype.Forexample:Click
heretoviewcodeimage
template<typenameT,TV>structS;
S<int,42>*ps;Inthisexample,havingtospecifythetypeofthe
nontypetemplateargument—thatis,specifyingintinadditionto42—
canbetedious.C++17thereforeaddedtheabilitytodeclarenontype
templateparameterswhoseactualtypesarededucedfromthe
correspondingtemplateargument.Theyaredeclaredasfollows:
template<autoV>structS;whichenables
S<42>*ps;
HerethetypeofVforS<42>isdeducedtobeintbecause42hastypeint.
HadwewrittenS<42u>instead,thetypeofVwouldhavebeendeducedtobe
unsignedint(seeSection15.10.1onpage294forthedetailsofdeducing
autotypespecifiers).
Notethatthegeneralconstraintsonthetypeofnontypetemplateparameters
remainineffect.Forexample:Clickheretoviewcodeimage
S<3.14>*pd;//ERROR:floating-pointnontypeargument
Atemplatedefinitionwiththatkindofdeduciblenontypeparameteroftenalso
needstoexpresstheactualtypeofthecorrespondingargument.Thatiseasily
doneusingthedecltypeconstruct(seeSection15.10.2onpage298).For
example:Clickheretoviewcodeimage
template<autoV>structValue{
usingArgType=decltype(V);
};
autonontypetemplateparametersarealsousefultoparameterizetemplateson
membersofclasses.Forexample:Clickheretoviewcodeimage
template<typename>structPMClassT;
template<typenameC,typenameM>structPMClassT<MC::*>{
usingType=C;
};
template<typenamePM>usingPMClass=typenamePMClassT<PM>::Type;
template<autoPMD>structCounterHandle{
PMClass<decltype(PMD)>&c;
CounterHandle(PMClass<decltype(PMD)>&c):c(c){
}
voidincr(){
++(c.*PMD);
}
};
structS{
inti;
};
intmain(){
Ss{41};
CounterHandle<&S::i>h(s);
h.incr();//increasess.i
}
HereweusedahelperclasstemplatePMClassTtoretrievefromapointer-to-
membertypeits“parent”classtype,usingclasstemplatepartialspecialization10
(describedinSection16.4onpage347).Withanautotemplateparameter,we
onlyhavetospecifythepointer-to-memberconstant&S::iasatemplate
argument.PriortoC++17,we’dalsohavetospecifyapointer-member-type;that
is,somethinglikeClickheretoviewcodeimage
OldCounterHandle<intS::*,&S::i>
whichisunwieldyandfeelsredundant.
Asyou’dexpect,thatfeaturecanalsobeusedfornontypeparameterpacks:
Clickheretoviewcodeimage
template<auto…VS>structValues{
};
Values<1,2,3>beginning;
Values<1,’x’,nullptr>triplet;
Thetripletexampleshowsthateachnontypeparameterelementofthepack
canbededucedtoadistincttype.Unlikethecaseofmultiplevariabledeclarators
(seeSection15.10.4onpage303),thereisnorequirementthatallthedeductions
beequivalent.
Ifwewanttoforceahomogeneouspackofnontypetemplateparameters,that
ispossibletoo:Clickheretoviewcodeimage
template<autoV1,decltype(V1)…VRest>structHomogeneousValues{
};
However,thetemplateargumentlistcannotbeemptyinthatparticularcase.
SeeSection3.4onpage50foracompleteexampleusingautoastemplate
parametertype.
15.10.2ExpressingtheTypeofanExpressionwith
decltype
Whileautoavoidstheneedtowriteoutthetypeofthevariable,itdoesn’t
easilyallowonetousethetypeofthatvariable.Thedecltypekeyword
resolvesthatissue:Itallowsaprogrammertoexpresstheprecisetypeofan
expressionordeclaration.However,programmersshouldbecarefulabouta
subtledifferenceinwhatdecltypeproduces,dependingonwhetherthe
passedargumentisadeclaredentityoranexpression:•Ifeisthenameofan
entity(suchasavariable,function,enumerator,ordatamember)oraclass
memberaccess,decltype(e)yieldsthedeclaredtypeofthatentityorthe
denotedclassmember.Thus,decltypecanbeusedtoinspectthetypeofa
variable.
Thisisusefulwhenonewantstoexactlymatchthetypeofanexisting
declaration.Forexample,considerthefollowingvariablesy1andy2:autox=
…;
autoy1=x+1;
decltype(x)y2=x+1;Dependingontheinitializerforx,y1mayormaynot
havethesametypeasx:Itdependsonbehaviorof+.Ifxwerededucedtoan
int,y1wouldalsobeanint.Ifxwerededucedtoachar,y1wouldbean
int,becausethesumofacharwith1(whichbydefinitionisanint)isan
int.Theuseofdecltype(x)inthetypeofy2ensuresthatitalwayshasthe
sametypeasx.
•Otherwise,ifeisanyotherexpression,decltype(e)producesatypethat
reflectsthetypeandvaluecategoryofthatexpressionasfollows:–Ifeisan
lvalueoftypeT,decltype(e)producesT&.
–IfeisanxvalueoftypeT,decltype(e)producesT&&.
–IfeisaprvalueoftypeT,decltype(e)producesT.
SeeAppendixBforadetaileddiscussionaboutvaluecategories.The
differencecanbedemonstratedbythefollowingexample:Clickheretoview
codeimage
voidg(std::string&&s)
{
//checkthetypeofs:
std::is_lvalue_reference<decltype(s)>::value;//false
std::is_rvalue_reference<decltype(s)>::value;//true(sasdeclared)
std::is_same<decltype(s),std::string&>::value;//false
std::is_same<decltype(s),std::string&&>::value;//true
//checkthevaluecategoryofsusedasexpression:
std::is_lvalue_reference<decltype((s))>::value;//true(sisan
lvalue)
std::is_rvalue_reference<decltype((s))>::value;//false
std::is_same<decltype((s)),std::string&>::value;//true(T&signals
anlvalue)
std::is_same<decltype((s)),std::string&&>::value;//false
}
Inthefirstfourexpressions,decltypeisinvokedforthevariables:Click
heretoviewcodeimage
decltype(s)//declaredtypeofentityedesignatedbys
whichmeansthatdecltypeproducesthedeclaredtypeofs,
std::string&&.Inthelastfourexpressions,theoperandofthedecltype
constructisnotjustanamebecauseineverycasetheexpressionis(s),which
isaparenthesizedname.Inthatcase,thetypewillreflectthevaluecategoryof
(s):Clickheretoviewcodeimage
decltype((s))//checkthevaluecategoryof(s)
Ourexpressionreferstoavariablebynameandisthusanlvalue:11Bytherules
above,thismeansthatdecltype(s)isanordinary(i.e.,lvalue)referenceto
std::string(sincethetypeof(s)isstd::string).Thisisoneofthe
fewplacesinC++whereparenthesizinganexpressionchangesthemeaningof
theprogramotherthanaffectingtheassociativityofoperators.
Thefactthatdecltypecomputesthetypeofanarbitraryexpressionecan
behelpfulinvariousplaces.Specifically,decltype(e)preservesenough
informationaboutanexpressiontomakeitpossibletodescribethereturntypeof
afunctionthatreturnstheexpressioneitself“perfectly”:decltypecomputesthe
typeofthatexpression,butitalsopropagatesthevaluecategoryofthe
expressiontothecallerofthefunction.Forexample,considerasimple
forwardingfunctiong()thatreturnstheresultsofcallingf():Clickheretoview
codeimage
???f();
decltype(f())g()
{
returnf();
}
Thereturntypeofg()dependsonthereturntypeoff().Iff()weretoreturn
int&,thecomputationofg()’sreturntypewouldfirstdeterminethatthe
expressionf()hastypeint.Thisexpressionisanlvalue,becausef()returns
anlvaluereference,sothedeclaredreturntypeofg()becomesint&.
Similarly,ifthereturntypeoff()wereanrvaluereferencetype,thecallf()
wouldbeanxvalue,anddecltypewouldproduceanrvaluereferencetype
thatexactlymatchesthetypereturnedbyf().Essentially,thisformof
decltypetakestheprimarycharacteristicsofanarbitraryexpression—itstype
andvaluecategory—andencodestheminthetypesysteminamannerthat
enablesperfectforwardingofreturnvalues.
decltypecanalsobeusefulwhenthevalue-producingautodeductionis
notsufficient.Forexample,assumewehaveavariableposofsomeunknown
iteratortype,andwewanttocreateavariableelementthatreferstothe
elementstoredbypos.Wecoulduseautoelement=*pos;However,thiswill
alwaysmakeacopyoftheelement.Ifweinsteadtryauto&element=*pos;then
wewillalwaysreceiveareferencetotheelement,buttheprogramwillfailifthe
iterator’soperator*returnsavalue.12Toaddressthisproblem,wecanuse
decltypesothatthevalue-orreference-nessoftheiterator’soperator*is
preserved:Clickheretoviewcodeimage
decltype(*pos)element=*pos;Thiswilluseareferencewhenthe
iteratorsupportsitandcopythevaluewhentheiteratordoesnot.
Itsprimarydeficiencyisthatitrequirestheinitializerexpression
tobewrittentwice:onceinthedecltype(whereitisnotevaluated)
andonceastheactualinitializer.C++14introducesthe
decltype(auto)constructtoaddressthatissue,whichwewilldiscuss
next.
15.10.3decltype(auto)
C++14addsafeaturethatisacombinationofautoanddecltype:
decltype(auto).Liketheautotypespecifier,itisaplaceholdertype,and
thetypeofavariable,returntype,ortemplateargumentisdeterminedfromthe
typeoftheassociatedexpression(initializer,returnvalue,ortemplateargument).
However,unlikejustauto,whichusestherulesfortemplateargumentdeduction
todeterminethetypeofinterest,theactualtypeisdeterminedbyapplyingthe
decltypeconstructdirectlytotheexpression.Anexampleillustratesthis:Click
heretoviewcodeimage
inti=42;//ihastypeint
intconst&ref=i;//refhastypeintconst&andreferstoi
autox=ref;//x1hastypeintandisanewindependentobject
decltype(auto)y=ref;//yhastypeintconst&andalsoreferstoi
Thetypeofyisobtainedbyapplyingdecltypetotheinitializerexpression,
hereref,whichisintconst&.Incontrast,therulesforautotype
deductionproducetypeint.
Anotherexampleshowsthedifferencewhenindexingastd::vector
(whichproducesanlvalue):Clickheretoviewcodeimage
std::vector<int>v={42};
autox=v[0];//xdenotesanewobjectoftypeint
decltype(auto)y=v[0];//yisareference(typeint&)
Thisneatlyaddressestheredundancyinourpreviousexample:Clickhereto
viewcodeimage
decltype(*pos)element=*pos;whichcannowberewrittenas
Clickheretoviewcodeimage
decltype(auto)element=*pos;Itisfrequentlyconvenientforreturn
typestoo.Considerthefollowingexample:Clickheretoviewcode
image
template<typenameC>classAdapt
{
Ccontainer;
…
decltype(auto)operator[](std::size_tidx){
returncontainer[idx];
}
};
Ifcontainer[idx]producesanlvalue,wewanttopassthatlvaluetothe
caller(whomightwishtotakesitsaddressormodifyit):Thatrequiresanlvalue
referencetype,whichisexactlywhatdecltype(auto)resolvesto.Ifinstead
aprvalueisproduced,areferencetypewouldresultindanglingreferences,but,
fortunately,decltype(auto)willproduceanobjecttype(notareference
type)forthatcase.
Unlikeauto,decltype(auto)doesnotallowspecifiersordeclarator
operatorsthatmodifyitstype.Forexample:Clickheretoviewcodeimage
decltype(auto)*p=(void*)nullptr;//invalid
intconstN=100;
decltype(auto)constNN=N*N;//invalid
Notealsothatparenthesesintheinitializermaybesignificant(sincetheyare
significantforthedecltypeconstructasdiscussedinSection6.1onpage91):
Clickheretoviewcodeimage
intx;
decltype(auto)z=x;//objectoftypeint
decltype(auto)r=(x);//referenceoftypeint&
Thisespeciallymeansthatparenthesescanhaveasevereimpactonthevalidity
ofreturnstatements:Clickheretoviewcodeimage
intg();
…
decltype(auto)f(){
intr=g();
return(r);//run-timeERROR:returnsreferencetotemporary
}
SinceC++17,decltype(auto)canalsobeusedfordeduciblenontype
parameters(seeSection15.10.1onpage296).Thefollowingexampleillustrates
this:Clickheretoviewcodeimage
template<decltype(auto)Val>classS
{
…
};
constexprintc=42;
externintv=42;
S<c>sc;//#1producesS<42>
S<(v)>sv;//#2producesS<(int&)v>
Inline#1,thelackofparenthesesaroundccausesthededucibleparameterto
beofthetypeofcitself(i.e.,int).Becausecisaconstant-expressionofvalue
42,thisisequivalenttoS<42>.Inline#2,theparenthesescause
decltype(auto)tobecomeareferencetypeint&,whichcanbindtothe
globalvariablevoftypeint.Thus,withthisdeclarationtheclasstemplate
dependsonareferencetov,andanychangeofthevalueofvmightimpactthe
behaviorofclassS(seeSection11.4onpage167fordetails).(S<v>without
parentheses,ontheotherhand,wouldbeanerror,becausedecltype(v)is
int,andthereforeaconstantargumentoftypeintwouldbeexpected.
However,vdoesn’tdesignateaconstantintvalue.)Notethatthenatureofthe
twocasesissomewhatdifferent;wethereforethinkthatsuchnontypetemplate
parametersarelikelytocausesurpriseanddonotanticipatethattheywillbe
widelyused.
Finally,acommentaboutusingdeducednontypeparametersinfunction
templates:Clickheretoviewcodeimage
template<autoN>structS{};
template<autoN>intf(S<N>p);
S<42>x;
intr=f(x);Inthisexample,thetypeoftheparameterNof
functiontemplatef<>()isdeducedfromthetypeofthenontype
parameterofS.That’spossiblebecauseanameoftheformX<…>where
Xisaclasstemplateisadeducedcontext.However,therearealso
manypatternsthatcannotbededucedthatway:Clickheretoview
codeimage
template<autoV>intf(decltype(V)p);
intr1=deduce<42>(42);//OK
intr2=deduce(42);//ERROR:decltype(V)isanondeducedcontext
Inthiscase,decltype(V)isanondeducedcontext:Thereisnouniquevalue
ofVthatmatchestheargument42(e.g.,decltype(7)producesthesame
typeasdecltype(42)).Therefore,thenontypetemplateparametermustbe
specifiedexplicitlytobeabletocallthisfunction.
15.10.4SpecialSituationsforautoDeduction
Thereareafewspecialsituationsfortheotherwisesimpledeductionrulesof
auto.Thefirstiswhentheinitializerforavariableisaninitializerlist.The
correspondingdeductionforafunctioncallwouldfail,becausewecannot
deduceatemplatetypeparameterfromaninitializerlistargument:Clickhereto
viewcodeimage
template<typenameT>
voiddeduceT(T);
…
deduceT({2,3,4});//ERROR
deduceT({1});//ERROR
However,ifourfunctionhasamorespecificparameterasfollowsClickhereto
viewcodeimage
template<typenameT>
voiddeduceInitList(std::initializer_list<T>);
…
deduceInitList({2,3,5,7});//OK:Tdeducedasint
thendeductionsucceeds.Copy-initializing(i.e.,initializationwiththe=token)
anautovariablewithaninitializerlististhereforedefinedintermsofthatmore
specificparameter:Clickheretoviewcodeimage
autoprimes={2,3,5,7};//primesisstd::initializer_list<int>
deduceT(primes);//Tdeducedasstd::initializer_list<int>
BeforeC++17,thecorrespondingdirect-initializationofautovariables(i.e.,
withoutthe=token)wasalsohandledthatway,butthiswaschangedinC++17
tobettermatchthebehaviorexpectedbymostprogrammers:Clickheretoview
codeimage
autooops{0,8,15};//ERRORinC++17
autoval{2};//OK:valhastypeintinC++17
PriortoC++17,bothinitializationswerevalid,initializingbothbothoopsand
valoftypeinitializer_list<int>.
Interestingly,returningabracedinitializerlistforafunctionwithadeducible
placeholdertypeisinvalid:Clickheretoviewcodeimage
autosubtleError(){
return{1,2,3};//ERROR
}
Thatisbecauseaninitializerlistinfunctionscopeisanobjectthatpointsintoan
underlyingarrayobject(withtheelementvaluesspecifiedinthelist)thatexpires
whenthefunctionreturns.Allowingtheconstructwouldthusencouragewhatis
ineffectadanglingreference.
Anotherspecialsituationoccurswhenmultiplevariabledeclarationssharethe
sameauto,asinthefollowing:Clickheretoviewcodeimage
autofirst=container.begin(),last=container.end();Insuch
cases,deductionisperformedindependentlyforeachdeclaration.In
otherwords,thereisaninventedtemplatetypeparameterT1for
firstandanotherinventedtemplatetypeparameterT2forlast.Only
ifbothdeductionssucceed,andthedeductionsforT1andT2arethe
sametype,arethedeclarationswell-formed.Thiscanproducesome
interestingcases:13
Clickheretoviewcodeimage
charc;
auto*cp=&c,d=c;//OK
autoe=c,f=c+1;//ERROR:deductionmismatchcharvs.int
Here,twopairsofvariablesaredeclaredwithasharedautospecifier.The
declarationsofcpandddeducethesametypecharforauto,sothisisvalid
code.Thedeclarationsofeandf,however,deducecharandintduetothe
promotiontointwhencomputingc+1,andthatinconsistencyresultsinan
error.
Asomewhatparallelspecialsituationcanalsooccurwithplaceholdersfor
deducedreturntypes.Considerthefollowingexample:Clickheretoviewcode
image
autof(boolb){
if(b){
return42.0;//deducesreturntypedouble
}else{
return0;//ERROR:deductionconflict
}
}
Inthiscase,eachreturnstatementisdeducedindependently,butifdifferent
typesarededuced,theprogramisinvalid.Ifthereturnedexpressioncallsthe
functionrecursively,deductioncannotoccurandtheprogramisinvalidunlessa
priordeductionalreadydeterminedthereturntype.Thatmeansthatthe
followingcodeisinvalid:Clickheretoviewcodeimage
autof(intn)
{
if(n>1){
returnn*f(n-1);//ERROR:typeoff(n-1)unknown
}else{
return1;
}
}
butthefollowingotherwiseequivalentcodeisfine:
Clickheretoviewcodeimage
autof(intn)
{
if(n<=1){
return1;//returntypeisdeducedtobeint
}else{
returnn*f(n-1);//OK:typeoff(n-1)isintandsoistypeof
n*f(n-1)
}
}
Deducedreturntypeshaveanotherspecialcasewithnocounterpartindeduced
variabletypesordeducednontypeparametertypes:Clickheretoviewcode
image
autof1(){}//OK:returntypeisvoid
autof2(){return;}//OK:returntypeisvoid
Bothf1()andf2()arevalidandhaveavoidreturntype.However,ifthe
returntypepatterncannotmatchvoid,suchcasesareinvalid:Clickhereto
viewcodeimage
auto*f3(){}//ERROR:auto*cannotdeduceasvoid
Asyou’dexpect,anyuseofafunctiontemplatewithadeducedreturntype
requirestheimmediateinstantiationofthattemplatetodeterminethereturntype
withcertainty.That,however,hasasurprisingconsequencewhenitcomesto
SFINAE(describedinSection8.4onpage129andSection15.7onpage284).
Considerthefollowingexample:Clickheretoviewcodeimage
deduce/resulttypetmpl.cpp
template<typenameT,typenameU>
autoaddA(Tt,Uu)->decltype(t+u)
{
returnt+u;
}
voidaddA(…);
template<typenameT,typenameU>
autoaddB(Tt,Uu)->decltype(auto)
{
returnt+u;
}
voidaddB(…);
structX{
};
usingAddResultA=decltype(addA(X(),X()));//OK:AddResultAisvoid
usingAddResultB=decltype(addB(X(),X()));//ERROR:instantiation
ofaddB<X>
//isill-formed
Here,theuseofdecltype(auto)ratherthandecltype(t+u)for
addB()causesanerrorduringoverloadresolution:Thefunctionbodyofthe
addB()templatemustbefullyinstantiatedtodetermineitsreturntype.That
instantiationisn’tintheimmediatecontext(seeSection15.7.1onpage285)of
thecalltoaddB()andthereforedoesn’tfallundertheSFINAEfilterbutresults
inanoutrighterror.Itisthereforeimportanttorememberthatdeducedreturn
typesarenotmerelyashorthandforacomplexexplicitreturntypeandthey
shouldbeusedwithcare(i.e.,withtheunderstandingthattheyshouldn’tbe
calledinthesignaturesofotherfunctiontemplatesthatwouldcountonSFINAE
properties).
15.10.5StructuredBindings
C++17addedanewfeatureknownasstructuredbindings.14Itismosteasily
introducedwithasmallexample:Clickheretoviewcodeimage
structMaybeInt{boolvalid;intvalue;};
MaybeIntg();
autoconst&&[b,N]=g();//bindsbandNtothemembersofthe
resultofg()
Thecalltog()producesavalue(inthiscaseasimpleclassaggregateoftype
MaybeInt)thatcanbedecomposedinto“elements”(inthiscase,thedata
membersofMaybeInt).Thevalueofthatcallisproducedasifthebracketed
listofidentifiers[b,N]werereplacedbyadistinctvariablename.Ifthat
nameweree,theinitializationwouldbeequivalentto:autoconst&&e=g();
Thebracketedidentifiersarethenboundtotheelementsofe.Thus,youcan
thinkof[b,N]asintroducingnamesforthepiecesofe(wewilldiscusssome
detailsofthatbindingbelow).
Syntactically,astructuredbindingmustalwayshaveanautotypeoptionally
extendedbyconstand/orvolatilequalifiersand/or&and/or&&declarator
operators(butnota*pointerdeclaratororsomeotherdeclaratorconstruct).Itis
followedbyabracketedlistcontainingatleastoneidentifier(reminiscentofthe
“capture”listoflambdas).Thatinturnhastobefollowedbyaninitializer.
Threedifferentkindsofentitiescaninitializeastructuredbinding:1.Thefirst
caseisthesimpleclasstype,whereallthenonstaticdatamembersarepublic(as
inourexampleabove).Forthiscasetoapply,allthenonstaticdatamembers
havetobepublic(eitheralldirectlyintheclassitselforallinthesame,
unambiguouspublicbaseclass;noanonymousunionsmaybeinvolved).Inthat
case,thenumberofbracketedidentifiersmustequalthenumberofmembers,
andusingoneoftheseidentifierswithinthescopeofthestructuredbindings
amountstousingthecorrespondingmemberoftheobjectdenotedbye(withall
theassociatedproperties;e.g.,ifthecorrespondingmemberisabitfield,itisnot
possibletotakeitsaddress).
2.Thesecondcasecorrespondstoarrays.Hereisanexample:Clickhereto
viewcodeimage
intmain(){
doublept[3];
auto&[x,y,z]=pt;
x=3.0;y=4.0;z=0.0;
plot(pt);
}
Unsurprisingly,thebracketedinitializersarejustshorthandforthecorresponding
elementsoftheunnamedarrayvariable.Thenumberofarrayelementsmust
equalthenumberofbracketedinitializers.
Hereisanotherexample:
Clickheretoviewcodeimage
autof()->int(&)[2];//f()returnsreferencetointarray
auto[x,y]=f();//#1
auto&[r,s]=f();//#2
Line#1isspecial:Ordinarily,theentityedescribedearlierwouldbededuced
fromthefollowingforthiscase:autoe=f();However,thatwoulddeducethe
decayedpointertothearray,whichisnotwhathappenswhenperformingthe
structuredbindingofanarray.Instead,eisdeducedtobeavariableofarraytype
correspondingtothetypeoftheinitializer.Thenthatarrayiscopiedfromthe
initializer,elementbyelement:Thatisasomewhatunusualconceptforbuilt-in
arrays.15Finally,xandybecomealiasesfortheexpressionse[0]ande[1],
respectively.
Line#2,doesnotinvolvearraycopyingandfollowstheusualrulesforauto.
Sothehypotheticaleisdeclaredasfollows:auto&e=f();whichyieldsa
referencetoanarray,andxandyagainbecomealiasesfortheexpressionse[0]
ande[1],respectively(whicharelvaluesreferringdirectlytotheelementsof
thearrayproducedbythecalltof()).
3.Finally,athirdoptionallowsstd::tuple-likeclassestohavetheir
elementsdecomposedthroughatemplate-basedprotocolusingget<>().Let
Ebethetypeoftheexpression(e)withedeclaredasabove.BecauseEisthe
typeofanexpression,itisneverareferencetype.Iftheexpression
std::tuple_size<E>::valueisavalidintegralconstantexpression,it
mustequalthenumberofbracketedidentifiers(andtheprotocolkicksin,
takingprecedenceoverthefirstoptionbutnotthesecondoptionforarrays).
Let’sdenotethebracketedidentifiersbyn0,n1,n2,andsoforth.Ifehasany
membernamedget,thenthebehaviorisasiftheseidentifiersaredeclaredas
Clickheretoviewcodeimage
std::tuple_element<i,E>::type&ni=e.get<i>();
ifewasdeducedtohaveareferencetype,orClickheretoviewcodeimage
std::tuple_element<i,E>::type&&ni=e.get<i>();
otherwise.Ifehasnomemberget,thenthecorrespondingdeclarations
becomeinsteadClickheretoviewcodeimage
std::tuple_element<i,E>::type&ni=get<i>(e);
or
Clickheretoviewcodeimage
std::tuple_element<i,E>::type&&ni=get<i>(e);
wheregetisonlylookedupinassociatedclassesandnamespaces.(Inall
cases,getisassumedtobeatemplateandthereforethe<followsisanangle
bracket.)Thestd::tuple,std::pair,andstd::arraytemplatesall
implementthisprotocol,making,forexample,thefollowingcodevalid:Click
heretoviewcodeimage
#include<tuple>
std::tuple<bool,int>bi{true,42};
auto[b,i]=bi;
intr=i;//initializesrto42
However,itisnotdifficulttoaddspecializationsofstd::tuple_size,
std::tuple_element,andafunctiontemplateormemberfunction
templateget<>()thatwillmakethismechanismworkforanarbitraryclassor
enumerationtype.Forexample:Clickheretoviewcodeimage
#include<utility>
enumM{};
template<>classstd::tuple_size<M>{
public:
staticunsignedconstvalue=2;//mapMtoapairofvalues
};
template<>classstd::tuple_element<0,M>{
public:
usingtype=int;//thefirstvaluewillhavetypeint
};
template<>classstd::tuple_element<1,M>{
public:
usingtype=double;//thesecondvaluewillhavetypedouble
};
template<int>autoget(M);
template<>autoget<0>(M){return42;}
template<>autoget<1>(M){return7.0;}
auto[i,d]=M();//asif:int&&i=42;double&&d=7.0;
Notethatyouonlyneedtoinclude<utility>tousethetwotuple-likeaccess
helperfunctionsstd::tuple_size<>andstd::tuple_element<>.
Inaddition,notethatthethirdcaseabove(usingthetuple-likeprotocol)
performsanactualinitializationofthebracketedinitializersandthebindingsare
actualreferencevariables;theyarenotjustaliasesforanotherexpression(unlike
thefirsttwocasesusingsimpleclasstypesandarrays).That’sofinterestbecause
thatreferenceinitializationcouldgowrong;forexample,itmightthrowan
exception,andthatexceptionisnowunavoidable.However,theC++
standardizationcommitteealsodiscussedthepossibilityofnotassociatingthe
identifierswithinitializedreferencesbutinsteadhavingeachlateruseofthe
identifiersevaluateaget<>()expression.Thatwouldhaveallowedstructured
bindingstobeusedwithtypeswherethe“first”valuemustbetestedbefore
accessingthe“second”value(e.g.,basedonstd::optional).
15.10.6GenericLambdas
LambdashavequicklybecomeoneofthemostpopularC++11features,inpart
becausetheysignificantlyeasetheuseofthefunctionalconstructsintheC++
standardlibraryandinmanyothermodernC++libraries,duelargelytotheir
concisesyntax.However,withintemplatesthemselves,lambdascanbecome
fairlyverboseduetotheneedtospellouttheparameterandresulttypes.For
example,considerafunctiontemplatethatfindsthefirstnegativevaluefroma
sequence:Clickheretoviewcodeimage
template<typenameIter>
IterfindNegative(Iterfirst,Iterlast)
{
returnstd::find_if(first,last,
[](typenamestd::iterator_traits<Iter>::value_type
value){
returnvalue<0;
});
}
Inthisfunctiontemplate,themostcomplicatedpartofthelambda(byfar)isits
parametertype.C++14introducedthenotionof“generic”lambdas,whereone
ormoreoftheparametertypesuseautotodeducethetyperatherthanwriting
itspecifically:Clickheretoviewcodeimage
template<typenameIter>
IterfindNegative(Iterfirst,Iterlast)
{
returnstd::find_if(first,last,
[](autovalue){
returnvalue<0;
});
}
Anautoinaparameterofalambdaishandledsimilarlytoanautointhetype
ofavariablewithaninitializer:Itisreplacedbyaninventedtemplatetype
parameterT.However,unlikeinthevariablecase,thedeductionisn’tperformed
immediatelybecausetheargumentisn’tknownatthetimethelambdaiscreated.
Instead,thelambdaitselfbecomesgeneric(ifitwasn’talready),andthe
inventedtemplatetypeparameterisaddedtoitstemplateparameterlist.Thus,
thelambdaabovecanbeinvokedwithanyargumenttype,solongasthat
argumenttypesupportsthe<0operationwhoseresultisconvertibletobool.
Forexample,thislambdacouldbecalledwitheitheranintorafloatvalue.
Tounderstandwhatitmeansforalambdatobegeneric,wefirstconsiderthe
implementationmodelforanongenericlambda.Giventhelambda[](int
i){
returni<0;
}
theC++compilertranslatesthisexpressionintoaninstanceofanewlyinvented
classspecifictothislambda.Thisinstanceiscalledaclosureorclosureobject,
andtheclassiscalledaclosuretype.Theclosuretypehasafunctioncall
operator,andhencetheclosureisafunctionobject.16Forthislambda,the
closuretypewouldlooksomethinglikethefollowing(weleaveoutthe
conversionfunctiontoapointer-to-functionvalueforbrevityandsimplicity):
Clickheretoviewcodeimage
classSomeCompilerSpecificNameX
{
public:
SomeCompilerSpecificNameX();//onlycallablebythecompiler
booloperator()(inti)const
{
returni<0;
}
};
Ifyoucheckthetypecategoryforalambda,std::is_class<>willyield
true(seeSectionD.2.1onpage705).
Alambdaexpressionthusresultsinanobjectofthisclass(theclosuretype).
Forexample:foo(…,
[](inti){
returni<0;
});
createsanobject(theclosure)oftheinternalcompiler-specificclass
SomeCompilerSpecificNameX:Clickheretoviewcodeimage
foo(…,
SomeCompilerSpecificNameX{});//passanobjectoftheclosuretype
Ifthelambdaweretocapturelocalvariables:
intx,y;
…
[x,y](inti){
returni>x&&i<y;
}
thosecaptureswouldbemodeledasinitializingmembersoftheassociatedclass
type:Clickheretoviewcodeimage
classSomeCompilerSpecificNameY{
private
int_x,_y;
public:
SomeCompilerSpecificNameY(intx,inty)//onlycallablebythe
compiler
:_x(x),_y(y){
}
booloperator()(inti)const{
returni>_x&&i<_y;
}
};
Foragenericlambda,thefunctioncalloperatorbecomesamemberfunction
template,sooursimplegenericlambda[](autoi){
returni<0;
}
istransformedintothefollowinginventedclass(again,ignoringtheconversion
function,whichbecomesaconversionfunctiontemplateinthegenericlambda
case):Clickheretoviewcodeimage
classSomeCompilerSpecificNameZ
{
public:
SomeCompilerSpecificNameZ();//onlycallablebycompiler
template<typenameT>
autooperator()(Ti)const
{
returni<0;
}
};
Thememberfunctiontemplateisinstantiatedwhentheclosureisinvoked,which
isusuallynotatthepointwherethelambdaexpressionappears.Forexample:
Clickheretoviewcodeimage
#include<iostream>
template<typenameF,typename…Ts>voidinvoke(Ff,Ts…ps)
{
f(ps…);
}
intmain()
{
invoke([](autox,autoy){
std::cout<<x+y<<’\n’
},
21,21);
}
Herethelambdaexpressionappearsinmain(),andthat’swhereanassociated
closureiscreated.However,thecalloperatoroftheclosureisn’tinstantiatedat
thatpoint.Instead,theinvoke()functiontemplateisinstantiatedwiththe
closuretypeasthefirstparametertypeandint(thetypeof21)asasecondand
thirdparametertype.Thatinstantiationofinvokeiscalledwithacopyofthe
closure(whichisstillaclosureassociatedwiththeoriginallambda),andit
instantiatestheoperator()templateoftheclosuretosatisfytheinstantiated
callf(ps…).
15.11AliasTemplates
Aliastemplates(seeSection2.8onpage39)are“transparent”withrespectto
deduction.Thatmeansthatwhereveranaliastemplateappearswithsome
templatearguments,thatalias’sdefinition(i.e.,thetypetotherightofthe=)is
substitutedwiththearguments,andtheresultingpatterniswhatisusedforthe
deduction.Forexample,templateargumentdeductionsucceedsinthefollowing
threecalls:Clickheretoviewcodeimage
deduce/aliastemplate.cpp
template<typenameT,typenameCont>
classStack;
template<typenameT>
usingDequeStack=Stack<T,std::deque<T>>;
template<typenameT,typenameCont>
voidf1(Stack<T,Cont>);
template<typenameT>
voidf2(DequeStack<T>);
template<typenameT>
voidf3(Stack<T,std::deque<T>);//equivalenttof2
voidtest(DequeStack<int>intStack)
{
f1(intStack);//OK:Tdeducedtoint,Contdeducedtostd::deque<int>
f2(intStack);//OK:Tdeducedtoint
f3(intStack);//OK:Tdeducedtoint
}
Inthefirstcall(tof1()),theuseofthealiastemplateDequeStackinthe
typeofintStackhasnoeffectondeduction:Thespecifiedtype
DequeStack<int>istreatedasitssubstitutedtypeStack<int,
std::deque<int>>.Thesecondandthirdcallshavethesamededuction
behavior,becauseDequeStack<T>inf2()andthesubstitutedform
Stack<T,std::deque<T>>inf3()areequivalent.Forthepurposesof
templateargumentdeduction,templatealiasesaretransparent:Theycanbeused
toclarifyandsimplifycodebuthavenoeffectonhowdeductionoperates.
Notethatthisispossiblebecausealiastemplatescannotbespecialized(see
Chapter16fordetailsonthetopicoftemplatespecialization).Supposethe
followingwerepossible:Clickheretoviewcodeimage
template<typenameT>usingA=T;
template<>usingA<int>=void;//ERROR,butsupposeitwere
possible…
ThenwewouldnotbeabletomatchA<T>againsttypevoidandconcludethat
TmustbevoidbecausebothA<int>andA<void>areequivalenttovoid.
Thefactthatthisisnotpossibleguaranteesthateachuseofanaliascanbe
genericallyexpandedaccordingtoitsdefinition,whichallowsittobe
transparentfordeduction.
15.12ClassTemplateArgumentDeduction
C++17introducesanewkindofdeduction:Deducingthetemplateparametersof
aclasstypefromtheargumentsspecifiedinaninitializerofavariable
declarationorafunctional-notationtypeconversion.Forexample:Clickhereto
viewcodeimage
Clickheretoviewcodeimage
template<typenameT1,typenameT2,typenameT3=T2>
classC
{
public:
//constructorfor0,1,2,or3arguments:
C(T1x=T1{},T2y=T2{},T3z=T3{});
…
};
Cc1(22,44.3,"hi");//OKinC++17:T1isint,T2isdouble,T3is
charconst*
Cc2(22,44.3);//OKinC++17:T1isint,T2andT3aredouble
Cc3("hi","guy");//OKinC++17:T1,T2,andT3arecharconst*
Cc4;//ERROR:T1andT2areundefined
Cc5("hi");//ERROR:T2isundefined
Notethatallparametersmustbedeterminedbythedeductionprocessorfrom
defaultarguments.Itisnotpossibletoexplicitlyspecifyafewargumentsand
deduceothers.Forexample:Clickheretoviewcodeimage
C<string>c10("hi","my",42);//ERROR:onlyT1explicitlyspecified,
T2notdeduced
C<>c11(22,44.3,42);//ERROR:neitherT1norT2explicitly
specified
C<string,string>c12("hi","my");//OK:T1andT2arededuced,T3has
default
15.12.1DeductionGuides
ConsiderfirstasmallchangetoourearlierexamplefromSection15.8.2onpage
288:Clickheretoviewcodeimage
template<typenameT>
classS{
private:
Ta;
public:
S(Tb):a(b){
}
};
template<typenameT>S(T)->S<T>;//deductionguide
Sx{12};//OKsinceC++17,sameas:S<int>x{12};
Sy(12);//OKsinceC++17,sameas:S<int>y(12);
autoz=S{12};//OKsinceC++17,sameas:autoz=S<int>{12};
Noteinparticulartheadditionofanewtemplate-likeconstructcalleda
deductionguide.Itlooksalittlelikeafunctiontemplate,butitdiffers
syntacticallyfromafunctiontemplateinafewways:•Thepartthatlookslikea
trailingreturntypecannotbewrittenasatraditionalreturntype.Wecallthetype
itdesignates(S<T>inourexample)astheguidedtype.
•Thereisnoleadingautokeywordtoindicatethatatrailingreturntype
follows.
•The“name”ofadeductionguidemustbetheunqualifiednameofaclass
templatedeclaredearlierinthesamescope.
•Theguidedtypeoftheguidemustbeatemplate-idwhosetemplatename
correspondstotheguidename.
•Itcanbedeclaredwiththeexplicitspecifier.
InthedeclarationSx(12);thespecifierSiscalledaplaceholderclasstype.17
Whensuchaplaceholderisused,thenameofthevariablebeingdeclaredmust
followimmediatelyandthatinturnmustbefollowedbyaninitializer.The
followingisthereforeinvalid:Clickheretoviewcodeimage
S*p=&x;//ERROR:syntaxnotpermitted
Withtheguideaswrittenintheexample,thedeclarationSx(12);deduces
thetypeofthevariablebytreatingthedeductionguidesassociatedwithclassS
asanoverloadsetandattemptingoverloadresolutionwiththeinitializeragainst
thatoverloadset.Inthiscase,thesethasonlyoneguideinit,anditsuccessfully
deducesTtobeintandtheguide’sguidedtypetobeS<int>.18Thatguided
typeisthereforeselectedasthetypeofthedeclaration.
Notethatinthecaseofmultipledeclaratorsfollowingaclasstemplatename
requiringdeduction,theinitializerforeachofthosedeclaratorshastoproduce
thesametype.Forexample,withthedeclarationsabove:Clickheretoview
codeimage
Ss1(1),s2(2.0);//ERROR:deducesSbothasS<int>andS<double>
ThisissimilartotheconstraintswhendeducingtheC++11placeholdertype
auto.
Inthepreviousexample,thereisanimplicitconnectionbetweenthededuction
guidewedeclaredandtheconstructorS(Tb)declaredinclassS.However,
suchaconnectionisnotrequired,whichmeansthatdeductionguidescanbe
usedwithaggregateclasstemplates:Clickheretoviewcodeimage
template<typenameT>
structA
{
Tval;
};
template<typenameT>A(T)->A<T>;//deductionguide
Withoutthedeductionguide,wearealwaysrequired(eveninC++17)tospecify
explicittemplatearguments:Clickheretoviewcodeimage
A<int>a1{42};//OK
A<int>a2(42);//ERROR:notaggregateinitialization
A<int>a3={42};//OK
Aa4=42;//ERROR:can’tdeducetype
Butwiththeguideaswrittenabove,wecanwrite:
Aa4={42};//OK
Asubtletyincaseslikethese,however,isthattheinitializermuststillbea
validaggregateinitializer;thatis,itmustuseabracedinitializerlist.The
followingalternativesarethereforenotpermitted:Clickheretoviewcodeimage
Aa5(42);//ERROR:notaggregateinitialization
Aa6=42;//ERROR:notaggregateinitialization
15.12.2ImplicitDeductionGuides
Quiteoften,adeductionguideisdesirableforeveryconstructorinaclass
template.Thatledthedesignersofclasstemplateargumentdeductiontoinclude
animplicitmechanismforthededuction.Itisequivalenttointroducingforevery
constructorandconstructortemplateoftheprimaryclasstemplate19animplicit
deductionguideasfollows:•Thetemplateparameterlistfortheimplicitguide
consistsofthetemplateparametersfortheclasstemplate,followed,inthe
constructortemplatecase,bythetemplateparametersoftheconstructor
template.Thetemplateparametersoftheconstructortemplateretainanydefault
arguments.
•The“function-like”parametersoftheguidearecopiedfromtheconstructoror
constructortemplate.
•Theguidedtypeoftheguideisthenameofthetemplatewithargumentsthat
arethetemplateparameterstakenfromtheclasstemplate.
Let’sapplythisonouroriginalsimpleclasstemplate:
Clickheretoviewcodeimage
template<typenameT>
classS{
private:
Ta;
public:
S(Tb):a(b){
}
};
ThetemplateparameterlististypenameT,thefunction-likeparameterlist
becomesjust(Tb),andtheguidedtypeisthenS<T>.Thus,weobtainaguide
that’sequivalenttotheuser-declaredguidewewroteearlier:Thatguidewas
thereforenotrequiredtoachieveourdesiredeffect!Thatis,withjustthesimple
classtemplateasoriginallywritten(andnodeductionguide),wecanvalidly
writeSx(12);withtheexpectedresultthatxhastypeS<int>.
Deductionguideshaveanunfortunateambiguity.Consideragainoursimple
classtemplateSandthefollowinginitializations:Sx{12};//xhastype
S<int>
Sy{s1};Sz(s1);
WealreadysawthatxhastypeS<int>,butwhatshouldthetypeofxandy
be?ThetwotypesthatariseintuitivelyareS<S<int>>andS<int>.The
committeedecidedsomewhatcontroversiallythatitshouldbeS<int>inboth
cases.Whyisthiscontroversial?Considerasimilarexamplewithavector
type:Clickheretoviewcodeimage
std::vectorv{1,2,3};//vector<int>,notsurprising
std::vectorw2{v,v};//vector<vector<int>>
std::vectorw1{v};//vector<int>!
Inotherwords,abracedinitializerwithoneelementdeducesdifferentlyfroma
bracedinitializerwithmultipleelements.Often,theone-elementoutcomeis
whatisdesired,buttheinconsistencyissomewhatsubtle.Ingenericcode,
however,itiseasytomissthesubtlety:Clickheretoviewcodeimage
template<typenameT,typename…Ts>
autof(Tp,Ts…ps){
std::vectorv{p,ps…};//typedependsonpacklength
…
}
HereitiseasytoforgetthatifTisdeducedtobeavectortype,thetypeofvwill
befundamentallydifferentdependingonwhetherpsisanemptyoranonempty
pack.
Theadditionofimplicittemplateguidesthemselveswasnotwithout
controversy.Themainargumentagainsttheirinclusionisthatthefeature
automaticallyaddsinterfacestoexistinglibraries.Tounderstandthis,consider
oncemoreoursimpleclasstemplateSabove.Itsdefinitionhasbeenvalidsince
templateswereintroducedinC++.Suppose,however,thattheauthorofS
expandslibrarycausingStobedefinedinamoreelaborateway:Clickhereto
viewcodeimage
template<typenameT>
structValueArg{
usingType=T;
};
template<typenameT>
classS{
private:
Ta;
public:
usingArgType=typenameValueArg<T>::Type;
S(ArgTypeb):a(b){
}
};
PriortoC++17,transformationslikethese(whicharenotuncommon)didnot
affectexistingcode.However,inC++17theydisableimplicitdeductionguides.
Toseethis,let’swriteadeductionguidecorrespondingtotheoneproducedby
theimplicitdeductionguideconstructionprocessoutlinedabove:Thetemplate
parameterlistandtheguidedtypeareunchanged,butthefunction-like
parameterisnowwrittenintermsofArgType,whichistypename
ValueArg<T>::Type:Clickheretoviewcodeimage
template<typename>S(typenameValueArg<T>::Type)->S<T>;Recallfrom
Section15.2onpage271thatanamequalifierlikeValueArg<T>::is
notadeducedcontext.Soadeductionguideofthisformisuseless
andwillnotresolveadeclarationlikeSx(12);.Inotherwords,a
librarywriterperformingthiskindoftransformationislikelyto
breakclientcodeinC++17.
Whatisalibrarywritertodogiventhatsituation?Ouradviceistocarefully
considerforeachconstructorwhetheryouwanttoofferitasasourceforan
implicitdeductionguidefortheremainderofthelibrary’slifetime.Ifnot,
replaceeachinstanceofadeducibleconstructorparameteroftypeXby
somethingliketypenameValueArg<X>::Type.Thereisunfortunatelyno
simplerwayto“optout”ofimplicitdeductionguides.
15.12.3OtherSubtleties
InjectedClassNames
Considerthefollowingexample:
Clickheretoviewcodeimage
template<typenameT>structX{
template<typenameIter>X(Iterb,Itere);
template<typenameIter>autof(Iterb,Itere){
returnX(b,e);//Whatisthis?
}
};
ThiscodeisvalidC++14:TheXinX(b,e)istheinjectedclassnameandis
equivalenttoX<T>inthiscontext(seeSection13.2.3onpage221).Therules
forclasstemplateargumentdeduction,however,wouldnaturallymakethatX
equivalenttoX<Iter>.
Inordertomaintainbackwardcompatibility,however,classtemplate
argumentdeductionisdisabledifthenameofthetemplateisaninjectedclass
name.
ForwardingReferences
Consideranotherexample:
Clickheretoviewcodeimage
template<typenameT>structY{
Y(Tconst&);
Y(T&&);
};
voidg(std::strings){
Yy=s;
}
Clearly,theintenthereisthatwededuceTtobestd::stringthroughthe
implicitdeductionguideassociatedwiththecopyconstructor.Writingthe
implicitdeductionguidesasexplicitlydeclaredguidesrevealsasurprise,
however:Clickheretoviewcodeimage
template<typenameT>Y(Tconst&)->Y<T>;//#1
template<typenameT>Y(T&&)->Y<T>;//#2
RecallfromSection15.6onpage277thatT&&behavesspeciallyduring
templateargumentdeduction:Asaforwardingreference,itcausesTtobe
deducedtoareferencetypeifthecorrespondingcallargumentisanlvalue.In
ourexampleabove,theargumentinthedeductionprocessistheexpressions,
whichisanlvalue.Implicitguide#1deducesTtobestd::stringbut
requirestheargumenttobeadjustedfromstd::stringtostd::string
const.Guide#2,however,wouldnormallydeduceTtobeareferencetype
std::string&andproduceaparameterofthatsametype(becauseofthe
referencecollapsingrule),whichisabettermatchbecausenoconstmustbe
addedfortypeadjustmentpurposes.
Thisoutcomewouldberathersurprisingandlikelywouldresultin
instantiationerrors(whentheclasstemplateparameterisusedincontextsthatdo
notpermitreferencetypes)or,worse,silentproductionofmisbehaving
instantiations(e.g.,producingdanglingreferences).
TheC++standardizationcommitteethereforedecidedtodisablethespecial
deductionruleforT&&whenperformingdeductionforimplicitdeductionguides
iftheTwasoriginallyaclasstemplateparameter(asopposedtoaconstructor
templateparameter;forthose,thespecialdeductionruleremains).Theexample
abovethusdeducesTtobestd::string,aswouldbeexpected.
TheexplicitKeyword
Adeductionguidecanbedeclaredwiththekeywordexplicit.Itisthen
consideredonlyfordirect-initializationcases,notforcopy-initializationcases.
Forexample:Clickheretoviewcodeimage
template<typenameT,typenameU>structZ{
Z(Tconst&);
Z(T&&);
};
template<typenameT>Z(Tconst&)->Z<T,T&>;//#1
template<typenameT>explicitZ(T&&)->Z<T,T>;//#2
Zz1=1;//onlyconsiders#1;sameas:Z<int,int&>z1=1;
Zz2{2};//prefers#2;sameas:Z<int,int>z2{2};
Notehowtheinitializationofz1iscopy-initialization,andthereforethe
deductionguide#2isnotconsideredbecauseitisdeclaredexplicit.
CopyConstructionandInitializerLists
Considerthefollowingclasstemplate:
Clickheretoviewcodeimage
template<typename…Ts>structTuple{
Tuple(Ts…);
Tuple(Tuple<Ts…>const&);
};
Tounderstandtheeffectoftheimplicitguides,let’swritethemasexplicit
declarations:Clickheretoviewcodeimage
template<typename…Ts>Tuple(Ts…)->Tuple<Ts…>;
template<typename…Ts>Tuple(Tuple<Ts…>const&)->Tuple<Ts…>;Now
considersomeexamples:
autox=Tuple{1,2};Thisclearlyselectsthefirstguideand
thereforethefirstconstructor:xisthereforeaTuple<int,int>.
Let’scontinuewithsomeexamplesthatusesyntaxthatissuggestive
ofcopyingx:Tuplea=x;
Tupleb(x);
Forbothaandb,bothguidesmatch.Thefirstguideselectstype
Tuple<Tuple<int,int>,whereastheguideassociatedwiththecopy
constructorproducesTuple<int,int>.Fortunately,thesecondguideisa
bettermatch,andthereforebothaandbarecopy-constructedfromx.
Now,considersomeexamplesusingbracedinitializerlists:Tuplec{x,
x};
Tupled{x};
Thefirstoftheseexamples(x)canonlymatchthefirstguide,andsoproduces
Tuple<Tuple<int,int>,Tuple<int,int>>.Thatisentirely
intuitiveandnotasurprise.Thatwouldsuggestthatthesecondexampleshould
deducedtobeoftypeTuple<Tuple<int>>.Instead,however,itistreated
asacopyconstruction(i.e.,thesecondimplicitguideispreferred).Thisalso
happenswithfunctional-notationcasts:autoe=Tuple{x};Here,eisdeducedto
beaTuple<int,int>,notaTuple<Tuple<int>>.
GuidesAreforDeductionOnly
Deductionguidesarenotfunctiontemplates:Theyareonlyusedtodeduce
templateparametersandarenot“called.”Thatmeansthatthedifference
betweenpassingargumentsbyreferenceorbyvalueisnotimportantforguiding
declarations.Forexample:Clickheretoviewcodeimage
template<typenameT>structX{
…
};
template<typenameT>structY{
Y(X<T>const&);
Y(X<T>&&);
};
template<typenameT>Y(X<T>)->Y<T>;Notehowthedeductionguide
doesnotquitecorrespondtothetwoconstructorsofY.However,that
doesnotmatter,becausetheguideisonlyusedfordeduction.Given
avaluexttoftypeX<TT>—lvalueorrvalue—itwillselectthe
deducedtypeY<TT>.Then,initializationwillperformoverload
resolutionontheconstructorsofY<TT>todecidewhichonetocall
(whichwilldependonwhetherxttisanlvalueoranrvalue).
15.13Afternotes
Templateargumentdeductionforfunctiontemplateswaspartoftheoriginal
C++design.Infact,thealternativeprovidedbyexplicittemplateargumentsdid
notbecomepartofC++untilmanyyearslater.
SFINAEisatermthatwasintroducedinthefirsteditionofthisbook.It
quicklybecameverypopularthroughouttheC++programmingcommunity.
However,inC++98,SFINAEwasnotaspowerfulasitisnow:Itonlyappliedto
alimitedsetoftypeoperationsanddidnotcoverarbitraryexpressionsoraccess
control.AsmoretemplatetechniquesbegantorelyonSFINAE(seeSection
19.4onpage416),theneedtogeneralizetheSFINAEconditionsbecame
apparent.SteveAdamczykandJohnSpicerdevelopedthewordingthatachieved
thatinC++11(throughpaperN2634).Althoughthewordingchangesinthe
standardarerelativelysmall,theimplementationeffortinsomecompilersturned
outtobedisproportionalylarge.
Theautotypespecifierandthedecltypeconstructwereamongthe
earliestadditionstoC++03thatwouldeventuallybecomeC++11.Their
developmentwasspearheadedbyBjarneStroustrupandJaakkoJ¨arvi(seetheir
papersN1607fortheautotypespecifierandN2343fordecltype).
Stroustruphadalreadyconsideredtheautosyntaxinhisoriginal
implementationofC++(knownasCfront).Whenthefeaturewasaddedto
C++11,theoriginalmeaningofautoasastoragespecifier(inheritedfromthe
Clanguage)wasretainedandadisambiguationruledecidedhowthekeyword
shouldbeinterpreted.WhileimplementingthefeatureintheEdisonDesign
Group’sfrontend,DavidVandevoordediscoveredthatthisrulewaslikelyto
producesurprisesforC++11programmers(N2337).Afterexaminingtheissue,
thestandardizationcommitteedecidedtodropthetraditionaluseofauto
altogether(everywherethekeywordautoisusedinaC++03program,itcould
justaswellbeleftout)throughpaperN2546(byDavidVandevoordeandJens
Maurer).Thiswasanunusualprecedentofdroppingafeaturefromthelanguage
withoutfirstdeprecatingit,butithassincebeenproventobetherightdecision.
GNU’sGCCcompileracceptedanextensiontypeofnotunlikethe
decltypefeature,andprogrammershadfounditusefulintemplate
programming.Unfortunately,itwasafeaturedevelopedinthecontextoftheC
languageandnotaperfectfitforC++.TheC++committeecouldthereforenot
incorporateitasis,butneithercoulditmodifyit,becausethatwouldbreak
existingcoderelyingonGCC’sbehavior.Thatiswhydecltypeisnotspelled
typeof.JasonMerrillandothershavemadestrongargumentsthatitwouldbe
preferabletohavedistinctoperatorsinsteadofthecurrentsubtledifference
betweendecltype(x)anddecltype((x)),buttheywerenotconvincing
enoughtochangethefinalspecification.
TheabilitytodeclarenontypetemplateparameterswithautoinC++17was
primarilydevelopedbyMikeSpertus,withhelpfromJamesTouton,David
Vandevoorde,andmanyothers.Thespecificationchangesforthefeatureare
writtenupinP0127R2.Interestingly,itisnotclearthattheabilitytouse
decltype(auto)insteadofautowasintentionallymadepartofthe
language(itwasapparentlynotdiscussedbythecommittee,butitfallsoutofthe
specification).
MikeSpertusalsodrovethedevelopmentofclasstemplateargument
deductioninC++17,withRichardSmithandFaisalValicontributingsignificant
technicalideas(includingtheideaofdeductionguides).PaperP0091R3hasthe
specificationthatwasvotedintotheworkingpaperforthenextlanguage
standard.
StructuredbindingswereprimarilydrivenbyHerbSutter,whowrotepaper
P0144R1withGabrielDosReisandBjarneStroustruptoproposethefeature.
Manytweaksweremadeduringcommitteediscussions,includingtheuseof
bracketstodelimitthedecomposingidentifiers.JensMaurertranslatedthe
proposalintoafinalspecificationforthestandard(P0217R3).
1Inthiscase,deductionfailureleadstoanerror.However,thisfallsunderthe
SFINAEprinciple(seeSection8.4onpage129):Iftherewereanother
functionforwhichdeductionsucceeds,thecodecouldbevalid.
2Decayisthetermusedtorefertotheimplicitconversionoffunctionandarray
typestopointertypes.
3Ifapackexpansionoccursanywhereelseinafunctionparameterlistor
templateargumentlist,thatpackexpansionisconsideredanondeduced
context.
4ReferencecollapsingwasintroducedintotheC++2003standardwhenitwas
notedthatthestandardpairclasstemplatewouldnotworkwithreference
types.The2011standardextendedreferencecollapsingfurtherby
incorporatingrulesforrvaluereferences.
5Bitfieldsareanexception.
6Treatingaparameterofrvaluereferencetypeasanlvalueisintendedasa
safetyfeature,becauseanythingwithaname(likeaparameter)caneasilybe
referencedmultipletimesinafunction.Ifeachofthosereferencescouldbe
implicitlytreatedasanrvalue,itsvaluecouldbedestroyedunbeknownstto
theprogrammer.Therefore,onemustexplicitlystatewhenanamedentity
shouldbetreatedasanrvalue.Forthispurpose,theC++standardlibrary
functionstd::movetreatsanyvalueasanrvalue(or,moreprecisely,an
xvalue;seeAppendixBfordetails).
7SFINAEalsoappliestothesubstitutionofpartialclasstemplate
specializations.SeeSection16.4onpage347.
8Theimmediatecontextincludesmanythings,includingvariouskindsof
lookup,aliastemplatesubstitutions,overloadresolution,etc.Arguablythe
termisabitofamisnomer,becausesomeoftheactivitiesitincludesarenot
closelytiedtothefunctiontemplatebeingsubstituted.
9AlthoughC++14introduceddeducedreturntypesingeneral,theywere
alreadyavailabletoC++11lambdasusingaspecificationthatwasnotworded
intermsofdeduction.InC++14,thatspecificationwasupdatedtousethe
generalautodeductionmechanism(fromaprogrammer’spointofview,
thereisnodifference).
10Thesametechniquecanbeusedtoextracttheassociatedmembertype:
InsteadofusingType=C;useusingType=M;.
11Asmentionedelsewhere,treatingaparameterofrvaluereferencetypeasan
lvalueratherthananxvalueisintendedasasafetyfeature,becauseanything
withaname(likeaparameter)caneasilybereferencedmultipletimesina
function.Ifitwereanxvalue,itsfirstusemightcauseitsvaluetobe“moved
away,”causingsurprisingbehaviorforeverysubsequentuse.SeeSection6.1
onpage91andSection15.6.3onpage280.
12Whenweusedthelatterformulationinourintroductoryexampleofauto,
weimplicitlyassumedthattheiteratorsproducedreferencestosome
underlyingstorage.Whilethisisgenerallytrueforcontaineriterator(and
requiredbythestandardcontainersotherthanvector<bool>),itisnotthe
caseforalliterators.
13Thisexampledoesnotuseourusualstyleforplacingthe*immediately
adjacenttotheauto,becauseitcouldmisleadthereaderintothinkingweare
declaringtwopointers.Ontheotherhand,theopacityofthesedeclarationsis
agoodargumentforbeingconservativewhendeclaringmultipleentitiesina
singledeclaration.
14Thetermstructuredbindingswasusedintheoriginalproposalforthefeature
andwasalsoeventuallyusedfortheformalspecificationinthelanguage.
Briefly,however,thatspecificationusedthetermdecompositiondeclarations
instead.
15Theothertwoplaceswherebuilt-inarraysarecopiedarelambdacaptures
andgeneratedcopyconstructors.
16Thistranslationmodeloflambdasisactuallyusedinthespecificationofthe
C++language,makingitbothaconvenientandanaccuratedescriptionofthe
semantics.Capturedvariablesbecomedatamembers,theconversionofa
noncapturinglambdatoafunctionpointerismodeledasaconversionfunction
intheclass,andsoon.Andbecauselambdasarefunctionobjects,whenever
rulesforfunctionobjectsaredefined,theyalsoapplytolambdas.
17Notethedistinctionbetweenaplaceholdertype,whichisautoor
decltype(auto)andcanresolvetoanykindoftype,andaplaceholder
classtype,whichisatemplatenameandcanonlyresolvetoaclasstypethat
isaninstanceoftheindicatedtemplate.
18Aswithordinaryfunctiontemplatededuction,SFINAEcouldapplyif,for
example,substitutingthededucedargumentsintheguidedtypefailed.Thatis
notthecaseinthissimpleexample.
19Chapter16introducestheabilityto“specialize”classtemplatesinvarious
ways.Suchspecializationsdonotparticipateinclasstemplateargument
deduction.
Chapter16
SpecializationandOverloading
SofarwehavestudiedhowC++templatesallowagenericdefinitiontobe
expandedintoafamilyofrelatedclasses,functions,orvariables.Althoughthis
isapowerfulmechanism,therearemanysituationsinwhichthegenericformof
anoperationisfarfromoptimalforaspecificsubstitutionoftemplate
parameters.
C++issomewhatuniqueamongotherpopularprogramminglanguageswith
supportforgenericprogrammingbecauseithasarichsetoffeaturesthatenable
thetransparentreplacementofagenericdefinitionbyamorespecializedfacility.
InthischapterwestudythetwoC++languagemechanismsthatallowpragmatic
deviationsfrompuregeneric-ness:templatespecializationandoverloadingof
functiontemplates.
16.1When“GenericCode”Doesn’tQuiteCutIt
Considerthefollowingexample:
Clickheretoviewcodeimage
template<typenameT>
classArray{
private:
T*data;
…
public:
Array(Array<T>const&);
Array<T>&operator=(Array<T>const&);
voidexchangeWith(Array<T>*b){
T*tmp=data;
data=b->data;
b->data=tmp;
}
T&operator[](std::size_tk){
returndata[k];
}
…
};
template<typenameT>inline
voidexchange(T*a,T*b)
{
Ttmp(*a);
*a=*b;
*b=tmp;
}
Forsimpletypes,thegenericimplementationofexchange()workswell.
However,fortypeswithexpensivecopyoperations,thegenericimplementation
maybemuchmoreexpensive—bothintermsofmachinecyclesandintermsof
memoryusage—thananimplementationthatistailoredtotheparticular,given
structure.Inourexample,thegenericimplementationrequiresonecalltothe
copyconstructorofArray<T>andtwocallstoitscopy-assignmentoperator.
Forlargedatastructuresthesecopiescanofteninvolvecopyingrelativelylarge
amountsofmemory.However,thefunctionalityofexchange()could
presumablyoftenbereplacedjustbyswappingtheinternaldatapointers,asis
doneinthememberfunctionexchangeWith().
16.1.1TransparentCustomization
Inourpreviousexample,thememberfunctionexchangeWith()providesan
efficientalternativetothegenericexchange()function,buttheneedtousea
differentfunctionisinconvenientinseveralways:1.UsersoftheArrayclass
havetorememberanextrainterfaceandmustbecarefultouseitwhenpossible.
2.Genericalgorithmscangenerallynotdiscriminatebetweenvarious
possibilities.Forexample:Clickheretoviewcodeimage
template<typenameT>
voidgenericAlgorithm(T*x,T*y)
{
…
exchange(x,y);//Howdoweselecttherightalgorithm?
…
}
Becauseoftheseconsiderations,C++templatesprovidewaystocustomize
functiontemplatesandclasstemplatestransparently.Forfunctiontemplates,this
isachievedthroughtheoverloadingmechanism.Forexample,wecanwritean
overloadedsetofquickExchange()functiontemplatesasfollows:
template<typenameT>
voidquickExchange(T*a,T*b)//#1
{
Ttmp(*a);
*a=*b;
*b=tmp;
}
template<typenameT>
voidquickExchange(Array<T>*a,Array<T>*b)//#2
{
a->exchangeWith(b);
}
voiddemo(Array<int>*p1,Array<int>*p2)
{
intx=42,y=-7;
quickExchange(&x,&y);//uses#1
quickExchange(p1,p2);//uses#2
}
ThefirstcalltoquickExchange()hastwoargumentsoftypeint*,and
thereforedeductionsucceedsonlywiththefirsttemplate,declaredatpoint#1,
whenTissubstitutedbyint.Thereisthereforenodoubtregardingwhich
functionshouldbecalled.Incontrast,thesecondcallcanbematchedwitheither
template:ViablefunctionsforthecallquickExchange(p1,p2)are
obtainedbothwhensubstitutingArray<int>forTinthefirsttemplateand
whensubstitutingintinthesecondtemplate.Furthermore,bothsubstitutions
resultinfunctionswithparametertypesthatexactlymatchtheargumenttypesof
thesecondcall.Ordinarily,thiswouldleadustoconcludethatthecallis
ambiguous,but(aswewilldiscusslater)theC++languageconsidersthesecond
templatetobe“morespecialized”thanthefirst.Allotherthingsbeingequal,
overloadresolutionprefersthemorespecializedtemplateandhenceselectsthe
templateatpoint#2.
16.1.2SemanticTransparency
Theuseofoverloadingasshownintheprevioussectionisveryusefulin
achievingtransparentcustomizationoftheinstantiationprocess,butitis
importanttorealizethatthis“transparency”dependsagreatdealonthedetails
oftheimplementation.Toillustratethis,considerourquickExchange()
solution.Althoughboththegenericalgorithmandtheonecustomizedfor
Array<T>typesendupswappingthevaluesthatarebeingpointedto,theside
effectsoftheoperationsareverydifferent.Thisisdramaticallyillustratedby
consideringsomecodethatcomparestheexchangeofstructobjectswiththe
exchangeofArray<T>s:
structS{
intx;
}s1,s2;
voiddistinguish(Array<int>a1,Array<int>a2)
{
int*p=&a1[0];
int*q=&s1.x;
a1[0]=s1.x=1;
a2[0]=s2.x=2;
quickExchange(&a1,&a2);//*p==1afterthis(still)
quickExchange(&s1,&s2);//*q==2afterthis
}
ThisexampleshowsthatapointerpintothefirstArraybecomesapointerinto
thesecondarrayafterquickExchange()iscalled.However,thepointerinto
thenon-Arrays1remainspointingintos1evenaftertheexchangeoperation:
Onlythevaluesthatwerepointedtowereexchanged.Thedifferenceis
significantenoughthatitmayconfuseclientsofthetemplateimplementation.
Theprefixquick_ishelpfulinattractingattentiontothefactthatashortcut
maybetakentorealizethedesiredoperation.However,theoriginalgeneric
exchange()templatecanstillhaveausefuloptimizationforArray<T>s:
Clickheretoviewcodeimage
template<typenameT>
voidexchange(Array<T>*a,Array<T>*b)
{
T*p=&(*a)[0];
T*q=&(*b)[0];
for(std::size_tk=a->size();k--!=0;){
exchange(p++,q++);
}
}
Theadvantageofthisversionoverthegenericcodeisthatno(potentially)large
temporaryArray<T>isneeded.Theexchange()templateiscalled
recursivelysothatgoodperformanceisachievedevenfortypessuchas
Array<Array<char>>.Notealsothatthemorespecializedversionofthe
templateisnotdeclaredinlinebecauseitdoesaconsiderableamountofwork
ofitsown,whereastheoriginalgenericimplementationisinlinebecauseit
performsonlyafewoperations(eachofwhichispotentiallyexpensive).
16.2OverloadingFunctionTemplates
Intheprevioussectionwesawthattwofunctiontemplateswiththesamename
cancoexist,eventhoughtheymaybeinstantiatedsothatbothhaveidentical
parametertypes.Hereisanothersimpleexampleofthis:
details/funcoverload1.hpp
template<typenameT>
intf(T)
{
return1;
}
template<typenameT>
intf(T*)
{
return2;
}
WhenTissubstitutedbyint*inthefirsttemplate,afunctionisobtainedthat
hasexactlythesameparameter(andreturn)typesastheoneobtainedby
substitutingintforTinthesecondtemplate.Notonlycanthesetemplates
coexist,theirrespectiveinstantiationscancoexisteveniftheyhaveidentical
parameterandreturntypes.
Thefollowingdemonstrateshowtwosuchgeneratedfunctionscanbecalled
usingexplicittemplateargumentsyntax(assumingtheprevioustemplate
declarations):Clickheretoviewcodeimage
details/funcoverload1.cpp
#include<iostream>
#include"funcoverload1.hpp"
intmain()
{
std::cout<<f<int*>((int*)nullptr);//callsf<T>(T)
std::cout<<f<int>((int*)nullptr);//callsf<T>(T*)
}
Thisprogramhasthefollowingoutput:
12
Toclarifythis,let’sanalyzethecallf<int*>((int*)nullptr)indetail.
Thesyntaxf<int*>()indicatesthatwewanttosubstitutethefirsttemplate
parameterofthetemplatef()withint*withoutrelyingontemplateargument
deduction.Inthiscasethereismorethanonetemplatef(),andthereforean
overloadsetiscreatedcontainingtwofunctionsgeneratedfromtemplates:
f<int*>(int*)(generatedfromthefirsttemplate)andf<int*>(int**)(generated
fromthesecondtemplate).Theargumenttothecall(int*)nullptrhastypeint*.
Thismatchesonlythefunctiongeneratedfromthefirsttemplate,andhencethat
isthefunctionthatendsupbeingcalled.
Forthesecondcall,ontheotherhand,thecreatedoverloadingsetcontains
f<int>(int)(generatedfromthefirsttemplate)andf<int>(int*)
(generatedfromthesecondtemplate),sothatonlythesecondtemplatematches.
16.2.1Signatures
Twofunctionscancoexistinaprogramiftheyhavedistinctsignatures.We
definethesignatureofafunctionasthefollowinginformation:1.Theunqualified
nameofthefunction(orthenameofthefunctiontemplatefromwhichitwas
generated)2.Theclassornamespacescopeofthatnameand,ifthenamehas
internallinkage,thetranslationunitinwhichthenameisdeclared3.The
const,volatile,orconstvolatilequalificationofthefunction(ifit
isamemberfunctionwithsuchaqualifier)4.The&or&&qualificationofthe
function(ifitisamemberfunctionwithsuchaqualifier)5.Thetypesofthe
functionparameters(beforetemplateparametersaresubstitutedifthefunctionis
generatedfromafunctiontemplate)6.Itsreturntype,ifthefunctionisgenerated
fromafunctiontemplate7.Thetemplateparametersandthetemplate
arguments,ifthefunctionisgeneratedfromafunctiontemplateThismeansthat
thefollowingtemplatesandtheirinstantiationscould,inprinciple,coexistinthe
sameprogram:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
voidf1(T1,T2);
template<typenameT1,typenameT2>
voidf1(T2,T1);
template<typenameT>
longf2(T);
template<typenameT>
charf2(T);However,theycannotalwaysbeusedwhenthey’redeclared
inthesamescopebecauseinstantiatingbothcreatesanoverload
ambiguity.Forexample,callingf2(42)whenboththetemplatesabove
aredeclaredwillclearlycreateanambiguity.Anotherexampleis
illustratedbelow:
#include<iostream>
template<typenameT1,typenameT2>
voidf1(T1,T2)
{
std::cout<<"f1(T1,T2)\n";
}
template<typenameT1,typenameT2>
voidf1(T2,T1)
{
std::cout<<"f1(T2,T1)\n";
}
//finesofar
intmain()
{
f1<char,char>(’a’,’b’);//ERROR:ambiguous
}
Here,thefunction
Clickheretoviewcodeimage
f1<T1=char,T2=char>(T1,T2)
cancoexistwiththefunction
Clickheretoviewcodeimage
f1<T1=char,T2=char>(T2,T1)
butoverloadresolutionwillneverpreferoneovertheother.Ifthetemplates
appearindifferenttranslationunits,thenthetwoinstantiationscanactuallyexist
inthesameprogram(and,e.g.,alinkershouldnotcomplainaboutduplicate
definitionsbecausethesignaturesoftheinstantiationsaredistinct):Clickhereto
viewcodeimage
//translationunit1:
#include<iostream>
template<typenameT1,typenameT2>
voidf1(T1,T2)
{
std::cout<<"f1(T1,T2)\n";
}
voidg()
{
f1<char,char>(’a’,’b’);
}
//translationunit2:
#include<iostream>
template<typenameT1,typenameT2>
voidf1(T2,T1)
{
std::cout<<"f1(T2,T1)\n";
}
externvoidg();//definedintranslationunit1
intmain()
{
f1<char,char>(’a’,’b’);
g();
}
Thisprogramisvalidandproducesthefollowingoutput:f1(T2,T1)
f1(T1,T2)
16.2.2PartialOrderingofOverloadedFunction
Templates
Reconsiderourearlierexample:Wefoundthataftersubstitutingthegiven
templateargumentlists(<int*>and<int>),overloadresolutionendedup
selectingtherightfunctiontocall:Clickheretoviewcodeimage
std::cout<<f<int*>((int*)nullptr);//callsf<T>(T)
std::cout<<f<int>((int*)nullptr);//callsf<T>(T*)
However,afunctionisselectedevenwhenexplicittemplateargumentsarenot
provided.Inthiscase,templateargumentdeductioncomesintoplay.Let’s
slightlymodifyfunctionmain()inthepreviousexampletodiscussthis
mechanism:Clickheretoviewcodeimage
details/funcoverload2.cpp
#include<iostream>
template<typenameT>
intf(T)
{
return1;
}
template<typenameT>
intf(T*)
{
return2;
}
intmain()
{
std::cout<<f(0);//callsf<T>(T)
std::cout<<f(nullptr);//callsf<T>(T)
std::cout<<f((int*)nullptr);//callsf<T>(T*)
}
Considerthefirstcall,f(0):Thetypeoftheargumentisint,whichmatches
thetypeoftheparameterofthefirsttemplateifwesubstituteTwithint.
However,theparametertypeofthesecondtemplateisalwaysapointerand,
hence,afterdeduction,onlyaninstancegeneratedfromthefirsttemplateisa
candidateforthecall.Inthiscaseoverloadresolutionistrivial.
Thesameappliestothesecondcall:(f(nullptr):Thetypeofthe
argumentisstd::nullptr_t,whichagainonlymatchesforthefirst
template.
Thethirdcall(f((int*)nullptr))ismoreinteresting:Argument
deductionsucceedsforbothtemplates,yieldingthefunctionsf<int*>
(int*)andf<int>(int*).Fromatraditionaloverloadresolution
perspective,bothareequallygoodfunctionstocallwithanint*argument,
whichwouldsuggestthatthecallisambiguous(seeAppendixC).However,in
thissortofcase,anadditionaloverloadresolutioncriterioncomesintoplay:The
functiongeneratedfromthemorespecializedtemplateisselected.Here(aswe
seeshortly),thesecondtemplateisconsideredmorespecializedandthusthe
outputofourexampleis112
16.2.3FormalOrderingRules
Inourlastexample,itmayseemveryintuitivethatthesecondtemplateismore
specialthanthefirstbecausethefirstcanaccommodatejustaboutanyargument
type,whereasthesecondallowsonlypointertypes.However,otherexamplesare
notnecessarilyasintuitive.Inwhatfollows,wedescribetheexactprocedureto
determinewhetheronefunctiontemplateparticipatinginanoverloadsetismore
specializedthantheother.Notethatthesearepartialorderingrules:Itispossible
thatgiventwotemplates,neithercanbeconsideredmorespecializedthanthe
other.Ifoverloadresolutionmustselectbetweentwosuchtemplates,nodecision
canbemade,andtheprogramcontainsanambiguityerror.
Let’sassumewearecomparingtwoidenticallynamedfunctiontemplatesthat
seemviableforagivenfunctioncall.Overloadresolutionisdecidedasfollows:
•Functioncallparametersthatarecoveredbyadefaultargumentandellipsis
parametersthatarenotusedareignoredinwhatfollows.
•Wethensynthesizetwoartificiallistsofargumenttypes(orforconversion
functiontemplates,areturntype)bysubstitutingeverytemplateparameteras
follows:1.Replaceeachtemplatetypeparameterwithauniqueinventedtype.
2.Replaceeachtemplatetemplateparameterwithauniqueinventedclass
template.
3.Replaceeachnontypetemplateparameterwithauniqueinventedvalueof
theappropriatetype.(Types,templates,andvaluesthatareinventedinthis
contextaredistinctfromanyothertypes,templates,orvaluesthateitherthe
programmerusedorthecompilersynthesizedinothercontexts.)•Iftemplate
argumentdeductionofthesecondtemplateagainstthefirstsynthesizedlistof
argumenttypessucceedswithanexactmatch,butnotviceversa,thenthefirst
templateismorespecializedthanthesecond.Conversely,iftemplateargument
deductionofthefirsttemplateagainstthesecondsynthesizedlistofargument
typessucceedswithanexactmatch,butnotviceversa,thenthesecond
templateismorespecializedthanthefirst.Otherwise(eithernodeduction
succeedsorbothsucceed),thereisnoorderingbetweenthetwotemplates.
Let’smakethisconcretebyapplyingittothetwotemplatesinourlast
example.Fromthesetwotemplates,wesynthesizetwolistsofargumenttypes
byreplacingthetemplateparametersasdescribedearlier:(A1)and(A2*)
(whereA1andA2areuniquemadeuptypes).Clearly,deductionofthefirst
templateagainstthesecondlistofargumenttypessucceedsbysubstituting
A2*forT.However,thereisnowaytomakeT*ofthesecondtemplatematch
thenonpointertypeA1inthefirstlist.Hence,weformallyconcludethatthe
secondtemplateismorespecializedthanthefirst.
Consideramoreintricateexampleinvolvingmultiplefunctionparameters:
Clickheretoviewcodeimage
template<typenameT>
voidt(T*,Tconst*=nullptr,…);
template<typenameT>
voidt(Tconst*,T*,T*=nullptr);
voidexample(int*p)
{
t(p,p);
}
First,becausetheactualcalldoesnotusetheellipsisparameterforthefirst
templateandthelastparameterofthesecondtemplateiscoveredbyitsdefault
argument,theseparametersareignoredinthepartialordering.Notethatthe
defaultargumentofthefirsttemplateisnotused;hencethecorresponding
parameterparticipatesintheordering.
Thesynthesizedlistsofargumenttypesare(A1*,A1const*)and(A2
const*,A2*).Templateargumentdeductionof(A1*,A1const*)
versusthesecondtemplateactuallysucceedswiththesubstitutionofTwithA1
const,buttheresultingmatchisnotexactbecauseaqualificationadjustmentis
neededtocallt<A1const>(A1const*,A1const*,A1const*
=0)withargumentsoftypes(A1*,A1const*).Similarly,noexact
matchcanbefoundbydeducingtemplateargumentsforthefirsttemplatefrom
theargumenttypelist(A2const*,A2*).Therefore,thereisnoordering
relationshipbetweenthetwotemplates,andthecallisambiguous.
Theformalorderingrulesgenerallyresultintheintuitiveselectionoffunction
templates.Onceinawhile,however,anexamplecomesupforwhichtherules
donotselecttheintuitivechoice.Itisthereforepossiblethattheruleswillbe
revisedtoaccommodatethoseexamplesinthefuture.
16.2.4TemplatesandNontemplates
Functiontemplatescanbeoverloadedwithnontemplatefunctions.Allelsebeing
equal,thenon-templatefunctionispreferredinselectingtheactualfunction
beingcalled.Thefollowingexampleillustratesthis:
details/nontmpl1.cpp
#include<string>
#include<iostream>
template<typenameT>
std::stringf(T)
{
return"Template";
}
std::stringf(int&)
{
return"Nontemplate";
}
intmain()
{
intx=7;
std::cout<<f(x)<<’\n’;//prints:Nontemplate
}
Thisoutputs
Nontemplate
However,whenconstandreferencequalifiersdiffer,prioritiesforoverload
resolutioncanchange.Forexample:Clickheretoviewcodeimage
details/nontmpl2.cpp
#include<string>
#include<iostream>
template<typenameT>
std::stringf(T&)
{
return"Template";
}
std::stringf(intconst&)
{
return"Nontemplate";
}
intmain()
{
intx=7;
std::cout<<f(x)<<’\n’;//prints:Template
intconstc=7;
std::cout<<f(c)<<’\n’;//prints:Nontemplate
}
Theprogramhasthefollowingoutput:Template
Nontemplate
Now,thefunctiontemplatef<>(T&)isabettermatchwhenpassinga
nonconstantint.Thereasonisthatforaninttheinstantiatedf<>(int&)is
abettermatchthanf(intconst&).Thus,thedifferenceisnotonlythefact
thatonefunctionisatemplateandtheotherisnot.Inthatcasethegeneralrules
ofoverloadresolutionapply(seeSectionC.2onpage682).Onlywhencalling
f()foraintconst,dobothsignatureshavethesametypeintconst&,
sothatthenontemplateispreferred.
Forthisreason,it’sagoodideatodeclarethememberfunctiontemplateas
Clickheretoviewcodeimage
template<typenameT>
std::stringf(Tconst&)
{
return"Template";
}
Nevertheless,thiseffectcaneasilyoccuraccidentallyandcausesurprising
behaviorwhenmemberfunctionsaredefinedthatacceptthesameargumentsas
copyormoveconstructors.Forexample:Clickheretoviewcodeimage
details/tmplconstr.cpp
#include<string>
#include<iostream>
classC{
public:
C()=default;
C(Cconst&){
std::cout<<"copyconstructor\n";
}
C(C&&){
std::cout<<"moveconstructor\n";
}
template<typenameT>
C(T&&){
std::cout<<"templateconstructor\n";
}
};
intmain()
{
Cx;
Cx2{x};//prints:templateconstructor
Cx3{std::move(x)};//prints:moveconstructor
Cconstc;
Cx4{c};//prints:copyconstructor
Cx5{std::move(c)};//prints:templateconstructor
}
Theprogramhasthefollowingoutput:templateconstructor
moveconstructor
copyconstructor
templateconstructorThus,thememberfunctiontemplateisabettermatchfor
copyingaCthanthecopyconstructor.Andforstd::move(c),whichyields
typeCconst&&(atypethatispossiblebutusuallydoesn’thavemeaningful
semantics),thememberfunctiontemplatealsoisabettermatchthanthemove
constructor.
Forthisreason,usuallyyouhavetopartiallydisablesuchmemberfunction
templateswhentheymighthidecopyormoveconstructors.Thisisexplainedin
Section6.4onpage99.
16.2.5VariadicFunctionTemplates
Variadicfunctiontemplates(seeSection12.4onpage200)requiresomespecial
treatmentduringpartialordering,becausedeductionforaparameterpack
(describedinSection15.5onpage275)matchesasingleparametertomultiple
arguments.Thisbehaviorintroducesseveralinterestingsituationsforfunction
templateordering,illustratedbythefollowingexample:Clickheretoviewcode
image
details/variadicoverload.cpp
#include<iostream>
template<typenameT>
intf(T*)
{
return1;
}
template<typename…Ts>
intf(Ts…)
{
return2;
}
template<typename…Ts>
intf(Ts*…)
{
return3;
}
intmain()
{
std::cout<<f(0,0.0);//callsf<>(Ts…)
std::cout<<f((int*)nullptr,(double*)nullptr);//callsf<>(Ts*…)
std::cout<<f((int*)nullptr);//callsf<>(T*)
}
Theoutputofthisexample,whichwewilldiscussinamoment,is231
Inthefirstcall,f(0,0.0),eachofthefunctiontemplatesnamedfis
considered.Forthefirstfunctiontemplate,f(T*),deductionfailsbothbecause
thetemplateparameterTcannotbededucedandbecausetherearemorefunction
argumentsthanparametersforthisnonvariadicfunctiontemplate.Thesecond
functiontemplate,f(Ts…),isvariadic:Deductioninthiscasecomparesthe
patternofafunctionparameterpack(Ts)againstagainstthetypesofthetwo
arguments(intanddouble,respectively),deducingTstothesequence(int,
double).Forthethirdfunctiontemplate,f(Ts*…),deductioncomparesthe
patternofthefunctionparameterpackTs*againsteachoftheargumenttypes.
Thisdeductionfails(Tscannotbededuced),leavingonlythesecondfunction
templateviable.Functiontemplateorderingisnotrequired.
Thesecondcall,f((int*)nullptr,(double*)nullptr),ismore
interesting:Deductionfailsforthefirstfunctiontemplatebecausetherearemore
functionargumentsthanparameters,butdeductionsucceedsforthesecondand
thirdtemplates.Writtenexplicitly,theresultingcallswouldbeClickhereto
viewcodeimage
f<int*,double*>((int*)nullptr,(double*)nullptr)//forsecond
template
and
Clickheretoviewcodeimage
f<int,double>((int*)nullptr,(double*)nullptr)//forthirdtemplate
Partialorderingthenconsidersthesecondandthirdtemplates,bothofwhichare
variadicasfollows:Whenapplyingtheformalorderingrulesdescribedin
Section16.2.3onpage331toavariadictemplate,eachtemplateparameterpack
isreplacedbyasinglemade-uptype,classtemplate,orvalue.Forexample,this
meansthatthesynthesizedargumenttypesforthesecondandthirdfunction
templatesareA1andA2*,respectively,whereA1andA2areunique,made-up
types.Deductionofthesecondtemplateagainstthethird’slistofargumenttypes
succeedsbysubstitutingthesingle-elementsequence(A2*)fortheparameter
packTs.However,thereisnowaytomakethepatternTs*ofthethird
template’sparameterpackmatchthenonpointertypeA1,sothethirdfunction
template(whichacceptspointerarguments)isconsideredmorespecializedthan
thesecondfunctiontemplate(whichacceptsanyarguments).
Thethirdcall,f((int*)nullptr),introducesanewwrinkle:Deduction
succeedsforallthreeofthefunctiontemplates,requiringpartialorderingto
compareanonvariadictemplatetoavariadictemplate.Toillustrate,wecompare
thefirstandthirdfunctiontemplates.Here,thesynthesizedargumenttypesare
A1*andA2*,whereA1andA2areunique,made-uptypes.Deductionofthe
firsttemplateagainstthethird’ssynthesizedargumentlistwouldnormally
succeedbysubstitutingA2forT.Intheotherdirection,deductionofthethird
templateagainstthefirst’ssynthesizedargumentlistsucceedsbysubstitutingthe
single-elementsequence(A1)fortheparameterpackTs.Partialordering
betweenthefirstandthirdtemplateswouldnormallyresultinanambiguity.
However,aspecialruleprohibitsanargumentthatoriginallycamefroma
functionparameterpack(e.g.,thethirdtemplate’sparameterpackTs*…)from
matchingaparameterthatisnotaparameterpack(thefirsttemplate’sparameter
T*).Hence,templatedeductionofthefirsttemplateagainstthethird’s
synthesizedargumentlistfails,andthefirsttemplateisconsideredmore
specializedthanthethird.Thisspecialruleeffectivelyconsidersnonvariadic
templates(thosewithafixednumberofparameters)tobemorespecializedthan
variadictemplates(withavariablenumberofparameters).
Therulesdescribedaboveapplyequallytopackexpansionsthatoccurin
typesinthefunctionsignature.Forexample,wecanwraptheparametersand
argumentsofeachofthefunctiontemplatesinourpreviousexampleintoa
variadicclasstemplateTupletoarriveatasimilarexamplenotinvolving
functionparameterpacks:Clickheretoviewcodeimage
details/tupleoverload.cpp
#include<iostream>
template<typename…Ts>classTuple
{
};
template<typenameT>
intf(Tuple<T*>)
{
return1;
}
template<typename…Ts>
intf(Tuple<Ts…>)
{
return2;
}
template<typename…Ts>
intf(Tuple<Ts*…>)
{
return3;
}
intmain()
{
std::cout<<f(Tuple<int,double>());//callsf<>(Tuple<Ts…>)
std::cout<<f(Tuple<int*,double*>());//callsf<>(Tuple<Ts*…>)
std::cout<<f(Tuple<int*>());//callsf<>(Tuple<T*>)
}
Functiontemplateorderingconsidersthepackexpansionsinthetemplate
argumentstoTupleanalogouslytothefunctionparameterpacksinour
previousexample,resultinginthesameoutput:231
16.3ExplicitSpecialization
Theabilitytooverloadfunctiontemplates,combinedwiththepartialordering
rulestoselectthe“best”matchingfunctiontemplate,allowsustoaddmore
specializedtemplatestoagenericimplementationtotunecodetransparentlyfor
greaterefficiency.However,classtemplatesandvariabletemplatescannotbe
overloaded.Instead,anothermechanismwaschosentoenabletransparent
customizationofclasstemplates:explicitspecialization.Thestandardterm
explicitspecializationreferstoalanguagefeaturethatwecallfullspecialization
instead.Itprovidesanimplementationforatemplatewithtemplateparameters
thatarefullysubstituted:Notemplateparametersremain.Classtemplates,
functiontemplates,andvariabletemplatescanbefullyspecialized.2
Socanmembersofclasstemplatesthatmaybedefinedoutsidethebodyofa
classdefinition(i.e.,memberfunctions,nestedclasses,staticdatamembers,and
memberenumerationtypes).
Inalatersection,wewilldescribepartialspecialization.Thisissimilartofull
specialization,butinsteadoffullysubstitutingthetemplateparameters,some
parameterizationisleftinthealternativeimplementationofatemplate.Full
specializationsandpartialspecializationsarebothequally“explicit”inour
sourcecode,whichiswhyweavoidthetermexplicitspecializationinour
discussion.Neitherfullnorpartialspecializationintroducesatotallynew
templateortemplateinstance.Instead,theseconstructsprovidealternative
definitionsforinstancesthatarealreadyimplicitlydeclaredinthegeneric(or
unspecialized)template.Thisisarelativelyimportantconceptualobservation,
anditisakeydifferencewithoverloadedtemplates.
16.3.1FullClassTemplateSpecialization
Afullspecializationisintroducedwithasequenceofthreetokens:template,
<,and>.3Inaddition,theclassnameisfollowedbythetemplatearguments
forwhichthespecializationisdeclared.Thefollowingexampleillustratesthis:
Clickheretoviewcodeimage
template<typenameT>
classS{
public:
voidinfo(){
std::cout<<"generic(S<T>::info())\n";
}
};
template<>
classS<void>{
public:
voidmsg(){
std::cout<<"fullyspecialized(S<void>::msg())\n";
}
};
Notehowtheimplementationofthefullspecializationdoesnotneedtobe
relatedinanywaytothegenericdefinition:Thisallowsustohavemember
functionsofdifferentnames(infoversusmsg).Theconnectionissolely
determinedbythenameoftheclasstemplate.
Thelistofspecifiedtemplateargumentsmustcorrespondtothelistof
templateparameters.Forexample,itisnotvalidtospecifyanontypevaluefora
templatetypeparameter.However,templateargumentsforparameterswith
defaulttemplateargumentsareoptional:template<typenameT>classTypes{
public:
usingI=int;
};
template<typenameT,typenameU=typenameTypes<T>::I>
classS;//#1
template<>
classS<void>{//#2
public:
voidf();
};
template<>classS<char,char>;//#3
template<>classS<char,0>;//ERROR:0cannotsubstituteU
intmain()
{
S<int>*pi;//OK:uses#1,nodefinitionneeded
S<int>e1;//ERROR:uses#1,butnodefinitionavailable
S<void>*pv;//OK:uses#2
S<void,int>sv;//OK:uses#2,definitionavailable
S<void,char>e2;//ERROR:uses#1,butnodefinitionavailable
S<char,char>e3;//ERROR:uses#3,butnodefinitionavailable
}
template<>
classS<char,char>{//definitionfor#3
};
Asthisexamplealsoshows,declarationsoffullspecializations(andof
templates)donotnecessarilyhavetobedefinitions.However,whenafull
specializationisdeclared,thegenericdefinitionisneverusedforthegivensetof
templatearguments.Hence,ifadefinitionisneededbutnoneisprovided,the
programisinerror.Forclasstemplatespecialization,itissometimesusefulto
“forwarddeclare”typessothatmutuallydependenttypescanbeconstructed.A
fullspecializationdeclarationisidenticaltoanormalclassdeclarationinthis
way(itisnotatemplatedeclaration).Theonlydifferencesarethesyntaxandthe
factthatthedeclarationmustmatchaprevioustemplatedeclaration.Becauseit
isnotatemplatedeclaration,themembersofafullclasstemplatespecialization
canbedefinedusingtheordinaryout-of-classmemberdefinitionsyntax(in
otherwords,thetemplate<>prefixcannotbespecified):Clickheretoview
codeimage
template<typenameT>
classS;
template<>classS<char**>{
public:
voidprint()const;
};
//thefollowingdefinitioncannotbeprecededbytemplate<>
voidS<char**>::print()const
{
std::cout<<"pointertopointertochar\n";
}
Amorecomplexexamplemayreinforcethisnotion:
Clickheretoviewcodeimage
template<typenameT>
classOutside{
public:template<typenameU>
classInside{
};};
template<>
classOutside<void>{
//thereisnospecialconnectionbetweenthefollowingnestedclass
//andtheonedefinedinthegenerictemplate
template<typenameU>
classInside{
private:
staticintcount;
};
};
//thefollowingdefinitioncannotbeprecededbytemplate<>
template<typenameU>
intOutside<void>::Inside<U>::count=1;
Afullspecializationisareplacementfortheinstantiationofacertaingeneric
template,anditisnotvalidtohaveboththeexplicitandthegeneratedversions
ofatemplatepresentinthesameprogram.Anattempttousebothinthesame
fileisusuallycaughtbyacompiler:Clickheretoviewcodeimage
template<typenameT>
classInvalid{
};
Invalid<double>x1;//causestheinstantiationofInvalid<double>
template<>
classInvalid<double>;//ERROR:Invalid<double>alreadyinstantiated
Unfortunately,iftheusesoccurindifferenttranslationunits,theproblemmay
notbecaughtsoeasily.ThefollowinginvalidC++exampleconsistsoftwofiles
andcompilesandlinksonmanyimplementations,butitisinvalidand
dangerous:Clickheretoviewcodeimage
//Translationunit1:
template<typenameT>
classDanger{
public:
enum{max=10};
};
charbuffer[Danger<void>::max];//usesgenericvalue
externvoidclear(char*);
intmain()
{
clear(buffer);
}
//Translationunit2:
template<typenameT>
classDanger;
template<>
classDanger<void>{
public:
enum{max=100};
};
voidclear(char*buf)
{
//mismatchinarraybound:
for(intk=0;k<Danger<void>::max;++k){
buf[k]=’\0’;
}
}
Thisexampleisclearlycontrivedtokeepitshort,butitillustratesthatcaremust
betakentoensurethatthedeclarationofthespecializationisvisibletoallthe
usersofthegenerictemplate.Inpracticalterms,thismeansthatadeclarationof
thespecializationshouldnormallyfollowthedeclarationofthetemplateinits
headerfile.Whenthegenericimplementationcomesfromanexternalsource
(suchthatthecorrespondingheaderfilesshouldnotbemodified),thisisnot
necessarilypractical,butitmaybeworthcreatingaheaderincludingthegeneric
templatefollowedbydeclarationsofthespecializationstoavoidthesehard-to-
finderrors.Wefindthat,ingeneral,itisbettertoavoidspecializingtemplates
comingfromanexternalsourceunlessitisclearlymarkedasbeingdesignedfor
thatpurpose.
16.3.2FullFunctionTemplateSpecialization
Thesyntaxandprinciplesbehind(explicit)fullfunctiontemplatespecialization
aremuchthesameasthoseforfullclasstemplatespecialization,butoverloading
andargumentdeductioncomeintoplay.
Thefullspecializationdeclarationcanomitexplicittemplateargumentswhen
thetemplatebeingspecializedcanbedeterminedviaargumentdeduction(using
asargumenttypestheparametertypesprovidedinthedeclaration)andpartial
ordering.Forexample:Clickheretoviewcodeimage
template<typenameT>
intf(T)//#1
{
return1;
}
template<typenameT>
intf(T*)//#2
{
return2;
}
template<>intf(int)//OK:specializationof#1
{
return3;
}
template<>intf(int*)//OK:specializationof#2
{
return4;
}
Afullfunctiontemplatespecializationcannotincludedefaultargumentvalues.
However,anydefaultargumentsthatwerespecifiedforthetemplatebeing
specializedremainapplicabletotheexplicitspecialization:Clickheretoview
codeimage
template<typenameT>
intf(T,Tx=42)
{
returnx;
}
template<>intf(int,int=35)//ERROR
{
return0;
}
(That’sbecauseafullspecializationprovidesanalternativedefinition,butnotan
alternativedeclaration.Atthepointofacalltoafunctiontemplate,thecallis
entirelyresolvedbasedonthefunctiontemplate.)Afullspecializationisin
manywayssimilartoanormaldeclaration(orrather,anormalredeclaration).In
particular,itdoesnotdeclareatemplate,andthereforeonlyonedefinitionofa
noninlinefullfunctiontemplatespecializationshouldappearinaprogram.
However,wemuststillensurethatadeclarationofthefullspecialization
followsthetemplatetopreventattemptsatusingthefunctiongeneratedfromthe
template.Thedeclarationsforatemplateg()andonefullspecializationwould
thereforetypicallybeorganizedintwofilesasfollows:•Theinterfacefile
containsthedefinitionsofprimarytemplatesandpartialspecializationsbut
declaresonlythefullspecializations:Clickheretoviewcodeimage
#ifndefTEMPLATE_G_HPP
#defineTEMPLATE_G_HPP
//templatedefinitionshouldappearinheaderfile:
template<typenameT>
intg(T,Tx=42)
{
returnx;
}
//specializationdeclarationinhibitsinstantiationsofthe
template;
//definitionshouldnotappearheretoavoidmultipledefinition
errors
template<>intg(int,inty);
#endif//TEMPLATE_G_HPP
•Thecorrespondingimplementationfiledefinesthefullspecialization:Click
heretoviewcodeimage
#include"template_g.hpp"
template<>intg(int,inty)
{
returny/2;
}
Alternatively,thespecializationcouldbemadeinline,inwhichcaseits
definitioncanbe(andshouldbe)placedintheheaderfile.
16.3.3FullVariableTemplateSpecialization
Variabletemplatescanalsobefullyspecialized.Bynow,thesyntaxshouldbe
intuitive:Clickheretoviewcodeimage
template<typenameT>constexprstd::size_tSZ=sizeof(T);
template<>constexprstd::size_tSZ<void>=0;Clearly,the
specializationcanprovideaninitializerthatisdistinctfromthat
resultingfromthetemplate.Interestingly,avariabletemplate
specializationisnotrequiredtohaveatypematchingthatofthe
templatebeingspecialized:Clickheretoviewcodeimage
template<typenameT>typenameT::iteratornull_iterator;
template<>BitIteratornull_iterator<std::bitset<100>>;
//BitIteratordoesn’tmatchT::iterator,andthatisfine
16.3.4FullMemberSpecialization
Notonlymembertemplates,butalsoordinarystaticdatamembersandmember
functionsofclasstemplates,canbefullyspecialized.Thesyntaxrequires
template<>prefixforeveryenclosingclasstemplate.Ifamembertemplateis
beingspecialized,atemplate<>mustalsobeaddedtodenotethatitisbeing
specialized.Toillustratetheimplicationsofthis,let’sassumethefollowing
declarations:Clickheretoviewcodeimage
template<typenameT>
classOuter{//#1
public:
template<typenameU>
classInner{//#2
private:
staticintcount;//#3
};
staticintcode;//#4
voidprint()const{//#5
std::cout<<"generic";
}
};
template<typenameT>
intOuter<T>::code=6;//#6
template<typenameT>template<typenameU>
intOuter<T>::Inner<U>::count=7;//#7
template<>
classOuter<bool>{//#8
public:
template<typenameU>
classInner{//#9
private:
staticintcount;//#10
};
voidprint()const{//#11
}
};
Theordinarymemberscodeatpoint#4andprint()atpoint#5ofthe
genericOutertemplate#1haveasingleenclosingclasstemplateandhence
needonetemplate<>prefixtospecializethemfullyforaspecificsetof
templatearguments:Clickheretoviewcodeimage
template<>
intOuter<void>::code=12;
template<>
voidOuter<void>::print()const
{
std::cout<<"Outer<void>";
}
Thesedefinitionsareusedoverthegenericonesatpoints#4and#5forclass
Outer<void>,butothermembersofclassOuter<void>arestillgenerated
fromthetemplateatpoint#1.Notethatafterthesedeclarations,itisnolonger
validtoprovideanexplicitspecializationforOuter<void>.
Justaswithfullfunctiontemplatespecializations,weneedawaytodeclare
thespecializationofanordinarymemberofaclasstemplatewithoutspecifyinga
definition(topreventmultipledefinitions).Althoughnondefiningout-of-class
declarationsarenotallowedinC++formemberfunctionsandstaticdata
membersofordinaryclasses,theyarefinewhenspecializingmembersofclass
templates.ThepreviousdefinitionscouldbedeclaredwithClickheretoview
codeimage
template<>
intOuter<void>::code;
template<>
voidOuter<void>::print()const;Theattentivereadermightpointout
thatthenondefiningdeclarationofthefullspecializationof
Outer<void>::codelooksexactlylikeadefinitiontobeinitialized
withadefaultconstructor.Thisisindeedso,butsuchdeclarations
arealwaysinterpretedasnondefiningdeclarations.Forafull
specializationofastaticdatamemberwithatypethatcanonlybe
initializedusingadefaultconstructor,wemustresortto
initializerlistsyntax.Giventhefollowing:Clickheretoviewcode
image
classDefaultInitOnly{
public:
DefaultInitOnly()=default;
DefaultInitOnly(DefaultInitOnlyconst&)=delete;
};
template<typenameT>
classStatics{
private:
staticTsm;
};
thefollowingisadeclaration:
Clickheretoviewcodeimage
template<>
DefaultInitOnlyStatics<DefaultInitOnly>::sm;
whilethefollowingisadefinitionthatcallsthedefaultconstructor:Clickhereto
viewcodeimage
template<>
DefaultInitOnlyStatics<DefaultInitOnly>::sm{};PriortoC++11,this
wasnotpossible.Defaultinitializationwasthusnotavailablefor
suchspecializations.Typically,aninitializercopyingadefault
valuewasused:Clickheretoviewcodeimage
template<>
DefaultInitOnlyStatics<DefaultInitOnly>::sm=DefaultInitOnly();
Unfortunately,forourexamplethatwasnotpossiblebecausethecopy
constructorisdeleted.However,C++17introducedmandatorycopy-elision
rules,whichmakethatalternativevalid,becausenocopyconstructorinvocation
isinvolvedanymore.
ThemembertemplateOuter<T>::Innercanalsobespecializedfora
giventemplateargumentwithoutaffectingtheothermembersofthespecific
instantiationofOuter<T>,forwhichwearespecializingthemembertemplate.
Again,becausethereisoneenclosingtemplate,wewillneedonetemplate<>
prefix.Thisresultsincodelikethefollowing:Clickheretoviewcodeimage
template<>
template<typenameX>
classOuter<wchar_t>::Inner{
public:
staticlongcount;//membertypechanged
};
template<>
template<typenameX>
longOuter<wchar_t>::Inner<X>::count;ThetemplateOuter<T>::Inner
canalsobefullyspecialized,butonlyforagiveninstanceof
Outer<T>.Wenowneedtwotemplate<>prefixes:onebecauseofthe
enclosingclassandonebecausewe’refullyspecializingthe(inner)
template:Clickheretoviewcodeimage
template<>
template<>
classOuter<char>::Inner<wchar_t>{
public:
enum{count=1};
};
//thefollowingisnotvalidC++:
//template<>cannotfollowatemplateparameterlist
template<typenameX>
template<>classOuter<X>::Inner<void>;//ERROR
ContrastthiswiththespecializationofthemembertemplateofOuter<bool>.
Becausethelatterisalreadyfullyspecialized,thereisnoenclosingtemplate,and
weneedonlyonetemplate<>prefix:Clickheretoviewcodeimage
template<>
classOuter<bool>::Inner<wchar_t>{
public:
enum{count=2};
};
16.4PartialClassTemplateSpecialization
Fulltemplatespecializationisoftenuseful,butsometimesitisnaturaltowantto
specializeaclasstemplateorvariabletemplateforafamilyoftemplate
argumentsratherthanjustonespecificsetoftemplatearguments.Forexample,
let’sassumewehaveaclasstemplateimplementingalinkedlist:Clickhereto
viewcodeimage
template<typenameT>
classList{//#1
public:
…
voidappend(Tconst&);
inlinestd::size_tlength()const;
…
};
Alargeprojectmakinguseofthistemplatemayinstantiateitsmembersfor
manytypes.Formemberfunctionsthatarenotexpandedinline(say,
List<T>::append()),thismaycausenoticeablegrowthintheobjectcode.
However,wemayknowthatfromalow-levelpointofview,thecodefor
List<int*>::append()andList<void*>::append()isthesame.
Inotherwords,we’dliketospecifythatallListsofpointerssharean
implementation.AlthoughthiscannotbeexpressedinC++,wecanachieve
somethingquiteclosebyspecifyingthatallListsofpointersshouldbe
instantiatedfromadifferenttemplatedefinition:Clickheretoviewcodeimage
template<typenameT>
classList<T*>{//#2
private:
List<void*>impl;
…
public:
…
inlinevoidappend(T*p){
impl.append(p);
}
inlinestd::size_tlength()const{
returnimpl.length();
}
…
};
Inthiscontext,theoriginaltemplateatpoint#1iscalledtheprimarytemplate,
andthelatterdefinitioniscalledapartialspecialization(becausethetemplate
argumentsforwhichthistemplatedefinitionmustbeusedhavebeenonly
partiallyspecified).Thesyntaxthatcharacterizesapartialspecializationisthe
combinationofatemplateparameterlistdeclaration(template<…>)anda
setofexplicitlyspecifiedtemplateargumentsonthenameoftheclasstemplate
(<T*>inourexample).
OurcodecontainsaproblembecauseList<void*>recursivelycontainsa
memberofthatsameList<void*>type.Tobreakthecycle,wecanprecede
thepreviouspartialspecializationwithafullspecialization:Clickheretoview
codeimage
template<>
classList<void*>{//#3
…
voidappend(void*p);
inlinestd::size_tlength()const;
…
};
Thisworksbecausematchingfullspecializationsarepreferredoverpartial
specializations.Asaresult,allmemberfunctionsofListsofpointersare
forwarded(througheasilyinlineablefunctions)totheimplementationof
List<void*>.Thisisaneffectivewaytocombatcodebloat(ofwhichC++
templatesareoftenaccused).
Thereexistseverallimitationsontheparameterandargumentlistsofpartial
specializationdeclarations.Someofthemareasfollows:1.Theargumentsof
thepartialspecializationmustmatchinkind(type,nontype,ortemplate)the
correspondingparametersoftheprimarytemplate.
2.Theparameterlistofthepartialspecializationcannothavedefaultarguments;
thedefaultargumentsoftheprimaryclasstemplateareusedinstead.
3.Thenontypeargumentsofthepartialspecializationshouldbeeither
nondependentvaluesorplainnontypetemplateparameters.Theycannotbe
morecomplexdependentexpressionslike2*N(whereNisatemplate
parameter).
4.Thelistoftemplateargumentsofthepartialspecializationshouldnotbe
identical(ignoringrenaming)tothelistofparametersoftheprimarytemplate.
5.Ifoneofthetemplateargumentsisapackexpansion,itmustcomeattheend
ofatemplateargumentlist.
Anexampleillustratestheselimitations:
Clickheretoviewcodeimage
template<typenameT,intI=3>
classS;//primarytemplate
template<typenameT>
classS<int,T>;//ERROR:parameterkindmismatch
template<typenameT=int>
classS<T,10>;//ERROR:nodefaultarguments
template<intI>
classS<int,I*2>;//ERROR:nonontypeexpressions
template<typenameU,intK>
classS<U,K>;//ERROR:nosignificantdifferencefromprimary
template
template<typename…Ts>
classTuple;
template<typenameTail,typename…Ts>
classTuple<Ts…,Tail>;//ERROR:packexpansionnotattheend
template<typenameTail,typename…Ts>
classTuple<Tuple<Ts…>,Tail>;//OK:packexpansionisattheendof
a
//nestedtemplateargumentlist
Everypartialspecialization—likeeveryfullspecialization—isassociatedwith
theprimarytemplate.Whenatemplateisused,theprimarytemplateisalways
theonethatislookedup,butthentheargumentsarealsomatchedagainstthose
oftheassociatedspecializations(usingtemplateargumentdeduction,as
describedinChapter15)todeterminewhichtemplateimplementationispicked.
Justaswithfunctiontemplateargumentdeduction,theSFINAEprincipleapplies
here:If,whileattemptingtomatchapartialspecializationaninvalidconstructis
formed,thatspecializationissilentlyabandonedandanothercandidateis
examinedifoneisavailable.Ifnomatchingspecializationsisfound,theprimary
templateisselected.Ifmultiplematchingspecializationsarefound,themost
specializedone(inthesensedefinedforoverloadedfunctiontemplates)is
selected;ifnonecanbecalledmostspecialized,theprogramcontainsan
ambiguityerror.
Finally,weshouldpointoutthatitisentirelypossibleforaclasstemplate
partialspecializationtohavemoreorfewerparametersthantheprimary
template.ConsiderourgenerictemplateList,declaredatpoint#1,again.We
havealreadydiscussedhowtooptimizethelist-of-pointerscase,butwemay
wanttodothesamewithcertainpointer-to-membertypes.Thefollowingcode
achievesthisforpointer-to-member-pointers:Clickheretoviewcodeimage
//partialspecializationforanypointer-to-void*member
template<typenameC>
classList<void*C::*>{//#4
public:
usingElementType=void*C::*;
…
voidappend(ElementTypepm);
inlinestd::size_tlength()const;
…
};
//partialspecializationforanypointer-to-member-pointertype
except
//pointer-to-void*member,whichishandledearlier
//(notethatthispartialspecializationhastwotemplate
parameters,
//whereastheprimarytemplateonlyhasoneparameter)
//thisspecializationmakesuseoftheprioronetoachievethe
//desiredoptimization
template<typenameT,typenameC>
classList<T*C::*>{//#5
private:
List<void*C::*>impl;
…
public:
usingElementType=T*C::*;
…
inlinevoidappend(ElementTypepm){
impl.append((void*C::*)pm);
}
inlinestd::size_tlength()const{
returnimpl.length();
}
…
};
Inadditiontoourobservationregardingthenumberoftemplateparameters,note
thatthecommonimplementationdefinedat#4towhichallothersareforwarded
bythedeclarationatpoint#5isitselfapartialspecialization(forthesimple
pointercaseitisafullspecialization).However,itisclearthatthespecialization
atpoint#4ismorespecializedthanthatatpoint#5;thusnoambiguityshould
occur.
Moreover,itisevenpossiblethatthenumberofexplicitlywrittentemplate
argumentscandifferfromthenumberoftemplateparametersintheprimary
template.Thiscanhappenbothwithdefaulttemplateargumentsand,inafar
moreusefulmanner,withvariadictemplates:Clickheretoviewcodeimage
template<typename…Elements>
classTuple;//primarytemplate
template<typenameT1>
classTuple<T>;//one-elementtuple
template<typenameT1,typenameT2,typename…Rest>
classTuple<T1,T2,Rest…>;//tuplewithtwoormoreelements
16.5PartialVariableTemplateSpecialization
WhenvariabletemplateswereaddedtothedraftC++11standard,several
aspectsoftheirspecificationswereoverlooked,andsomeofthoseissueshave
stillnotbeenformallyresolved.However,actualimplementationsgenerally
agreeonthehandlingoftheseissues.
Perhapsthemostsurprisingoftheseissuesisthatthestandardreferstothe
abilitytopartiallyspecializevariabletemplates,butitdoesnotdescribehow
theyaredeclaredorwhattheymean.WhatfollowsisthusbasedonC++
implementationsinpractice(whichdopermitsuchpartialspecializations),and
notontheC++standard.
Asonewouldexpect,thesyntaxissimilartofullvariabletemplate
specialization,exceptthattemplate<>isreplacedbyanactualtemplate
declarationheader,andthetemplateargumentlistfollowingthevariable
templatenamemustdependontemplateparameters.Forexample:Clickhereto
viewcodeimage
template<typenameT>constexprstd::size_tSZ=sizeof(T);
template<typenameT>constexprstd::size_tSZ<T&>=sizeof(void*);As
withthefullspecializationofvariabletemplates,thetypeofa
partialspecializationisnotrequiredtomatchthatoftheprimary
template:Clickheretoviewcodeimage
template<typenameT>typenameT::iteratornull_iterator;
template<typenameT,std::size_tN>T*null_iterator<T[N]>=
null_ptr;
//T*doesn’tmatchT::iterator,andthatisfine
Therulesregardingthekindsoftemplateargumentsthatcanbespecifiedfora
variabletemplatepartialspecializationareidenticaltothoseforclasstemplate
specializations.Similarly,therulestoselectaspecializationforagivenlistof
concretetemplateargumentsareidenticaltoo.
16.6Afternotes
FulltemplatespecializationwaspartoftheC++templatemechanismfromthe
start.Functiontemplateoverloadingandclasstemplatepartialspecialization,on
theotherhand,camemuchlater.TheHPaC++compilerwasthefirstto
implementfunctiontemplateoverloading,andEDG’sC++frontendwasthe
firsttoimplementclasstemplatepartialspecialization.Thepartialordering
principlesdescribedinthischapterwereoriginallyinventedbySteveAdamczyk
andJohnSpicer(whoarebothofEDG).
Theabilityoftemplatespecializationstoterminateanotherwiseinfinitely
recursivetemplatedefinition(suchastheList<T*>examplepresentedin
Section16.4onpage348)wasknownforalongtime.However,ErwinUnruh
wasperhapsthefirsttonotethatthiscouldleadtotheinterestingnotionof
templatemetaprogramming:usingthetemplateinstantiationmechanismto
performnontrivialcomputationsatcompiletime.WedevoteChapter23tothis
topic.
Youmaylegitimatelywonderwhyonlyclasstemplatesandvariabletemplates
canbepartiallyspecialized.Thereasonsaremostlyhistorical.Itisprobably
possibletodefinethesamemechanismforfunctiontemplates(seeChapter17).
Insomeways,theeffectofoverloadingfunctiontemplatesissimilar,butthere
arealsosomesubtledifferences.Thesedifferencesaremostlyrelatedtothefact
thatonlytheprimarytemplateneedstobelookedupwhenauseisencountered.
Thespecializationsareconsideredonlyafterward,todeterminewhich
implementationshouldbeused.Incontrast,alloverloadedfunctiontemplates
mustbebroughtintoanoverloadsetbylookingthemup,andtheymaycome
fromdifferentnamespacesorclasses.Thisincreasesthelikelihoodof
unintentionallyoverloadingatemplatenamesomewhat.
Conversely,itisalsoimaginabletoallowaformofoverloadingofclass
templatesandvariabletemplates.Hereisanexample:Clickheretoviewcode
image
//invalidoverloadingofclasstemplates
template<typenameT1,typenameT2>classPair;
template<intN1,intN2>classPair;However,theredoesn’tseemto
beapressingneedforsuchamechanism.
1ThisdefinitiondiffersfromthatgivenintheC++standard,butits
consequencesareequivalent.
2Aliastemplatesaretheonlyformoftemplatethatcannotbespecialized,
eitherbyafullspecializationorapartialspecialization.Thisrestrictionis
necessarytomaketheuseoftemplatealiasestransparenttothetemplate
argumentdeductionprocessSection15.11onpage312.
3Thesameprefixisalsoneededtodeclarefullfunctiontemplate
specializations.EarlierdesignsoftheC++languagedidnotincludethis
prefix,buttheadditionofmembertemplatesrequiredadditionalsyntaxto
disambiguatecomplexspecializationcases.
Chapter17
FutureDirections
C++templateshavebeenevolvingalmostcontinuouslyfromtheirinitialdesign
in1988,throughthevariousstandardizationmilestonesin1998,2011,2014,and
2017.Itcouldbearguedthattemplateswereatleastsomewhatrelatedtomost
majorlanguageadditionsaftertheoriginal1998standard.
Thefirsteditionofthisbooklistedanumberofextensionsthatwemightsee
afterthefirststandard,andseveralofthosebecamereality:•Theanglebracket
hack:C++11removedtheneedtoinsertaspacebetweentwoclosingangle
brackets.
•Defaultfunctiontemplatearguments:C++11allowsfunctiontemplatestohave
defaulttemplatearguments.
•Typedeftemplates:C++11introducedaliastemplates,whicharesimilar.
•Thetypeofoperator:C++11introducedthedecltypeoperator,whichfills
thesamerole(butusesadifferenttokentoavoidaconflictwithanexisting
extensionthatdoesn’tquitemeettheneedsoftheC++programmers’
community).
•Staticproperties:Thefirsteditionanticipatedaselectionoftypetraitsbeing
supporteddirectlybycompilers.Thishascometopass,althoughtheinterface
isexpressedusingthestandardlibrary(whichisthenimplementedusing
compilerextensionsformanyofthetraits).
•Custominstantiationdiagnostics:Thenewkeywordstatic_assert
implementstheideadescribedbystd::instantiation_errorinthe
firsteditionofthisbook.
•Listparameters:ThisbecameparameterpacksinC++11.
•Layoutcontrol:C++11’salignofandalignascovertheneedsdescribed
inthefirstedition.Furthermore,theC++17libraryaddedastd::variant
templatetosupportdiscriminatedunions.
•Initializerdeduction:C++17addedclasstemplateargumentdeduction,which
addressesthesameissue.
•Functionexpressions:C++11’slambdaexpressionsprovidesexactlythis
functionality(withasyntaxsomewhatdifferentfromthatdiscussedinthefirst
edition).
Otherdirectionshypothesizedinthefirsteditionhavenotmadeitintothe
modernlanguage,butmostarestilldiscussedonoccasionandwekeeptheir
presentationinthisvolume.Meanwhile,otherideasareemergingandwepresent
someofthoseaswell.
17.1RelaxedtypenameRules
Inthefirsteditionofthisbook,thissectionsuggestedthatthefuturemightbring
twokindsofrelaxationstotherulesfortheuseoftypename(seeSection
13.3.2onpage228):Allowtypenamewhereitwasnotpreviouslyallowed,
andmaketypenameoptionalwhereacompilercanrelativelyeasilyinferthata
qualifiednamewithadependentqualifiermustnameatype.Theformercameto
pass(typenameinC++11canbeusedredundantlyinmanyplaces),butthe
latterhasnot.
Recently,however,therehasbeenarenewedcalltomaketypename
optionalinvariouscommoncontextswheretheexpectationofatypespecifieris
unambiguous:•Thereturntypeandparametertypesoffunctionandmember
functiondeclarationsinnamespaceandclassscope.Similarlywithfunctionand
memberfunctiontemplatesandwithlambdaexpressionsappearinginanyscope.
•Thetypesofvariable,variabletemplate,andstaticdatamemberdeclarations.
Again,similarlywithvariabletemplates.
•Thetypeafterthe=tokeninanaliasoraliastemplatedeclaration.
•Thedefaultargumentofatypeparameterofatemplate.
•Thetypeappearingintheanglebracketsfollowingastatic_cast,
const_cast,dynamic_cast,orreinterpret_cast,construct.
•Thetypenamedinanewexpression.
Althoughthisisarelativelyadhoclist,itturnsoutthatsuchachangeinthe
languagewouldallowbyfarmostinstancesofthisuseoftypenametobe
dropped,whichwouldmakecodemorecompactandmorereadable.
17.2GeneralizedNontypeTemplateParameters
Amongtherestrictionsonnontypetemplatearguments,perhapsthemost
surprisingtobeginningandadvancedtemplatewritersalikeistheinabilityto
provideastringliteralasatemplateargument.
Thefollowingexampleseemsintuitiveenough:Clickheretoviewcode
image
template<charconst*msg>
classDiagnoser{
public:
voidprint();
};
intmain()
{
Diagnoser<"Surprise!">().print();
}
However,therearesomepotentialproblems.InstandardC++,twoinstancesof
Diagnoserarethesametypeifandonlyiftheyhavethesamearguments.In
thiscasetheargumentisapointervalue—inotherwords,anaddress.However,
twoidenticalstringliteralsappearingindifferentsourcelocationsarenot
requiredtohavethesameaddress.Wecouldthusfindourselvesintheawkward
situationthatDiagnoser<"X">andDiagnoser<"X">areinfacttwo
differentandincompatibletypes!(Notethatthetypeof"X"ischar
const[2],butitdecaystocharconst*whenpassedasatemplate
argument.)Becauseofthese(andrelated)considerations,theC++standard
prohibitsstringliteralsasargumentstotemplates.However,some
implementationsdoofferthefacilityasanextension.Theyenablethisbyusing
theactualstringliteralcontentsintheinternalrepresentationofthetemplate
instance.Althoughthisisclearlyfeasible,someC++languagecommentators
feelthatanontypetemplateparameterthatcanbesubstitutedbyastringliteral
valueshouldbedeclareddifferentlyfromonethatcanbesubstitutedbyan
address.Onepossibilitywouldbetocapturestringliteralsinaparameterpackof
characters.Forexample:Clickheretoviewcodeimage
template<char…msg>
classDiagnoser{
public:
voidprint();
};
intmain()
{
//instantiatesDiagnoser<’S’,’u’,’r’,’p’,’r’,’i’,’s’,’e’,’!’>
Diagnoser<"Surprise!">().print();
}
Weshouldalsonoteanadditionaltechnicalwrinkleinthisissue.Considerthe
followingtemplatedeclarations,andlet’sassumethatthelanguagehasbeen
extendedtoacceptstringliteralsastemplateargumentsinthiscase:Clickhereto
viewcodeimage
template<charconst*str>
classBracket{
public:
staticcharconst*address();
staticcharconst*bytes();
};
template<charconst*str>
charconst*Bracket<str>::address()
{
returnstr;
}
template<charconst*str>
charconst*Bracket<str>::bytes()
{
returnstr;
}
Inthepreviouscode,thetwomemberfunctionsareidenticalexceptfortheir
names—asituationthatisnotthatuncommon.Imaginethatanimplementation
wouldinstantiateBracket<"X">usingaprocessmuchlikemacroexpansion:
Inthiscase,ifthetwomemberfunctionsareinstantiatedindifferenttranslation
units,theymayreturndifferentvalues.Interestingly,atestofsomeC++
compilersthatcurrentlyprovidethisextensionrevealsthattheydosufferfrom
thissurprisingbehavior.
Arelatedissueistheabilitytoprovidefloating-pointliterals(andsimple
constantfloating-pointexpressions)astemplatearguments.Forexample:Click
heretoviewcodeimage
template<doubleRatio>
classConverter{
public:
staticdoubleconvert(doubleval){
returnval*Ratio;
}
};
usingInchToMeter=Converter<0.0254>;Thistooisprovidedbysome
C++implementationsandpresentsnoserioustechnicalchallenges
(unlikethestringliteralarguments).
C++11introducedanotionofaliteralclasstype:aclasstypethatcantake
constantvaluescomputedatcompiletime(includingnontrivialcomputations
throughconstexprfunctions).Oncesuchclasstypesbecameavailable,it
quicklybecamedesirabletoallowthemfornontypetemplateparameters.
However,problemssimilartothoseofthestringliteralparametersdescribed
abovearose.Inparticular,the“equality”oftwoclass-typevaluesisnotatrivial
matter,becauseitisingeneraldeterminedbyoperator==definitions.This
equalitydeterminesiftwoinstantiationsareequivalent,butinpractice,that
equivalencemustbecheckablebythelinkerbycomparingmanglednames.One
wayoutmaybeanoptiontomarkcertainliteralclassesashavingatrivial
equalitycriterionthatamountstopairwisecomparingofthescalarmembersof
theclass.Onlyclasstypeswithsuchatrivialequalitycriterionwouldthenbe
permittedasnontypetemplateparametertypes.
17.3PartialSpecializationofFunctionTemplates
InChapter16wediscussedhowclasstemplatescanbepartiallyspecialized,
whereasfunctiontemplatesaresimplyoverloaded.Thetwomechanismsare
somewhatdifferent.
Partialspecializationdoesn’tintroduceacompletelynewtemplate:Itisan
extensionofanexistingtemplate(theprimarytemplate).Whenaclasstemplate
islookedup,onlyprimarytemplatesareconsideredatfirst.If,aftertheselection
ofaprimarytemplate,itturnsoutthatthereisapartialspecializationofthat
templatewithatemplateargumentpatternthatmatchesthatoftheinstantiation,
itsdefinition(inotherwords,itsbody)isinstantiatedinsteadofthedefinitionof
theprimarytemplate.(Fulltemplatespecializationsworkexactlythesameway.)
Incontrast,overloadedfunctiontemplatesareseparatetemplatesthatare
completelyindependentofoneanother.Whenselectingwhichtemplateto
instantiate,alltheoverloadedtemplatesareconsideredtogether,andoverload
resolutionattemptstochooseoneasthebestfit.Atfirstthismightseemlikean
adequatealternative,butinpracticethereareanumberoflimitations:•Itis
possibletospecializemembertemplatesofaclasswithoutchangingthe
definitionofthatclass.However,addinganoverloadedmemberdoesrequirea
changeinthedefinitionofaclass.Inmanycasesthisisnotanoptionbecause
wemaynotowntherightstodoso.Furthermore,theC++standarddoesnot
currentlyallowustoaddnewtemplatestothestdnamespace,butitdoesallow
ustospecializetemplatesfromthatnamespace.
•Tooverloadfunctiontemplates,theirfunctionparametersmustdifferinsome
materialway.ConsiderafunctiontemplateRconvert(Tconst&)where
RandTaretemplateparameters.Wemayverywellwanttospecializethis
templateforR=void,butthiscannotbedoneusingoverloading.
•Codethatisvalidforanonoverloadedfunctionmaynolongerbevalidwhen
thefunctionisoverloaded.Specifically,giventwofunctiontemplatesf(T)
andg(T)(whereTisatemplateparameter),theexpressiong(&f<int>)is
validonlyiffisnotoverloaded(otherwise,thereisnowaytodecidewhichf
ismeant).
•Frienddeclarationsrefertoaspecificfunctiontemplateoraninstantiationofa
specificfunctiontemplate.Anoverloadedversionofafunctiontemplatewould
notautomaticallyhavetheprivilegesgrantedtotheoriginaltemplate.
Together,thislistformsacompellingargumentinsupportofapartial
specializationconstructforfunctiontemplates.
Anaturalsyntaxforpartiallyspecializingfunctiontemplatesisthe
generalizationoftheclasstemplatenotation:Clickheretoviewcodeimage
template<typenameT>
Tconst&max(Tconst&,Tconst&);//primarytemplate
template<typenameT>
T*const&max<T*>(T*const&,T*const&);//partialspecialization
Somelanguagedesignersworryabouttheinteractionofthispartial
specializationapproachwithfunctiontemplateoverloading.Forexample:Click
heretoviewcodeimage
template<typenameT>
voidadd(T&x,inti);//aprimarytemplate
template<typenameT1,typenameT2>
voidadd(T1a,T2b);//another(overloaded)primarytemplate
template<typenameT>
voidadd<T*>(T*&,int);//Whichprimarytemplatedoesthis
specialize?
However,weexpectsuchcaseswouldbedeemederrorswithoutmajorimpact
ontheutilityofthefeature.
ThisextensionwasbrieflydiscussedduringthestandardizationofC++11but
gatheredrelativelylittleinterestintheend.Still,thetopicoccasionallyarises
becauseitneatlysolvessomecommonprogrammingproblems.Perhapsitwill
betakenupagaininsomefutureversionoftheC++standard.
17.4NamedTemplateArguments
Section21.4onpage512describesatechniquethatallowsustoprovidea
nondefaulttemplateargumentforaspecificparameterwithouthavingtospecify
othertemplateargumentsforwhichadefaultvalueisavailable.Althoughitisan
interestingtechnique,itisalsoclearthatitresultsinafairamountofworkfora
relativelysimpleeffect.Hence,providingalanguagemechanismtoname
templateargumentsisanaturalthought.
Weshouldnoteatthispointthatasimilarextension(sometimescalled
keywordarguments)wasproposedearlierintheC++standardizationprocessby
RolandHartinger(seeSection6.5.1of[StroustrupDnE]).Althoughtechnically
sound,theproposalwasultimatelynotacceptedintothelanguageforvarious
reasons.Atthispointthereisnoreasontobelievenamedtemplatearguments
willevermakeitintothelanguage,butthetopicarisesregularlyincommittee
discussions.
However,forthesakeofcompleteness,wementiononesyntacticideathathas
beendiscussed:Clickheretoviewcodeimage
template<typenameT,
typenameMove=defaultMove<T>,
typenameCopy=defaultCopy<T>,
typenameSwap=defaultSwap<T>,
typenameInit=defaultInit<T>,
typenameKill=defaultKill<T>>
classMutator{
…
};
voidtest(MatrixListml)
{
mySort(ml,Mutator<Matrix,.Swap=matrixSwap>);
}
Here,theperiodprecedingtheargumentnameisusedtoindicatethatwe’re
referringtoatemplateargumentbyname.Thissyntaxissimilartothe
“designatedinitializer”syntaxintroducedinthe1999Cstandard:Clickhereto
viewcodeimage
structRectangle{inttop,left,width,height;};
structRectangler={.width=10,.height=10,.top=0,.left=0
};Ofcourse,introducingnamedtemplateargumentsmeansthatthe
templateparameternamesofatemplatearenowpartofthepublic
interfacetothattemplateandcannotbefreelychanged.Thiscould
beaddressedbyamoreexplicit,opt-insyntax,suchasthe
following:Clickheretoviewcodeimage
template<typenameT,
Move:typenameM=defaultMove<T>,
Copy:typenameC=defaultCopy<T>,
Swap:typenameS=defaultSwap<T>,
Init:typenameI=defaultInit<T>,
Kill:typenameK=defaultKill<T>>
classMutator{
…
};
voidtest(MatrixListml)
{
mySort(ml,Mutator<Matrix,.Swap=matrixSwap>);
}
17.5OverloadedClassTemplates
Itisentirelypossibletoimaginethatclasstemplatescouldbeoverloadedon
theirtemplateparameters.Forexample,onecanimaginecreatingafamilyof
Arraytemplatesthatcontainsbothdynamicallyandstaticallysizedarrays:
Clickheretoviewcodeimage
template<typenameT>
classArray{
//dynamicallysizedarray
…
};
template<typenameT,unsignedSize>
classArray{
//fixedsizearray
…
};
Theoverloadingisn’tnecessarilyrestrictedtothenumberoftemplate
parameters;thekindofparameterscanbevariedtoo:Clickheretoviewcode
image
template<typenameT1,typenameT2>
classPair{
//pairoffields
…
};
template<intI1,intI2>
classPair{
//pairofconstantintegervalues
…
};
Althoughthisideahasbeendiscussedinformallybysomelanguagedesigners,it
hasnotyetbeenformallypresentedtotheC++standardizationcommittee.
17.6DeductionforNonfinalPackExpansions
Templateargumentdeductionforpackexpansionsonlyworkswhenthepack
expansionoccursattheendoftheparameterorargumentlist.Thismeansthat
whileitisfairlysimpletoextractthefirstelementfromalist:Clickheretoview
codeimage
template<typename…Types>
structFront;
template<typenameFrontT,typename…Types>
structFront<FrontT,Types…>{
usingType=FrontT;
};
onecannoteasilyextractthelastelementofthelistduetotherestrictionsplaced
onpartialspecializationsdescribedinSection16.4onpage347:Clickhereto
viewcodeimage
template<typename…Types>
structBack;
template<typenameBackT,typename…Types>
structBack<Types…,BackT>{//ERROR:packexpansionnotattheend
of
usingType=BackT;//templateargumentlist
};
Templateargumentdeductionforvariadicfunctiontemplatesissimilarly
restricted.Itisplausiblethattherulesregardingtemplateargumentdeductionof
packexpansionsandpartialspecializationswillberelaxedtoallowthepack
expansiontooccuranywhereinthetemplateargumentlist,makingthiskindof
operationfarsimpler.Moreover,itispossible—albeitlesslikely—thatdeduction
couldpermitmultiplepackexpansionswithinthesameparameterlist:Clickhere
toviewcodeimage
template<typename…Types>classTuple{
};
template<typenameT,typename…Types>
structSplit;
template<typenameT,typename…Before,typename…After>
structSplit<T,Before…,T,After…>{
usingbefore=Tuple<Before…>;
usingafter=Tuple<After…>;
};
Allowingmultiplepackexpansionsintroducesadditionalcomplexity.For
example,doesSplitseparateatthefirstoccurrenceofT,thelastoccurrence,
oroneinbetween?Howcomplicatedcanthedeductionprocessbecomebefore
thecompilerispermittedtogiveup?
17.7Regularizationofvoid
Whenprogrammingtemplates,regularityisavirtue:Ifasingleconstructcan
coverallcases,itmakesourtemplatesimpler.Oneaspectofourprogramsthatis
somewhatirregulararetypes.Forexample,considerthefollowing:Clickhereto
viewcodeimage
auto&&r=f();//erroriff()returnsvoid
Thatworksforjustaboutanytypethatf()returnsexceptvoid.Thesame
problemoccurswhenusingdecltype(auto):Clickheretoviewcodeimage
decltype(auto)r=f();//erroriff()returnsvoid
voidisnottheonlyirregulartype:Functiontypesandreferencetypesoften
alsoexhibitbehaviorsthatmakethemexceptionalinsomeway.However,it
turnsoutthatvoidoftencomplicatesourtemplatesandthatthereisnodeep
reasonforvoidtobeunusualthatway.Forexample,seeSection11.1.3on
page162foranexamplehowthiscomplicatestheimplementationofaperfect
std::invoke()wrapper.
Wecouldjustdecreethatvoidisanormalvaluetypewithauniquevalue
(likestd::nullptr_tfornullptr).Forbackwardcompatibilitypurposes,
we’dstillhavetokeepaspecialcaseforfunctiondeclarationslikethefollowing:
Clickheretoviewcodeimage
voidg(void);//sameasvoidg();
However,inmostotherways,voidwouldbecomeacompletevaluetype.We
couldthenforexampledeclarevoidvariablesandreferences:voidv=void{};
void&&rrv=f();Mostimportantly,manytemplateswouldnolongerneedtobe
specializedforthevoidcase.
17.8TypeCheckingforTemplates
Muchofthecomplexityofprogrammingwithtemplatescomesfromthe
compiler’sinabilitytolocallycheckwhetheratemplatedefinitioniscorrect.
Instead,mostofthecheckingofatemplateoccursduringtemplateinstantiation,
whenthetemplatedefinitioncontextandthetemplateinstantiationcontextare
intertwined.Thismixingofdifferentcontextsmakesithardtoassignblame:
Wasthetemplatedefinitionatfault,becauseituseditstemplatearguments
incorrectly,orwasthetemplateuseratfault,becausethesuppliedtemplate
argumentsdidn’tmeettherequirementsofthetemplate?Theproblemcanbe
illustratedwithasimpleexample,whichwehaveannotatedwiththediagnostics
producedbyatypicalcompiler:Clickheretoviewcodeimage
template<typenameT>
Tmax(Ta,Tb)
{
returnb<a?a:b;
//ERROR:“nomatchforoperator<
//(operatortypesare’X’and’X’)”
}
structX{
};
booloperator>(X,X);
intmain()
{
Xa,b;
Xm=max(a,b);//NOTE:“ininstantiationoffunctiontemplate
specialization
//’max<X>’requestedhere”
}
Notethattheactualerror(thelackofaproperoperator<)isdetectedwithin
thedefinitionofthefunctiontemplatemax().Itispossiblethatthisistrulythe
error—perhapsmax()shouldhaveusedoperator>instead?However,the
compileralsoprovidesanotethatpointstotheplacethatcausedtheinstantiation
ofmax<X>,whichmaybetherealerror—perhapsmax()isdocumentedto
requireoperator<?Theinabilitytoanswerthisquestioniswhatoftenleadsto
the“errornovel”describedinSection9.4onpage143,wherethecompiler
providestheentiretemplateinstantiationhistoryfromtheinitialcauseofthe
instantiationdowntotheactualtemplatedefinitioninwhichtheerrorwas
detected.Theprogrammeristhenexpectedtodeterminewhichofthetemplate
definitions(orperhapstheoriginaluseofthetemplate)isactuallyinerror.
Theideabehindtypecheckingoftemplatesistodescribetherequirementsof
atemplatewithinthetemplateitself,sothatthecompilercandeterminewhether
thetemplatedefinitionorthetemplateuseisatfaultwhencompilationfails.One
solutiontothisproblemistodescribethetemplate’srequirementsaspartofthe
signatureofthetemplateitselfusingaconcept:Clickheretoviewcodeimage
template<typenameT>requiresLessThanComparable<T>
T
max(Ta,Tb)
{
returnb<a?a:b;
}
structX{};
booloperator>(X,X);
intmain()
{
Xa,b;
Xm=max(a,b);//ERROR:XdoesnotmeettheLessThanComparable
requirement
}
BydescribingtherequirementsonthetemplateparameterT,thecompilerisable
toensurethatthefunctiontemplatemax()onlyusesoperationsonTthatthe
userisexpectedtoprovide(inthiscase,LessThanComparabledescribesthe
needforoperator<).Moreover,whenusingatemplate,thecompilercan
checkthatthesuppliedtemplateargumentprovidesallofthebehaviorrequired
forthemax()functiontemplatetoworkproperly.Byseparatingthetype-
checkingproblem,itbecomesfareasierforthecompilertoprovideanaccurate
diagnosisoftheproblem.
Intheexampleabove,LessThanComparableiscalledaconcept:It
representsconstraintsonatype(inthemoregeneralcase,constraintsonasetof
types)thatacompilercancheck.Conceptsystemscanbedesignedindifferent
ways.
DuringthestandardizationcycleforC++11,anelaboratesystemwasfully
designedandimplementedforconceptsthatarepowerfulenoughtocheckboth
thepointofinstantiationofatemplateandthedefinitionofatemplate.The
formermeans,inourexampleabove,thatanerrorinmain()couldbecaught
earlywithadiagnosticexplainingthatXdoesn’tsatisfytheconstraintsof
LessThanComparable.Thelattermeansthatwhenprocessingthemax()
template,thecompilerchecksthatnooperationnotpermittedbythe
LessThanComparableconceptisused(andadiagnosticisemittedifthat
constraintisviolated).TheC++11proposalwaseventuallypulledfromthe
languagespecificationbecauseofvariouspracticalconsiderations(e.g.,there
werestillmanyminorspecificationissueswhoseresolutionwasthreateninga
standardthatwasalreadyrunninglate).
AfterC++11eventuallyshipped,anewproposal(firstcalledconceptslite)
waspresentedanddevelopedbymembersofthecommittee.Thissystemdoes
notaimatcheckingthecorrectnessofatemplatebasedontheconstraints
attachedtoit.Insteaditfocusesonthepointofinstantiationsonly.Soifinour
examplemax()wereimplementedusingthe>operator,noerrorwouldbe
issuedatthatpoint.However,anerrorwouldstillbeissuedinmain()because
Xdoesn’tsatisfytheconstraintsofLessThanComparable.Thenew
conceptsproposalwasimplementedandspecifiedinwhatiscalledtheConcepts
TS(TSstandsforTechnicalSpecification),calledC++extensionsforConcepts.1
Currently,theessentialelementsofthattechnicalspecificationhavebeen
integratedintothedraftforthenextstandard(expectedtobecomeC++20).
AppendixEcoversthelanguagefeatureasspecifiedinthatdraftatthetimethis
bookwenttopress.
17.9ReflectiveMetaprogramming
Inthecontextofprogramming,reflectionreferstotheabilityto
programmaticallyinspectfeaturesoftheprogram(e.g.,answeringquestions
suchasIsatypeaninteger?orWhatnonstaticdatamembersdoesaclasstype
contain?).Metaprogrammingisthecraftof“programmingtheprogram,”which
usuallyamountstoprogrammaticallygeneratingnewcode.Reflective
metaprogramming,then,isthecraftofautomaticallysynthesizingcodethat
adaptsitselftoexistingproperties(often,types)ofaprogram.
InPartIIIofthisbook,wewillexplorehowtemplatescanachievesome
simpleformsofreflectionandmetaprogramming(insomesense,template
instantiationisaformofmetaprogramming,becauseitcausesthesynthesisof
newcode).However,thecapabilitiesofC++17templatesareratherlimited
whenitcomestoreflection(e.g.,itisnotpossibletoanswerthequestionWhat
nonstaticdatamembersdoesaclasstypecontain?)andtheoptionsfor
metaprogrammingareofteninconvenientinvariousways(inparticular,the
syntaxbecomesunwieldyandtheperformanceisdisappointing).
Recognizingthepotentialofnewfacilitiesinthisarea,theC++
standardizationcommitteecreatedastudygroup(SG7)toexploreoptionsfor
morepowerfulreflection.Thatgroup’scharterwaslaterextendedtocover
metaprogrammingalso.Hereisanexampleofoneoftheoptionsbeing
considered:Clickheretoviewcodeimage
template<typenameT>voidreport(Tp){
constexpr{
std::meta::infoinfoT=reflexpr(T);
for(std::meta::info:std::meta::data_members(infoT)){
->{
std::cout<<(:std::meta::name(info):)
<<":"<<p.(.info.)<<’\n’;
}
}
}
//codewillbeinjectedhere
}
Quiteafewnewthingsarepresentinthiscode.First,theconstexpr{…}
constructforcesthestatementsinittobeevaluatedatcompiletime,butifit
appearsinatemplate,thisevaluationisonlydonewhenthetemplateis
instantiated.Second,thereflexpr()operatorproducesanexpressionof
opaquetypestd::meta::infothatisahandletoreflectedinformation
aboutitsargument(thetypesubstitutedforTinthisexample).Alibraryof
standardmetafunctionspermitsqueryingthismeta-information.Oneofthose
standardmetafunctionsisstd::meta::data_members,whichproducesa
sequenceofstd::meta::infoobjectsdescribingthedirectnonstaticdata
membersofitsoperand.Sotheforloopaboveisreallyaloopoverthe
nonstaticdatamembersofp.
Atthecoreofthemetaprogrammingcapabilitiesofthissystemistheabilityto
“inject”codeinvariousscopes.Theconstruct->{…}injectsstatementsand/or
declarationsrightafterthestatementordeclarationthatkickedoffa
constexprevaluation.Inthisexample,thatmeansaftertheconstexpr{…
}construct.Thecodefragmentsbeinginjectedcancontaincertainpatternstobe
replacedbycomputedvalues.Inthisexample,(:…:)producesastringliteral
value(theexpressionstd::meta::name(info)producesastring-like
objectrepresentingtheunqualifiednameoftheentity—datamemberinthiscase
—representedbyinfo).Similarly,theexpression(.info.)producesan
identifiernamingtheentityrepresentedbyinfo.Otherpatternstoproduce
types,templateargumentlists,etc.arealsoproposed.
Withallthatinplace,instantiatingthefunctiontemplatereport()fora
type:Clickheretoviewcodeimage
structX{
intx;
std::strings;
};
wouldproduceaninstantiationsimilartoClickheretoviewcodeimage
template<>voidreport(Xconst&p){
std::cout<<"x"<<":"<<"p.x"<<’\n’;
std::cout<<"s"<<":"<<"p.s"<<’\n’;
}
Thatis,thisfunctionautomaticallygeneratesafunctiontooutputthenonstatic
datamembervaluesofaclasstype.
Therearemanyapplicationsforthesetypesofcapabilities.Whileitislikely
thatsomethinglikethiswilleventuallybeadoptedintothelanguage,itisunclear
inwhattimeframeitcanbeexpected.Thatsaid,afewexperimental
implementationsofsuchsystemshavebeendemonstratedatthetimeofthis
writing.(Justbeforegoingtopresswiththisbook,SG7agreedonthegeneral
directionofusingconstexprevaluationandavaluetypesomewhatlike
std::meta::infotodealwithreflectivemetaprogramming.Theinjection
mechanismpresentedhere,however,wasnotagreedon,andmostlikelya
differentsystemwillbepursued.)
17.10PackFacilities
ParameterpackswereintroducedinC++11,butdealingwiththemoftenrequires
recursivetemplateinstantiationtechniques.Recallthisoutlineofcodediscussed
inSection14.6onpage263:Clickheretoviewcodeimage
template<typenameHead,typename…Remainder>
voidf(Head&&h,Remainder&&…r){
doSomething(h);
ifconstexpr(sizeof…(r)!=0){
//handletheremainderrecursively(perfectlyforwardingthe
arguments):
f(r…);
}
}
ThisexampleismadesimplerbyexploitingthefeaturesoftheC++17compile-
timeifstatement(seeSection8.5onpage134),butitremainsarecursive
instantiationtechniquethatmaybesomewhatexpensivetocompile.
Severalcommitteeproposalshavetriedtosimplifythisstateofaffairs
somewhat.Oneexampleistheintroductionofanotationtopickaspecific
elementfromapack.Inparticular,forapackPthenotationP.[N]hasbeen
suggestedasawaytodenoteelementN+1ofthatpack.Similarly,therehave
beenproposalstodenote“slices”ofpacks(e.g.,usingthenotationP.[b,e]).
Whileexaminingsuchproposals,ithasbecomeclearthattheyinteract
somewhatwiththenotionofreflectivemetaprogrammingdiscussedabove.Itis
unclearatthistimewhetherspecificpackselectionmechanismswillbeaddedto
thelanguageorwhethermetaprogrammingfacilitiescoveringthisneedwillbe
providedinstead.
17.11Modules
Anotherupcomingmajorextension,modules,isonlyperipherallyrelatedto
templates,butitisstillworthmentioningherebecausetemplatelibrariesare
amongthegreatestbeneficiariesofthem.
Currently,libraryinterfacesarespecifiedinheaderfilesthataretextually
#includedintotranslationunits.Thereareseveraldownsidestothis
approach,butthetwomostobjectionableonesarethat(a)themeaningofthe
interfacetextmaybeaccidentallymodifiedbypreviouslyincludedcode(e.g.,
throughmacros),and(b)reprocessingthattexteverytimequicklydominates
buildtimes.
Modulesareafeaturethatallowslibraryinterfacestobecompiledintoa
compiler-specificformat,andthenthoseinterfacescanbe“imported”into
translationunitswithoutbeingsubjecttomacroexpansionormodificationofthe
meaningofcodebytheaccidentalpresenceofadditiondeclarations.What’s
more,acompilercanarrangetoreadonlythosepartsofacompiledmodulefile
thatarerelevanttotheclientcode,therebydrasticallyacceleratingthe
compilationprocess.
Hereiswhatamoduledefinitionmaylooklike:Clickheretoviewcode
image
moduleMyLib;
voidhelper(){
…
}
exportinlinevoidlibFunc(){
…
helper();
…
}
ThismoduleexportsafunctionlibFunc()thatcanbeusedinclientcodeas
follows:Clickheretoviewcodeimage
importMyLib;
intmain(){
libFunc();
}
NotethatlibFunc()ismadevisibletoclientcodebutthefunction
helper()isnot,eventhoughthecompiledmodulefilewilllikelycontain
informationabouthelper()toenableinlining.
TheproposaltoaddmodulestoC++iswellonitsway,andthe
standardizationcommitteeisaimingatintegratingitafterC++17.Oneofthe
concernsindevelopingsuchaproposalishowtotransitionfromaworldof
headerfilestoaworldofmodules.Therearealreadyfacilitiestoenablethisto
somedegree(e.g.,theabilitytoincludeheaderfileswithoutmakingtheir
contentspartofthemodule),andadditionalonesstillunderdiscussion(e.g.,the
abilitytoexportmacrosfrommodules).
Modulesareparticularlyusefulfortemplatelibrariesbecausetemplatesare
almostalwaysfullydefinedinheaderfiles.Evenincludingabasicstandard
headerlike<vector>amountstoprocessingtensofthousandsoflinesofC++
code(evenwhenonlyasmallnumberofthedeclarationsinthatheaderwillbe
referenced).Otherpopularlibrariesincreasethisbyanorderofmagnitude.
AvoidingthecostsofallthiscompilationwillbeofmajorinteresttotheC++
programmersdealingwithlarge,complexcodebases.
1See,forexample,documentN4641fortheversionoftheConceptsTSinthe
beginningof2017.
PartIII
TemplatesandDesign
Programsaregenerallyconstructedusingdesignpatternsthatmaprelatively
wellonthemechanismsofferedbyachosenprogramminglanguage.Because
templatesintroduceawholenewlanguagemechanism,itisnotsurprisingtofind
thattheycallfornewdesignelements.Weexploretheseelementsinthispartof
thebook.NotethatseveralofthemarecoveredorusedbytheC++standard
library.
Templatesdifferfrommoretraditionallanguageconstructsinthattheyallow
ustoparameterizethetypesandconstantsofourcode.Whencombinedwith(1)
partialspecializationand(2)recursiveinstantiation,thisleadstoasurprising
amountofexpressivepower.
Ourpresentationaimsnotonlyatlistingvarioususefuldesignelementsbut
alsoatconveyingtheprinciplesthatinspiresuchdesignssothatnewtechniques
maybecreated.Thus,thefollowingchaptersillustratealargenumberofdesign
techniques,including:•Advancedpolymorphicdispatching•Generic
programmingwithtraits•Dealingwithoverloadingandinheritance•
Metaprogramming•Heterogeneousstructuresandalgorithms•Expression
templatesWealsopresentsomenotestoaidthedebuggingoftemplates.
Chapter18
ThePolymorphicPowerofTemplates
Polymorphismistheabilitytoassociatedifferentspecificbehaviorswithasingle
genericnotation.1Polymorphismisalsoacornerstoneoftheobject-oriented
programmingparadigm,whichinC++issupportedmainlythroughclass
inheritanceandvirtualfunctions.Becausethesemechanismsare(atleastinpart)
handledatruntime,wetalkaboutdynamicpolymorphism.Thisisusuallywhat
isthoughtofwhentalkingaboutplainpolymorphisminC++.However,
templatesalsoallowustoassociatedifferentspecificbehaviorswithasingle
genericnotation,butthisassociationisgenerallyhandledatcompiletime,which
werefertoasstaticpolymorphism.Inthischapter,wereviewthetwoformsof
polymorphismanddiscusswhichformisappropriateinwhichsituations.
NotethatChapter22willdiscusssomewaystodealwithpolymorphismafter
introducinganddiscussingsomedesignissuesinbetween.
18.1DynamicPolymorphism
Historically,C++startedwithsupportingpolymorphismonlythroughtheuseof
inheritancecombinedwithvirtualfunctions.2Theartofpolymorphicdesignin
thiscontextconsistsofidentifyingacommonsetofcapabilitiesamongrelated
objecttypesanddeclaringthemasvirtualfunctioninterfacesinacommonbase
class.
Theposterchildforthisdesignapproachisanapplicationthatmanages
geometricshapesandallowsthemtoberenderedinsomeway(e.g.,ona
screen).Insuchanapplication,wemightidentifyanabstractbaseclass(ABC)
GeoObj,whichdeclaresthecommonoperationsandpropertiesapplicableto
geometricobjects.Eachconcreteclassforspecificgeometricobjectsthen
derivesfromGeoObj(seeFigure18.1):
classGeoObj{
public:
//drawgeometricobject:
virtualvoiddraw()const=0;
//returncenterofgravityofgeometricobject:
virtualCoordcenter_of_gravity()const=0;
…
virtual~GeoObj()=default;
};
//concretegeometricobjectclassCircle
//-derivedfromGeoObj
classCircle:publicGeoObj{
public:
virtualvoiddraw()constoverride;
virtualCoordcenter_of_gravity()constoverride;
…
};
//concretegeometricobjectclassLine
//-derivedfromGeoObj
classLine:publicGeoObj{
public:
virtualvoiddraw()constoverride;
virtualCoordcenter_of_gravity()constoverride;
…
};
…
Aftercreatingconcreteobjects,clientcodecanmanipulatetheseobjectsthrough
referencesorpointerstothecommonbaseclassbyusingthevirtualfunction
dispatchmechanism.Callingavirtualmemberfunctionthroughapointeror
referencetoabaseclasssubobjectresultsinaninvocationoftheappropriate
memberofthespecific(“most-derived”)concreteobjectbeingreferredto.
Inourexample,theconcretecodecanbesketchedasfollows:Clickhereto
viewcodeimage
poly/dynapoly.cpp
#include"dynahier.hpp"
#include<vector>
//drawanyGeoObj
voidmyDraw(GeoObjconst&obj)
{
obj.draw();//calldraw()accordingtotypeofobject
}
//computedistanceofcenterofgravitybetweentwoGeoObjs
Coorddistance(GeoObjconst&x1,GeoObjconst&x2)
{
Coordc=x1.center_of_gravity()-x2.center_of_gravity();
returnc.abs();//returncoordinatesasabsolutevalues
}
//drawheterogeneouscollectionofGeoObjs
voiddrawElems(std::vector<GeoObj*>const&elems)
{
for(std::size_typei=0;i<elems.size();++i){
elems[i]->draw();//calldraw()accordingtotypeofelement
}
}
intmain()
{
Linel;
Circlec,c1,c2;
myDraw(l);//myDraw(GeoObj&)=>Line::draw()
myDraw(c);//myDraw(GeoObj&)=>Circle::draw()
distance(c1,c2);//distance(GeoObj&,GeoObj&)
distance(l,c);//distance(GeoObj&,GeoObj&)
std::vector<GeoObj*>coll;//heterogeneouscollection
coll.push_back(&l);//insertline
coll.push_back(&c);//insertcircle
drawElems(coll);//drawdifferentkindsofGeoObjs
}
Thekeypolymorphicinterfaceelementsarethefunctionsdraw()and
center_of_gravity().Botharevirtualmemberfunctions.Ourexample
demonstratestheiruseinthefunctionsmydraw(),distance(),and
drawElems().Thelatterfunctionsareexpressedusingthecommonbasetype
GeoObj.Aconsequenceofthisapproachisthatitisgenerallyunknownat
compiletimewhichversionofdraw()orcenter_of_gravity()mustbe
called.However,atruntime,thecompletedynamictypeoftheobjectsforwhich
thevirtualfunctionsareinvokedisaccessedtodispatchthefunctioncalls.3
Hence,dependingontheactualtypeofageometricobject,theappropriate
operationisperformed:Ifmydraw()iscalledforaLineobject,the
expressionobj.draw()callsLine::draw(),whereasforaCircle
object,thefunctionCircle::draw()iscalled.Similarly,with
distance(),thememberfunctionscenter_of_gravity()appropriate
fortheargumentobjectsarecalled.
Perhapsthemostcompellingfeatureofthisdynamicpolymorphismisthe
abilitytohandleheterogeneouscollectionsofobjects.drawElems()illustrates
thisconcept:Thesimpleexpressionelems[i]->draw()
resultsininvocationsofdifferentmemberfunctions,dependingonthedynamic
typeoftheelementbeingiteratedover.
18.2StaticPolymorphism
Templatescanalsobeusedtoimplementpolymorphism.However,theydon’t
relyonthefactoringofcommonbehaviorinbaseclasses.Instead,the
commonalityisimplicitinthatthedifferent“shapes”ofanapplicationmust
supportoperationsusingcommonsyntax(i.e.,therelevantfunctionsmusthave
thesamenames).Concreteclassesaredefinedindependentlyfromeachother
(seeFigure18.2).Thepolymorphicpoweristhenenabledwhentemplatesare
instantiatedwiththeconcreteclasses.
Figure18.2.Polymorphismimplementedviatemplates
Forexample,thefunctionmyDraw()intheprevioussection:Clickhereto
viewcodeimage
voidmyDraw(GeoObjconst&obj)//GeoObjisabstractbaseclass
{
obj.draw();
}
couldconceivablyberewrittenas
Clickheretoviewcodeimage
template<typenameGeoObj>
voidmyDraw(GeoObjconst&obj)//GeoObjistemplateparameter
{
obj.draw();
}
ComparingthetwoimplementationsofmyDraw(),wemayconcludethatthe
maindifferenceisthespecificationofGeoObjasatemplateparameterinstead
ofacommonbaseclass.Thereare,however,morefundamentaldifferences
underthehood.Forexample,usingdynamicpolymorphism,wehadonlyone
myDraw()functionatruntime,whereaswiththetemplatewehavedistinct
functions,suchasmyDraw<Line>()andmyDraw<Circle>().
Wemayattempttorecodethecompleteexampleoftheprevioussectionusing
staticpolymorphism.First,insteadofahierarchyofgeometricclasses,wehave
severalindividualgeometricclasses:Clickheretoviewcodeimage
poly/statichier.hpp
#include"coord.hpp"
//concretegeometricobjectclassCircle
//-notderivedfromanyclass
classCircle{
public:
voiddraw()const;
Coordcenter_of_gravity()const;
…
};
//concretegeometricobjectclassLine
//-notderivedfromanyclass
classLine{
public:
voiddraw()const;
Coordcenter_of_gravity()const;
…
};
…
Now,theapplicationoftheseclasseslooksasfollows:Clickheretoviewcode
image
poly/staticpoly.cpp
#include"statichier.hpp"
#include<vector>
//drawanyGeoObj
template<typenameGeoObj>
voidmyDraw(GeoObjconst&obj)
{
obj.draw();//calldraw()accordingtotypeofobject
}
//computedistanceofcenterofgravitybetweentwoGeoObjs
template<typenameGeoObj1,typenameGeoObj2>
Coorddistance(GeoObj1const&x1,GeoObj2const&x2)
{
Coordc=x1.center_of_gravity()-x2.center_of_gravity();
returnc.abs();//returncoordinatesasabsolutevalues
}
//drawhomogeneouscollectionofGeoObjs
template<typenameGeoObj>
voiddrawElems(std::vector<GeoObj>const&elems)
{
for(unsignedi=0;i<elems.size();++i){
elems[i].draw();//calldraw()accordingtotypeofelement
}
}
intmain()
{
Linel;
Circlec,c1,c2;
myDraw(l);//myDraw<Line>(GeoObj&)=>Line::draw()
myDraw(c);//myDraw<Circle>(GeoObj&)=>Circle::draw()
distance(c1,c2);//distance<Circle,Circle>(GeoObj1&,GeoObj2&)
distance(l,c);//distance<Line,Circle>(GeoObj1&,GeoObj2&)
//std::vector<GeoObj*>coll;//ERROR:noheterogeneouscollection
possible
std::vector<Line>coll;//OK:homogeneouscollectionpossible
coll.push_back(l);//insertline
drawElems(coll);//drawalllines
}
AswithmyDraw(),GeoObjcannolongerbeusedasaconcreteparameter
typefordistance().Instead,weprovidefortwotemplateparameters,
GeoObj1andGeoObj2,whichenablesdifferentcombinationsofgeometric
objecttypestobeacceptedforthedistancecomputation:Clickheretoviewcode
image
distance(l,c);//distance<Line,Circle>(GeoObj1&,GeoObj2&)
However,heterogeneouscollectionscannolongerbehandledtransparently.This
iswherethestaticpartofstaticpolymorphismimposesitsconstraint:Alltypes
mustbedeterminedatcompiletime.Instead,wecaneasilyintroducedifferent
collectionsfordifferentgeometricobjecttypes.Thereisnolongerarequirement
thatthecollectionbelimitedtopointers,whichcanhavesignificantadvantages
intermsofperformanceandtypesafety.
18.3DynamicversusStaticPolymorphism
Let’scategorizeandcomparebothformsofpolymorphism.
Terminology
DynamicandstaticpolymorphismprovidesupportfordifferentC++
programmingidioms:4
•Polymorphismimplementedviainheritanceisboundedanddynamic:–
Boundedmeansthattheinterfacesofthetypesparticipatinginthepolymorphic
behaviorarepredeterminedbythedesignofthecommonbaseclass(other
termsforthisconceptareinvasiveandintrusive).
–Dynamicmeansthatthebindingoftheinterfacesisdoneatruntime
(dynamically).
•Polymorphismimplementedviatemplatesisunboundedandstatic:–
Unboundedmeansthattheinterfacesofthetypesparticipatinginthe
polymorphicbehaviorarenotpredetermined(othertermsforthisconceptare
noninvasiveandnonintrusive).
–Staticmeansthatthebindingoftheinterfacesisdoneatcompiletime
(statically).
So,strictlyspeaking,inC++parlance,dynamicpolymorphismandstatic
polymorphismareshortcutsforboundeddynamicpolymorphismandunbounded
staticpolymorphism.Inotherlanguages,othercombinationsexist(e.g.,
Smalltalkprovidesunboundeddynamicpolymorphism).However,inthecontext
ofC++,themoreconcisetermsdynamicpolymorphismandstaticpolymorphism
donotcauseconfusion.
StrengthsandWeaknesses
DynamicpolymorphisminC++exhibitsthefollowingstrengths:•
Heterogeneouscollectionsarehandledelegantly.
•Theexecutablecodesizeispotentiallysmaller(becauseonlyonepolymorphic
functionisneeded,whereasdistincttemplateinstancesmustbegeneratedto
handledifferenttypes).
•Codecanbeentirelycompiled;hencenoimplementationsourcemustbe
published(distributingtemplatelibrariesusuallyrequiresdistributionofthe
sourcecodeofthetemplateimplementations).
Incontrast,thefollowingcanbesaidaboutstaticpolymorphisminC++:•
Collectionsofbuilt-intypesareeasilyimplemented.Moregenerally,the
interfacecommonalityneednotbeexpressedthroughacommonbaseclass.
•Generatedcodeispotentiallyfaster(becausenoindirectionthroughpointersis
neededaprioriandnonvirtualfunctionscanbeinlinedmuchmoreoften).
•Concretetypesthatprovideonlypartialinterfacescanstillbeusedifonlythat
partendsupbeingexercisedbytheapplication.
Staticpolymorphismisoftenregardedasmoretypesafethandynamic
polymorphismbecauseallthebindingsarecheckedatcompiletime.For
example,thereislittledangerofinsertinganobjectofthewrongtypeina
containerinstantiatedfromatemplate.However,inacontainerexpecting
pointerstoacommonbaseclass,thereisapossibilitythatthesepointers
unintentionallyenduppointingtocompleteobjectsofdifferenttypes.
Inpractice,templateinstantiationscanalsocausesomegriefwhendifferent
semanticassumptionshidebehindidentical-lookinginterfaces.Forexample,
surprisescanoccurwhenatemplatethatassumesanassociativeoperator+is
instantiatedforatypethatisnotassociativewithrespecttothatoperator.In
practice,thiskindofsemanticmismatchoccurslessoftenwithinheritance-based
hierarchies,presumablybecausetheinterfacespecificationismoreexplicitly
specified.
CombiningBothForms
Ofcourse,youcouldcombinebothformsofpolymorphism.Forexample,you
couldderivedifferentkindsofgeometricobjectsfromacommonbaseclassto
beabletohandleheterogeneouscollectionsofgeometricobjects.However,you
canstillusetemplatestowritecodeforacertainkindofgeometricobject.
ThecombinationofinheritanceandtemplatesisfurtherdescribedinChapter
21.Wewillsee(amongotherthings)howthevirtualityofamemberfunction
canbeparameterizedandhowanadditionalamountofflexibilityisaffordedto
staticpolymorphismusingtheinheritance-basedcuriouslyrecurringtemplate
pattern(orCRTP).
18.4UsingConcepts
Oneargumentagainststaticpolymorphismwithtemplatesisthatthebindingof
theinterfacesisdonebyinstantiatingthecorrespondingtemplates.Thismeans
thatthereisnocommoninterface(class)toprogramagainst.Instead,anyusage
ofatemplatesimplyworksifallinstantiatedcodeisvalid.Ifitisnot,thismight
leadtohard-to-understanderrormessagesorevencausevalidbutunintended
behavior.
Forthisreason,C++languagedesignershavebeenworkingontheabilityto
explicitlyprovide(andcheck)interfacesfortemplateparameters.Suchan
interfaceisusuallycalledaconceptinC++.Itdenotesasetofconstraintsthat
templateargumentshavetofulfilltosuccessfullyinstantiateatemplate.
Despitemanyyearsofworkinthisarea,conceptsarestillnotpartofstandard
C++asofC++17.Somecompilersprovideexperimentalsupportforsucha
feature,5however,andconceptswilllikelybepartofthenextstandardafter
C++17.
Conceptscanbeunderstoodasakindof“interface”forstaticpolymorphism.
Inourexample,thismightlookasfollows:Clickheretoviewcodeimage
poly/conceptsreq.hpp
#include"coord.hpp"
template<typenameT>
conceptGeoObj=requires(Tx){
{x.draw()}->void;
{x.center_of_gravity()}->Coord;
…
};
Here,weusethekeywordconcepttodefineaconceptGeoObj,which
constrainsatypetohavecallablemembersdraw()and
center_of_gravity()withappropriateresulttypes.
Now,wecanrewritesomeofourexampletemplatestoincludearequires
clausethatconstrainsthetemplateparameterswiththeGeoObjconcept:Click
heretoviewcodeimage
poly/conceptspoly.hpp
#include"conceptsreq.hpp"
#include<vector>
//drawanyGeoObj
template<typenameT>
requiresGeoObj<T>
voidmyDraw(Tconst&obj)
{
obj.draw();//calldraw()accordingtotypeofobject
}
//computedistanceofcenterofgravitybetweentwoGeoObjs
template<typenameT1,typenameT2>
requiresGeoObj<T1>&&GeoObj<T2>
Coorddistance(T1const&x1,T2const&x2)
{
Coordc=x1.center_of_gravity()-x2.center_of_gravity();
returnc.abs();//returncoordinatesasabsolutevalues
}
//drawhomogeneouscollectionofGeoObjs
template<typenameT>
requiresGeoObj<T>
voiddrawElems(std::vector<T>const&elems)
{
for(std::size_typei=0;i<elems.size();++i){
elems[i].draw();//calldraw()accordingtotypeofelement
}
}
Thisapproachisstillnoninvasivewithrespecttothetypesthatcanparticipatein
the(static)polymorphicbehavior:Clickheretoviewcodeimage
//concretegeometricobjectclassCircle
//-notderivedfromanyclassorimplementinganyinterface
classCircle{
public:
voiddraw()const;
Coordcenter_of_gravity()const;
…
};
Thatis,suchtypesarestilldefinedwithoutanyspecificbaseclassor
requirementsclauseandcanstillbefundamentaldatatypesortypesfrom
independentframeworks.
AppendixEincludesamoredetaileddiscussionofconceptsforC++,asthey
areexpectedforthenextC++standard.
18.5NewFormsofDesignPatterns
TheavailabilityofstaticpolymorphisminC++leadstonewwaysof
implementingclassicdesignpatterns.Take,forexample,theBridgepattern,
whichplaysamajorroleinmanyC++programs.OnegoalofusingtheBridge
patternistoswitchbetweendifferentimplementationsofaninterface.
Figure18.3.Bridgepatternimplementedusinginheritance
Accordingto[DesignPatternsGoF],thisisusuallydoneusinganinterfaceclass
thatembedsapointertorefertotheactualimplementationanddelegatingall
callsthroughthispointer(seeFigure18.3).
However,ifthetypeoftheimplementationisknownatcompiletime,we
exploitthepoweroftemplatesinstead(seeFigure18.4).Thisleadstomoretype
safety(inpart,byavoidingpointerconversions)andbetterperformance.
Figure18.4.Bridgepatternimplementedusingtemplates
18.6GenericProgramming
Staticpolymorphismleadstotheconceptofgenericprogramming.However,
thereisnosingleagreed-ondefinitionofgenericprogramming(justasthereis
nosingleagreed-ondefinitionofobject-orientedprogramming).Accordingto
[CzarneckiEiseneckerGenProg],definitionsgofromprogrammingwithgeneric
parameterstofindingthemostabstractrepresentationofefficientalgorithms.
Thebooksummarizes:Genericprogrammingisasubdisciplineofcomputer
sciencethatdealswithfindingabstractrepresentationsofefficientalgorithms,
datastructures,andothersoftwareconcepts,andwiththeirsystematic
organization.…Genericprogrammingfocusesonrepresentingfamiliesof
domainconcepts.(pp.169-170)InthecontextofC++,genericprogrammingis
sometimesdefinedasprogrammingwithtemplates(whereasobject-oriented
programmingisthoughtofasprogrammingwithvirtualfunctions).Inthissense,
justaboutanyuseofC++templatescouldbethoughtofasaninstanceof
genericprogramming.However,practitionersoftenthinkofgeneric
programmingashavinganadditionalessentialingredient:Templateshavetobe
designedinaframeworkforthepurposeofenablingamultitudeofuseful
combinations.
ByfarthemostsignificantcontributioninthisareaistheStandardTemplate
Library(STL),whichlaterwasadaptedandincorporatedintotheC++standard
library).TheSTLisaframeworkthatprovidesanumberofusefuloperations,
calledalgorithms,foranumberoflineardatastructuresforcollectionsof
objects,calledcontainers.Bothalgorithmsandcontainersaretemplates.
However,thekeyisthatthealgorithmsarenotmemberfunctionsofthe
containers.Instead,thealgorithmsarewritteninagenericwaysothattheycan
beusedbyanycontainer(andlinearcollectionofelements).Todothis,the
designersofSTLidentifiedanabstractconceptofiteratorsthatcanbeprovided
foranykindoflinearcollection.Essentially,thecollection-specificaspectsof
containeroperationshavebeenfactoredoutintotheiterators’functionality.
Asaconsequence,wecanimplementanoperationsuchascomputingthe
maximumvalueinasequencewithoutknowingthedetailsofhowvaluesare
storedinthatsequence:Clickheretoviewcodeimage
template<typenameIterator>
Iteratormax_element(Iteratorbeg,//referstostartofcollection
Iteratorend)//referstoendofcollection
{
//useonlycertainIteratoroperationstotraverseallelements
//ofthecollectiontofindtheelementwiththemaximumvalue
//andreturnitspositionasIterator
…
}
Insteadofprovidingallusefuloperationssuchasmax_element()byevery
linearcontainer,thecontainerhastoprovideonlyaniteratortypetotraversethe
sequenceofvaluesitcontainsandmemberfunctionstocreatesuchiterators:
Clickheretoviewcodeimage
namespacestd{
template<typenameT,…>
classvector{
public:
usingconst_iterator=…;//implementation-specificiterator
…//typeforconstantvectors
const_iteratorbegin()const;//iteratorforstartofcollection
const_iteratorend()const;//iteratorforendofcollection
…
};
template<typenameT,…>
classlist{
public:
usingconst_iterator=…;//implementation-specificiterator
…//typeforconstantlists
const_iteratorbegin()const;//iteratorforstartofcollection
const_iteratorend()const;//iteratorforendofcollection
…
};
}
Now,wecanfindthemaximumofanycollectionbycallingthegeneric
max_element()operationwiththebeginningandendofthecollectionas
arguments(specialhandlingofemptycollectionsisomitted):Clickheretoview
codeimage
poly/printmax.cpp
#include<vector>
#include<list>
#include<algorithm>
#include<iostream>
#include"MyClass.hpp"
template<typenameT>
voidprintMax(Tconst&coll)
{
//computepositionofmaximumvalue
autopos=std::max_element(coll.begin(),coll.end());
//printvalueofmaximumelementofcoll(ifany):
if(pos!=coll.end()){
std::cout<<*pos<<’\n’;
}
else{
std::cout<<"empty"<<’\n’;
}
}
intmain()
{
std::vector<MyClass>c1;
std::list<MyClass>c2;
…
printMax(c1);
printMax(c2);
}
Byparameterizingitsoperationsintermsoftheseiterators,theSTLavoidsan
explosioninthenumberofoperationdefinitions.Insteadofimplementingeach
operationforeverycontainer,weimplementthealgorithmoncesothatitcanbe
usedforeverycontainer.Thegenericglueistheiterators,whichareprovidedby
thecontainersandusedbythealgorithms.Thisworksbecauseiteratorshavea
certaininterfacethatisprovidedbythecontainersandusedbythealgorithms.
Thisinterfaceisusuallycalledaconcept,whichdenotesasetofconstraintsthat
atemplatehastofulfilltofitintothisframework.Inaddition,thisconceptis
openforadditionaloperationsanddatastructures.
You’llrecallthatwedescribedaconceptslanguagefeatureearlierinSection
18.4onpage377(andinmoredetailinAppendixE),andindeed,thelanguage
featuremapsexactlyontothenotionhere.Infact,thetermconceptinthis
contextwasfirstintroducedbythedesignersoftheSTLtoformalizetheirwork.
Soonthereafter,workcommencedtotrytomakethesenotionsexplicitinour
templates.
Theforthcominglanguagefeaturewillhelpustospecifyanddoublecheck
requirementsoniterators(sincetherearedifferentiteratorcategories,suchas
forwardandbidirectionaliterators,multiplecorrespondingconceptswouldbe
involved;seeSectionE.3.1onpage744).Intoday’sC++,however,theconcepts
aremostlyimplicitinthespecificationsofourgenericlibraries(andthestandard
C++libraryinparticular).Somefeaturesandtechniques(e.g.,
static_assertandSFINAE)dopermitsomeamountofautomated
checking,fortunately.
Inprinciple,functionalitysuchasanSTL-likeapproachcouldbe
implementedwithdynamicpolymorphism.Inpractice,however,itwouldbeof
limitedusebecausetheiteratorconceptistoolightweightcomparedwiththe
virtualfunctioncallmechanism.Addinganinterfacelayerbasedonvirtual
functionswouldmostlikelyslowdownouroperationsbyanorderofmagnitude
(ormore).
Genericprogrammingispracticalpreciselybecauseitreliesonstatic
polymorphism,whichresolvesinterfacesatcompiletime.Ontheotherhand,the
requirementthattheinterfacesberesolvedatcompiletimealsocallsfornew
designprinciplesthatdifferinmanywaysfromobject-orienteddesign
principles.Manyofthemostimportantofthesegenericdesignprinciplesare
describedintheremainderofthisbook.Additionally,AppendixEdelvesdeeper
intogenericprogrammingasadevelopmentparadigmbydescribingdirect
languagesupportforthenotionofconcepts.
18.7Afternotes
Containertypeswereaprimarymotivationfortheintroductionoftemplatesinto
theC++programminglanguage.Priortotemplates,polymorphichierarchies
wereapopularapproachtocontainers.ApopularexamplewastheNational
InstitutesofHealthClassLibrary(NIHCL),whichtoalargeextenttranslatedthe
containerclasshierarchyofSmalltalk(seeFigure18.5).
Figure18.5.ClasshierarchyoftheNIHCL
MuchliketheC++standardlibrary,theNIHCLsupportedarichvarietyof
containersaswellasiterators.However,theimplementationfollowedthe
Smalltalkstyleofdynamicpolymorphism:Iteratorsusedtheabstractbase
classCollectiontooperateondifferenttypesofcollections:Bagc1;
Setc2;
…
Iteratori1(c1);
Iteratori2(c2);
…
Unfortunately,thepriceofthisapproachwashighintermsofbothrunningtime
andmemoryusage.Runningtimewastypicallyordersofmagnitudeworsethan
equivalentcodeusingtheC++standardlibrarybecausemostoperationsended
uprequiringavirtualcall(whereasintheC++standardlibrary,manyoperations
areinlined,andnovirtualfunctionsareinvolvediniteratorandcontainer
interfaces).Furthermore,because(unlikeSmalltalk)theinterfaceswere
bounded,built-intypeshadtobewrappedinlargerpolymorphicclasses(such
wrapperswereprovidedbytheNIHCL),whichinturncouldleadtodramatic
increasesinstoragerequirements.
Evenintoday’sageoftemplates,manyprojectsstillmakesuboptimalchoices
intheirapproachtopolymorphism.Clearly,therearemanysituationsinwhich
dynamicpolymorphismistherightchoice.Heterogeneousiterationsarean
example.However,inthesamevein,manyprogrammingtasksarenaturallyand
efficientlysolvedusingtemplates,andhomogeneouscontainersareanexample
ofthis.
Staticpolymorphismlendsitselfwelltocodefundamentalcomputing
structures.Incontrast,theneedtochooseacommonbasetypeimpliesthata
dynamicpolymorphiclibrarywillnormallyhavetomakedomain-specific
choices.It’snosurprisethenthattheSTLpartoftheC++standardlibrarynever
includedpolymorphiccontainers,butitcontainsarichsetofcontainersand
iteratorsthatusestaticpolymorphism(asdemonstratedinSection18.6onpage
380).
MediumandlargeC++programstypicallyneedtohandlebothkindsof
polymorphismdiscussedinthischapter.Insomesituations,itmayevenbe
necessarytocombinethemveryintimately.Inmanycases,theoptimaldesign
choicesareclearinlightofourdiscussion,butspendingsometimethinking
aboutlong-term,potentialevolutionsalmostalwayspaysoff.
1Polymorphismliterallyreferstotheconditionofhavingmanyformsorshapes
(fromtheGreekpolymorphos).
2Strictlyspeaking,macroscanalsobethoughtofasanearlyformofstatic
polymorphism.However,theyareleftoutofconsiderationbecausetheyare
mostlyorthogonaltotheotherlanguagemechanisms.
3Thatis,theencodingofpolymorphicbaseclasssubobjectsincludessome
(mostlyhidden)datathatenablesthisrun-timedispatch.
4Foradetaileddiscussionofpolymorphismterminology,seealsoSections6.5
to6.7of[CzarneckiEiseneckerGenProg].
5GCC7,forexample,providestheoption-fconcepts.
Chapter19
ImplementingTraits
Templatesenableustoparameterizeclassesandfunctionsforvarioustypes.It
couldbetemptingtointroduceasmanytemplateparametersaspossibleto
enablethecustomizationofeveryaspectofatypeoralgorithm.Inthisway,our
“templatized”componentscouldbeinstantiatedtomeettheexactneedsofclient
code.However,fromapracticalpointofview,itisrarelydesirabletointroduce
dozensoftemplateparametersformaximalparameterization.Havingtospecify
allthecorrespondingargumentsintheclientcodeisoverlytedious,andeach
additionaltemplateparametercomplicatesthecontractbetweenthecomponent
anditsclient.
Fortunately,itturnsoutthatmostoftheextraparameterswewouldintroduce
havereasonabledefaultvalues.Insomecases,theextraparametersareentirely
determinedbyafewmainparameters,andwe’llseethatsuchextraparameters
canbeomittedaltogether.Otherparameterscanbegivendefaultvaluesthat
dependonthemainparametersandwillmeettheneedsofmostsituations,but
thedefaultvaluesmustoccasionallybeoverridden(forspecialapplications).Yet
otherparametersareunrelatedtothemainparameters:Inasense,theyare
themselvesmainparametersexceptthatthereexistdefaultvaluesthatalmost
alwaysfitthebill.
Traits(ortraitstemplates)areC++programmingdevicesthatgreatlyfacilitate
themanagementofthesortofextraparametersthatcomeupinthedesignof
industrial-strengthtemplates.Inthischapter,weshowanumberofsituationsin
whichtheyproveusefulanddemonstratevarioustechniquesthatwillenableyou
towriterobustandpowerfuldevicesofyourown.
MostofthetraitspresentedhereareavailableintheC++standardlibraryin
someform.However,forclarity’ssake,weoftenpresentsimplified
implementationsthatomitsomedetailspresentinindustrial-strength
implementations(likethoseofthestandardlibrary).Forthisreason,wealsouse
ourownnamingscheme,which,however,mapseasilytothestandardtraits.
19.1AnExample:AccumulatingaSequence
Computingthesumofasequenceofvaluesisafairlycommoncomputational
task.However,thisseeminglysimpleproblemprovidesuswithanexcellent
exampletointroducevariouslevelsatwhichpolicyclassesandtraitscanhelp.
19.1.1FixedTraits
Let’sfirstassumethatthevaluesofthesumwewanttocomputearestoredinan
array,andwearegivenapointertothefirstelementtobeaccumulatedanda
pointeronepastthelastelementtobeaccumulated.Becausethisbookisabout
templates,wewishtowriteatemplatethatwillworkformanytypes.The
followingmayseemstraightforwardbynow:1
Clickheretoviewcodeimage
traits/accum1.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
template<typenameT>
Taccum(Tconst*beg,Tconst*end)
{
Ttotal{};//assumethisactuallycreatesazerovalue
while(beg!=end){
total+=*beg;
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Theonlyslightlysubtledecisionhereishowtocreateazerovalueofthecorrect
typetostartoursummation.Weusevalueinitialization(withthe{…}notation)
hereasintroducedinSection5.2onpage68.Itmeansthatthelocalobject
totalisinitializedeitherbyitsdefaultconstructororbyzero(whichmeans
nullptrforpointersandfalseforBooleanvalues).
Tomotivateourfirsttraitstemplate,considerthefollowingcodethatmakes
useofouraccum():Clickheretoviewcodeimage
traits/accum1.cpp
#include"accum1.hpp"
#include<iostream>
intmain()
{
//createarrayof5integervalues
intnum[]={1,2,3,4,5};
//printaveragevalue
std::cout<<"theaveragevalueoftheintegervaluesis"
<<accum(num,num+5)/5
<<’\n’;
//createarrayofcharactervalues
charname[]="templates";
intlength=sizeof(name)-1;
//(tryto)printaveragecharactervalue
std::cout<<"theaveragevalueofthecharactersin\""
<<name<<"\"is"
<<accum(name,name+length)/length
<<’\n’;
}
Inthefirsthalfoftheprogram,weuseaccum()tosumfiveintegervalues:
Clickheretoviewcodeimage
intnum[]={1,2,3,4,5};
…
accum(num0,num+5)
Theaverageintegervalueisthenobtainedbysimplydividingtheresultingsum
bythenumberofvaluesinthearray.
Thesecondhalfoftheprogramattemptstodothesameforalllettersinthe
wordtemplates(providedthecharactersfromatozformacontiguous
sequenceintheactualcharacterset,whichistrueforASCIIbutnotfor
EBCDIC).2Theresultshouldpresumablyliebetweenthevalueofaandthe
valueofz.Onmostplatformstoday,thesevaluesaredeterminedbytheASCII
codes:aisencodedas97andzisencodedas122.Hence,wemayexpecta
resultbetween97and122.However,onourplatform,theoutputoftheprogram
isasfollows:Clickheretoviewcodeimage
theaveragevalueoftheintegervaluesis3
theaveragevalueofthecharactersin"templates"is-5
Theproblemhereisthatourtemplatewasinstantiatedforthetypechar,which
turnsouttobetoosmallarangefortheaccumulationofevenrelativelysmall
values.Clearly,wecouldresolvethisbyintroducinganadditionaltemplate
parameterAccTthatdescribesthetypeusedforthevariabletotal(andhence
thereturntype).However,thiswouldputanextraburdenonallusersofour
template:Theywouldhavetospecifyanextratypeineveryinvocationofour
template.Inourexample,wemaythereforeneedtowritethefollowing:
accum<int>(name,name+5)Thisisnotanexcessiveconstraint,butitcanbe
avoided.
Analternativeapproachtotheextraparameteristocreateanassociation
betweeneachtypeTforwhichaccum()iscalledandthecorrespondingtype
thatshouldbeusedtoholdtheaccumulatedvalue.Thisassociationcouldbe
consideredcharacteristicofthetypeT,andthereforethetypeinwhichthesum
iscomputedissometimescalledatraitofT.Asisturnsout,ourassociationcan
beencodedasspecializationsofatemplate:Clickheretoviewcodeimage
traits/accumtraits2.hpp
template<typenameT>
structAccumulationTraits;
template<>
structAccumulationTraits<char>{
usingAccT=int;
};
template<>
structAccumulationTraits<short>{
usingAccT=int;
};
template<>
structAccumulationTraits<int>{
usingAccT=long;
};
template<>
structAccumulationTraits<unsignedint>{
usingAccT=unsignedlong;
};
template<>
structAccumulationTraits<float>{
usingAccT=double;
};
ThetemplateAccumulationTraitsiscalledatraitstemplatebecauseit
holdsatraitofitsparametertype.(Ingeneral,therecouldbemorethanonetrait
andmorethanoneparameter.)Wechosenottoprovideagenericdefinitionof
thistemplatebecausethereisn’tagreatwaytoselectagoodaccumulationtype
whenwedon’tknowwhatthetypeis.However,anargumentcouldbemadethat
Titselfisoftenagoodcandidateforsuchatype(althoughclearlynotinour
earlierexample).
Withthisinmind,wecanrewriteouraccum()templateasfollows:3
Clickheretoviewcodeimage
traits/accum2.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include"accumtraits2.hpp"
template<typenameT>
autoaccum(Tconst*beg,Tconst*end)
{
//returntypeistraitsoftheelementtype
usingAccT=typenameAccumulationTraits<T>::AccT;
AccTtotal{};//assumethisactuallycreatesazerovalue
while(beg!=end){
total+=*beg;
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Theoutputofoursampleprogramthenbecomeswhatweexpect:Clickhereto
viewcodeimage
theaveragevalueoftheintegervaluesis3
theaveragevalueofthecharactersin"templates"is108
Overall,thechangesaren’tverydramaticconsideringthatwehaveaddedavery
usefulmechanismtocustomizeouralgorithm.Furthermore,ifnewtypesarise
forusewithaccum(),anappropriateAccTcanbeassociatedwithitsimplyby
declaringanadditionalexplicitspecializationoftheAccumulationTraits
template.Notethatthiscanbedoneforanytype:fundamentaltypes,typesthat
aredeclaredinotherlibraries,andsoforth.
19.1.2ValueTraits
Sofar,wehaveseenthattraitsrepresentadditionaltypeinformationrelatedtoa
given“main”type.Inthissection,weshowthatthisextrainformationneednot
belimitedtotypes.Constantsandotherclassesofvaluescanbeassociatedwith
atypeaswell.
Ouroriginalaccum()templateusesthedefaultconstructorofthereturn
valuetoinitializetheresultvariablewithwhatishopedtobeazero-likevalue:
Clickheretoviewcodeimage
AccTtotal{};//assumethisactuallycreatesazerovalue
…
returntotal;Clearly,thereisnoguaranteethatthisproducesa
goodvaluetostarttheaccumulationloop.TypeAccTmaynoteven
haveadefaultconstructor.
Again,traitscancometotherescue.Forourexample,wecanaddanewvalue
traittoourAccumulationTraits:traits/accumtraits3.hpp
template<typenameT>
structAccumulationTraits;
template<>
structAccumulationTraits<char>{
usingAccT=int;
staticAccTconstzero=0;
};
template<>
structAccumulationTraits<short>{
usingAccT=int;
staticAccTconstzero=0;
};
template<>
structAccumulationTraits<int>{
usingAccT=long;
staticAccTconstzero=0;
};
…
Inthiscase,ournewtraitprovidesanzeroelementasaconstantthatcanbe
evaluatedatcompiletime.Thus,accum()becomesthefollowing:Clickhere
toviewcodeimage
traits/accum3.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include"accumtraits3.hpp"
template<typenameT>
autoaccum(Tconst*beg,Tconst*end)
{
//returntypeistraitsoftheelementtype
usingAccT=typenameAccumulationTraits<T>::AccT;
AccTtotal=AccumulationTraits<T>::zero;//inittotalbytraitvalue
while(beg!=end){
total+=*beg;
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Inthiscode,theinitializationoftheaccumulationvariableremains
straightforward:Clickheretoviewcodeimage
AccTtotal=AccumulationTraits<T>::zero;
AdrawbackofthisformulationisthatC++allowsustoinitializeastatic
constantdatamemberinsideitsclassonlyifithasanintegralorenumeration
type.
constexprstaticdatamembersareslightlymoregeneral,allowingfloating-
pointtypesaswellasotherliteraltypes:Clickheretoviewcodeimage
template<>
structAccumulationTraits<float>{
usingAcct=float;
staticconstexprfloatzero=0.0f;
};
However,neitherconstnorconstexprpermitnonliteraltypestobe
initializedthisway.Forexample,auser-definedarbitrary-precisionBigInt
typemightnotbealiteraltype,becausetypicallyithastoallocatecomponents
ontheheap,whichusuallyprecludesitfrombeingaliteraltype,orjustbecause
therequiredconstructorisnotconstexpr.Thefollowingspecializationisthen
anerror:Clickheretoviewcodeimage
classBigInt{
BigInt(longlong);
…
};
…
template<>
structAccumulationTraits<BigInt>{
usingAccT=BigInt;
staticconstexprBigIntzero=BigInt{0};//ERROR:notaliteral
type
};
Thestraightforwardalternativeisnottodefinethevaluetraitinitsclass:Click
heretoviewcodeimage
template<>
structAccumulationTraits<BigInt>{
usingAccT=BigInt;
staticBigIntconstzero;//declarationonly
};
Theinitializerthengoesinasourcefileandlookssomethinglikethefollowing:
Clickheretoviewcodeimage
BigIntconstAccumulationTraits<BigInt>::zero=BigInt{0};Although
thisworks,ithasthedisadvantageofbeingmoreverbose(codemust
beaddedintwoplaces),anditispotentiallylessefficientbecause
compilersaretypicallyunawareofdefinitionsinotherfiles.
InC++17,thiscanbeaddressedusinginlinevariables:Clickheretoview
codeimage
template<>
structAccumulationTraits<BigInt>{
usingAccT=BigInt;
inlinestaticBigIntconstzero=BigInt{0};//OKsinceC++17
};
AnalternativethatworkspriortoC++17istouseinlinememberfunctionsfor
valuetraitsthatwon’talwaysyieldintegralvalues.Again,suchafunctioncanbe
declaredconstexprifitreturnsaliteraltype.4
Forexample,wecouldrewriteAccumulationTraitsasfollows:Click
heretoviewcodeimage
traits/accumtraits4.hpp
template<typenameT>
structAccumulationTraits;
template<>
structAccumulationTraits<char>{
usingAccT=int;
staticconstexprAccTzero(){
return0;
}
};
template<>
structAccumulationTraits<short>{
usingAccT=int;
staticconstexprAccTzero(){
return0;
}
};
template<>
structAccumulationTraits<int>{
usingAccT=long;
staticconstexprAccTzero(){
return0;
}
};
template<>
structAccumulationTraits<unsignedint>{
usingAccT=unsignedlong;
staticconstexprAccTzero(){
return0;
}
};
template<>
structAccumulationTraits<float>{
usingAccT=double;
staticconstexprAccTzero(){
return0;
}
};
…
andthenextendthesetraitsforourowntypes:Clickheretoviewcodeimage
traits/accumtraits4bigint.hpp
template<>
structAccumulationTraits<BigInt>{
usingAccT=BigInt;
staticBigIntzero(){
returnBigInt{0};
}
};
Fortheapplicationcode,theonlydifferenceistheuseoffunctioncallsyntax
(insteadoftheslightlymoreconciseaccesstoastaticdatamember):Clickhere
toviewcodeimage
AccTtotal=AccumulationTraits<T>::zero();//inittotalbytrait
function
Clearly,traitscanbemorethanjustextratypes.Inourexample,theycanbea
mechanismtoprovideallthenecessaryinformationthataccum()needsabout
theelementtypeforwhichitiscalled.Thisisthekeytothenotionoftraits:
Traitsprovideanavenuetoconfigureconcreteelements(mostlytypes)for
genericcomputations.
19.1.3ParameterizedTraits
Theuseoftraitsinaccum()intheprevioussectionsiscalledfixed,because
oncethedecoupledtraitisdefined,itcannotbereplacedinthealgorithm.There
maybecaseswhensuchoverridingisdesirable.Forexample,wemayhappento
knowthatasetoffloatvaluescansafelybesummedintoavariableofthe
sametype,anddoingsomaybuyussomeefficiency.
WecanaddressthisproblembyaddingatemplateparameterATforthetrait
itselfhavingadefaultvaluedeterminedbyourtraitstemplate:Clickhereto
viewcodeimage
traits/accum5.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include"accumtraits4.hpp"
template<typenameT,typenameAT=AccumulationTraits<T>>
autoaccum(Tconst*beg,Tconst*end)
{
typenameAT::AccTtotal=AT::zero();
while(beg!=end){
total+=*beg;
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Inthisway,manyuserscanomittheextratemplateargument,butthosewith
moreexceptionalneedscanspecifyanalternativetothepresetaccumulation
type.Presumably,mostusersofthistemplatewouldneverhavetoprovidethe
secondtemplateargumentexplicitlybecauseitcanbeconfiguredtoan
appropriatedefaultforeverytypededucedforthefirstargument.
19.2TraitsversusPoliciesandPolicyClasses
Sofarwehaveequatedaccumulationwithsummation.However,wecan
imagineotherkindsofaccumulations.Forexample,wecouldmultiplythe
sequenceofgivenvalues.Or,ifthevalueswerestrings,wecouldconcatenate
them.Evenfindingthemaximumvalueinasequencecouldbeformulatedasan
accumulationproblem.Inallthesealternatives,theonlyaccum()operation
thatneedstochangeistotal+=*beg.Thisoperationcanbecalledapolicy
ofouraccumulationprocess.
Hereisanexampleofhowwecouldintroducesuchapolicyinouraccum()
functiontemplate:Clickheretoviewcodeimage
traits/accum6.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include"accumtraits4.hpp"
#include"sumpolicy1.hpp"
template<typenameT,
typenamePolicy=SumPolicy,
typenameTraits=AccumulationTraits<T>>
autoaccum(Tconst*beg,Tconst*end)
{
usingAccT=typenameTraits::AccT;
AccTtotal=Traits::zero();
while(beg!=end){
Policy::accumulate(total,*beg);
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Inthisversionofaccum()SumPolicyisapolicyclass,thatis,aclassthat
implementsoneormorepoliciesforanalgorithmthroughanagreed-upon
interface.5SumPolicycouldbewrittenasfollows:Clickheretoviewcode
image
traits/sumpolicy1.hpp
#ifndefSUMPOLICY_HPP
#defineSUMPOLICY_HPP
classSumPolicy{
public:
template<typenameT1,typenameT2>
staticvoidaccumulate(T1&total,T2const&value){
total+=value;
}
};
#endif//SUMPOLICY_HPP
Byspecifyingadifferentpolicytoaccumulatevalues,wecancomputedifferent
things.Consider,forexample,thefollowingprogram,whichintendsto
determinetheproductofsomevalues:Clickheretoviewcodeimage
traits/accum6.cpp
#include"accum6.hpp"
#include<iostream>
classMultPolicy{
public:
template<typenameT1,typenameT2>
staticvoidaccumulate(T1&total,T2const&value){
total*=value;
}
};
intmain()
{
//createarrayof5integervalues
intnum[]={1,2,3,4,5};
//printproductofallvalues
std::cout<<"theproductoftheintegervaluesis"
<<accum<int,MultPolicy>(num,num+5)
<<’\n’;
}
However,theoutputofthisprogramisn’twhatwewouldlike:Clickhereto
viewcodeimage
theproductoftheintegervaluesis0
Theproblemhereiscausedbyourchoiceofinitialvalue:Although0works
wellforsummation,itdoesnotworkformultiplication(azeroinitialvalue
forcesazeroresultforaccumulatedmultiplications).Thisillustratesthat
differenttraitsandpoliciesmayinteract,underscoringtheimportanceofcareful
templatedesign.
Inthiscase,wemayrecognizethattheinitializationofanaccumulationloop
isapartoftheaccumulationpolicy.Thispolicymayormaynotmakeuseofthe
traitzero().Otheralternativesarenottobeforgotten:Noteverythingmustbe
solvedwithtraitsandpolicies.Forexample,thestd::accumulate()
functionoftheC++standardlibrarytakestheinitialvalueasathird(function
call)argument.
19.2.1TraitsandPolicies:What’stheDifference?
Areasonablecasecanbemadeinsupportofthefactthatpoliciesarejusta
specialcaseoftraits.Conversely,itcouldbeclaimedthattraitsjustencodea
policy.
TheNewShorterOxfordEnglishDictionary(see[NewShorterOED])hasthis
tosay:•traitn….adistinctivefeaturecharacterizingathing
•policyn….anycourseofactionadoptedasadvantageousorexpedient
Basedonthis,wetendtolimittheuseofthetermpolicyclassestoclassesthat
encodeanactionofsomesortthatislargelyorthogonalwithrespecttoanyother
templateargumentwithwhichitiscombined.ThisisinagreementwithAndrei
Alexandrescu’sstatementinhisbookModernC++Design(seepage8of
[AlexandrescuDesign]):6
Policieshavemuchincommonwithtraitsbutdifferinthattheyputless
emphasisontypeandmoreonbehavior.
NathanMyers,whointroducedthetraitstechnique,proposedthefollowingmore
open-endeddefinition(see[MyersTraits]):Traitsclass:Aclassusedinplaceof
templateparameters.Asaclass,itaggregatesusefultypesandconstants;asa
template,itprovidesanavenueforthat“extralevelofindirection”thatsolves
allsoftwareproblems.
Ingeneral,wethereforetendtousethefollowing(slightlyfuzzy)definitions:•
Traitsrepresentnaturaladditionalpropertiesofatemplateparameter.
•Policiesrepresentconfigurablebehaviorforgenericfunctionsandtypes(often
withsomecommonlyuseddefaults).
Toelaboratefurtheronthepossibledistinctionsbetweenthetwoconcepts,we
listthefollowingobservationsabouttraits:•Traitscanbeusefulasfixedtraits
(i.e.,withoutbeingpassedthroughtemplateparameters).
•Traitsparametersusuallyhaveverynaturaldefaultvalues(whicharerarely
overridden,orsimplycannotbeoverridden).
•Traitsparameterstendtodependtightlyononeormoremainparameters.
•Traitsmostlycombinetypesandconstantsratherthanmemberfunctions.
•Traitstendtobecollectedintraitstemplates.
Forpolicyclasses,wemakethefollowingobservations:
•Policyclassesdon’tcontributemuchiftheyaren’tpassedastemplate
parameters.
•Policyparametersneednothavedefaultvaluesandareoftenspecified
explicitly(althoughmanygenericcomponentsareconfiguredwithcommonly
useddefaultpolicies).
•Policyparametersaremostlyorthogonaltootherparametersofatemplate.
•Policyclassesmostlycombinememberfunctions.
•Policiescanbecollectedinplainclassesorinclasstemplates.
However,thereiscertainlyanindistinctlinebetweenbothterms.Forexample,
thecharactertraitsoftheC++standardlibraryalsodefinefunctionalbehavior
suchascomparing,moving,andfindingcharacters.Andbyreplacingthese
traits,wecandefinestringclassesthatbehaveinacase-insensitivemanner(see
Section13.2.15in[JosuttisStdLib])whilekeepingthesamecharactertype.Thus,
althoughtheyarecalledtraits,theyhavesomepropertiesassociatedwith
policies.
19.2.2MemberTemplatesversusTemplateTemplate
Parameters
Toimplementanaccumulationpolicy,wechosetoexpressSumPolicyand
MultPolicyasordinaryclasseswithamembertemplate.Analternative
consistsofdesigningthepolicyclassinterfaceusingclasstemplates,whichare
thenusedastemplatetemplatearguments(seeSection5.7onpage83and
Section12.2.3onpage187).Forexample,wecouldrewriteSumPolicyasa
template:Clickheretoviewcodeimage
traits/sumpolicy2.hpp
#ifndefSUMPOLICY_HPP
#defineSUMPOLICY_HPP
template<typenameT1,typenameT2>
classSumPolicy{
public:
staticvoidaccumulate(T1&total,T2const&value){
total+=value;
}
};
#endif//SUMPOLICY_HPP
TheinterfaceofAccumcanthenbeadaptedtouseatemplatetemplate
parameter:Clickheretoviewcodeimage
traits/accum7.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include"accumtraits4.hpp"
#include"sumpolicy2.hpp"
template<typenameT,
template<typename,typename>classPolicy=SumPolicy,
typenameTraits=AccumulationTraits<T>>
autoaccum(Tconst*beg,Tconst*end)
{
usingAccT=typenameTraits::AccT;
AccTtotal=Traits::zero();
while(beg!=end){
Policy<AccT,T>::accumulate(total,*beg);
++beg;
}
returntotal;
}
#endif//ACCUM_HPP
Thesametransformationcanbeappliedtothetraitsparameter.(Othervariations
onthisthemearepossible:Forexample,insteadofexplicitlypassingtheAccT
typetothepolicytype,itmaybeadvantageoustopasstheaccumulationtrait
andhavethepolicydeterminethetypeofitsresultfromatraitsparameter.)The
majoradvantageofaccessingpolicyclassesthroughtemplatetemplate
parametersisthatitmakesiteasiertohaveapolicyclasscarrywithitsomestate
information(i.e.,staticdatamembers)withatypethatdependsonthetemplate
parameters.(Inourfirstapproach,thestaticdatamemberswouldhavetobe
embeddedinamemberclasstemplate.)However,adownsideofthetemplate
templateparameterapproachisthatpolicyclassesmustnowbewrittenas
templates,withtheexactsetoftemplateparametersdefinedbyourinterface.
Thiscanmaketheexpressionofthetraitsthemselvesmoreverboseandless
naturalthanasimplenontemplateclass.
19.2.3CombiningMultiplePoliciesand/orTraits
Asourdevelopmenthasshown,traitsandpoliciesdon’tentirelydoawaywith
havingmultipletemplateparameters.However,theydoreducetheirnumberto
somethingmanageable.Aninterestingquestion,then,ishowtoordersuch
multipleparameters.
Asimplestrategyistoordertheparametersaccordingtotheincreasing
likelihoodoftheirdefaultvaluetobeselected.Typically,thiswouldmeanthat
thetraitsparametersfollowthepolicyparametersbecausethelatteraremore
oftenoverriddeninclientcode.(Theobservantreadermayhavenoticedthis
strategyinourdevelopment.)Ifwearewillingtoaddasignificantamountof
complexitytoourcode,analternativeexiststhatessentiallyallowsustospecify
thenondefaultargumentsinanyorder.RefertoSection21.4onpage512for
details.
19.2.4AccumulationwithGeneralIterators
Beforeweendthisintroductiontotraitsandpolicies,itisinstructivetolookat
oneversionofaccum()thataddsthecapabilitytohandlegeneralizediterators
(ratherthanjustpointers),asexpectedfromanindustrial-strengthgeneric
component.Interestingly,thisstillallowsustocallaccum()withpointers
becausetheC++standardlibraryprovidesiteratortraits.(Traitsare
everywhere!)Thus,wecouldhavedefinedourinitialversionofaccum()as
follows(ignoringourlaterrefinements):7
Clickheretoviewcodeimage
traits/accum0.hpp
#ifndefACCUM_HPP
#defineACCUM_HPP
#include<iterator>
template<typenameIter>
autoaccum(Iterstart,Iterend)
{
usingVT=typenamestd::iterator_traits<Iter>::value_type;
VTtotal{};//assumethisactuallycreatesazerovalue
while(start!=end){
total+=*start;
++start;
}
returntotal;
}
#endif//ACCUM_HPP
Thestd::iterator_traitsstructureencapsulatesalltherelevant
propertiesofiterators.Becauseapartialspecializationforpointersexists,these
traitsareconvenientlyusedwithanyordinarypointertypes.Hereishowa
standardlibraryimplementationmayimplementthissupport:Clickheretoview
codeimage
namespacestd{
template<typenameT>
structiterator_traits<T*>{
usingdifference_type=ptrdiff_t;
usingvalue_type=T;
usingpointer=T*;
usingreference=T&;
usingiterator_category=random_access_iterator_tag;
};
}
However,thereisnotypefortheaccumulationofvaluestowhichaniterator
refers;hencewestillneedtodesignourownAccumulationTraits.
19.3TypeFunctions
Theinitialtraitsexampledemonstratesthatwecandefinebehaviorthatdepends
ontypes.Traditionally,inCandC++,wedefinefunctionsthatcouldmore
specificallybecalledvaluefunctions:Theytakesomevaluesasargumentsand
returnanothervalueasaresult.Withtemplates,wecanadditionallydefinetype
functions:functionsthattakessometypeasargumentsandproduceatypeora
constantasaresult.
Averyusefulbuilt-intypefunctionissizeof,whichreturnsaconstant
describingthesize(inbytes)ofthegiventypeargument.Classtemplatescan
alsoserveastypefunctions.Theparametersofthetypefunctionarethetemplate
parameters,andtheresultisextractedasamembertypeormemberconstant.For
example,thesizeofoperatorcouldbegiventhefollowinginterface:Click
heretoviewcodeimage
traits/sizeof.cpp
#include<cstddef>
#include<iostream>
template<typenameT>
structTypeSize{
staticstd::size_tconstvalue=sizeof(T);
};
intmain()
{
std::cout<<"TypeSize<int>::value="
<<TypeSize<int>::value<<’\n’;
}
Thismaynotseemveryuseful,sincewehavethebuilt-insizeofoperator
available,butnotethatTypeSize<T>isatype,anditcanthereforebepassed
asaclasstemplateargumentitself.Alternatively,TypeSizeisatemplateand
canbepassedasatemplatetemplateargument.
Inwhatfollows,wedevelopafewmoregeneral-purposetypefunctionsthat
canbeusedastraitsclassesinthisway.
19.3.1ElementTypes
Assumethatwehaveanumberofcontainertemplates,suchas
std::vector<>andstd::list<>,aswellasbuilt-inarrays.Wewanta
typefunctionthat,givensuchacontainertype,producestheelementtype.This
canbeachievedusingpartialspecialization:Clickheretoviewcodeimage
traits/elementtype.hpp
#include<vector>
#include<list>
template<typenameT>
structElementT;//primarytemplate
template<typenameT>
structElementT<std::vector<T>>{//partialspecializationfor
std::vector
usingType=T;
};
template<typenameT>
structElementT<std::list<T>>{//partialspecializationforstd::list
usingType=T;
};
…
template<typenameT,std::size_tN>
structElementT<T[N]>{//partialspecializationforarraysofknown
bounds
usingType=T;
};
template<typenameT>
structElementT<T[]>{//partialspecializationforarraysofunknown
bounds
usingType=T;
};
…
Notethatweshouldprovidepartialspecializationsforallpossiblearraytypes
(seeSection5.4onpage71fordetails).
Wecanusethetypefunctionasfollows:
Clickheretoviewcodeimage
traits/elementtype.cpp
#include"elementtype.hpp"
#include<vector>
#include<iostream>
#include<typeinfo>
template<typenameT>
voidprintElementType(Tconst&c)
{
std::cout<<"Containerof"
<<typeid(typenameElementT<T>::Type).name()
<<"elements.\n";
}
intmain()
{
std::vector<bool>s;
printElementType(s);
intarr[42];
printElementType(arr);
}
Theuseofpartialspecializationallowsustoimplementthetypefunction
withoutrequiringthecontainertypestoknowaboutit.Inmanycases,however,
thetypefunctionisdesignedalongwiththeapplicabletypes,andthe
implementationcanbesimplified.Forexample,ifthecontainertypesdefinea
membertypevalue_type(asthestandardcontainersdo),wecanwritethe
following:Clickheretoviewcodeimage
template<typenameC>
structElementT{
usingType=typenameC::value_type;
};
Thiscanbethedefaultimplementation,anditdoesnotexcludespecializations
forcontainertypesthatdonothaveanappropriatemembertypevalue_type
defined.
Nonetheless,itisusuallyadvisabletoprovidemembertypedefinitionsfor
classtemplatetypeparameterssothattheycanbeaccessedmoreeasilyin
genericcode(likethestandardcontainertemplatesdo).Thefollowingsketches
theidea:Clickheretoviewcodeimage
template<typenameT1,typenameT2,…>
classX{
public:
using…=T1;
using…=T2;
…
};
Howisatypefunctionuseful?Itallowsustoparameterizeatemplateinterms
ofacontainertypewithoutalsorequiringparametersfortheelementtypeand
othercharacteristics.Forexample,insteadofClickheretoviewcodeimage
template<typenameT,typenameC>
TsumOfElements(Cconst&c);whichrequiressyntaxlike
sumOfElements<int>(list)tospecifytheelementtypeexplicitly,we
candeclareClickheretoviewcodeimage
template<typenameC>
typenameElementT<C>::TypesumOfElements(Cconst&c);wherethe
elementtypeisdeterminedfromthetypefunction.
Observehowthetraitsareimplementedasanextensiontoexistingtypes;that
is,wecandefinethesetypefunctionsevenforfundamentaltypesandtypesof
closedlibraries.
Inthiscase,thetypeElementTiscalledatraitsclassbecauseitisusedto
accessatraitofthegivencontainertypeC(ingeneral,morethanonetraitcanbe
collectedinsuchaclass).Thus,traitsclassesarenotlimitedtodescribing
characteristicsofcontainerparametersbutofanykindof“mainparameters.”
Asaconvenience,wecancreateanaliastemplatefortypefunctions.For
example,wecouldintroduceClickheretoviewcodeimage
template<typenameT>
usingElementType=typenameElementT<T>::Type;whichallowsusto
furthersimplifythedeclarationofsumOfElementsabovetoClickhere
toviewcodeimage
template<typenameC>
ElementType<C>sumOfElements(Cconst&c);
19.3.2TransformationTraits
Inadditiontoprovidingaccesstoparticularaspectsofamainparametertype,
traitscanalsoperformtransformationsontypes,suchasaddingorremoving
referencesorconstandvolatilequalifiers.
RemovingReferences
Forexample,wecanimplementaRemoveReferenceTtraitthatturns
referencetypesintotheirunderlyingobjectorfunctiontypes,andleaves
nonreferencetypesalone:Clickheretoviewcodeimage
traits/removereference.hpp
template<typenameT>
structRemoveReferenceT{
usingType=T;
};
template<typenameT>
structRemoveReferenceT<T&>{
usingType=T;
};
template<typenameT>
structRemoveReferenceT<T&&>{
usingType=T;
};
Again,aconveniencealiastemplatemakestheusagesimpler:Clickheretoview
codeimage
template<typenameT>
usingRemoveReference=typenameRemoveReference<T>::Type;Removing
thereferencefromatypeistypicallyusefulwhenthetypewas
derivedusingaconstructthatsometimesproducesreferencetypes,
suchasthespecialdeductionruleforfunctionparametersoftype
T&&discussedinSection15.6onpage277.
TheC++standardlibraryprovidesacorrespondingtypetrait
std::remove_reference<>,whichisdescribedinSectionD.4onpage
729.
AddingReferences
Similarly,wecantakeanexistingtypeandcreateanlvalueorrvaluereference
fromit(alongwiththeusualconveniencealiastemplates):Clickheretoview
codeimage
traits/addreference.hpp
template<typenameT>
structAddLValueReferenceT{
usingType=T&;
};
template<typenameT>
usingAddLValueReference=typenameAddLValueReferenceT<T>::Type;
template<typenameT>
structAddRValueReferenceT{
usingType=T&&;
};
template<typenameT>
usingAddRValueReference=typenameAddRValueReferenceT<T>::Type;The
rulesofreferencecollapsing(Section15.6onpage277)applyhere.
Forexample,callingAddLValueReference<int&&>producestypeint&
(thereisthereforenoneedtoimplementthemmanuallyviapartial
specialization).
IfweleaveAddLValueReferenceTandAddRValueReferenceTas
theyareanddonotintroducespecializationsofthem,thentheconvenience
aliasescanactuallybesimplifiedtoClickheretoviewcodeimage
template<typenameT>
usingAddLValueReferenceT=T&;
template<typenameT>
usingAddRValueReferenceT=T&&;whichcanbeinstantiatedwithout
instantiatingaclasstemplate(andisthereforealighter-weight
process).However,thisisrisky,aswemaywellwanttospecialize
thesetemplateforspecialcases.Forexample,aswrittenabove,we
cannotusevoidasatemplateargumentforthesetemplates.Afew
explicitspecializationscantakecareofthat:Clickheretoview
codeimage
template<>
structAddLValueReferenceT<void>{
usingType=void;
};
template<>
structAddLValueReferenceT<voidconst>{
usingType=voidconst;
};
template<>
structAddLValueReferenceT<voidvolatile>{
usingType=voidvolatile;
};
template<>
structAddLValueReferenceT<voidconstvolatile>{
usingType=voidconstvolatile;
};
andsimilarlyforAddRValueReferenceT.
Withthatinplace,theconveniencealiastemplatemustbeformulatedinterms
oftheclasstemplatestoensurethatthespecializationsarepickedupalso(since
aliastemplatescannotbespecialized).
TheC++standardlibraryprovidescorrespondingtypetraits
std::add_lvalue_reference<>and
std::add_rvalue_reference<>,whicharedescribedinSectionD.4on
page729.Thestandardtemplatesincludethespecializationsforvoidtypes.
RemovingQualifiers
Transformationtraitscanbreakdownorintroduceanykindofcompoundtype,
notjustreferences.Forexample,wecanremoveaconstqualifierifpresent:
Clickheretoviewcodeimage
traits/removeconst.hpp
template<typenameT>
structRemoveConstT{
usingType=T;
};
template<typenameT>
structRemoveConstT<Tconst>{
usingType=T;
};
template<typenameT>
usingRemoveConst=typenameRemoveConstT<T>::Type;Moreover,
transformationtraitscanbecomposed,suchascreatingaRemoveCVT
traitthatremovesbothconstandvolatile:Clickheretoviewcode
image
traits/removecv.hpp
#include"removeconst.hpp"
#include"removevolatile.hpp"
template<typenameT>
structRemoveCVT:RemoveConstT<typenameRemoveVolatileT<T>::Type>{
};
template<typenameT>
usingRemoveCV=typenameRemoveCVT<T>::Type;Therearetwothingsto
notewiththedefinitionofRemoveCVT.First,itismakinguseofboth
RemoveConstTandtherelatedRemoveVolatileT,firstremovingthe
volatile(ifpresent)andthenpassingtheresultingtypeto
RemoveConstT.8Second,itisusingmetafunctionforwardingtoinherit
theTypememberfromRemoveConstTratherthandeclaringitsownType
memberthatisidenticaltotheoneintheRemoveConstT
specialization.Here,metafunctionforwardingisusedsimplytoreduce
theamountoftypinginthedefinitionofRemoveCVT.However,
metafunctionforwardingisalsousefulwhenthemetafunctionisnot
definedforallinputs,atechniquethatwillbediscussedfurtherin
Section19.4onpage416.
TheconveniencealiastemplateRemoveCVcouldbesimplifiedtoClickhere
toviewcodeimage
template<typenameT>
usingRemoveCV=RemoveConst<RemoveVolatile<T>>;Again,thisworks
onlyifRemoveCVTisnotspecialized.Unlikeinthecaseof
AddLValueReferenceandAddRValueReference,wecannotthinkofany
reasonsforsuchspecializations.
TheC++standardlibraryalsoprovidescorrespondingtypetraits
std::remove_volatile<>,std::remove_const<>,and
std::remove_cv<>,whicharedescribedinSectionD.4onpage728.
Decay
Toroundoutourdiscussionoftransformationtraits,wedevelopatraitthat
mimicstypeconversionswhenpassingargumentstoparametersbyvalue.
DerivedfromC,thismeansthattheargumentsdecay(turningarraytypesinto
pointersandfunctiontypesintopointer-to-functiontypes;seeSection7.4on
page115andSection11.1.1onpage159)anddeleteanytop-levelconst,
volatile,orreferencequalifiers(becausetop-leveltypequalifierson
parametertypesareignoredwhenresolvingafunctioncall).
Theeffectofthispass-by-valuecanbeseeninthefollowingprogram,which
printstheactualparametertypeproducedafterthecompilerdecaysthespecified
type:Clickheretoviewcodeimage
traits/passbyvalue.cpp
#include<iostream>
#include<typeinfo>
#include<type_traits>
template<typenameT>
voidf(T)
{
}
template<typenameA>
voidprintParameterType(void(*)(A))
{
std::cout<<"Parametertype:"<<typeid(A).name()<<’\n’;
std::cout<<"-isint:"<<std::is_same<A,int>::value<<’\n’;
std::cout<<"-isconst:"<<std::is_const<A>::value<<’\n’;
std::cout<<"-ispointer:"<<std::is_pointer<A>::value<<’\n’;
}
intmain()
{
printParameterType(&f<int>);
printParameterType(&f<intconst>);
printParameterType(&f<int[7]>);
printParameterType(&f<int(int)>);
}
Intheoutputoftheprogram,theintparameterhasbeenleftunchanged,butthe
intconst,int[7],andint(int)parametershavedecayedtoint,
int*,andint(*)(int),respectively.
Wecanimplementatraitthatproducesthesametypeconversionofpassing
byvalue.TomatchtotheC++standardlibrarytraitstd::decay,wenameit
DecayT.9Itsimplementationcombinesseveralofthetechniquesdescribed
aboveFirst,wedefinethenonarray,nonfunctioncase,whichsimplydeletesany
constandvolatilequalifiers:Clickheretoviewcodeimage
template<typenameT>
structDecayT:RemoveCVT<T>{
};
Next,wehandlethearray-to-pointerdecay,whichrequiresustorecognizeany
arraytypes(withorwithoutabound)usingpartialspecialization:Clickhereto
viewcodeimage
template<typenameT>
structDecayT<T[]>{
usingType=T*;
};
template<typenameT,std::size_tN>
structDecayT<T[N]>{
usingType=T*;
};
Finally,wehandlethefunction-to-pointerdecay,whichhastomatchany
functiontype,regardlessofthereturntypeorthenumberofparametertypes.For
this,weemployvariadictemplates:Clickheretoviewcodeimage
template<typenameR,typename…Args>
structDecayT<R(Args…)>{
usingType=R(*)(Args…);
};
template<typenameR,typename…Args>
structDecayT<R(Args…,…)>{
usingType=R(*)(Args…,…);
};
NotethatthesecondpartialspecializationmatchesanyfunctiontypethatusesC-
stylevarargs.10Together,theprimaryDecayTtemplateanditsfourpartial
specializationimplementparametertypedecay,asillustratedbythisexample
program:Clickheretoviewcodeimage
traits/decay.cpp
#include<iostream>
#include<typeinfo>
#include<type_traits>
#include"decay.hpp"
template<typenameT>
voidprintDecayedType()
{
usingA=typenameDecayT<T>::Type;
std::cout<<"Parametertype:"<<typeid(A).name()<<’\n’;
std::cout<<"-isint:"<<std::is_same<A,int>::value<<’\n’;
std::cout<<"-isconst:"<<std::is_const<A>::value<<’\n’;
std::cout<<"-ispointer:"<<std::is_pointer<A>::value<<’\n’;
}
intmain()
{
printDecayedType<int>();
printDecayedType<intconst>();
printDecayedType<int[7]>();
printDecayedType<int(int)>();
}
Asusual,weprovideaconveniencealiastemplate:Clickheretoviewcode
image
templatetypenameT>
usingDecay=typenameDecayT<T>::Type;Aswritten,theC++standard
libraryalsoprovidesacorrespondingtypetraitsstd::decay<>,which
isdescribedinSectionD.4onpage731.
19.3.3PredicateTraits
Sofarwehavestudiedanddevelopedtypefunctionsofasingletype:Givenone
type,provideotherrelatedtypesorconstants.Ingeneral,however,wecan
developtypefunctionsthatdependonmultiplearguments.Thisalsoleadstoa
specialformoftypetraits,typepredicates(typefunctionsyieldingaBoolean
value).
IsSameT
TheIsSameTtraityieldswhethertwotypesareequal:Clickheretoviewcode
image
traits/issame0.hpp
template<typenameT1,typenameT2>
structIsSameT{
staticconstexprboolvalue=false;
};
template<typenameT>
structIsSameT<T,T>{
staticconstexprboolvalue=true;
};
Heretheprimarytemplatedefinesthat,ingeneral,twodifferenttypespassedas
templateargumentsdiffer.sothatthevaluememberisfalse.However,
usingpartialspecialization,whenwehavethespecialcasethatthetwopassed
typesarethesame,valueistrue.
Forexample,thefollowingexpressioncheckswhetherapassedtemplate
parametersisaninteger:Clickheretoviewcodeimage
if(IsSameT<T,int>::value)…
Fortraitsthatproduceaconstantvalue,wecannotprovideanaliastemplate,but
wecanprovideaconstexprvariabletemplatethatfulfillsthesamerole:
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
constexprboolisSame=IsSameT<T1,T2>::value;TheC++standard
libraryprovidesacorrespondingtypetraitstd::is_same<>,whichis
describedinSectionD.3.3onpage726
true_typeandfalse_type
WecansignificantlyimprovethedefinitionofIsSameTbyprovidingdifferent
typesforthepossibletwooutcomes,trueandfalse.Infact,ifwedeclarea
classtemplateBoolConstantwiththetwopossibleinstantiationsTrueType
andFalseType:Clickheretoviewcodeimage
traits/boolconstant.hpp
template<boolval>
structBoolConstant{
usingType=BoolConstant<val>;
staticconstexprboolvalue=val;
};
usingTrueType=BoolConstant<true>;
usingFalseType=BoolConstant<false>;wecandefineIsSameTsothat,
dependingonwhetherthetwotypesmatch,itderivesfromTrueTypeor
FalseType:Clickheretoviewcodeimage
traits/issame.hpp
#include"boolconstant.hpp"
template<typenameT1,typenameT2>
structIsSameT:FalseType
{
};
template<typenameT>
structIsSameT<T,T>:TrueType
{
};
Now,theresultingtypeofIsSameT<T,int>implicitlyconvertstoitsbaseclass
TrueTypeorFalseType,whichnotonlyprovidesthecorrespondingvalue
memberbutalsoallowsustodispatchtodifferentfunctionimplementationsor
partialclasstemplatespecializationsatcompiletime.Forexample:Clickhereto
viewcodeimage
traits/issame.cpp
#include"issame.hpp"
#include<iostream>
template<typenameT>
voidfooImpl(T,TrueType)
{
std::cout<<"fooImpl(T,true)forintcalled\n";
}
template<typenameT>
voidfooImpl(T,FalseType)
{
std::cout<<"fooImpl(T,false)forothertypecalled\n";
}
template<typenameT>
voidfoo(Tt)
{
fooImpl(t,IsSameT<T,int>{});//chooseimpl.dependingonwhetherT
isint
}
intmain()
{
foo(42);//callsfooImpl(42,TrueType)
foo(7.7);//callsfooImpl(42,FalseType)
}
ThistechniqueiscalledtagdispatchingandisintroducedinSection20.2on
page467.
NotethatourBoolConstantimplementationincludesaTypemember,
whichallowsustoreintroduceanaliastemplateforIsSameT:Clickhereto
viewcodeimage
template<typenameT>
usingIsSame=typenameIsSameT<T>::Type;Thataliastemplatecan
coexistwiththevariabletemplateisSame.
Ingeneral,traitsyieldingBooleanvaluesshouldsupporttagdispatchingby
derivingfromtypessuchasTrueTypeandFalseType.However,tobeas
genericaspossible,thereshouldbeonlyonetyperepresentingtrueandone
typerepresentingfalseinsteadofhavingeachgenericlibrarydefiningitsown
typesforBooleanconstants.
Fortunately,theC++standardlibraryprovidescorrespondingtypesin
<type_traits>sinceC++11:std::true_typeandstd::false_type.InC++11
andC++14,theyaredefinedasfollows:Clickheretoviewcodeimage
namespacestd{
usingtrue_type=integral_constant<bool,true>;
usingfalse_type=integral_constant<bool,false>;
}
SinceC++17,theyaredefinedas
Clickheretoviewcodeimage
namespacestd{
usingtrue_type=bool_constant<true>;
usingfalse_type=bool_constant<false>;
}
wherebool_constantisdefinedinnamespacestdasClickheretoview
codeimage
template<boolB>
usingbool_constant=integral_constant<bool,B>;SeeSectionD.1.1
onpage699forfurtherdetails.
Forthisreason,weusestd::true_typeandstd::false_type
directlyfortherestofthisbook,especiallywhendefiningtypepredicates.
19.3.4ResultTypeTraits
Anotherexampleoftypefunctionsthatdealwithmultipletypesareresulttype
traits.Theyareveryusefulwhenwritingoperatortemplates.Tomotivatethe
idea,let’swriteafunctiontemplatethatallowsustoaddtwoArraycontainers:
Clickheretoviewcodeimage
template<typenameT>
Array<T>operator+(Array<T>const&,Array<T>const&);Thiswouldbe
nice,butbecausethelanguageallowsustoaddacharvaluetoan
intvalue,wereallywouldprefertoallowsuchmixed-typeoperations
witharraystoo.Wearethenfacedwithdeterminingwhatthereturn
typeoftheresultingtemplateshouldbeClickheretoviewcode
image
template<typenameT1,typenameT2>
Array<???>operator+(Array<T1>const&,Array<T2>const&);Besides
thedifferentapproachesintroducedinSection1.3onpage9,a
resulttypetemplateallowsustofillinthequestionmarksinthe
previousdeclarationasfollows:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
Array<typenamePlusResultT<T1,T2>::Type>
operator+(Array<T1>const&,Array<T2>const&);or,ifweassumethe
availabilityofaconveniencealiastemplate,Clickheretoviewcode
image
template<typenameT1,typenameT2>
Array<PlusResult<T1,T2>>
operator+(Array<T1>const&,Array<T2>const&);ThePlusResultTtrait
determinesthetypeproducedbyaddingvaluesoftwo(possibly
different)typeswiththe+operator:Clickheretoviewcodeimage
traits/plus1.hpp
template<typenameT1,typenameT2>
structPlusResultT{
usingType=decltype(T1()+T2());
};
template<typenameT1,typenameT2>
usingPlusResult=typenamePlusResultT<T1,T2>::Type;Thistrait
templateusesdecltypetocomputethetypeoftheexpressionT1()+
T2(),leavingthehardworkofdeterminingtheresulttype(including
handlingpromotionrulesandoverloadedoperators)tothecompiler.
However,forthepurposeofourmotivatingexample,decltypeactually
preservestoomuchinformation(seeSection15.10.2onpage298fora
descriptionofdecltype’sbehavior).Forexample,ourformulationof
PlusResultTmayproduceareferencetype,butmostlikelyourArrayclass
templateisnotdesignedtohandlereferencetypes.Morerealistically,an
overloadedoperator+mightreturnavalueofconstclasstype:Clickhere
toviewcodeimage
classInteger{…};
Integerconstoperator+(Integerconst&,Integerconst&);Addingtwo
Array<Integer>valueswillresultinanarrayofIntegerconst,which
ismostlikelynotwhatweintended.Infact,whatwewantisto
transformtheresulttypebyremovingreferencesandqualifiers,as
discussedintheprevioussection:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
Array<RemoveCV<RemoveReference<PlusResult<T1,T2>>>>
operator+(Array<T1>const&,Array<T2>const&);Suchnestingof
traitsiscommonintemplatelibrariesandisoftenusedinthe
contextofmetaprogramming.Metaprogrammingwillbecoveredindetail
inChapter23.(Theconveniencealiastemplatesareparticularly
helpfulwithmultilevelnestinglikethis.Withoutthem,we’dhaveto
addatypenameanda::Typesuffixateverylevel.)Atthispoint,
thearrayadditionoperatorproperlycomputestheresulttypewhen
addingtwoarraysof(possiblydifferent)elementtypes.However,our
formulationofPlusResultTplacesanundesirablerestrictiononthe
elementtypesT1andT2:BecausetheexpressionT1()+T2()attempts
tovalue-initializevaluesoftypesT1andT2,bothofthesetypes
musthaveanaccessible,nondeleted,defaultconstructor(orbe
nonclasstypes).TheArrayclassitselfmightnototherwiserequire
value-initializationofitselementtype,sothisisanadditional,
unnecessaryrestriction.
declval
Fortunately,itisfairlyeasytoproducevaluesforthe+expressionwithout
requiringaconstructor,byusingafunctionthatproducesvaluesofagiventype
T.Forthis,theC++standardprovidesstd::declval<>,asintroducedin
Section11.2.3onpage166.Itisdefinedin<utility>simplyasfollows:
Clickheretoviewcodeimage
namespacestd{
template<typenameT>
add_rvalue_reference_t<T>declval()noexcept;
}
Theexpressiondeclval<T>()producesavalueoftypeTwithoutrequiringa
defaultconstructor(oranyotheroperation).
Thisfunctiontemplateisintentionallyleftundefinedbecauseit’sonlymeant
tobeusedwithindecltype,sizeof,orsomeothercontextwhereno
definitioniseverneeded.Ithastwootherinterestingattributes:•For
referenceabletypes,thereturntypeisalwaysanrvaluereferencetothetype,
whichallowsdeclvaltoworkevenwithtypesthatcouldnotnormallybe
returnedfromafunction,suchasabstractclasstypes(classeswithpurevirtual
functions)orarraytypes.ThetransformationfromTtoT&&otherwisehasno
practicaleffectonthebehaviorofdeclval<T>()whenusedasan
expression:Botharervalues(ifTisanobjecttype),whilelvaluereferencetypes
areunchangedduetoreferencecollapsing(describedinSection15.6onpage
277).11
•Thenoexceptexceptionspecificationdocumentsthatdeclvalitselfdoes
notcauseanexpressiontobeconsideredtothrowexceptions.Itbecomes
usefulwhendeclvalisusedinthecontextofthenoexceptoperator
(Section19.7.2onpage443).
Withdeclval,wecaneliminatethevalue-initializationrequirementfor
PlusResultT:Clickheretoviewcodeimage
traits/plus2.hpp
#include<utility>
template<typenameT1,typenameT2>
structPlusResultT{
usingType=decltype(std::declval<T1>()+std::declval<T2>());
};
template<typenameT1,typenameT2>
usingPlusResult=typenamePlusResultT<T1,T2>::Type;Resulttype
traitsofferawaytodeterminetheprecisereturntypeofa
particularoperationandareoftenusefulwhendescribingtheresult
typesoffunctiontemplates.
19.4SFINAE-BasedTraits
TheSFINAEprinciple(substitutionfailureisnotanerror;seeSection8.4on
page129andSection15.7onpage284)turnspotentialerrorsduringthe
formationofinvalidtypesandexpressionsduringtemplateargumentdeduction
(whichwouldcausetheprogramtobeill-formed)intosimpledeductionfailures,
allowingoverloadresolutiontoselectadifferentcandidate.Whileoriginally
intendedtoavoidspuriousfailureswithfunctiontemplateoverloading,SFINAE
alsoenablesremarkablecompile-timetechniquesthatcandetermineifa
particulartypeorexpressionisvalid.Thisallowsustowritetraitsthat
determine,forexample,whetheratypehasaspecificmember,supportsa
specificoperation,orisaclass.
ThetwomainapproachesforSFINAE-basedtraitsaretoSFINAEout
functionsoverloadsandtoSFINAEoutpartialspecializations.
19.4.1SFINAEOutFunctionOverloads
OurfirstforayintoSFINAE-basedtraitsillustratesthebasictechnologyusing
SFINAEwithfunctionoverloadingtofindoutwhetheratypeisdefault
constructible,sothatyoucancreateobjectswithoutanyvalueforinitialization.
Thatis,foragiventypeT,anexpressionsuchasT()hastobevalid.
Abasicimplementationcanlookasfollows:
Clickheretoviewcodeimage
traits/isdefaultconstructible1.hpp
#include"issame.hpp"
template<typenameT>
structIsDefaultConstructibleT{
private:
//test()tryingsubstitutecallofadefaultconstructorforTpassed
asU:
template<typenameU,typename=decltype(U())>
staticchartest(void*);
//test()fallback:
template<typename>
staticlongtest(…);
public:
staticconstexprboolvalue
=IsSameT<decltype(test<T>(nullptr)),char>::value;
};
TheusualapproachtoimplementaSFINAE-basetraitwithfunctionoverloading
istodeclaretwooverloadedfunctiontemplatesnamedtest()withdifferent
returntypes:Clickheretoviewcodeimage
template<…>staticchartest(void*);
template<…>staticlongtest(…);Thefirstoverloadisdesignedto
matchonlyiftherequestedchecksucceeds(wewilldiscussbelowhow
thatisachieved).Thesecondoverloadisthefallback:12Italways
matchesthecall,butbecauseitmatches“withellipsis”(i.e.,a
varargparameter),anyothermatchwouldbepreferred(seeSection
C.2onpage682).
Our“returnvalue”valuedependsonwhichoverloadedtestmemberis
selected:Clickheretoviewcodeimage
staticconstexprboolvalue
=IsSameT<decltype(test<…>(nullptr)),char>::value;Ifthefirst
test()member—whosereturntypeischar—isselected,valuewillbe
initializedtoisSame<char,char>,whichistrue.Otherwise,itwill
beinitializedtoisSame<long,char>,whichisfalse.
Now,wehavetodealwiththespecificpropertieswewanttotest.Thegoalis
tomakethefirsttest()overloadvalidifandonlyiftheconditionwewantto
checkapplies.Inthiscase,wewanttofindoutwhetherwecandefaultconstruct
anobjectofthepassedtypeT.Toachievethis,wepassTasUandgiveourfirst
declarationoftest()asecondunnamed(dummy)templateargument
initializedwithanconstructthatisvalidifandonlyiftheconversionisvalid.In
thiscase,weusetheexpressionthatcanonlybevalidifanimplicitorexplicit
defaultconstructorexists:U().Theexpressionissurroundedbydecltypeto
makethisavalidexpressiontoinitializeatypeparameter.
Thesecondtemplateparametercannotbededuced,asnocorresponding
argumentispassed.Andwewillnotprovideanexplicittemplateargumentforit.
Therefore,itwillbesubstituted,andifthesubstitutionfails,accordingto
SFINAE,thatdeclarationoftest()willbediscardedsothatonlythefallback
declarationwillmatch.
Thus,wecanusethetraitasfollows:
Clickheretoviewcodeimage
IsDefaultConstructibleT<int>::value//yieldstrue
structS{
S()=delete;
};
IsDefaultConstructibleT<S>::value//yieldsfalse
Notethatwecan’tusethetemplateparameterTinthefirsttest()directly:
Clickheretoviewcodeimage
template<typenameT>
structIsDefaultConstructibleT{
private:
//ERROR:test()usesTdirectly:
template<typename,typename=decltype(T())>
staticchartest(void*);
//test()fallback:
template<typename>
staticlongtest(…);
public:
staticconstexprboolvalue
=IsSameT<decltype(test<T>(nullptr)),char>::value;
};
Thisdoesn’twork,however,becauseforanyT,always,allmemberfunctions
aresubstituted,sothatforatypethatisn’tdefaultconstructible,thecodefailsto
compileinsteadofignoringthefirsttest()overload.Bypassingtheclass
templateparameterTtoafunctiontemplateparameterU,wecreateaspecific
SFINAEcontextonlyforthesecondtest()overload.
AlternativeImplementationStrategiesforSFINAE-basedTraits
SFINAE-basedtraitshavebeenpossibletoimplementsincebeforethefirstC++
standardwaspublishedin1998.13Thekeytotheapproachalwaysconsistedin
declaringtwooverloadedfunctiontemplatesreturningdifferentreturntypes:
Clickheretoviewcodeimage
template<…>staticchartest(void*);
template<…>staticlongtest(…);However,theoriginalpublished
technique14usedthesizeofthereturntypetodeterminewhich
overloadwasselected(alsousing0andenum,becausenullptrand
constexprwerenotavailable):Clickheretoviewcodeimage
enum{value=sizeof(test<…>(0))==1};Onsomeplatforms,itmight
happenthatsizeof(char)==sizeof(long).Forexample,ondigital
signalprocessors(DSP)oroldCraymachines,allintegral
fundamentaltypescouldhavethesamesize.Asbydefinition
sizeof(char)equals1,onthosemachinessizeof(long)andeven
sizeof(longlong)alsoequal1.
Giventhatobservation,wewanttoensurethatthereturntypesofthetest()
functionshavedifferentsizesonallplatforms.Forexample,afterdefiningClick
heretoviewcodeimage
usingSize1T=char;
usingSize2T=struct{chara[2];};or
Clickheretoviewcodeimage
usingSize1T=char(&)[1];
usingSize2T=char(&)[2];wecoulddefinetesttest()overloadsas
follows:Clickheretoviewcodeimage
template<…>staticSize1Ttest(void*);//checkingtest()
template<…>staticSize2Ttest(…);//fallback
Here,weeitherreturnaSize1T,whichisasinglecharofsize1,orwereturn
(astructureof)anarrayoftwochars,whichhasasizeofatleast2onall
platforms.
Codeusingoneoftheseapproachesisstillcommonlyfound.
Notealsothatthetypeofthecallargumentpassedtofunc()doesn’tmatter.
Allthatmattersisthatthepassedargumentmatchestheexpectedtype.For
example,youcouldalsodefinetopasstheinteger42:Clickheretoviewcode
image
template<…>staticSize1Ttest(int);//checkingtest()
template<…>staticSize2Ttest(…);//fallback
…
enum{value=sizeof(test<…>(42))==1};
MakingSFINAE-basedTraitsPredicateTraits
AsintroducedinSection19.3.3onpage410,apredicatetrait,whichreturnsa
Booleanvalue,shouldreturnavaluederivedfromstd::true_typeor
std::false_type.Thisway,wecanalsosolvetheproblemthatonsome
platformssizeof(char)==sizeof(long).
Forthis,weneedanindirectdefinitionofIsDefaultConstructibleT.
ThetraititselfshouldderivefromtheTypeofahelperclass,whichyieldsthe
necessarybaseclass.Fortunately,wecansimplyprovidethecorrespondingbase
classesasreturntypesofthetest()overloads:Clickheretoviewcodeimage
template<…>staticstd::true_typetest(void*);//checkingtest()
template<…>staticstd::false_typetest(…);//fallback
Thatway,theTypememberforthebaseclasssimplycanbedeclaredas
follows:Clickheretoviewcodeimage
usingType=decltype(test<FROM>(nullptr));andwenolongerneedthe
IsSameTtrait.
ThecompleteimprovedimplementationofIsDefaultConstructibleT
thereforebecomesasfollows:Clickheretoviewcodeimage
traits/isdefaultconstructible2.hpp
#include<type_traits>
template<typenameT>
structIsDefaultConstructibleHelper{
private:
//test()tryingsubstitutecallofadefaultconstructorforTpassed
asU:
template<typenameU,typename=decltype(U())>
staticstd::true_typetest(void*);
//test()fallback:
template<typename>
staticstd::false_typetest(…);
public:
usingType=decltype(test<T>(nullptr));
};
template<typenameT>
structIsDefaultConstructibleT:IsDefaultConstructibleHelper<T>::Type
{
};
Now,ifthefirsttest()functiontemplateisvalid,itisthepreferredoverload,
sothatthememberIsDefaultConstructibleHelper::Typeis
initializedbyitsreturntypestd::true_type.Asaconsequence,
IsConvertibleT<…>derivesfromstd::true_type.
Ifthefirsttest()functiontemplateisnotvalid,itbecomesdisableddueto
SFINAE,andIsDefaultConstructibleHelper::Typeisinitialized
bythereturntypeofthetest()fall-back,thatis,std::false_type.The
effectisthatIsConvertibleT<…>thenderivesfrom
std::false_type.
19.4.2SFINAEOutPartialSpecializations
ThesecondapproachtoimplementSFINAE-basedtraitsusespartial
specialization.Again,wecanusetheexampletofindoutwhetheratypeTis
defaultconstructible:Clickheretoviewcodeimage
traits/isdefaultconstructible3.hpp
#include"issame.hpp"
#include<type_traits>//definestrue_typeandfalse_type
//helpertoignoreanynumberoftemplateparameters:
template<typename…>usingVoidT=void;
//primarytemplate:
template<typename,typename=VoidT<>>
structIsDefaultConstructibleT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT>
structIsDefaultConstructibleT<T,VoidT<decltype(T())>>:
std::true_type
{
};
AswiththeimprovedversionofIsDefaultConstructibleTforpredicate
traitsabove,wedefinethegeneralcasetobederivedfrom
std::false_type,becausebydefaultatypedoesn’thavethemember
size_type.
Theinterestingfeaturehereisthesecondtemplateargumentthatdefaultsto
thetypeofahelperVoidT.Itenablesustoprovidepartialspecializationsthat
useanarbitrarynumberofcompile-timetypeconstructs.
Inthiscase,weneedonlyoneconstruct:
decltype(T())tocheckagainwhetherthedefaultconstructorforTis
valid.If,foraspecificT,theconstructisinvalid,SFINAEthis
timecausesthewholepartialspecializationtobediscarded,andwe
fallbacktotheprimarytemplate.Otherwise,thepartial
specializationisvalidandpreferred.
InC++17,theC++standardlibraryintroducedatypetraitstd::void_t<>
thatcorrespondstothetypeVoidTintroducedhere.BeforeC++17,itmightbe
helpfultodefineitourselvesasaboveoreveninnamespacestdasfollows:15
Clickheretoviewcodeimage
#include<type_traits>
#ifndef__cpp_lib_void_t
namespacestd{
template<typename…>usingvoid_t=void;
}
#endif
StartingwithC++14,theC++standardizationcommitteehasrecommendedthat
compilersandstandardlibrariesindicatewhichpartsofthestandardtheyhave
implementedbydefiningagreed-uponfeaturemacros.Thisisnota
requirementforstandardconformance,butimplementerstypicallyfollow
therecommendationtobehelpfultotheirusers.16Themacro
__cpp_lib_void_tisthemacrorecommendedtoindicatethatalibrary
implementsstd::void_t,andthusourcodeaboveismadeconditionalonit.
Obviously,thiswaytodefineatypetraitlooksmorecondensedthatthefirst
approachtooverloadfunctiontemplates.Butitrequirestheabilitytoformulate
theconditioninsidethedeclarationofatemplateparameter.Usingaclass
templatewithfunctionoverloadsenablesustouseadditionalhelperfunctionsor
helpertypes.
19.4.3UsingGenericLambdasforSFINAE
Whichevertechniqueweuse,someboilerplatecodeisalwaysneededtodefine
traits:overloadingandcallingtwotest()memberfunctionsorimplementing
multiplepartialspecializations.Next,wewillshowhowinC++17,wecan
minimizethisboilerplatebyspecifyingtheconditiontobecheckedinageneric
lambda.17
Tostartwith,weintroduceatoolconstructedfromtwonestedgenericlambda
expressions:Clickheretoviewcodeimage
traits/isvalid.hpp
#include<utility>
//helper:checkingvalidityoff(args…)forFfandArgs…args:
template<typenameF,typename…Args,
typename=decltype(std::declval<F>()(std::declval<Args&&>()…))>
std::true_typeisValidImpl(void*);
//fallbackifhelperSFINAE’dout:
template<typenameF,typename…Args>
std::false_typeisValidImpl(…);
//definealambdathattakesalambdafandreturnswhethercallingf
withargsisvalid
inlineconstexpr
autoisValid=[](autof){
return[](auto&&…args){
returndecltype(isValidImpl<decltype(f),
decltype(args)&&…
>(nullptr)){};
};
};
//helpertemplatetorepresentatypeasavalue
template<typenameT>
structTypeT{
usingType=T;
};
//helpertowrapatypeasavalue
template<typenameT>
constexprautotype=TypeT<T>{};
//helpertounwrapawrappedtypeinunevaluatedcontexts
template<typenameT>
TvalueT(TypeT<T>);//nodefinitionneeded
Let’sstartwiththedefinitionofisValid:Itisaconstexprvariablewhose
typeisalambda’sclosuretype.Thedeclarationmustnecessarilyusea
placeholdertype(autoinourcode)becauseC++hasnowaytoexpressclosure
typesdirectly.PriortoC++17,lambdaexpressionscouldnotappearinconstant-
expressions,whichiswhythiscodeisonlyvalidinC++17.SinceisValidhas
aclosuretype,itcanbeinvoked(i.e.,called),buttheitemthatitreturnsisitself
anobjectofalambdaclosuretype,producedbytheinnerlambdaexpression.
Beforedelvingintothedetailsofthatinnerlambdaexpression,let’sexaminea
typicaluseofisValid:Clickheretoviewcodeimage
constexprautoisDefaultConstructible
=isValid([](autox)->decltype((void)decltype(valueT(x))(){
});
WealreadyknowthatisDefaultConstructiblehasalambdaclosure
typeand,asthenamesuggests,itisafunctionobjectthatchecksthetraitofa
typebeingdefault-constructible(we’llseewhyinwhatfollows).Inotherwords,
isValidisatraitsfactory:Acomponentthatgeneratestraitscheckingobjects
fromitsargument.
Thetypehelpervariabletemplateallowsustorepresentatypeasavalue.A
valuexobtainedthatwaycanbeturnedbackintotheoriginaltypewith
decltype(valueT(x))18,andthat’sexactlywhatisdoneinthelambda
passedtoisValidabove.Ifthatextractedtypecannotbedefault-constructed,
decltype(valueT(x))()isinvalid,andwewilleithergetacompiler
error,oranassociateddeclarationwillbe“SFINAE’dout”(andthelattereffect
iswhatwe’llachievethankstothedetailsofthedefinitionofisValid).
isDefaultConstructiblecanbeusedasfollows:Clickheretoview
codeimage
isDefaultConstructible(type<int>)//true(intisdefault-
constructible)
isDefaultConstructible(type<int&>)//false(referencesarenot
default-constructible)
Toseehowallthepiecesworktogether,considerwhattheinnerlambda
expressioninisValidbecomeswithisValid’sparameterfboundtothe
genericlambdaargumentspecifiedinthedefinitionof
isDefaultConstructible.ByperformingthesubstitutioninisValid’s
definition,wegettheequivalentof:19
Clickheretoviewcodeimage
constexprautoisDefaultConstructible
=[](auto&&…args){
returndecltype(
isValidImpl<
decltype([](autox)
->decltype((void)decltype(valueT(x))())),
decltype(args)&&…
>(nullptr)){};
};
IfwelookbackatthefirstdeclarationofisValidImpl()above,wenotethat
itincludesadefaulttemplateargumentoftheformClickheretoviewcode
image
decltype(std::declval<F>()(std::declval<Args&&>()…))>whichattempts
toinvokeavalueofthetypeofitsfirsttemplateargument,which
istheclosuretypeofthelambdainthedefinitionof
isDefaultConstructible,withvaluesofthetypesofthearguments
(decltype(args)&&…)passedtoisDefaultConstructible.Asthereis
onlyoneparameterxinthelambda,argsmustexpandtoonlyone
argument;inourstatic_assertexamplesabove,thatargumenthastype
TypeT<int>orTypeT<int&>.IntheTypeT<int&>case,
decltype(valueT(x))isint&whichmakesdecltype(valueT(x))()
invalid,andthusthesubstitutionofthedefaulttemplateargument
inthefirstdeclarationofisValidImpl()failsandisSFINAE’dout.
Thatleavesjusttheseconddeclaration(whichwouldotherwisebea
lessermatch),whichproducesafalse_typevalue.Overall,
isDefaultConstructibleproducesfalse_typewhentype<int&>ispassed.
Ifinsteadtype<int>ispassed,thesubstitutiondoesnotfail,and
thefirstdeclarationofisValidImpl()isselected,producinga
true_typevalue.
RecallthatforSFINAEtowork,substitutionshavetohappeninthe
immediatecontextofthesubstitutedtemplates.Inthiscase,thesubstituted
templatesarethefirstdeclarationofisValidImplandthecalloperatorofthe
genericlambdapassedtoisValid.Therefore,theconstructtobetestedhasto
appearinthereturntypeofthatlambda,notinitsbody!
OurisDefaultConstructibletraitisalittledifferentfromprevious
traitsimplementationsinthatitrequiresfunction-styleinvocationinsteadof
specifyingtemplatearguments.Thatisarguablyamorereadablenotation,but
thepriorstylecanbeobtainedalso:Clickheretoviewcodeimage
template<typenameT>
usingIsDefaultConstructibleT
=decltype(isDefaultConstructible(std::declval<T>()));Sincethisis
atraditionaltemplatedeclaration,however,itcanonlyappearin
namespacescope,whereasthedefinitionofisDefaultConstructible
couldconceivablyhavebeenintroducedinblockscope.
Sofar,thistechniquemightnotseemcompellingbecauseboththeexpressions
involvedintheimplementationandthestyleofusearemorecomplexthanthe
previoustechniques.However,onceisValidisinplaceandunderstood,many
traitscanbeimplementwithjustonedeclaration.Forexample,testingforaccess
toamembernamedfirst,isratherclean(seeSection19.6.4onpage438for
thecompleteexample):Clickheretoviewcodeimage
constexprautohasFirst
=isValid([](autox)->decltype((void)valueT(x).first){
});
19.4.4SFINAE-FriendlyTraits
Ingeneral,atypetraitshouldbeabletoansweraparticularquerywithout
causingtheprogramtobecomeill-formed.SFINAE-basedtraitsaddressthis
problembycarefullytrappingpotentialproblemswithinaSFINAEcontext,
turningthosewould-beerrorsintonegativeresults.
However,sometraitspresentedthusfar(suchasthePlusResultTtrait
describedinSection19.3.4onpage413)donotbehavewellinthepresenceof
errors.RecallthedefinitionofPlusResultTfromthatsection:Clickhereto
viewcodeimage
traits/plus2.hpp
#include<utility>
template<typenameT1,typenameT2>
structPlusResultT{
usingType=decltype(std::declval<T1>()+std::declval<T2>());
};
template<typenameT1,typenameT2>
usingPlusResult=typenamePlusResultT<T1,T2>::Type;Inthis
definition,the+isusedinacontextthatisnotprotectedby
SFINAE.Therefore,ifaprogramattemptstoevaluatePlusResultTfor
typesthatdonothaveasuitable+operator,theevaluationof
PlusResultTitselfwillcausetheprogramtobecomeill-formed,asin
thefollowingattempttodeclarethereturntypeofaddingarraysof
unrelatedtypesAandB:20
Clickheretoviewcodeimage
template<typenameT>
classArray{
…
};
//declare+forarraysofdifferentelementtypes:
template<typenameT1,typenameT2>
Array<typenamePlusResultT<T1,T2>::Type>
operator+(Array<T1>const&,Array<T2>const&);Clearly,using
PlusResultT<>herewillleadtoanerrorifnocorrespondingoperator
+isdefinedforthearrayelement.
Clickheretoviewcodeimage
classA{
};
classB{
};
voidaddAB(Array<A>arrayA,Array<B>arrayB){
autosum=arrayA+arrayB;//ERROR:failsininstantiationof
PlusResultT<A,B>
…
}
Thepracticalproblemisnotthatthisfailureoccurswithcodethatisclearlyill-
formedlikethis(thereisnowaytoaddanarrayofAtoanarrayofB)butthatit
occursduringtemplateargumentdeductionforoperator+,deepinthe
instantiationofPlusResultT<A,B>.
Thishasaremarkableconsequence:Itmeansthattheprogrammayfailto
compileevenifweaddaspecificoverloadtoaddingAandBarrays,because
C++doesnotspecifywhetherthetypesinafunctiontemplateareactually
instantiatedifanotheroverloadwouldbebetter:Clickheretoviewcodeimage
//declaregeneric+forarraysofdifferentelementtypes:
template<typenameT1,typenameT2>
Array<typenamePlusResultT<T1,T2>::Type>
operator+(Array<T1>const&,Array<T2>const&);
//overload+forconcretetypes:
Array<A>operator+(Array<A>const&arrayA,Array<B>const&arrayB);
voidaddAB(Array<A>const&arrayA,Array<B>const&arrayB){
autosum=arrayA+arrayB;//ERROR?:dependsonwhetherthe
compiler
…//instantiatesPlusResultT<A,B>
}
Ifthecompilercandeterminethattheseconddeclarationofoperator+isa
bettermatchwithoutperformingdeductionandsubstitutiononthefirst
(template)declarationofoperator+,itwillacceptthiscode.
However,whilededucingandsubstitutingafunctiontemplatecandidate,
anythingthathappensduringtheinstantiationofthedefinitionofaclass
templateisnotpartoftheimmediatecontextofthatfunctiontemplate
substitution,andSFINAEdoesnotprotectusfromattemptstoforminvalid
typesorexpressionsthere.Insteadofjustdiscardingthefunctiontemplate
candidate,anerrorisissuedrightawaybecausewetrytocalloperator+for
twoelementsoftypesAandBinsidePlusResultT<>:Clickheretoview
codeimage
template<typenameT1,typenameT2>
structPlusResultT{
usingType=decltype(std::declval<T1>()+std::declval<T2>());
};
Tosolvethisproblem,wehavetomakethePlusResultTSFINAE-friendly,
whichmeanstomakeitmoreresilientbygivingitasuitabledefinitioneven
whenitsdecltypeexpressionisill-formed.
FollowingtheexampleofHasLessTdescribedintheprevioussection,we
defineaHasPlusTtraitthatallowsustodetectwhetherthereisasuitable+
operationforthegiventypes:Clickheretoviewcodeimage
traits/hasplus.hpp
#include<utility>//fordeclval
#include<type_traits>//fortrue_type,false_type,andvoid_t
//primarytemplate:
template<typename,typename,typename=std::void_t<>>
structHasPlusT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT1,typenameT2>
structHasPlusT<T1,T2,std::void_t<decltype(std::declval<T1>()
+std::declval<T2>())>>
:std::true_type
{
};
Ifityieldsatrueresult,PlusResultTcanusetheexistingimplementation.
Otherwise,PlusResultTneedsasafedefault.Thebestdefaultforatraitthat
hasnomeaningfulresultforasetoftemplateargumentsistonotprovideany
memberTypeatall.Thatway,ifthetraitisusedwithinaSFINAEcontext—
suchasthereturntypeofthearrayoperator+templateabove—themissing
memberTypewillmaketemplateargumentdeductionfail,whichisprecisely
thebehaviordesiredforthearrayoperator+template.
ThefollowingimplementationofPlusResultTprovidesthisbehavior:
Clickheretoviewcodeimage
traits/plus3.hpp
#include"hasplus.hpp"
template<typenameT1,typenameT2,bool=HasPlusT<T1,T2>::value>
structPlusResultT{//primarytemplate,usedwhenHasPlusTyields
true
usingType=decltype(std::declval<T1>()+std::declval<T2>());
};
template<typenameT1,typenameT2>
structPlusResultT<T1,T2,false>{//partialspecialization,used
otherwise
};
InthisversionofPlusResultT,weaddatemplateparameterwithadefault
argumentthatdeterminesifthefirsttwoparameterssupportadditionas
determinedbyourHasPlusTtraitabove.Wethenpartiallyspecialize
PlusResultTforfalsevaluesofthatextraparameter,andourpartial
specializationdefinitionhasnomembersatall,avoidingtheproblemswe
described.Forcaseswhereadditionissupported,thedefaultargumentevaluates
totrueandtheprimarytemplateisselected,withourexistingdefinitionofthe
Typemember.Thus,wefulfillthecontractthatPlusResultTprovidethe
resulttypeonlyifinfactthe+operationiswell-formed.(Notethattheadded
templateparametershouldneverhaveanexplicittemplateargument.)Consider
againtheadditionofArray<A>andArray<B>:Withourlatest
implementationofthePlusResultTtemplate,theinstantiationof
PlusResultT<A,B>willnothaveaTypemember,becauseAandBvalues
arenotaddable.Therefore,theresulttypeofthearrayoperator+templateis
invalid,andSFINAEwilleliminatethefunctiontemplatefromconsideration.
Theoverloadedoperator+thatisspecifictoArray<A>andArray<B>
willthereforebechosen.
Asageneraldesignprinciple,atraittemplateshouldneverfailatinstantiation
timeifgivenreasonabletemplateargumentsasinputs.Andthegeneralapproach
isoftentoperformthecorrespondingchecktwice:1.Oncetofindoutwhether
theoperationisvalid2.OncetotocomputeitsresultWesawthatalreadywith
PlusResultT,wherewecallHasPlusT<>tofindoutwhetherthecallof
operator+inPlusResultImpl<>isvalid.
Let’sapplythisprincipletoElementTasintroducedinSection19.3.1on
page401:Itproducesanelementtypefromacontainertype.Again,sincethe
answerreliesona(container)typehavingamembertypevalue_type,the
primarytemplateshouldattempttodefinethememberTypeonlywhenthe
containertypehassuchavalue_typemember:Clickheretoviewcode
image
template<typenameC,bool=HasMemberT_value_type<C>::value>
structElementT{
usingType=typenameC::value_type;
};
template<typenameC>
structElementT<C,false>{
};
AthirdexampleofmakingtraitsSFINAE-friendlyisshownSection19.7.2on
page444,whereIsNothrowMoveConstructibleTfirsthastocheck
whetheramoveconstructorexistsbeforecheckingwhetheritisdeclaredwith
noexcept.
19.5IsConvertibleT
Detailsmatter.Andforthatreason,thegeneralapproachforSFINAE-based
traitsmightbecomemorecomplicatedinpractice.Let’sillustratethisby
definingatraitthatcandeterminewhetheragiventypeisconvertibletoanother
giventype—forexample,ifweexpectacertainbaseclassoroneofitsderived
classes.TheIsConvertibleTtraityieldswhetherwecanconvertapassed
firsttypetoapassedsecondtype:Clickheretoviewcodeimage
traits/isconvertible.hpp
#include<type_traits>//fortrue_typeandfalse_type
#include<utility>//fordeclval
template<typenameFROM,typenameTO>
structIsConvertibleHelper{
private:
//test()tryingtocallthehelperaux(TO)foraFROMpassedasF:
staticvoidaux(TO);
template<typenameF,typenameT,
typename=decltype(aux(std::declval<F>()))>
staticstd::true_typetest(void*);
//test()fallback:
template<typename,typename>
staticstd::false_typetest(…);
public:
usingType=decltype(test<FROM>(nullptr));
};
template<typenameFROM,typenameTO>
structIsConvertibleT:IsConvertibleHelper<FROM,TO>::Type{
};
template<typenameFROM,typenameTO>
usingIsConvertible=typenameIsConvertibleT<FROM,TO>::Type;
template<typenameFROM,typenameTO>
constexprboolisConvertible=IsConvertibleT<FROM,TO>::value;Here,
weusetheapproachwithfunctionoverloading,asintroducedin
Section19.4.1onpage416.Thatis,insideahelperclasswedeclare
twooverloadedfunctiontemplatesnamedtest()withdifferentreturn
typesanddeclareaTypememberforthebaseclassoftheresulting
trait:Clickheretoviewcodeimage
template<…>staticstd::true_typetest(void*);
template<…>staticstd::false_typetest(…);
…
usingType=decltype(test<FROM>(nullptr));
…
template<typenameFROM,typenameTO>
structIsConvertibleT:IsConvertibleHelper<FROM,TO>::Type{
};
Asusual,thefirsttest()overloadisdesignedtomatchonlyiftherequested
checksucceeds,whilethesecondoverloadisthefallback.Thus,thegoalisto
makethefirsttest()overloadvalidifandonlyiftypeFROMconvertstotype
TO.Toachievethis,againwegiveourfirstdeclarationoftestadummy
(unnamed)templateargumentinitializedwithaconstructthatisvalidifandonly
iftheconversionisvalid.Thistemplateparametercannotbededuced,andwe
willnotprovideanexplicittemplateargumentforit.Therefore,itwillbe
substituted,andifthesubstitutionfails,thatdeclarationoftest()willbe
discarded.
Again,notethatthefollowingdoesn’twork:
Clickheretoviewcodeimage
staticvoidaux(TO);
template<typename=decltype(aux(std::declval<FROM>()))>
staticchartest(void*);Here,FROMandTOarecompletelydetermined
whenthismemberfunctiontemplateisparsed,andthereforeapairof
typesforwhichtheconversionisnotvalid(e.g.,double*andint*)
willtriggeranerrorrightaway,beforeanycalltotest()(and
thereforeoutsideanySFINAEcontext).
Forthatreason,weintroduceFasaspecificmemberfunctiontemplate
parameterClickheretoviewcodeimage
staticvoidaux(TO);
template<typenameF,typename=decltype(aux(std::declval<F>()))>
staticchartest(void*);andprovidetheFROMtypeasanexplicit
templateargumentinthecalltotest()thatappearsinthe
initializationofvalue:Clickheretoviewcodeimage
staticconstexprboolvalue
=isSame<decltype(test<FROM>(nullptr)),char>;Notehowstd::declval,
introducedinSection19.3.4onpage415,isusedtoproduceavalue
withoutcallinganyconstructor.IfthatvalueisconvertibletoTO,
thecalltoaux()isvalid,andthisdeclarationoftest()matches.
Otherwise,aSFINAEfailureoccursandonlythefallbackdeclaration
willmatch.
Asaresult,wecanusethetraitasfollows:Clickheretoviewcodeimage
IsConvertibleT<int,int>::value//yieldstrue
IsConvertibleT<int,std::string>::value//yieldsfalse
IsConvertibleT<charconst*,std::string>::value//yieldstrue
IsConvertibleT<std::string,charconst*>::value//yieldsfalse
HandlingSpecialCases
ThreecasesarenotyethandledcorrectlybyIsConvertibleT:1.
Conversionstoarraytypesshouldalwaysyieldfalse,butinourcode,the
parameteroftypeTOinthedeclarationofaux()willjustdecaytoapointer
typeandthereforeenablea“true”resultforsomeFROMtypes.
2.Conversionstofunctiontypesshouldalwaysyieldfalse,butjustaswiththe
arraycase,ourimplementationjusttreatsthemasthedecayedtype.
3.Conversionto(const/volatile-qualified)voidtypesshouldyield
true.Unfortunately,ourimplementationabovedoesn’tevensuccessfully
instantiatethecasewhereTOisavoidtypebecauseparametertypescannot
havetypevoid(andaux()isdeclaredwithsuchaparameter).
Forallthesecases,we’llneedadditionalpartialspecializations.However,
addingsuchspecializationsforeverypossiblecombinationofconstand
volatilequalifiersquicklybecomesunwieldy.Instead,wecanaddan
additionaltemplateparametertoourhelperclasstemplateasfollows:Clickhere
toviewcodeimage
template<typenameFROM,typenameTO,bool=IsVoidT<TO>::value
||IsArrayT<TO>::value
||IsFunctionT<TO>::value>
structIsConvertibleHelper{
usingType=std::integral_constant<bool,
IsVoidT<TO>::value
&&IsVoidT<FROM>::value>;
};
template<typenameFROM,typenameTO>
structIsConvertibleHelper<FROM,TO,false>{
…//previousimplementationofIsConvertibleHelperhere
};
TheextraBooleantemplateparameterensuresthatforallthesespecialcasesthe
implementationoftheprimaryhelpertraitisused.Ityieldsfalse_typeifwe
converttoarraysorfunctions(becausethenIsVoidT<TO>isfalse)orifFROM
isvoidandTOisnot,butfortwovoidtypesitwillproducefalse_type.
Allothercasesproduceafalseargumentforthethirdparameterandtherefore
pickupthepartialspecialization,whichcorrespondstotheimplementationwe
alreadydiscussed.
SeeSection19.8.2onpage453foradiscussionofhowtoimplement
IsArrayTandSection19.8.3onpage454foradiscussionofhowto
implementIsFunctionT.
TheC++standardlibraryprovidesacorrespondingtypetrait
std::is_convertible<>,whichisdescribedinSectionD.3.3onpage
727.
19.6DetectingMembers
AnotherforayintoSFINAE-basedtraitsinvolvescreatingatrait(or,rather,aset
oftraits)thatcandeterminewhetheragiventypeThasamemberofagiven
nameX(atypeoranontypemember).
19.6.1DetectingMemberTypes
Let’sfirstdefineatraitthatcandeterminewhetheragiventypeThasamember
typesize_type:Clickheretoviewcodeimage
traits/hassizetype.hpp
#include<type_traits>//definestrue_typeandfalse_type
//helpertoignoreanynumberoftemplateparameters:
template<typename…>usingVoidT=void;
//primarytemplate:
template<typename,typename=VoidT<>>
structHasSizeTypeT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT>
structHasSizeTypeT<T,VoidT<typenameT::size_type>>:std::true_type
{
};
Here,weusetheapproachtoSFINAEoutpartialspecializationsintroducedin
Section19.4.2onpage420.
Asusualforpredicatetraits,wedefinethegeneralcasetobederivedfrom
std::false_type,becausebydefaultatypedoesn’thavethemember
size_type.
Inthiscase,weonlyneedoneconstruct:
typenameT::size_typeThisconstructisvalidifandonlyiftypeT
hasamembertypesize_type,whichisexactlywhatwearetryingto
determine.If,foraspecificT,theconstructisinvalid(i.e.,type
Thasnomembertypesize_type),SFINAEcausesthepartial
specializationtobediscarded,andwefallbacktotheprimary
template.Otherwise,thepartialspecializationisvalidand
preferred.
Wecanusethetraitasfollows:
Clickheretoviewcodeimage
std::cout<<HasSizeTypeT<int>::value;//false
structCX{
usingsize_type=std::size_t;
};
std::cout<<HasSizeType<CX>::value;//true
Notethatifthemembertypesize_typeisprivate,HasSizeTypeTyields
falsebecauseourtraitstemplateshavenospecialaccesstotheirargument
type,andthereforetypenameT::size_typeisinvalid(i.e.,triggers
SFINAE).Inotherwords,thetraittestswhetherwehaveanaccessiblemember
typesize_type.
DealingwithReferenceTypes
Asprogrammers,wearefamiliarwiththesurprisesthatcanarise“ontheedges”
ofthedomainsweconsider.WithatraitstemplatelikeHasSizeTypeT,
interestingissuescanarisewithreferencetypes.Forexample,whilethe
followingworksfine:Clickheretoviewcodeimage
structCXR{
usingsize_type=char&;//Note:typesize_typeisareferencetype
};
std::cout<<HasSizeTypeT<CXR>::value;//OK:printstrue
thefollowingfails:
Clickheretoviewcodeimage
std::cout<<HasSizeTypeT<CX&>::value;//OOPS:printsfalse
std::cout<<HasSizeTypeT<CXR&>::value;//OOPS:printsfalse
andthatispotentiallysurprising.Itistruethatareferencetypehasnotmembers
perse,butwheneverweusereferences,theresultingexpressionshavethe
underlyingtype,andsoperhapsitwouldbepreferabletoconsiderthe
underlyingtypeinthatcase.Here,thatcouldbeachievedbyusingourearlier
RemoveReferencetraitinthepartialspecializationofHasSizeTypeT:
Clickheretoviewcodeimage
template<typenameT>
structHasSizeTypeT<T,VoidT<RemoveReference<T>::size_type>>
:std::true_type{
};
InjectedClassNames
It’salsoworthnotingthatourtraitstechniquetodetectmembertypeswillalso
produceatruevalueforinjectedclassnames(seeSection13.2.3onpage221).
Forexample:Clickheretoviewcodeimage
structsize_type{
};
structSizeable:size_type{
};
static_assert(HasSizeTypeT<Sizeable>::value,
"Compilerbug:Injectedclassnamemissing");Thelatterstatic
assertionsucceedsbecausesize_typeintroducesitsownnameasa
membertype,andthatnameisinherited.Ifitdidn’tsucceed,we
wouldhavefoundadefectinthecompiler.
19.6.2DetectingArbitraryMemberTypes
DefiningatraitsuchasHasSizeTypeTraisesthequestionofhowto
parameterizethetraittobeabletocheckforanymembertypename.
Unfortunately,thiscancurrentlybeachievedonlyviamacros,becausethereis
nolanguagemechanismtodescribea“potential”name.21Theclosestwecanget
forthemomentwithoutusingmacrosistousegenericlambdas,asillustratedin
Section19.6.4onpage438.
Thefollowingmacrowouldwork:
Clickheretoviewcodeimage
traits/hastype.hpp
#include<type_traits>//fortrue_type,false_type,andvoid_t
#defineDEFINE_HAS_TYPE(MemType)\
template<typename,typename=std::void_t<>>\
structHasTypeT_##MemType\
:std::false_type{};\
template<typenameT>\
structHasTypeT_##MemType<T,std::void_t<typenameT::MemType>>\
:std::true_type{}//;intentionallyskipped
EachuseofDEFINE_HAS_TYPE(MemberType)definesanew
HasTypeT_MemberTypetrait.Forexample,wecanuseittodetectwhethera
typehasavalue_typeorachar_typemembertypeasfollows:Clickhere
toviewcodeimage
traits/hastype.cpp
#include"hastype.hpp"
#include<iostream>
#include<vector>
DEFINE_HAS_TYPE(value_type);
DEFINE_HAS_TYPE(char_type);
intmain()
{
std::cout<<"int::value_type:"
<<HasTypeT_value_type<int>::value<<’\n’;
std::cout<<"std::vector<int>::value_type:"
<<HasTypeT_value_type<std::vector<int>>::value<<’\n’;
std::cout<<"std::iostream::value_type:"
<<HasTypeT_value_type<std::iostream>::value<<’\n’;
std::cout<<"std::iostream::char_type:"
<<HasTypeT_char_type<std::iostream>::value<<’\n’;
}
19.6.3DetectingNontypeMembers
Wecanmodifythetraittoalsocheckfordatamembersand(single)member
functions:Clickheretoviewcodeimage
traits/hasmember.hpp
#include<type_traits>//fortrue_type,false_type,andvoid_t
#defineDEFINE_HAS_MEMBER(Member)\
template<typename,typename=std::void_t<>>\
structHasMemberT_##Member\
:std::false_type{};\
template<typenameT>\
structHasMemberT_##Member<T,std::void_t<decltype(&T::Member)>>\
:std::true_type{}//;intentionallyskipped
Here,weuseSFINAEtodisablethepartialspecializationwhen&T::Member
isnotvalid.Forthatconstructtobevalid,thefollowingmustbetrue:•Member
mustunambiguouslyidentifyamemberofT(e.g.,itcannotbeanoverloaded
memberfunctionname,orthenameofmultipleinheritedmembersofthesame
name),•Themembermustbeaccessible,
•Themembermustbeanontype,nonenumeratormember(otherwisetheprefix
&wouldbeinvalid),and•IfT::Memberisastaticdatamember,itstype
mustnotprovideanoperator&thatmakes&T::Memberinvalid(e.g.,by
makingthatoperatorinaccessible).Wecanusethetemplateasfollows:Click
heretoviewcodeimage
traits/hasmember.cpp
#include"hasmember.hpp"
#include<iostream>
#include<vector>
#include<utility>
DEFINE_HAS_MEMBER(size);
DEFINE_HAS_MEMBER(first);
intmain()
{
std::cout<<"int::size:"
<<HasMemberT_size<int>::value<<’\n’;
std::cout<<"std::vector<int>::size:"
<<HasMemberT_size<std::vector<int>>::value<<’\n’;
std::cout<<"std::pair<int,int>::first:"
<<HasMemberT_first<std::pair<int,int>>::value<<’\n’;
}
Itwouldnotbedifficulttomodifythepartialspecializationtoexcludecases
where&T::Memberisnotapointer-to-membertype(whichamountsto
excludingstaticdatamembers).Similarly,apointer-to-memberfunctioncould
beexcludedorrequiredtolimitthetraittodatamembersormemberfunctions.
DetectingMemberFunctions
NotethattheHasMembertraitonlycheckswhetherasinglememberwiththe
correspondingnameexists.Thetraitalsowillfailiftwomembersexists,which
mighthappenifwecheckforoverloadedmemberfunctions.Forexample:Click
heretoviewcodeimage
DEFINE_HAS_MEMBER(begin);
std::cout<<HasMemberT_begin<std::vector<int>>::value;//false
However,asexplainedinSection8.4.1onpage133,theSFINAEprinciple
protectsagainstattemptstocreatebothinvalidtypesandexpressionsina
functiontemplatedeclaration,allowingtheoverloadingtechniqueaboveto
extendtotestingwhetherarbitraryexpressionsarewell-formed.
Thatis,wecansimplycheckwhetherwecancallafunctionofinterestina
specificwayandthatcansucceedevenifthefunctionisoverloaded.Aswiththe
IsConvertibleTtraitinSection19.5onpage428,thetrickistoformulate
theexpressionthatcheckswhetherwecancallbegin()insideadecltype
expressionforthedefaultvalueofanadditionalfunctiontemplateparameter:
Clickheretoviewcodeimage
traits/hasbegin.hpp
#include<utility>//fordeclval
#include<type_traits>//fortrue_type,false_type,andvoid_t
//primarytemplate:
template<typename,typename=std::void_t<>>
structHasBeginT:std::false_type{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT>
structHasBeginT<T,std::void_t<decltype(std::declval<T>().begin())>>
:std::true_type{
};
Here,weuse
Clickheretoviewcodeimage
decltype(std::declval<T>().begin())totestwhether,givena
value/objectoftypeT(usingstd::declvaltoavoidanyconstructor
beingrequired),callingamemberbegin()isvalid.22
DetectingOtherExpressions
Wecanusethetechniqueaboveforotherkindsofexpressionsandevencombine
multipleexpressions.Forexample,wecantestwhether,giventypesT1andT2,
thereisasuitable<operatordefinedforvaluesofthesetypes:Clickheretoview
codeimage
traits/hasless.hpp
#include<utility>//fordeclval
#include<type_traits>//fortrue_type,false_type,andvoid_t
//primarytemplate:
template<typename,typename,typename=std::void_t<>>
structHasLessT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT1,typenameT2>
structHasLessT<T1,T2,std::void_t<decltype(std::declval<T1>()
<std::declval<T2>())>>
:std::true_type
{
};
Asalways,thechallengeistodefineavalidexpressionfortheconditionto
checkandusedecltypetoplaceitinaSFINAEcontext,whereitwillcausea
fallbacktotheprimarytemplateiftheexpressionisn’tvalid:Clickheretoview
codeimage
decltype(std::declval<T1>()<std::declval<T2>())Traitsthatdetect
validexpressionsinthismannerarefairlyrobust:Theywill
evaluatetrueonlywhentheexpressioniswell-formedandwill
correctlyreturnfalsewhenthe<operatorisambiguous,deleted,or
inaccessible.23
Wecanusethetraitasfollows:
Clickheretoviewcodeimage
HasLessT<int,char>::value//yieldstrue
HasLessT<std::string,std::string>::value//yieldstrue
HasLessT<std::string,int>::value//yieldsfalse
HasLessT<std::string,char*>::value//yieldstrue
HasLessT<std::complex<double>,std::complex<double>>::value//yields
false
AsintroducedinSection2.3.1onpage30,wecanusethistraittorequirethata
templateparameterTsupportsoperator<:Clickheretoviewcodeimage
template<typenameT>
classC
{
static_assert(HasLessT<T>::value,
"ClassCrequirescomparableelements");
…
};
Notethat,duetothenatureofstd::void_t,wecancombinemultiple
constraintsinatrait:Clickheretoviewcodeimage
traits/hasvarious.hpp
#include<utility>//fordeclval
#include<type_traits>//fortrue_type,false_type,andvoid_t
//primarytemplate:
template<typename,typename=std::void_t<>>
structHasVariousT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT>
structHasVariousT<T,std::void_t<decltype(std::declval<T>().begin()),
typenameT::difference_type,
typenameT::iterator>>
:std::true_type
{
};
Traitsthatdetectthevalidityofaspecificsyntaxarequitepowerful,allowinga
templatetocustomizeitsbehaviorbasedonthepresenceorabsenceofa
particularoperation.Thesetraitswillbeusedagainbothaspartofthedefinition
ofSFINAE-friendlytraits(Section19.4.4onpage424)andtoaidinoverloading
basedontypeproperties(Chapter20).
19.6.4UsingGenericLambdastoDetectMembers
TheisValidlambda,introducedinSection19.4.3onpage421,providesa
morecompacttechniquetodefinetraitsthatcheckformembers,helpingisto
avoidtheuseofmacrostohandlemembersifarbitrarynames.
Thefollowingexampleillustrateshowtodefinetraitscheckingwhetheradata
ortypemembersuchasfirstorsize_typeexistsorwhetheroperator<
isdefinedfortwoobjectsofdifferenttypes:Clickheretoviewcodeimage
traits/isvalid1.cpp
#include"isvalid.hpp"
#include<iostream>
#include<string>
#include<utility>
intmain()
{
usingnamespacestd;
cout<<boolalpha;
//definetocheckfordatamemberfirst:
constexprautohasFirst
=isValid([](autox)->decltype((void)valueT(x).first){
});
cout<<"hasFirst:"<<hasFirst(type<pair<int,int>>)<<’\n’;//true
//definetocheckformembertypesize_type:
constexprautohasSizeType
=isValid([](autox)->typenamedecltype(valueT(x))::size_type{
});
structCX{
usingsize_type=std::size_t;
};
cout<<"hasSizeType:"<<hasSizeType(type<CX>)<<’\n’;//true
ifconstexpr(!hasSizeType(type<int>)){
cout<<"inthasnosize_type\n";
…
}
//definetocheckfor<:
constexprautohasLess
=isValid([](autox,autoy)->decltype(valueT(x)<valueT(y)){
});
cout<<hasLess(42,type<char>)<<’\n’;//yieldstrue
cout<<hasLess(type<string>,type<string>)<<’\n’;//yieldstrue
cout<<hasLess(type<string>,type<int>)<<’\n’;//yieldsfalse
cout<<hasLess(type<string>,"hello")<<’\n’;//yieldstrue
}
NoteagainthathasSizeTypeusesstd::decaytoremovethereferences
fromthepassedxbecauseyoucan’taccessatypememberfromareference.If
youskipthat,thetraitswillalwaysyieldfalsebecausethesecondoverloadof
isValidImpl<>()isused.
Tobeabletousethecommongenericsyntax,takingtypesastemplate
parameters,wecanagaindefineadditionalhelpers.Forexample:Clickhereto
viewcodeimage
traits/isvalid2.cpp
#include"isvalid.hpp"
#include<iostream>
#include<string>
#include<utility>
constexprautohasFirst
=isValid([](auto&&x)->decltype((void)&x.first){
});
template<typenameT>
usingHasFirstT=decltype(hasFirst(std::declval<T>()));
constexprautohasSizeType
=isValid([](auto&&x)
->typenamestd::decay_t<decltype(x)>::size_type{
});
template<typenameT>
usingHasSizeTypeT=decltype(hasSizeType(std::declval<T>()));
constexprautohasLess
=isValid([](auto&&x,auto&&y)->decltype(x<y){
});
template<typenameT1,typenameT2>
usingHasLessT=decltype(hasLess(std::declval<T1>(),std::declval<T2>
()));
intmain()
{
usingnamespacestd;
cout<<"first:"<<HasFirstT<pair<int,int>>::value<<’\n’;//true
structCX{
usingsize_type=std::size_t;
};
cout<<"size_type:"<<HasSizeTypeT<CX>::value<<’\n’;//true
cout<<"size_type:"<<HasSizeTypeT<int>::value<<’\n’;//false
cout<<HasLessT<int,char>::value<<’\n’;//true
cout<<HasLessT<string,string>::value<<’\n’;//true
cout<<HasLessT<string,int>::value<<’\n’;//false
cout<<HasLessT<string,char*>::value<<’\n’;//true
}
Now
Clickheretoviewcodeimage
template<typenameT>
usingHasFirstT=decltype(hasFirst(std::declval<T>()));allowsusto
call
Clickheretoviewcodeimage
HasFirstT<std::pair<int,int>>::valuewhichresultsinthecallof
hasFirstforapairoftwoints,whichisevaluatedasdescribed
above.
19.7OtherTraitsTechniques
Let’sfinallyintroduceanddiscusssomeotherapproachestodefinetraits.
19.7.1If-Then-Else
Intheprevioussection,thefinaldefinitionofthePlusResultTtraithada
completelydifferentimplementationdependingontheresultofanothertype
trait,HasPlusT.Wecanformulatethisif-then-elsebehaviorwithaspecialtype
templateIfThenElsethattakesaBooleannontypetemplateparameterto
selectoneoftwotypeparameters:Clickheretoviewcodeimage
traits/ifthenelse.hpp
#ifndefIFTHENELSE_HPP
#defineIFTHENELSE_HPP
//primarytemplate:yieldthesecondargumentbydefaultandrelyon
//apartialspecializationtoyieldthethirdargument
//ifCONDisfalse
template<boolCOND,typenameTrueType,typenameFalseType>
structIfThenElseT{
usingType=TrueType;
};
//partialspecialization:falseyieldsthirdargument
template<typenameTrueType,typenameFalseType>
structIfThenElseT<false,TrueType,FalseType>{
usingType=FalseType;
};
template<boolCOND,typenameTrueType,typenameFalseType>
usingIfThenElse=typenameIfThenElseT<COND,TrueType,
FalseType>::Type;
#endif//IFTHENELSE_HPP
Thefollowingexampledemonstratesanapplicationofthistemplatebydefining
atypefunctionthatdeterminesthelowest-rankedintegertypeforagivenvalue:
Clickheretoviewcodeimage
traits/smallestint.hpp
#include<limits>
#include"ifthenelse.hpp"
template<autoN>
structSmallestIntT{
usingType=
typenameIfThenElseT<N<=std::numeric_limits<char>::max(),char,
typenameIfThenElseT<N<=std::numeric_limits<short>::max(),short,
typenameIfThenElseT<N<=std::numeric_limits<int>::max(),int,
typenameIfThenElseT<N<=std::numeric_limits<long>::max(),long,
typenameIfThenElseT<N<=std::numeric_limits<longlong>::max(),
longlong,//then
void//fallback
>::Type
>::Type
>::Type
>::Type
>::Type;
};
Notethat,unlikeanormalC++if-then-elsestatement,thetemplatearguments
forboththe“then”and“else”branchesareevaluatedbeforetheselectionis
made,soneitherbranchmaycontainill-formedcode,ortheprogramislikelyto
beill-formed.Consider,forexample,atraitthatyieldsthecorresponding
unsignedtypeforagivensignedtype.Thereisastandardtrait,
std::make_unsigned,whichdoesthisconversion,butitrequiresthatthe
passedtypeisasignedintegraltypeandnobool;otherwiseitsuseresultsin
undefinedbehavior(seeSectionD.4onpage729).Soitmightbeagoodideato
implementatraitthatyieldsthecorrespondingunsignedtypeifthisispossible
andthepassedtypeotherwise(thus,avoidingundefinedbehaviorifan
inappropriatetypeisgiven).Thenaiveimplementationdoesnotwork:Click
heretoviewcodeimage
//ERROR:undefinedbehaviorifTisboolornointegraltype:
template<typenameT>
structUnsignedT{
usingType=IfThenElse<std::is_integral<T>::value
&&!std::is_same<T,bool>::value,
typenamestd::make_unsigned<T>::type,
T>;
};
TheinstantiationofUnsignedT<bool>isstillundefinedbehavior,because
thecompilerwillstillattempttoformthetypefromClickheretoviewcode
image
typenamestd::make_unsigned<T>::typeToaddressthisproblem,weneed
toaddanadditionallevelofindirection,sothattheIfThenElse
argumentsarethemselvesusesoftypefunctionsthatwraptheresult:
Clickheretoviewcodeimage
//yieldTwhenusingmemberType:
template<typenameT>
structIdentityT{
usingType=T;
};
//tomakeunsignedafterIfThenElsewasevaluated:
template<typenameT>
structMakeUnsignedT{
usingType=typenamestd::make_unsigned<T>::type;
};
template<typenameT>
structUnsignedT{
usingType=typenameIfThenElse<std::is_integral<T>::value
&&!std::is_same<T,bool>::value,
MakeUnsignedT<T>,
IdentityT<T>
>::Type;
};
InthisdefinitionofUnsignedT,thetypeargumentstoIfThenElseareboth
instancesoftypefunctionsthemselves.However,thetypefunctionsarenot
actuallyevaluatedbeforeIfThenElseselectsone.Instead,IfThenElse
selectsthetypefunctioninstance(ofeitherMakeUnsignedTor
IdentityT).The::Typethenevaluatestheselectedtypefunctioninstance
toproduceType.
Itisworthemphasizingherethatthisreliesentirelyonthefactthatthenot-
selectedwrappertypeintheIfThenElseconstructisneverfullyinstantiated.
Inparticular,thefollowingdoesnotwork:Clickheretoviewcodeimage
template<typenameT>
structUnsignedT{
usingType=typenameIfThenElse<std::is_integral<T>::value
&&!std::is_same<T,bool>::value,
MakeUnsignedT<T>::Type,
T
>::Type;
};
Instead,wehavetoapplythe::TypeforMakeUnsignedT<T>later,which
meansthatweneedtheIdentityThelpertoalsoapply::TypelaterforTin
theelsebranch.
Thisalsomeansthatwecannotusesomethinglike
Clickheretoviewcodeimage
template<typenameT>
usingIdentity=typenameIdentityT<T>::Type;inthiscontext.Wecan
declaresuchanaliastemplate,anditmaybeusefulelsewhere,but
wecannotmakeeffectiveuseofitinthedefinitionofIfThenElse
becauseanyuseofIdentity<T>immediatelycausesthefull
instantiationofIdentityT<T>toretrieveitsTypemember.
TheIfThenElseTtemplateisavailableintheC++standardlibraryas
std::conditional<>(seeSectionD.5onpage732).Withit,the
UnsignedTtraitwouldbedefinedasfollows:Clickheretoviewcodeimage
template<typenameT>
structUnsignedT{
usingType
=typenamestd::conditional_t<std::is_integral<T>::value
&&!std::is_same<T,bool>::value,
MakeUnsignedT<T>,
IdentityT<T>
>::Type;
};
19.7.2DetectingNonthrowingOperations
Itisoccasionallyusefultodeterminewhetheraparticularoperationcanthrowan
exception.Forexample,amoveconstructorshouldbemarkednoexcept,
indicatingthatitdoesnotthrowexceptions,wheneverpossible.However,
whetheramoveconstructorforaparticularclassthrowsexceptionsornotoften
dependsonwhetherthemoveconstructorsofitsmembersandbasesthrow.For
example,considerthemoveconstructorforasimpleclasstemplatePair:Click
heretoviewcodeimage
template<typenameT1,typenameT2>
classPair{
T1first;
T2second;
public:
Pair(Pair&&other)
:first(std::forward<T1>(other.first)),
second(std::forward<T2>(other.second)){
}
};
Pair’smoveconstructorcanthrowexceptionswhenthemoveoperationsof
eitherT1orT2canthrow.Givenatrait
IsNothrowMoveConstructibleT,wecanexpressthispropertybyusinga
computednoexceptexceptionspecificationinPair’smoveconstructor.For
example:Clickheretoviewcodeimage
Pair(Pair&&other)noexcept(IsNothrowMoveConstructibleT<T1>::value&&
IsNothrowMoveConstructibleT<T2>::value)
:first(std::forward<T1>(other.first)),
second(std::forward<T2>(other.second))
{
}
AllthatremainsistoimplementtheIsNothrowMoveConstructibleT
trait.Wecandirectlyimplementthistraitusingthenoexceptoperator,which
determineswhetheragivenexpressionisguaranteedtobenonthrowing:Click
heretoviewcodeimage
traits/isnothrowmoveconstructible1.hpp
#include<utility>//fordeclval
#include<type_traits>//forbool_constant
template<typenameT>
structIsNothrowMoveConstructibleT
:std::bool_constant<noexcept(T(std::declval<T>()))>
{
};
Here,theoperatorversionofnoexceptisused,whichdetermineswhetheran
expressionisnon-throwing.BecausetheresultisaBooleanvalue,wecanpassit
directlytodefinethebaseclass,std::bool_constant<>,whichisusedto
definestd::true_typeandstd::false_type(seeSection19.3.3on
page411).24
However,thisimplementationshouldbeimprovedbecauseitisnotSFINAE-
friendly(seeSection19.4.4onpage424):Ifthetraitisinstantiatedwithatype
thatdoesnothaveausablemoveorcopyconstructor—makingtheexpression
T(std::declval<T&&>())invalid—theentireprogramisill-formed:
Clickheretoviewcodeimage
classE{
public:
E(E&&)=delete;
};
…
std::cout<<IsNothrowMoveConstructibleT<E>::value;//compile-time
ERROR
Insteadofabortingthecompilation,thetypetraitshouldyieldavalueof
false.
AsdiscussedinSection19.4.4onpage424,wehavetocheckwhetherthe
expressioncomputingtheresultisvalidbeforeitisevaluated.Here,wehaveto
findoutwhethermoveconstructionisvalidbeforecheckingwhetheritis
noexcept.Thus,werevisethefirstversionofthetraitbyaddingatemplate
parameterthatdefaultstovoidandapartialthatusesstd::void_tforthat
parameterwithanargumentthatisvalidonlyifmoveconstructionisvalid:
Clickheretoviewcodeimage
traits/isnothrowmoveconstructible2.hpp
#include<utility>//fordeclval
#include<type_traits>//fortrue_type,false_type,and
bool_constant<>
//primarytemplate:
template<typenameT,typename=std::void_t<>>
structIsNothrowMoveConstructibleT:std::false_type
{
};
//partialspecialization(maybeSFINAE’daway):
template<typenameT>
structIsNothrowMoveConstructibleT
<T,std::void_t<decltype(T(std::declval<T>()))>>
:std::bool_constant<noexcept(T(std::declval<T>()))>
{
};
Ifthesubstitutionofstd::void_t<…>inthepartialspecializationisvalid,
thatspecializationisselected,andthenoexcept(…)expressioninthebase
classspecifiercansafelybeevaluated.Otherwise,thepartialspecializationis
discardedwithoutinstantiatingit,andtheprimarytemplateisinstantiated
instead(producingastd::false_typeresult.
Notethatthereisnowaytocheckwhetheramoveconstructorthrowswithout
beingabletocallitdirectly.Thatis,itisnotenoughthatthemoveconstructoris
publicandnotdeleted,italsorequiresthatthecorrespondingtypeisnoabstract
class(referencesorpointerstoabstractclassesworkfine).Forthisreason,the
typetraitisnamedIsNothrowMoveConstructibleinsteadofHasNothrowMove-
Constructor.Foranythingelse,we’dneedcompilersupport.
TheC++standardlibraryprovidesacorrespondingtypetrait
std::is_move_constructible<>,whichisdescribedinSectionD.3.2
onpage721.
19.7.3TraitsConvenience
Onecommoncomplaintwithtypetraitsistheirrelativeverbosity,becauseeach
useofatypetraittypicallyrequiresatrailing::Typeand,inadependent
context,aleadingtypenamekeyword,bothofwhichareboilerplate.When
multipletypetraitsarecomposed,thiscanforcesomeawkwardformatting,asin
ourrunningexampleofthearrayoperator+,ifwewouldimplementit
correctly,ensuringthatnoconstantorreferencetypeisreturned:Clickhereto
viewcodeimage
template<typenameT1,typenameT2>
Array<
typenameRemoveCVT<
typenameRemoveReferenceT<
typenamePlusResultT<T1,T2>::Type
>::Type
>::Type
>
operator+(Array<T1>const&,Array<T2>const&);Byusingalias
templatesandvariabletemplates,wecanmaketheusageofthe
traits,yieldingtypesorvaluesrespectivelymoreconvenient.
However,notethatinsomecontextstheseshortcutsarenotusable,
andwehavetousetheoriginalclasstemplateinstead.Wealready
discussedonesuchsituationinourMemberPointerToIntTexample,but
amoregeneraldiscussionfollows.
AliasTemplatesandTraits
AsintroducedinSection2.8onpage39,aliastemplatesofferawaytoreduce
verbosity.Ratherthanexpressingatypetraitasaclasstemplatewithatype
memberType,wecanuseanaliastemplatedirectly.Forexample,thefollowing
threealiastemplateswrapthetypetraitsusedabove:Clickheretoviewcode
image
template<typenameT>
usingRemoveCV=typenameRemoveCVT<T>::Type;
template<typenameT>
usingRemoveReference=typenameRemoveReferenceT<T>::Type;
template<typenameT1,typenameT2>
usingPlusResult=typenamePlusResultT<T1,T2>::Type;Giventhese
aliastemplates,wecansimplifyouroperator+declarationdownto
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
Array<RemoveCV<RemoveReference<PlusResultT<T1,T2>>>>
operator+(Array<T1>const&,Array<T2>const&);Thissecondversion
isclearlyshorterandmakesthecompositionofthetraitsmore
obvious.Suchimprovementsmakealiastemplatesmoresuitablefor
someusesoftypetraits.
However,therearedownsidestousingaliastemplatesfortypetraits:1.Alias
templatescannotbespecialized(asnotedinSection16.3onpage338),and
sincemanyofthetechniquesforwritingtraitsdependonspecialization,analias
templatewilllikelyneedtoredirecttoaclasstemplateanyway.
2.Sometraitsaremeanttobespecializedbyusers,suchasatraitthatdescribes
whetheraparticularadditionoperationiscommutative,anditcanbe
confusingtospecializetheclasstemplatewhenmostusesinvolvethealias
template.
3.Theuseofanaliastemplatewillalwaysinstantiatethetype(e.g.,the
underlyingclasstemplatespecialization),whichmakesithardertoavoid
instantiatingtraitsthatdon’tmakesenseforagiventype(asdiscussedin
Section19.7.1onpage440).
Anotherwaytoexpressthislastpoint,isthataliastemplatescannotbeusedwith
metafunctionforwarding(seeSection19.3.2onpage404).
Becausetheuseofaliastemplatesfortypetraitshasbothpositiveand
negativeaspects,werecommendusingthemaswehaveinthissectionandasit
isdoneintheC++standardlibrary:Providebothclasstemplateswithaspecific
namingconvention(wehavechosentheTsuffixandtheTypetypemember)
andaliastemplateswithaslightlydifferentnamingconvention(wedroppedthe
Tsuffix),andhaveeachaliastemplatedefinedintermsoftheunderlyingclass
template.Thisway,wecanusealiastemplateswherevertheyprovideclearer
code,butfallbacktoclasstemplatesformoreadvanceduses.
Notethatforhistoricalreasons,theC++standardlibraryhasadifferent
convention.Thetypetraitsclasstemplatesyieldatypeintypeandhaveno
specificsuffix(manywereintroducedinC++11).Correspondingaliastemplates
(thatproducethetypedirectly)startedbeingintroducedinC++14,andwere
givena_tsuffix,sincetheunsuffixednamewasalreadystandardized(see
SectionD.1onpage697).
VariableTemplatesandTraits
Traitsreturningavaluerequireatrailing::value(orsimilarmember
selection)toproducetheresultofthetrait.Inthiscase,constexprvariable
templates(introducedinSection5.6onpage80)offerawaytoreducethis
verbosity.
Forexample,thefollowingvariabletemplateswraptheIsSameTtrait
definedinSection19.3.3onpage410andtheIsConvertibleTtraitdefined
inSection19.5onpage428:Clickheretoviewcodeimage
template<typenameT1,typenameT2>
constexprboolIsSame=IsSameT<T1,T2>::value;
template<typenameFROM,typenameTO>
constexprboolIsConvertible=IsConvertibleT<FROM,TO>::value;Now
wecansimplywrite
Clickheretoviewcodeimage
if(IsSame<T,int>||IsConvertible<T,char>)…
insteadof
Clickheretoviewcodeimage
if(IsSameT<T,int>::value||IsConvertibleT<T,char>::value)…
Again,forhistoricalreasons,theC++standardlibraryhasadifferent
convention.Thetraitsclasstemplatesproducingaresultinvaluehaveno
specificsuffix,andmanyofthemwereintroducedintheC++11standard.The
correspondingvariabletemplatesthatdirectlyproducetheresultingvaluewere
introducedinC++17witha_vsuffix(seeSectionD.1onpage697).25
19.8TypeClassification
Itissometimesusefultobeabletoknowwhetheratemplateparameterisa
built-intype,apointertype,aclasstype,andsoon.Inthefollowingsections,we
developasuiteoftypetraitsthatallowustodeterminevariouspropertiesofa
giventype.Asaresult,wewillbeabletowritecodespecificforsometypes:
Clickheretoviewcodeimage
if(IsClassT<T>::value){
…
}
or,usingthecompile-timeifavailablesinceC++17(seeSection8.5onpage
134)andthefeaturesfortraitsconvenience(seeSection19.7.3onpage446):
Clickheretoviewcodeimage
ifconstexpr(IsClass<T>){
…
}
or,byusingpartialspecializations:
Clickheretoviewcodeimage
template<typenameT,bool=IsClass<T>>
classC{//primarytemplateforthegeneralcase
…
};
template<typenameT>
classC<T,true>{//partialspecializationforclasstypes
…
};
Furthermore,expressionssuchasIsPointerT<T>::valuewillbeBoolean
constantsthatarevalidnontypetemplatearguments.Inturn,thisallowsthe
constructionofmoresophisticatedandmorepowerfultemplatesthatspecialize
theirbehavioronthepropertiesoftheirtypearguments.
TheC++standardlibrarydefinesseveralsimilartraitstodeterminethe
primaryandcompositetypecategoriesofatype.26SeeSectionD.2.1onpage
702andSectionD.2.2onpage706fordetails.
19.8.1DeterminingFundamentalTypes
Tostart,let’sdevelopatemplatetodeterminewhetheratypeisafundamental
type.Bydefault,weassumeatypeisnotfundamental,andwespecializethe
templateforthefundamentalcases:Clickheretoviewcodeimage
traits/isfunda.hpp
#include<cstddef>//fornullptr_t
#include<type_traits>//fortrue_type,false_type,and
bool_constant<>
//primarytemplate:ingeneralTisnotafundamentaltype
template<typenameT>
structIsFundaT:std::false_type{
};
//macrotospecializeforfundamentaltypes
#defineMK_FUNDA_TYPE(T)\
template<>structIsFundaT<T>:std::true_type{\
};
MK_FUNDA_TYPE(void)
MK_FUNDA_TYPE(bool)
MK_FUNDA_TYPE(char)
MK_FUNDA_TYPE(signedchar)
MK_FUNDA_TYPE(unsignedchar)
MK_FUNDA_TYPE(wchar_t)
MK_FUNDA_TYPE(char16_t)
MK_FUNDA_TYPE(char32_t)
MK_FUNDA_TYPE(signedshort)
MK_FUNDA_TYPE(unsignedshort)
MK_FUNDA_TYPE(signedint)
MK_FUNDA_TYPE(unsignedint)
MK_FUNDA_TYPE(signedlong)
MK_FUNDA_TYPE(unsignedlong)
MK_FUNDA_TYPE(signedlonglong)
MK_FUNDA_TYPE(unsignedlonglong)
MK_FUNDA_TYPE(float)
MK_FUNDA_TYPE(double)
MK_FUNDA_TYPE(longdouble)
MK_FUNDA_TYPE(std::nullptr_t)
#undefMK_FUNDA_TYPE
Theprimarytemplatedefinesthegeneralcase.Thatis,ingeneral,
IsFundaT<T>::valuewillevaluatetofalse:Clickheretoviewcode
image
template<typenameT>
structIsFundaT:std::false_type{
staticconstexprboolvalue=false;
};
Foreachfundamentaltype,aspecializationisdefinedsothat
IsFundaT<T>::valueistrue.Wedefineamacrothatexpandstothe
necessarycodeforconvenience.Forexample:Clickheretoviewcodeimage
MK_FUNDA_TYPE(bool)
expandstothefollowing:
Clickheretoviewcodeimage
template<>structIsFundaT<bool>:std::true_type{
staticconstexprboolvalue=true;
};
Thefollowingprogramdemonstratesapossibleuseofthistemplate:Clickhere
toviewcodeimage
traits/isfundatest.cpp
#include"isfunda.hpp"
#include<iostream>
template<typenameT>
voidtest(Tconst&)
{
if(IsFundaT<T>::value){
std::cout<<"Tisafundamentaltype"<<’\n’;
}
else{
std::cout<<"Tisnotafundamentaltype"<<’\n’;
}
}
intmain()
{
test(7);
test("hello");
}
Ithasthefollowingoutput:
Clickheretoviewcodeimage
Tisafundamentaltype
Tisnotafundamentaltype
Inthesameway,wecandefinetypefunctionsIsIntegralTand
IsFloatingTtoidentifywhichofthesetypesareintegralscalartypesand
whicharefloating-pointscalartypes.
TheC++standardlibraryusesamorefine-grainedapproachthanonlyto
checkwhetheratypeisafundamentaltype.Itfirstdefinesprimarytype
categories,whereeachtypematchesexactlyonetypecategory(seeSection
D.2.1onpage702),andthencompositetypecategoriessuchas
std::is_integralorstd::is_fundamental(seeSectionD.2.2on
page706).
19.8.2DeterminingCompoundTypes
Compoundtypesaretypesconstructedfromothertypes.Simplecompound
typesincludepointertypes,lvalueandrvaluereferencetypes,pointer-to-member
types,andarraytypes.Theyareconstructedfromoneortwounderlyingtypes.
Classtypesandfunctiontypesarealsocompoundtypes,buttheircomposition
caninvolveanarbitrarynumberoftypes(forparametersormembers).
Enumerationtypesarealsoconsiderednonsimplecompoundtypesinthis
classificationeventhoughtheyarenotcompoundfrommultipleunderlying
types.Simplecompoundtypescanbeclassifiedusingpartialspecialization.
Pointers
Westartwithonesuchsimpleclassification,forpointertypes:Clickhereto
viewcodeimage
traits/ispointer.hpp
template<typenameT>
structIsPointerT:std::false_type{//primarytemplate:bydefault
notapointer
};
template<typenameT>
structIsPointerT<T*>:std::true_type{//partialspecializationfor
pointers
usingBaseT=T;//typepointingto
};
Theprimarytemplateisacatch-allcasefornonpointertypesand,asusual,
providesitsvalueconstantfalsethroughisbaseclass
std::false_type,indicatingthatthetypeisnotapointer.Thepartial
specializationcatchesanykindofpointer(T*)andprovidesthevaluetrue
toindicatethattheprovidedtypeisapointer.Additionally,itprovidesatype
memberBaseTthatdescribesthetypethatthepointerpointsto.Notethatthis
typememberisonlyavailablewhentheoriginaltypewasapointer,makingthis
aSFINAE-friendlytypetrait(seeSection19.4.4onpage424).
TheC++standardlibraryprovidesacorrespondingtrait
std::is_pointer<>,which,however,doesnotprovideamemberforthe
typethepointerpointsto.ItisdescribedinSectionD.2.1onpage704.
References
Similarly,wecanidentifylvaluereferencetypes:
Clickheretoviewcodeimage
traits/islvaluereference.hpp
template<typenameT>
structIsLValueReferenceT:std::false_type{//bydefaultnolvalue
reference
};
template<typenameT>
structIsLValueReferenceT<T&>:std::true_type{//unlessTislvalue
references
usingBaseT=T;//typereferringto
};
andrvaluereferencetypes:
Clickheretoviewcodeimage
traits/isrvaluereference.hpp
template<typenameT>
structIsRValueReferenceT:std::false_type{//bydefaultnorvalue
reference
};
template<typenameT>
structIsRValueReferenceT<T&&>:std::true_type{//unlessTisrvalue
reference
usingBaseT=T;//typereferringto
};
whichcanbecombinedintoanIsReferenceT<>trait:Clickheretoview
codeimage
traits/isreference.hpp
#include"islvaluereference.hpp"
#include"isrvaluereference.hpp"
#include"ifthenelse.hpp"
template<typenameT>
classIsReferenceT
:publicIfThenElseT<IsLValueReferenceT<T>::value,
IsLValueReferenceT<T>,
IsRValueReferenceT<T>
>::Type{
};
Inthisimplementation,weuseIfThenElseT(fromSection19.7.1onpage
440)toselectbetweeneitherIsLValueReference<T>or
IsRValueReference<T>asourbaseclass,usingmetafunctionforwarding
(discussedinSection19.3.2onpage404).IfTisanlvaluereference,weinherit
fromIsLValueReference<T>togettheappropriatevalueandBaseT
members.Otherwise,weinheritfromIsRValueReference<T>,which
determineswhetherthetypeisanrvaluereferenceornot(andprovidesthe
appropriatemember(s)ineithercase).
TheC++standardlibraryprovidesthecorrespondingtraits
std::is_lvalue_reference<>and
std::is_rvalue_reference<>,whicharedescribedinSectionD.2.1on
page705,andstd::is_reference<>,whichisdescribedinSectionD.2.2
onpage706.Again,thesetraitsdonotprovideamemberforthetypethe
referencerefersto.
Arrays
Whendefiningtraitstodeterminearrays,itmaycomeasasurprisethatthe
partialspecializationsinvolvemoretemplateparametersthantheprimary
template:Clickheretoviewcodeimage
traits/isarray.hpp
#include<cstddef>
template<typenameT>
structIsArrayT:std::false_type{//primarytemplate:notanarray
};
template<typenameT,std::size_tN>
structIsArrayT<T[N]>:std::true_type{//partialspecializationfor
arrays
usingBaseT=T;
staticconstexprstd::size_tsize=N;
};
template<typenameT>
structIsArrayT<T[]>:std::true_type{//partialspecializationfor
unboundarrays
usingBaseT=T;
staticconstexprstd::size_tsize=0;
};
Here,multipleadditionalmembersprovideinformationaboutthearraysbeing
classified:theirbasetypeandtheirsize(with0usedtodenoteanunknownsize).
TheC++standardlibraryprovidesthecorrespondingtrait
std::is_array<>tocheckwhetheratypeisanarray,whichisdescribedin
SectionD.2.1onpage704.Inaddition,traitssuchasstd::rank<>and
std::extent<>allowustoquerytheirnumberofdimensionsandthesizeof
aspecificdimension(seeSectionD.3.1onpage715).
PointerstoMembers
Pointerstomemberscanbetreatedusingthesametechnique:Clickheretoview
codeimage
traits/ispointertomember.hpp
template<typenameT>
structIsPointerToMemberT:std::false_type{//bydefaultnopointer-
to-member
};
template<typenameT,typenameC>
structIsPointerToMemberT<TC::*>:std::true_type{//partial
specialization
usingMemberT=T;
usingClassT=C;
};
Here,theadditionalmembersprovideboththetypeofthememberandthetype
oftheclassinwhichthatmemberoccurs.
TheC++standardlibraryprovidesmorespecifictraits,
std::is_member_object_pointer<>and
std::is_member_function_pointer<>,whicharedescribedin
SectionD.2.1onpage705,aswellasstd::is_member_pointer<>,
whichisdescribedinSectionD.2.2onpage706.
19.8.3IdentifyingFunctionTypes
Functiontypesareinterestingbecausetheyhaveanarbitrarynumberof
parametersinadditiontotheresulttype.Therefore,withinthepartial
specializationmatchingafunctiontype,weemployaparameterpacktocapture
alloftheparametertypes,aswedidfortheDecayTtraitinSection19.3.2on
page404:Clickheretoviewcodeimage
traits/isfunction.hpp
#include"../typelist/typelist.hpp"
template<typenameT>
structIsFunctionT:std::false_type{//primarytemplate:nofunction
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…)>:std::true_type{//functions
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=false;
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…,…)>:std::true_type{//variadic
functions
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=true;
};
Notethateachpartofthefunctiontypeisexposed:Typeprovidestheresulttype,
whilealloftheparametersarecapturedinasingletypelistasParamsT(typelists
arecoveredinChapter24),andvariadicindicateswhetherthefunctiontypeuses
C-stylevarargs.
Unfortunately,thisformulationofIsFunctionTdoesnothandleall
functiontypes,becausefunctiontypescanhaveconstandvolatile
qualifiersaswellaslvalue(&)andrvalue(&&)referencequalifiers(describedin
SectionC.2.1onpage684)and,sinceC++17,noexceptqualifiers.For
example:Clickheretoviewcodeimage
usingMyFuncType=void(int&)const;Suchfunctiontypescanonlybe
meaningfullyusedfornonstaticmemberfunctionsbutarefunction
typesnonetheless.Moreover,afunctiontypemarkedconstisnot
actuallyaconsttype,27soRemoveConstisnotabletostripthe
constfromthefunctiontype.Therefore,torecognizefunctiontypes
thathavequalifiers,weneedtointroducealargenumberof
additionalpartialspecializations,coveringeverycombinationof
qualifiers(bothwithandwithoutC-stylevarargs).Here,we
illustrateonlyfiveofthemany28requiredpartialspecializations:
Clickheretoviewcodeimage
template<typenameR,typename…Params>
structIsFunctionT<R(Params…)const>:std::true_type{
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=false;
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…,…)volatile>:std::true_type{
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=true;
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…,…)constvolatile>:std::true_type{
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=true;
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…,…)&>:std::true_type{
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=true;
};
template<typenameR,typename…Params>
structIsFunctionT<R(Params…,…)const&>:std::true_type{
usingType=R;
usingParamsT=Typelist<Params…>;
staticconstexprboolvariadic=true;
};
…
Withallthisinplace,wecannowclassifyalltypesexceptclasstypesand
enumerationtypes.Wetacklethesecasesinthefollowingsections.
TheC++standardlibraryprovidesthetraitstd::is_function<>,which
isdescribedinSectionD.2.1onpage706.
19.8.4DeterminingClassTypes
Unliketheothercompoundtypeswehavehandledsofar,wehavenopartial
specializationpatternsthatmatchclasstypesspecifically.Norisitfeasibleto
enumerateallclasstypes,asitisforfundamentaltypes.Instead,weneedtouse
anindirectmethodtoidentifyclasstypes,bycomingupwithsomekindoftype
orexpressionthatisvalidforallclasstypes(andnotothertype).Withthattype
orexpression,wecanapplytheSFINAEtraittechniquesdiscussedinSection
19.4onpage416.
Themostconvenientpropertyofclasstypestoutilizeinthiscaseisthatonly
classtypescanbeusedasthebasisofpointer-to-membertypes.Thatis,inatype
constructoftheformXY::*,Ycanonlybeaclasstype.Thefollowing
formulationofIsClassT<>exploitsthisproperty(andpicksintarbitrarily
fortypeX):Clickheretoviewcodeimage
traits/isclass.hpp
#include<type_traits>
template<typenameT,typename=std::void_t<>>
structIsClassT:std::false_type{//primarytemplate:bydefaultno
class
};
template<typenameT>
structIsClassT<T,std::void_t<intT::*>>//classescanhavepointer-
to-member
:std::true_type{
};
TheC++languagespecifiesthatthetypeofalambdaexpressionisa“unique,
unnamednon-unionclasstype.”Forthisreason,lambdaexpressionsyieldtrue
whenexaminingwhethertheyareclasstypeobjects:Clickheretoviewcode
image
autol=[]{};
static_assert<IsClassT<decltype(l)>::value,"">;//succeeds
NotealsothattheexpressionintT::*isalsovalidforuniontypes(theyare
alsoclasstypesaccordingtotheC++standard).
TheC++standardlibraryprovidesthetraitsstd::is_class<>and
std::is_union<>,whicharedescribedinSectionD.2.1onpage705.
However,thesetraitsrequirespecialcompilersupportbecausedistinguishing
classandstructtypesfromuniontypescannotcurrentlybedoneusing
anystandardcorelanguagetechniques.29
19.8.5DeterminingEnumerationTypes
Theonlytypesnotyetclassifiedbyanyofourtraitsareenumerationtypes.
TestingforenumerationtypescanbeperformeddirectlybywritingaSFINAE-
basedtraitthatchecksforanexplicitconversiontoanintegraltype(say,int)
andexplicitlyexcludingfundamentaltypes,classtypes,referencetypes,pointer
types,andpointer-to-membertypes,allofwhichcanbeconvertedtoanintegral
typebutarenotenumerationtypes.30Instead,wesimplynotethatanytypethat
doesnotfallintoanyoftheothercategoriesmustbeanenumerationtype,which
wecanimplementasfollows:Clickheretoviewcodeimage
traits/isenum.hpp
template<typenameT>
structIsEnumT{
staticconstexprboolvalue=!IsFundaT<T>::value&&
!IsPointerT<T>::value&&
!IsReferenceT<T>::value&&
!IsArrayT<T>::value&&
!IsPointerToMemberT<T>::value&&
!IsFunctionT<T>::value&&
!IsClassT<T>::value;
};
TheC++standardlibraryprovidesthetraitstd::is_enum<>,whichis
describedinSectionD.2.1onpage705.Usually,toimprovecompilation
performance,compilerswilldirectlyprovidesupportforsuchatraitinsteadof
implementingitas“anythingelse.”
19.9PolicyTraits
Sofar,ourexamplesoftraitstemplateshavebeenusedtodetermineproperties
oftemplateparameters:whatsortoftypetheyrepresent,theresulttypeofan
operatorappliedtovaluesofthattype,andsoforth.Suchtraitsarecalled
propertytraits.
Incontrast,sometraitsdefinehowsometypesshouldbetreated.Wecallthem
policytraits.Thisisreminiscentofthepreviouslydiscussedconceptofpolicy
classes(andwealreadypointedoutthatthedistinctionbetweentraitsand
policiesisnotentirelyclear),butpolicytraitstendtobeuniqueproperties
associatedwithatemplateparameter(whereaspolicyclassesareusually
independentofothertemplateparameters).
Althoughpropertytraitscanoftenbeimplementedastypefunctions,policy
traitsusuallyencapsulatethepolicyinmemberfunctions.Toillustratethis
notion,let’slookatatypefunctionthatdefinesapolicyforpassingread-only
parameters.
19.9.1Read-OnlyParameterTypes
InCandC++,functioncallargumentsarepassedbyvaluebydefault.This
meansthatthevaluesoftheargumentscomputedbythecallerarecopiedto
locationscontrolledbythecallee.Mostprogrammersknowthatthiscanbe
costlyforlargestructuresandthatforsuchstructuresitisappropriatetopassthe
argumentsbyreference-to-const(orbypointer-to-constinC).Forsmaller
structures,thepictureisnotalwaysclear,andthebestmechanismfroma
performancepointofviewdependsontheexactarchitectureforwhichthecode
isbeingwritten.Thisisnotsocriticalinmostcases,butsometimeseventhe
smallstructuresmustbehandledwithcare.
Withtemplates,ofcourse,thingsgetalittlemoredelicate:Wedon’tknowa
priorihowlargethetypesubstitutedforthetemplateparameterwillbe.
Furthermore,thedecisiondoesn’tdependjustonsize:Asmallstructuremay
comewithanexpensivecopyconstructorthatwouldstilljustifypassingread-
onlyparametersbyreference-to-const.
Ashintedatearlier,thisproblemisconvenientlyhandledusingapolicytraits
templatethatisatypefunction:ThefunctionmapsanintendedargumenttypeT
ontotheoptimalparametertypeTorTconst&.Asafirstapproximation,the
primarytemplatecanuseby-valuepassingfortypesnolargerthantwopointers
andbyreference-to-constforeverythingelse:Clickheretoviewcodeimage
template<typenameT>
structRParam{
usingType=typenameIfThenElseT<sizeof(T)<=2*sizeof(void*),
T,
Tconst&>::Type;
};
Ontheotherhand,containertypesforwhichsizeofreturnsasmallvaluemay
involveexpensivecopyconstructors,sowemayneedmanyspecializationsand
partialspecializations,suchasthefollowing:Clickheretoviewcodeimage
template<typenameT>
structRParam<Array<T>>{
usingType=Array<T>const&;
};
BecausesuchtypesarecommoninC++,itmaybesafertomarkonlysmall
typeswithtrivialcopyandmoveconstructorsasbyvaluetypes31andthen
selectivelyaddotherclasstypeswhenperformanceconsiderationsdictateit(the
std::is_trivially_copy_constructibleand
std::is_trivially_move_constructibletypetraitsarepartofthe
C++standardlibrary).
Clickheretoviewcodeimage
traits/rparam.hpp
#ifndefRPARAM_HPP
#defineRPARAM_HPP
#include"ifthenelse.hpp"
#include<type_traits>
template<typenameT>
structRParam{
usingType
=IfThenElse<(sizeof(T)<=2*sizeof(void*)
&&std::is_trivially_copy_constructible<T>::value
&&std::is_trivially_move_constructible<T>::value),
T,
Tconst&>;
};
#endif//RPARAM_HPP
Eitherway,thepolicycannowbecentralizedinthetraitstemplatedefinition,
andclientscanexploitittogoodeffect.Forexample,let’ssupposewehavetwo
classes,withoneclassspecifyingthatcallingbyvalueisbetterforread-only
arguments:Clickheretoviewcodeimage
traits/rparamcls.hpp
#include"rparam.hpp"
#include<iostream>
classMyClass1{
public:
MyClass1(){
}
MyClass1(MyClass1const&){
std::cout<<"MyClass1copyconstructorcalled\n";
}
};
classMyClass2{
public:
MyClass2(){
}
MyClass2(MyClass2const&){
std::cout<<"MyClass2copyconstructorcalled\n";
}
};
//passMyClass2objectswithRParam<>byvalue
template<>
classRParam<MyClass2>{
public:
usingType=MyClass2;
};
Now,wecandeclarefunctionsthatuseRParam<>forread-onlyargumentsand
callthesefunctions:Clickheretoviewcodeimage
traits/rparam1.cpp
#include"rparam.hpp"
#include"rparamcls.hpp"
//functionthatallowsparameterpassingbyvalueorbyreference
template<typenameT1,typenameT2>
voidfoo(typenameRParam<T1>::Typep1,
typenameRParam<T2>::Typep2)
{
…
}
intmain()
{
MyClass1mc1;
MyClass2mc2;
foo<MyClass1,MyClass2>(mc1,mc2);
}
Unfortunately,therearesomesignificantdownsidestousingRParam.First,the
functiondeclarationissignificantlymessier.Second,andperhapsmore
objectionable,isthefactthatafunctionlikefoo()cannotbecalledwith
argumentdeductionbecausethetemplateparameterappearsonlyinthe
qualifiersofthefunctionparameters.Callsitesmustthereforespecifyexplicit
templatearguments.
Anunwieldyworkaroundforthisoptionistheuseofaninlinewrapper
functiontemplatethatprovidesperfectforwarding(Section15.6.3onpage280),
butitassumestheinlinefunctionwillbeelidedbythecompiler.Forexample:
Clickheretoviewcodeimage
traits/rparam2.cpp
#include"rparam.hpp"
#include"rparamcls.hpp"
//functionthatallowsparameterpassingbyvalueorbyreference
template<typenameT1,typenameT2>
voidfoo_core(typenameRParam<T1>::Typep1,
typenameRParam<T2>::Typep2)
{
…
}
//wrappertoavoidexplicittemplateparameterpassing
template<typenameT1,typenameT2>
voidfoo(T1&&p1,T2&&p2)
{
foo_core<T1,T2>(std::forward<T1>(p1),std::forward<T2>(p2));
}
intmain()
{
MyClass1mc1;
MyClass2mc2;
foo(mc1,mc2);//sameasfoo_core<MyClass1,MyClass2>(mc1,mc2)
}
19.10IntheStandardLibrary
WithC++11,typetraitsbecameanintrinsicpartoftheC++standardlibrary.
Theycomprisemoreorlessalltypefunctionsandtypetraitsdiscussedinthis
chapter.However,forsomeofthem,suchasthetrivialoperationdetectiontraits
andasdiscussedstd::is_union,therearenoknownin-languagesolutions.
Rather,thecompilerprovidesintrinsicsupportforthosetraits.Also,compilers
starttosupporttraitseveniftherearein-languagesolutionstoshortencompile
time.
Forthisreason,ifyouneedtypetraits,werecommendthatyouusetheones
fromtheC++standardlibrarywheneveravailable.Theyarealldescribedin
detailinAppendixD.
Notethat(asdiscussed)sometraitshavepotentiallysurprisingbehavior(atleast
forthenaiveprogrammer).InadditiontothegeneralhintswegiveinSection
11.2.1onpage164andSectionD.1.2onpage700,alsoconsiderthespecific
descriptionsweprovideinAppendixD.
TheC++standardlibraryalsodefinessomepolicyandpropertytraits:•The
classtemplatestd::char_traitsisusedasapolicytraitsparameterbythe
stringandI/Ostreamclasses.
•Toadaptalgorithmseasilytothekindofstandarditeratorsforwhichtheyare
used,averysimplestd::iterator_traitspropertytraitstemplateis
provided(andusedinstandardlibraryinterfaces).
•Thetemplatestd::numeric_limitscanalsobeusefulasaproperty
traitstemplate.
•Lastly,memoryallocationforthestandardcontainertypesishandledusing
policytraitsclasses.SinceC++98,thetemplatestd::allocatoris
providedasthestandardcomponentforthispurpose.WithC++11,the
templatestd::allocator_traitswasaddedtobeabletochangethe
policy/behaviorofallocators(switchingbetweentheclassicalbehaviorand
scopedallocators;thelattercouldnotbeaccommodatedwithinthepre-C++11
framework).
19.11Afternotes
NathanMyerswasthefirsttoformalizetheideaoftraitsparameters.He
originallypresentedthemtotheC++standardizationcommitteeasavehicleto
definehowcharactertypesshouldbetreatedinstandardlibrarycomponents
(e.g.,inputandoutputstreams).Atthattime,hecalledthembaggagetemplates
andnotedthattheycontainedtraits.However,someC++committeemembers
didnotlikethetermbaggage,andthenametraitswaspromotedinstead.The
lattertermhasbeenwidelyusedsincethen.
Clientcodeusuallydoesnotdealwithtraitsatall:Thedefaulttraitsclasses
satisfythemostcommonneeds,andbecausetheyaredefaulttemplate
arguments,theyneednotappearintheclientsourceatall.Thisarguesinfavor
oflongdescriptivenamesforthedefaulttraitstemplates.Whenclientcodedoes
adaptthebehaviorofatemplatebyprovidingacustomtraitsargument,itis
goodpracticetodeclareatypealiasnamefortheresultingspecializationsthatis
appropriateforthecustombehavior.Inthiscasethetraitsclasscanbegivena
longdescriptivenamewithoutsacrificingtoomuchsourceestate.
Traitscanbeusedasaformofreflection,inwhichaprograminspectsitsown
high-levelproperties(suchasitstypestructures).TraitssuchasIsClassTand
PlusResultT,aswellasmanyothertypetraitsthatinspectthetypesinthe
program,implementaformofcompile-timereflection,whichturnsouttobea
powerfulallytometaprogramming(seeChapter23andSection17.9onpage
363).
Theideaofstoringpropertiesoftypesasmembersoftemplatespecializations
datesbacktoatleastthemid-1990s.Amongtheearlierseriousapplicationsof
typeclassificationtemplateswasthe__type_traitsutilityintheSTL
implementationdistributedbySGI(thenknownasSiliconGraphics).TheSGI
templatewasmeanttorepresentsomepropertiesofitstemplateargument(e.g.,
whetheritwasaplainolddatatype(POD)orwhetheritsdestructorwastrivial).
ThisinformationwasthenusedtooptimizecertainSTLalgorithmsforthegiven
type.AninterestingfeatureoftheSGIsolutionwasthatsomeSGIcompilers
recognizedthe__type_traitsspecializationsandprovidedinformation
abouttheargumentsthatcouldnotbederivedusingstandardtechniques.(The
genericimplementationofthe__type_traitstemplatewassafetouse,
albeitsuboptimal.)Boostprovidesarathercompletesetoftypeclassification
templates(see[BoostTypeTraits])thatformedthebasisofthe
<type_traits>headerinthe2011C++standardlibrary.Whilemanyof
thesetraitscanbeimplementedwiththetechniquesdescribedinthischapter,
others(suchasstd::is_pod,fordetectingPODs)requirecompilersupport,
muchlikethe__type_traitsspecializationsprovidedbytheSGIcompilers.
TheuseoftheSFINAEprinciplefortypeclassificationpurposeshadbeen
notedwhenthetypedeductionandsubstitutionruleswereclarifiedduringthe
firststandardizationeffort.However,itwasneverformallydocumented,andasa
result,mucheffortwaslaterspenttryingtore-createsomeofthetechniques
describedinthischapter.Thefirsteditionofthisbookwasoneoftheearliest
sourcesforthistechnique,anditintroducedthetermSFINAE.Oneoftheother
notableearlycontributorsinthisareawasAndreiAlexandrescu,whomade
populartheuseofthesizeofoperatortodeterminetheoutcomeofoverload
resolution.Thistechniquebecamepopularenoughthatthe2011standard
extendedthereachofSFINAEfromsimpletypeerrorstoarbitraryerrorswithin
theimmediatecontextofthefunctiontemplate(see[SpicerSFINAE]).This
extension,incombinationwiththeadditionofdecltype,rvaluereferences,
andvariadictemplates,greatlyexpandedtheabilitytotestforspecificproperties
withintraits.
UsinggenericlambdaslikeisValidtoextracttheessenceofaSFINAE
conditionisatechniqueintroducedbyLouisDionnein2015,whichisusedby
Boost.Hana(see[BoostHana]),ametaprogramminglibrarysuitedforcompile-
timecomputationsonbothtypesandvalues.
Policyclasseshaveapparentlybeendevelopedbymanyprogrammersanda
fewauthors.AndreiAlexandrescumadethetermpolicyclassespopular,andhis
bookModernC++Designcoverstheminmoredetailthanourbriefsection(see
[AlexandrescuDesign]).
1Mostexamplesinthissectionuseordinarypointersforthesakeofsimplicity.
Clearly,anindustrial-strengthinterfacemayprefertouseiteratorparameters
followingtheconventionsoftheC++standardlibrary(see[JosuttisStdLib]).
Werevisitthisaspectofourexamplelater.
2EBCDICisanabbreviationofExtendedBinary-CodedDecimalInterchange
Code,whichisanIBMcharactersetthatiswidelyusedonlargeIBM
computers.
3InC++11,youhavetodeclarethereturntypeliketypeAccT.
4MostmodernC++compilerscan“seethrough”callsofsimpleinline
functions.Additionally,theuseofconstexprmakesitpossibletousethe
valuetraitsincontextswheretheexpressionmustbeaconstant(e.g.,ina
templateargument).
5Wecouldgeneralizethistoapolicyparameter,whichcouldbeaclass(as
discussed)orapointertoafunction.
6Alexandrescuhasbeenthemainvoiceintheworldofpolicyclasses,andhe
hasdevelopedarichsetoftechniquesbasedonthem.
7InC++11,youhavetodeclarethereturntypeasVT.
8Theorderinwhichthequalifiersisremovedhasnosemanticconsequence:
Wecouldfirstremovevolatileandthenconstinstead.
9UsingthetermdecaymightbeslightlyconfusingbecauseinCitonlyimplies
theconversionfromarray/-functiontypestotopointertypes,whereashereit
alsoincludestheremovaloftop-levelconst/volatilequalifiers.
10Strictlyspeaking,thecommapriortothesecondellipsis(…)isoptionalbutis
providedhereforclarity.Duetotheellipsisbeingoptional,thefunctiontype
inthefirstpartialspecializationisactuallysyntacticallyambiguous:Itcanbe
parsedaseitherR(Args,…)(aC-stylevarargsparameter)orR(Args…
name)(aparameterpack).ThesecondinterpretationispickedbecauseArgs
isanunexpandedparameterpack.Wecanexplicitlyaddthecommainthe
(rare)caseswheretheotherinterpretationisdesired.
11ThedifferencebetweenthereturntypesTandT&&isdiscoverablebydirect
useofdecltype.However,givendeclval’slimiteduse,thisisnotof
practicalinterest.
12Thefallbackdeclarationcansometimesbeaplainmemberfunction
declarationinsteadofamemberfunctiontemplate.
13However,theSFINAErulesweremorelimitedbackthen:Whensubstitution
oftemplateargumentsresultedinamalformedtypeconstruct(e.g.,T::X
whereTisint),SFINAEworkedasexpected,butifitresultedinaninvalid
expression(e.g.,sizeof(f())wheref()returnsvoid),SFINAEdidnot
kickinandanerrorwasissuedrightaway.
14Thefirsteditionofthisbookwasperhapsthefirstsourceforthistechnique.
15Definingvoid_tinsidenamespacestdisformallyinvalid:Usercodeisnot
permittedtoadddeclarationstonamespacestd.Inpractice,nocurrent
compilerenforcesthatrestriction,nordotheybehaveunexpectedly(the
standardindicatesthatdoingthisleadsto“undefinedbehavior,”whichallows
anythingtohappen).
16Atthetimeofthiswriting,MicrosoftVisualC++istheunfortunateexception.
17ThankstoLouisDionneforpointingoutthetechniquedescribedinthis
section.
18Thisverysimplepairofhelpertemplatesisafundamentaltechniquethatlies
attheheartofadvancedlibrariessuchasBoost.Hana!
19ThiscodeisnotvalidC++becausealambdaexpressioncannotappeardirectly
inadecltypeoperandforcompiler-technicalreasons,butthemeaningis
clear.
20Forsimplicity,thereturnvaluejustusesPlusResultT<T1,T2>::Type.
Inpractice,thereturntypeshouldalsobecomputedusing
RemoveReferenceT<>andRemoveCVT<>toavoidthatreferencesare
returned.
21Atthetimeofthiswriting,theC++standardizationcommitteeisexploring
waysto“reflect”variousprogramentities(likeclasstypesandtheirmembers)
inwaysthattheprogramcanexplore.SeeSection17.9onpage363.
22Exceptthatdecltype(call-expression)doesnotrequireanonreference,
non-voidreturntypetobecomplete,unlikecallexpressionsinother
contexts.Usingdecltype(std::declval<T>().begin(),0)
insteaddoesaddtherequirementthatthereturntypeofthecalliscomplete,
becausethereturnedvalueisnolongertheresultofthedecltypeoperand.
23PriortoC++11’sexpansionofSFINAEtocoverarbitraryinvalidexpressions,
thetechniquesfordetectingthevalidityofspecificexpressionscenteredon
introducinganewoverloadforthefunctionbeingtested(e.g.,<)thathadan
overlypermissivesignatureandastrangelysizedreturntypetobehaveasa
fallbackcase.However,suchapproacheswerepronetoambiguitiesand
causederrorsduetoaccesscontrolviolations.
24InC++11andC++14,wehavetospecifythebaseclassas
std::integral_constant<bool,…>insteadof
std::bool_constant<…>.
25TheC++standardizationcommitteeisfurtherboundbyalong-standing
traditionthatallstandardnamesconsistoflowercasecharactersandoptional
underscorestoseparatethem.Thatis,anamelikeisSameorIsSameis
unlikelytoeverbeseriouslyconsideredforstandardization(exceptfor
concepts,wherethisspellingstylewillbeused).
26Theuseof“primary”vs.“composite”typecategoriesshouldnotbeconfused
withthedistinctionbetween“fundamental”vs.“compound”types.The
standarddescribesfundamentaltypes(likeintorstd::nullptr_t)and
compoundtypes(likepointertypesandclasstypes).Thisisdifferentfrom
compositetypecategories(likearithmetic),whicharecategoriesthatarethe
unionofprimarytypecategories(likefloating-point).
27Specifically,whenafunctiontypeismarkedconst,itreferstoaqualifieron
theobjectpointedtobytheimplicitparameterthis,whereastheconstona
consttypereferstotheobjectofthatactualtype.
28Thelatestcountis48.
29Mostcompilerssupportintrinsicoperatorslike__is_uniontohelpstandard
librariesimplementvarioustraitstemplates.Thisistrueevenforsometraits
thatcouldtechnicallybeimplementedusingthetechniquesfromthischapter,
becausetheintrinsicscanimprovecompilationperformance.
30Thefirsteditionofthisbookdescribedenumerationtypedetectioninthisway.
However,itcheckedforanimplicitconversiontoanintegraltype,which
sufficedfortheC++98standard.Theintroductionofscopedenumerationtypes
intothelanguage,whichdonothavesuchanimplicitconversion,complicates
thedetectionofenumerationtypes.
31Acopyormoveconstructoriscalledtrivialif,ineffect,acalltoitcanbe
replacedbyasimplecopyoftheunderlyingbytes.
Chapter20
OverloadingonTypeProperties
Functionoverloadingallowsthesamefunctionnametobeusedformultiple
functions,solongasthosefunctionsaredistinguishedbytheirparametertypes.
Forexample:voidf(int);
voidf(charconst*);Withfunctiontemplates,oneoverloadsontypepatterns
suchaspointer-to-TorArray<T>:Clickheretoviewcodeimage
template<typenameT>voidf(T*);
template<typenameT>voidf(Array<T>);Giventheprevalenceoftype
traits(discussedinChapter19),itisnaturaltowanttooverload
functiontemplatesbasedonthepropertiesofthetemplatearguments.
Forexample:Clickheretoviewcodeimage
template<typenameNumber>voidf(Number);//onlyfornumbers
template<typenameContainer>voidf(Container);//onlyforcontainers
However,C++doesnotcurrentlyprovideanydirectwaytoexpressoverloads
basedontypeproperties.Infact,thetwoffunctiontemplatesimmediately
aboveareactuallydeclarationsofthesamefunctiontemplate,ratherthandistinct
overloads,becausethenamesoftemplateparametersareignoredwhen
comparingtwofunctiontemplates.
Fortunately,thereareanumberoftechniquesthatcanbeusedtoemulate
overloadingoffunctiontemplatesbasedontypeproperties.Thesetechniques,as
wellasthecommonmotivationsforsuchoverloading,arediscussedinthis
chapter.
20.1AlgorithmSpecialization
Oneofthecommonmotivationsbehindoverloadingoffunctiontemplatesisto
providemorespecializedversionsofanalgorithmbasedonknowledgeofthe
typesinvolved.Considerasimpleswap()operationtoexchangetwovalues:
Clickheretoviewcodeimage
template<typenameT>
voidswap(T&x,T&y)
{
Ttmp(x);
x=y;
y=tmp;
}
Thisimplementationinvolvesthreecopyoperations.Forsometypes,
however,wecanprovideamoreefficientswap()operation,suchasforan
Array<T>thatstoresitsdataasapointertothearraycontentsandalength:
Clickheretoviewcodeimage
template<typenameT>
voidswap(Array<T>&x,Array<T>&y)
{
swap(x.ptr,y.ptr);
swap(x.len,y.len);
}
Bothimplementationsofswap()willcorrectlyexchangethecontentsoftwo
Array<T>objects.However,thelatterimplementationismoreefficient,
becauseitmakesuseofadditionalpropertiesofanArray<T>(specifically,
knowledgeofptrandlenandtheirrespectiveroles)thatarenotavailablefor
anarbitrarytype.1Thelatterfunctiontemplateistherefore(conceptually)more
specializedthantheformer,becauseitperformsthesameoperationforasubset
ofthetypesacceptedbytheformerfunctiontemplate.Fortunately,thesecond
functiontemplateisalsomorespecializedbasedonthepartialorderingrulesfor
functiontemplates(seeSection16.2.2onpage330),sothecompilerwillpick
themorespecialized(and,therefore,moreefficient)functiontemplatewhenitis
applicable(i.e.,forArray<T>arguments)andfallbacktothemoregeneral
(potentiallylessefficient)algorithmwhenthemorespecializedversionisnot
applicable.
Thedesignandoptimizationapproachofintroducingmorespecialized
variantsofagenericalgorithmiscalledalgorithmspecialization.Themore
specializedvariantsapplytoasubsetofthevalidinputsforthegeneric
algorithm,identifyingthissubsetbasedonthespecifictypesorpropertiesofthe
types,andaretypicallymoreefficientthanthemostgeneralimplementationof
thatgenericalgorithm.
Crucialtotheimplementationofalgorithmspecializationisthepropertythat
themorespecializedvariantsareautomaticallyselectedwhentheyare
applicable,withoutthecallerhavingtobeawarethatthosevariantsevenexist.
Inourswap()example,thiswasaccomplishedbyoverloadingthe
(conceptually)morespecializedfunctiontemplate(thesecondswap())with
themostgeneralfunctiontemplate(thefirstswap()),andensuringthatthe
morespecializedfunctiontemplatewasalsomorespecializedbasedonC++’s
partialorderingrules.
Notallconceptuallymorespecializedalgorithmvariantscanbedirectly
translatedintofunctiontemplatesthatprovidetherightpartialorderingbehavior.
Forournextexample,considerHowever,thealternativepresentedhereismore
broadlyapplicable.
theadvanceIter()function(similartostd::advance()fromthe
C++standardlibrary),whichmovesaniteratorxforwardbynsteps.This
generalalgorithmcanoperateonanyinputiterator:Clickheretoviewcode
image
template<typenameInputIterator,typenameDistance>
voidadvanceIter(InputIterator&x,Distancen)
{
while(n>0){//lineartime
++x;
--n;
}
}
Foracertainclassofiterators—thosethatproviderandomaccessoperations—
wecanprovideamoreefficientimplementation:Clickheretoviewcodeimage
template<typenameRandomAccessIterator,typenameDistance>
voidadvanceIter(RandomAccessIterator&x,Distancen){
x+=n;//constanttime
}
Unfortunately,definingbothofthesefunctiontemplateswillresultinacompiler
error,because—asnotedintheintroduction—functiontemplatesthatdifferonly
basedontheirtemplateparameternamesarenotoverloadable.Theremainderof
thischapterwilldiscusstechniquesthatemulatethedesiredeffectofoverloading
thesefunctiontemplates.
20.2TagDispatching
Oneapproachtoalgorithmspecializationisto“tag”differentimplementation
variantsofanalgorithmwithauniquetypethatidentifiesthatvariant.For
example,todealwiththeadvanceIter()problem,justintroduced,wecan
usethestandardlibrary’siteratorcategorytagtypes(definedbelow)toidentify
thetwoimplementationvariantsoftheadvanceIter()algorithm:Clickhere
toviewcodeimage
template<typenameIterator,typenameDistance>
voidadvanceIterImpl(Iterator&x,Distancen,
std::input_iterator_tag)
{
while(n>0){//lineartime
++x;
--n;
}
}
template<typenameIterator,typenameDistance>
voidadvanceIterImpl(Iterator&x,Distancen,
std::random_access_iterator_tag){
x+=n;//constanttime
}
Then,theadvanceIter()functiontemplateitselfsimplyforwardsits
argumentsalongwiththeappropriatetag:Clickheretoviewcodeimage
template<typenameIterator,typenameDistance>
voidadvanceIter(Iterator&x,Distancen)
{
advanceIterImpl(x,n,
typename
std::iterator_traits<Iterator>::iterator_category());
}
Thetraitclassstd::iterator_traitsprovidesacategoryforthe
iteratorviaitsmembertypeiterator_category.Theiteratorcategoryis
oneofthe_tagtypesmentionedearlier,whichspec-ifieswhatkindofiterator
thetypeis.InsidetheC++standardlibrary,theavailabletagsaredefinedas
follows,usinginheritancetoreflectwhenonetagdescribesacategorythatis
derivedfromanother:2
Clickheretoviewcodeimage
namespacestd{
structinput_iterator_tag{};
structoutput_iterator_tag{};
structforward_iterator_tag:publicinput_iterator_tag{};
structbidirectional_iterator_tag:publicforward_iterator_tag{};
structrandom_access_iterator_tag:publicbidirectional_iterator_tag
{};
}
Thekeytoeffectiveuseoftagdispatchingisintherelationshipamongthetags.
OurtwovariantsofadvanceIterImpl()aretaggedwith
std::input_iterator_tagandwith
std::random_access_iterator_tag,andbecause
std::random_access_iterator_taginheritsfrom
std::input_iterator_tag,normalfunctionoverloadingwillpreferthe
morespecializedalgorithmvariant(whichuses
std::random_access_iterator_tag)whenever
advanceIterImpl()iscalledwitharandomaccessiterator.Therefore,tag
dispatchingreliesondelegationfromthesingle,primaryfunctiontemplatetoa
setof_implvariants,whicharetaggedsuchthatnormalfunctionoverloading
willselectthemostspecializedalgorithmthatisapplicabletothegiventemplate
arguments.
Tagdispatchingworkswellwhenthereisanaturalhierarchicalstructureto
thepropertiesusedbythealgorithmandanexistingsetoftraitsthatprovide
thosetagvalues.Itisnotasconvenientwhenalgorithmspecializationdepends
onadhoctypeproperties,suchaswhetherthetypeThasatrivialcopy
assignmentoperator.Forthat,weneedamorepowerfultechnique.
20.3Enabling/DisablingFunctionTemplates
Algorithmspecializationinvolvesprovidingdifferentfunctiontemplatesthatare
selectedonthebasisofthepropertiesofthetemplatearguments.Unfortunately,
neitherpartialorderingoffunctiontemplates(Section16.2.2onpage330)nor
overloadresolution(AppendixC)ispowerfulenoughtoexpressmoreadvanced
formsofalgorithmspecialization.
OnehelpertheC++standardlibraryprovidesforthisisstd::enable_if,
whichisintroducedinSection6.3onpage98.Thissectiondiscusseshowthis
helpercanbeimplementedbyintroducingacorrespondingaliastemplate,which
we’llcallEnableIftoavoidnameclashes.
Justlikestd::enable_if,theEnableIfaliastemplatecanbeusedto
enable(ordisable)aspecificfunctiontemplateunderspecificconditions.For
example,therandom-accessversionoftheadvanceIter()algorithmcanbe
implementedasfollows:Clickheretoviewcodeimage
template<typenameIterator>
constexprboolIsRandomAccessIterator=
IsConvertible<
typenamestd::iterator_traits<Iterator>::iterator_category,
std::random_access_iterator_tag>;
template<typenameIterator,typenameDistance>
EnableIf<IsRandomAccessIterator<Iterator>>
advanceIter(Iterator&x,Distancen){
x+=n;//constanttime
}
TheEnableIfspecializationhereisusedtoenablethisvariantof
advanceIter()onlywhentheiteratorisinfactarandomaccessiterator.The
twoargumentstoEnableIfareaBooleanconditionindicatingwhetherthis
templateshouldbeenabledandthetypeproducedbytheEnableIfexpansion
whentheconditionistrue.Inourexampleabove,weusedthetypetrait
IsConvertible,introducedinSection19.5onpage428andSection19.7.3
onpage447,asourcondition,todefineatypetrait
IsRandomAccessIterator.Thus,thisspecificversionofour
advanceIter()implementationisonlyconsiderediftheconcretetype
substitutedforIteratorisusableasarandom-accessiterator(i.e.,itis
associatedwithatagconvertibleto
std::random_access_iterator_tag.
EnableIfhasafairlysimpleimplementation:Clickheretoviewcode
image
typeoverload/enableif.hpp
template<bool,typenameT=void>
structEnableIfT{
};
template<typenameT>
structEnableIfT<true,T>{
usingType=T;
};
template<boolCond,typenameT=void>
usingEnableIf=typenameEnableIfT<Cond,T>::Type;EnableIfexpands
toatypeandisthereforeimplementedasanaliastemplate.Wewant
tousepartialspecialization(seeChapter16)foritsimplementation,
butaliastemplatescannotbepartiallyspecialized.Fortunately,we
canintroduceahelperclasstemplateEnableIfT,whichdoestheactual
workweneed,andhavethealiastemplateEnableIfsimplyselectthe
resulttypefromthehelpertemplate.Whentheconditionistrue,
EnableIfT<…>::Type(andthereforeEnableIf<…>)simplyevaluatestothe
secondtemplateargument,T.Whentheconditionisfalse,EnableIf
doesnotproduceavalidtype,becausetheprimaryclasstemplatefor
EnableIfThasnomembernamedType.Normally,thiswouldbeanerror,
butinaSFINAE(substitutionfailureisnotanerror,describedin
Section15.7onpage284)context—suchasthereturntypeofa
functiontemplate—ithastheeffectofcausingtemplateargument
deductiontofail,removingthefunctiontemplatefromconsideration.3
ForadvanceIter(),theuseofEnableIfmeansthatthefunction
templatewillbeavailable(andhaveareturntypeofvoid)whenthe
Iteratorargumentisarandomaccessiterator,andwillberemovedfrom
considerationwhentheIteratorargumentisnotarandomaccessiterator.We
canthinkofEnableIfasawayto“guard”templatesagainstinstantiationwith
templateargumentsthatdon’tmeettherequirementsofthetemplate
implementation,becausethisadvanceIter()canonlybeinstantiatedwitha
randomaccessiteratorasitrequiresoperationsonlyavailableonarandom
accessiterator.WhileusingEnableIfinthismannerisnotbulletproof—the
usercouldassertthatatypeisarandomaccessiteratorwithoutprovidingthe
necessaryoperations—itcanhelpdiagnosecommonmistakesearlier.
Wenowhaveestablishedhowtoexplicitly“activate”themorespecialized
templateforthetypestowhichisapplies.However,thatisnotsufficient:We
alsohaveto“de-activate”thelessspecializedtemplate,becauseacompilerhas
nowayto“order”thetwoandwillreportanambiguityerrorifbothversions
apply.Fortunately,achievingthatisnothard:WejustusethesameEnableIf
patternonthelessspecializedtemplate,exceptthatwenegatethecondition
expression.Doingsoensuresthatexactlyoneofthetwotemplateswillbe
activatedforanyconcreteIteratortype.Thus,ourversionof
advanceIter()foraniteratorthatisnotarandomaccessiteratorbecomes
thefollowing:Clickheretoviewcodeimage
template<typenameIterator,typenameDistance>
EnableIf<!IsRandomAccessIterator<Iterator>>
advanceIter(Iterator&x,Distancen)
{
while(n>0){//lineartime
++x;
--n;
}
}
20.3.1ProvidingMultipleSpecializations
Thepreviouspatterngeneralizestocaseswheremorethantwoalternative
implementationsareneeded:WeequipeachalternativewithEnableIf
constructswhoseconditionsaremutuallyexclusiveforaspecificsetofconcrete
templatearguments.Thoseconditionswilltypicallymakeuseofvarious
propertiesthatcanbeexpressedviatraits.
Consider,forexample,theintroductionofathirdvariantofthe
advanceIter()algorithm:Thistimewewanttopermitmoving“backward”
byspecifyinganegativedistance.4Thatisclearlyinvalidforaninputiterator,
andclearlyvalidforarandomaccessiterator.However,thestandardlibraryalso
includesthenotionofabidirectionaliterator,whichallowsbackwardmovement
withoutrequiringrandomaccess.Implementingthiscaserequiresslightlymore
sophisticatedlogic:EachfunctiontemplatemustuseEnableIfwitha
conditionthatismutuallyexclusivewiththeconditionsofalloftheother
functiontemplatesthatrepresentdifferentvariantsofthealgorithm.Thisresults
inthefollowingsetofconditions:•Randomaccessiterator:Randomaccesscase
(constanttime,forwardorbackward)•Bidirectionaliteratorandnotrandom
access:Bidirectionalcase(lineartime,forwardorbackward)•Inputiteratorand
notbidirectional:Generalcase(lineartime,forward)Thefollowingsetof
functiontemplatesimplementsthis:Clickheretoviewcodeimage
typeoverload/advance2.hpp
#include<iterator>
//implementationforrandomaccessiterators:
template<typenameIterator,typenameDistance>
EnableIf<IsRandomAccessIterator<Iterator>>
advanceIter(Iterator&x,Distancen){
x+=n;//constanttime
}
template<typenameIterator>
constexprboolIsBidirectionalIterator=
IsConvertible<
typenamestd::iterator_traits<Iterator>::iterator_category,
std::bidirectional_iterator_tag>;
//implementationforbidirectionaliterators:
template<typenameIterator,typenameDistance>
EnableIf<IsBidirectionalIterator<Iterator>&&
!IsRandomAccessIterator<Iterator>>
advanceIter(Iterator&x,Distancen){
if(n>0){
for(;n>0;++x,--n){//lineartime
}
}else{
for(;n<0;--x,++n){//lineartime
}
}
}
//implementationforallotheriterators:
template<typenameIterator,typenameDistance>
EnableIf<!IsBidirectionalIterator<Iterator>>
advanceIter(Iterator&x,Distancen){
if(n<0){
throw"advanceIter():invaliditeratorcategoryfornegativen";
}
while(n>0){//lineartime
++x;
--n;
}
}
BymakingtheEnableIfconditionofeachfunctiontemplatemutually
exclusivewiththeEnableIfconditionsofeveryotherfunctiontemplate,we
ensurethat,atmost,oneofthefunctiontemplateswillsucceedtemplate
argumentdeductionforagivensetofarguments.
OurexampleillustratesoneofthedisadvantagesofusingEnableIffor
algorithmspecialization:Eachtimeanewvariantofthealgorithmisintroduced,
theconditionsofallofthealgorithmvariantsneedtoberevisitedtoensurethat
allaremutuallyexclusive.Incontrast,introducingthebidirectional-iterator
variantusingtagdispatching(Section20.2onpage467)requiresjustthe
additionofanewadvanceIterImpl()overloadusingthetag
std::bidirectional_iterator_tag.
Bothtechniques—tagdispatchingandEnableIf—areusefulindifferent
contexts:Generallyspeaking,tagdispatchingsupportssimpledispatchingbased
onhierarchicaltags,whileEnableIfsupportsmoreadvanceddispatching
basedonarbitrarysetsofpropertiesdeterminedbytypetraits.
20.3.2WhereDoestheEnableIfGo?
EnableIfistypicallyusedinthereturntypeofthefunctiontemplate.
However,thisapproachdoesnotworkforconstructortemplatesorconversion
functiontemplates,becauseneitherhasaspecifiedreturntype.5Moreover,the
useofEnableIfcanmakethereturntypeveryhardtoread.Insuchcases,we
caninsteadembedtheEnableIfinadefaultedtemplateargument,asfollows:
Clickheretoviewcodeimage
typeoverload/container1.hpp
#include<iterator>
#include"enableif.hpp"
#include"isconvertible.hpp"
template<typenameIterator>
constexprboolIsInputIterator=
IsConvertible<
typenamestd::iterator_traits<Iterator>::iterator_category,
std::input_iterator_tag>;
template<typenameT>
classContainer{
public:
//constructfromaninputiteratorsequence:
template<typenameIterator,
typename=EnableIf<IsInputIterator<Iterator>>>
Container(Iteratorfirst,Iteratorlast);
//converttoacontainersolongasthevaluetypesareconvertible:
template<typenameU,typename=EnableIf<IsConvertible<T,U>>>
operatorContainer<U>()const;
};
However,thereisaproblemhere.Ifweattempttoaddyetanotheroverload
(e.g.,amoreefficientversionoftheContainerconstructorforrandomaccess
iterators),itwillresultinanerror:Clickheretoviewcodeimage
//constructfromaninputiteratorsequence:
template<typenameIterator,
typename=EnableIf<IsInputIterator<Iterator>&&
!IsRandomAccessIterator<Iterator>>>
Container(Iteratorfirst,Iteratorlast);
template<typenameIterator,
typename=EnableIf<IsRandomAccessIterator<Iterator>>>
Container(Iteratorfirst,Iteratorlast);//ERROR:redeclaration
//ofconstructortemplate
Theproblemisthatthetwoconstructortemplatesareidenticalexceptforthe
defaulttemplateargument,butdefaulttemplateargumentsarenotconsidered
whendeterminingwhethertwotemplatesareequivalent.
Wecanalleviatethisproblembyaddingyetanotherdefaultedtemplate
parameter,sothetwoconstructortemplateshaveadifferentnumberoftemplate
parameters:Clickheretoviewcodeimage
//constructfromaninputiteratorsequence:
template<typenameIterator,
typename=EnableIf<IsInputIterator<Iterator>&&
!IsRandomAccessIterator<Iterator>>>
Container(Iteratorfirst,Iteratorlast);
template<typenameIterator,
typename=EnableIf<IsRandomAccessIterator<Iterator>>,
typename=int>//extradummyparametertoenablebothconstructors
Container(Iteratorfirst,Iteratorlast);//OKnow
20.3.3Compile-Timeif
It’sworthnotingherethatC++17’sconstexpriffeature(seeSection8.5onpage
134)avoidstheneedforEnableIfinmanycases.Forexample,inC++17we
canrewriteouradvanceIter()exampleasfollows:Clickheretoviewcode
image
typeoverload/advance3.hpp
template<typenameIterator,typenameDistance>
voidadvanceIter(Iterator&x,Distancen){
ifconstexpr(IsRandomAccessIterator<Iterator>){
//implementationforrandomaccessiterators:
x+=n;//constanttime
}
elseifconstexpr(IsBidirectionalIterator<Iterator>){
//implementationforbidirectionaliterators:
if(n>0){
for(;n>0;++x,--n){//lineartimeforpositiven
}
}else{
for(;n<0;--x,++n){//lineartimefornegativen
}
}
}
else{
//implementationforallotheriteratorsthatareatleastinput
iterators:
if(n<0){
throw"advanceIter():invaliditeratorcategoryfornegativen";
}
while(n>0){//lineartimeforpositivenonly
++x;
--n;
}
}
}
Thisismuchclearer.Themore-specializedcodepaths(e.g.,forrandomaccess
iterators)willonlybeinstantiatedfortypesthatcansupportthem.Therefore,it
issafeforcodetoinvolveoperationsnotpresentonalliterators(suchas+=)so
longasitiswithinthebodyofanappropriately-guardedifconstexpr.
However,therearedownsides.Usingconstexprifinthiswayisonlypossible
whenthedifferenceinthegenericcomponentcanbeexpressedentirelywithin
thebodyofthefunctiontemplate.WestillneedEnableIfinthefollowing
situations:•Different“interfaces”areinvolved
•Differentclassdefinitionsareneeded
•Novalidinstantiationshouldexistforcertaintemplateargumentlists.
Itistemptingtohandlethatlastsituationwiththefollowingpattern:Clickhere
toviewcodeimage
template<typenameT>
voidf(Tp){
ifconstexpr(condition<T>::value){
//dosomethinghere…
}
else{
//notaTforwhichf()makessense:
static_assert(condition<T>::value,"can’tcallf()forsuchaT");
}
}
Doingsoisnotadvisablebecauseitdoesn’tworkwellwithSFINAE:The
functionf<T>()isnotremovedfromthecandidateslistandthereforemay
inhibitanotheroverloadresolutionoutcome.Inthealternative,using
EnableIff<T>()wouldberemovedaltogetherwhensubstituting
EnableIf<…>failssubstitution.
20.3.4Concepts
Thetechniquespresentedsofarworkwell,buttheyareoftensomewhatclumsy,
mayuseafairamountofcompilerresources,and,inerrorcases,mayleadto
unwieldydiagnostics.Manygenericlibraryauthorsarethereforelooking
forwardtoalanguagefeaturethatachievesthesameeffectmoredirectly.A
featurecalledconceptswilllikelybeaddedtothelanguageforthatreason;see
Section6.5onpage103,Section18.4onpage377,andAppendixE.
Forexample,weexpectouroverloadedcontainerconstructorswould
simplylookasfollows:Clickheretoviewcodeimage
typeoverload/container4.hpp
template<typenameT>
classContainer{
public:
//constructfromaninputiteratorsequence:
template<typenameIterator>
requiresIsInputIterator<Iterator>
Container(Iteratorfirst,Iteratorlast);
//constructfromarandomaccessiteratorsequence:
template<typenameIterator>
requiresIsRandomAccessIterator<Iterator>
Container(Iteratorfirst,Iteratorlast);
//converttoacontainersolongasthevaluetypesareconvertible:
template<typenameU>
requiresIsConvertible<T,U>
operatorContainer<U>()const;
};
Therequiresclause(discussedinSectionE.1onpage740)describesthe
requirementsofthetemplate.Ifanyoftherequirementsarenotsatisfied,the
templateisnotconsideredacandidate.Itisthereforeamoredirectexpressionof
theideaexpressedbyEnableIf,supportedbythelanguageitself.
TherequiresclausehasadditionalbenefitsoverEnableIf.Constraint
subsumption(describedinSectionE.3.1onpage744)providesanordering
amongtemplatesthatdifferonlyintheirrequiresclauses,eliminatingtheneed
fortagdispatching.Additionally,arequiresclausecanbeattachedtoa
nontemplate.Forexample,toprovideasort()memberfunctiononlywhen
thetypeTiscomparablewith<:Clickheretoviewcodeimage
template<typenameT>
classContainer{
public:
…
requiresHasLess<T>
voidsort(){
…
}
};
20.4ClassSpecialization
Classtemplatepartialspecializationscanbeusedtoprovidealternate,
specializedimplementationsofaclasstemplateforspecifictemplatearguments,
muchlikeweusedoverloadingforfunctiontemplates.And,likeoverloaded
functiontemplates,itcanmakesensetodifferentiatethosepartialspecializations
basedonpropertiesofthetemplatearguments.Considerageneric
Dictionaryclasstemplatewithkeyandvaluetypesasitstemplate
parameters.Asimple(butinefficient)Dictionarycanbeimplementedso
longasthekeytypeprovidesjustanequalityoperator:Clickheretoviewcode
image
template<typenameKey,typenameValue>
classDictionary
{
private:
vector<pair<Keyconst,Value>>data;
public:
//subscriptedaccesstothedata:
value&operator[](Keyconst&key)
{
//searchfortheelementwiththiskey:
for(auto&element:data){
if(element.first==key){
returnelement.second;
}
}
//thereisnoelementwiththiskey;addone
data.push_back(pair<Keyconst,Value>(key,Value()));
returndata.back().second;
}
…
};
Ifthekeytypesupportsa<operator,wecanprovideamoreefficient
implementationbasedonthestandardlibrary’smapcontainer.Similarly,ifthe
keytypesupportshashingoperations,wecanprovideanevenmoreefficient
implementationbasedonthestandardlibrary’sunordered_map.
20.4.1Enabling/DisablingClassTemplates
Thewaytoenable/disabledifferentimplementationsofclasstemplatesistouse
enabled/disabledpartialspecializationsofclasstemplates.TouseEnableIf
withclasstemplatepartialspecializations,wefirstintroduceanunnamed,
defaultedtemplateparametertoDictionary:Clickheretoviewcodeimage
template<typenameKey,typenameValue,typename=void>
classDictionary
{
…//vectorimplementationasabove
};
ThisnewtemplateparameterservesasananchorforEnableIf,whichnow
canbeembeddedinthetemplateargumentlistofthepartialspecializationfor
themapversionoftheDictionary:Clickheretoviewcodeimage
template<typenameKey,typenameValue>
classDictionary<Key,Value,
EnableIf<HasLess<Key>>>
{
private:
map<Key,Value>data;
public:
value&operator[](Keyconst&key){
returndata[key];
}
…
};
Unlikewithoverloadedfunctiontemplates,wedon’tneedtodisableany
conditionontheprimarytemplate,becauseanypartialspecializationtakes
precedenceovertheprimarytemplate.However,whenweaddanother
implementationforkeyswithahashingoperation,weneedtoensurethatthe
conditionsonthepartialspecializationsaremutuallyexclusive:Clickhereto
viewcodeimage
template<typenameKey,typenameValue,typename=void>
classDictionary
{
…//vectorimplementationasabove
};
template<typenameKey,typenameValue>
classDictionary<Key,Value,
EnableIfHasLessKey>&&!HasHashKey>>>{
{
…//mapimplementationasabove
};
templatetypenameKey,typenameValue>
classDictionaryKey,Value,
EnableIfHasHashKey>>>
{
private:
unordered_mapKey,Value>data;
public:
value&operator[](Keyconst&key){
returndata[key];
}
…
};
20.4.2TagDispatchingforClassTemplates
Tagdispatching,too,canbeusedtoselectamongclasstemplatepartial
specializations.Toillustrate,wedefineafunctionobjecttype
Advance<Iterator>akintotheadvanceIter()algorithmusedin
earliersections,whichadvancesaniteratorbysomenumberofsteps.We
provideboththegeneralimplementation(forinputiterators)aswellas
specializedimplementationsforbidirectionalandrandomaccessiterators,
relyingonanauxiliarytraitBestMatchInSet(describedbelow)topickthe
bestmatchfortheiterator’scategorytag:Clickheretoviewcodeimage
//primarytemplate(intentionallyundefined):
template<typenameIterator,
typenameTag=
BestMatchInSet<
typenamestd::iterator_traits<Iterator>
::iterator_category,
std::input_iterator_tag,
std::bidirectional_iterator_tag,
std::random_access_iterator_tag>>
classAdvance;
//general,linear-timeimplementationforinputiterators:
template<typenameIterator>
classAdvance<Iterator,std::input_iterator_tag>
{
public:
usingDifferenceType=
typenamestd::iterator_traits<Iterator>::difference_type;
voidoperator()(Iterator&x,DifferenceTypen)const
{
while(n>0){
++x;
--n;
}
}
};
//bidirectional,linear-timealgorithmforbidirectionaliterators:
template<typenameIterator>
classAdvance<Iterator,std::bidirectional_iterator_tag>
{
public:
usingDifferenceType=
typenamestd::iterator_traits<Iterator>::difference_type;
voidoperator()(Iterator&x,DifferenceTypen)const
{
if(n>0){
while(n>0){
++x;
--n;
}
}else{
while(n<0){
--x;
++n;
}
}
}
};
//bidirectional,constant-timealgorithmforrandomaccessiterators:
template<typenameIterator>
classAdvance<Iterator,std::random_access_iterator_tag>
{
public:
usingDifferenceType=
typenamestd::iterator_traits<Iterator>::difference_type;
voidoperator()(Iterator&x,DifferenceTypen)const
{
x+=n;
}
}
Thisformulationisquitesimilartothatoftagdispatchingforfunctiontemplates.
However,thechallengeisinwritingthetraitBestMatchInSet,which
intendstodeterminewhichisthemostcloselymatchingtag(oftheinput,
bidirectional,andrandomaccessiteratortags)forthegiveniterator.Inessence,
thistraitisintendedtotelluswhichofthefollowingoverloadswouldbepicked
givenavalueoftheiterator’scategorytagandtoreportitsparametertype:Click
heretoviewcodeimage
voidf(std::input_iterator_tag);
voidf(std::bidirectional_iterator_tag);
voidf(std::random_access_iterator_tag);Theeasiestwaytoemulate
overloadresolutionistoactuallyuseoverloadresolution,as
follows:Clickheretoviewcodeimage
//constructasetofmatch()overloadsforthetypesinTypes…:
template<typename…Types>
structMatchOverloads;
//basiscase:nothingmatched:
template<>
structMatchOverloads<>{
staticvoidmatch(…);
};
//recursivecase:introduceanewmatch()overload:
template<typenameT1,typename…Rest>
structMatchOverloads<T1,Rest…>:publicMatchOverloads<Rest…>{
staticT1match(T1);//introduceoverloadforT1
usingMatchOverloads<Rest…>::match;//collectoverloadsfrombases
};
//findthebestmatchforTinTypes…
:template<typenameT,typename…Types>
structBestMatchInSetT{
usingType=decltype(MatchOverloads<Types…>::match(declval<T>()));
};
template<typenameT,typename…Types>
usingBestMatchInSet=typenameBestMatchInSetT<T,Types…>::Type;The
MatchOverloadstemplateusesrecursiveinheritancetodeclarea
match()functionwitheachtypeintheinputsetofTypes.Each
instantiationoftherecursiveMatchOverloadspartialspecialization
introducesanewmatch()functionforthenexttypeinthelist.It
thenemploysausingdeclarationtopullinthematch()function(s)
definedinitsbaseclass,whichhandlestheremainingtypesinthe
list.Whenappliedrecursively,theresultisacompletesetof
match()overloadscorrespondingtothegiventypes,eachofwhich
returnsitsparametertype.TheBestMatchInSetTtemplatethenpasses
aTobjecttothissetofoverloadedmatch()functionsandproduces
thereturntypeoftheselected(best)match()function.6Ifnoneof
thefunctionsmatches,thevoid-returningbasiscase(whichusesan
ellipsistocaptureanyargument)indicatesfailure.7Tosummarize,
BestMatchInSetTtranslatesafunction-overloadingresultintoatrait
andmakesitrelativelyeasytousetagdispatchingtoselectamong
classtemplatepartialspecializations.
20.5Instantiation-SafeTemplates
TheessenceoftheEnableIftechniqueistoenableaparticulartemplateor
partialspecializationonlywhenthetemplateargumentsmeetsomespecific
criteria.Forexample,themostefficientformoftheadvanceIter()
algorithmchecksthattheiteratorargument’scategoryisconvertibleto
std::random_access_iterator_tag,whichimpliesthatthevarious
random-accessiteratoroperationswillbeavailabletothealgorithm.
Whatifwetookthisnotiontotheextremeandencodedeveryoperationthat
thetemplateperformsonitstemplateargumentsaspartoftheEnableIf
condition?Theinstantiationofsuchatemplatecouldneverfail,because
templateargumentsthatdonotprovidetherequiredoperationswouldcausea
deductionfailure(viaEnableIf)ratherthanallowingtheinstantiationto
proceed.Werefertosuchtemplatesas“instantiation-safe”templatesandsketch
theimplementationofsuchtemplateshere.
Westartwithaverybasictemplate,min(),whichcomputestheminimumof
twovalues.Wewouldtypicallyimplementsuchasatemplateasfollows:Click
heretoviewcodeimage
template<typenameT>
Tconst&min(Tconst&x,Tconst&y)
{
if(y<x){
returny;
}
returnx;
}
ThistemplaterequiresthetypeTtohavea<operatorabletocomparetwoT
values(specifically,twoTconstlvalues)andthenimplicitlyconvertthe
resultofthatcomparisontoboolforuseintheifstatement.Atraitthatchecks
forthe<operatorandcomputesitsresulttypeisanalogoustotheSFINAE-
friendlyPlusResultTtraitdiscussedinSection19.4.4onpage424,butwe
showtheLessResultTtraithereforconvenience:Clickheretoviewcode
image
typeoverload/lessresult.hpp
#include<utility>//fordeclval()
#include<type_traits>//fortrue_typeandfalse_type
template<typenameT1,typenameT2>
classHasLess{
template<typenameT>structIdentity;
template<typenameU1,typenameU2>staticstd::true_type
test(Identity<decltype(std::declval<U1>()<std::declval<U2>())>*);
template<typenameU1,typenameU2>staticstd::false_type
test(…);
public:
staticconstexprboolvalue=decltype(test<T1,T2>(nullptr))::value;
};
template<typenameT1,typenameT2,boolHasLess>
classLessResultImpl{
public:
usingType=decltype(std::declval<T1>()<std::declval<T2>());
};
template<typenameT1,typenameT2>
classLessResultImpl<T1,T2,false>{
};
template<typenameT1,typenameT2>
classLessResultT
:publicLessResultImpl<T1,T2,HasLess<T1,T2>::value>{
};
template<typenameT1,typenameT2>
usingLessResult=typenameLessResultT<T1,T2>::Type;Thistraitcan
thenbecomposedwiththeIsConvertibletraittomakemin()
instantiation-safe:Clickheretoviewcodeimage
typeoverload/min2.hpp
#include"isconvertible.hpp"
#include"lessresult.hpp"
template<typenameT>
EnableIf<IsConvertible<LessResult<Tconst&,Tconst&>,bool>,
Tconst&>
min(Tconst&x,Tconst&y)
{
if(y<x){
returny;
}
returnx;
}
Itisinstructivetotrytocallthismin()functionwithvarioustypeswith
different<operators(ormissingtheoperatorentirely),asinthefollowing
example:Clickheretoviewcodeimage
typeoverload/min.cpp
#include"min.hpp"
structX1{};
booloperator<(X1const&,X1const&){returntrue;}
structX2{};
booloperator<(X2,X2){returntrue;}
structX3{};
booloperator<(X3&,X3&){returntrue;}
structX4{};
structBoolConvertible{
operatorbool()const{returntrue;}//implicitconversiontobool
};
structX5{};
BoolConvertibleoperator<(X5const&,X5const&)
{
returnBoolConvertible();
}
structNotBoolConvertible{//noconversiontobool
};
structX6{};
NotBoolConvertibleoperator<(X6const&,X6const&)
{
returnNotBoolConvertible();
}
structBoolLike{
explicitoperatorbool()const{returntrue;}//explicitconversion
tobool
};
structX7{};
BoolLikeoperator<(X7const&,X7const&){returnBoolLike();}
intmain()
{
min(X1(),X1());//X1canbepassedtomin()
min(X2(),X2());//X2canbepassedtomin()
min(X3(),X3());//ERROR:X3cannotbepassedtomin()
min(X4(),X4());//ERROR:X4canbepassedtomin()
min(X5(),X5());//X5canbepassedtomin()
min(X6(),X6());//ERROR:X6cannotbepassedtomin()
min(X7(),X7());//UNEXPECTEDERROR:X7cannotbepassedtomin()
}
Whencompilingthisprogram,noticethatwhilethereareerrorsforfourofthe
differentmin()calls—forX3,X4,X6,andX7—theerrorsdonotcomefrom
thebodyofmin(),astheywouldhavewiththenon-instantiation-safevariant.
Rather,theycomplainthatthereisnosuitablemin()function,becausetheonly
optionhasbeeneliminatedbySFINAE.Clangproducesthefollowing
diagnostic:Clickheretoviewcodeimage
min.cpp:41:3:error:nomatchingfunctionforcallto’min’
min(X3(),X3());//ERROR:X3cannotbepassedtomin
^~~
./min.hpp:8:1:note:candidatetemplateignored:substitutionfailure
[withT=X3]:notypenamed’Type’in
’LessResultT<constX3&,constX3&>’
min(Tconst&x,Tconst&y)
Thus,theEnableIfisonlyallowinginstantiationforthosetemplate
argumentsthatmeettherequirementsofthetemplate(X1,X2,andX5),sowe
nevergetanerrorfromthebodyofmin().Moreover,ifwehadsomeother
overloadofmin()thatmightworkforthesetypes,overloadresolutioncould
haveselectedoneofthoseinsteadoffailing.
Thelasttypeinourexample,X7,illustratessomeofthesubtletiesof
implementinginstantiation-safetemplates.Inparticular,ifX7ispassedtothe
non-instantiation-safemin(),instantiationwillsucceed.However,the
instantiation-safemin()rejectsitbecauseBoolLikeisnotimplicitly
convertibletobool.Thedistinctionhereisparticularlysubtle:Anexplicit
conversiontoboolcanbeusedimplicitlyincertaincontexts,includingin
Booleanconditionsforcontrol-flowstatements(if,while,for,anddo),the
built-in!,&&,and||operators,andtheternaryoperator?:.Inthesecontexts,
thevalueissaidtobecontextuallyconvertedtobool.8
However,ourinsistenceonhavingageneral,implicitconversiontoboolhas
theeffectthatourinstantiation-safetemplateisoverconstrained;thatis,its
specifiedrequirements(intheEnableIf)arestrongerthanitsactual
requirements(whatthetemplateneedstoinstantiateproperly).If,ontheother
hand,wehadentirelyforgottentheconversion-to-boolrequirement,our
min()templatewouldhavebeenunderconstrained,anditwouldhaveallowed
sometemplateargumentsthatcouldcauseaninstantiationfailure(suchasX6).
Tofixtheinstantiation-safemin(),weneedatraittodeterminewhethera
typeTiscontextuallyconvertibletobool.Thecontrol-flowstatementsarenot
helpfulindefiningthistrait,becausestatementscannotoccurwithinaSFINAE
context,norarethelogicaloperations,whichcanbeoverloadedforanarbitrary
type.Fortunately,theternaryoperator?:isanexpressionandisnot
overloadable,soitcanbeexploitedtotestwhetheratypeiscontextually
convertibletobool:Clickheretoviewcodeimage
typeoverload/iscontextualbool.hpp
#include<utility>//fordeclval()
#include<type_traits>//fortrue_typeandfalse_type
template<typenameT>
classIsContextualBoolT{
private:
template<typenameT>structIdentity;
template<typenameU>staticstd::true_type
test(Identity<decltype(declval<U>()?0:1)>*);
template<typenameU>staticstd::false_type
test(…);
public:
staticconstexprboolvalue=decltype(test<T>(nullptr))::value;
};
template<typenameT>
constexprboolIsContextualBool=IsContextualBoolT<T>::value;
Withthisnewtrait,wecanprovideaninstantiation-safemin()withthe
correctsetofrequirementsintheEnableIf:Clickheretoviewcodeimage
typeoverload/min3.hpp
#include"iscontextualbool.hpp"
#include"lessresult.hpp"
template<typenameT>
EnableIf<IsContextualBool<LessResult<Tconst&,Tconst&>>,
Tconst&>
min(Tconst&x,Tconst&y)
{
if(y<x){
returny;
}
returnx;
}
Thetechniquesusedheretomakemin()instantiation-safecanbeextended
todescriberequirementsfornontrivialtemplatesbycomposingvarious
requirementchecksintotraitsthatdescribesomeclassoftypes,suchasforward
iterators,andcombiningthosetraitswithinEnableIf.Doingsohasthe
advantagesofbothbetteroverloadingbehaviorandtheeliminationoftheerror
“novel”thatcompilerstendtoproducewhileprintingerrorsdeepwithinanested
templateinstantiation.Ontheotherhand,theerrormessagesprovidedtendto
lackspecificityregardingwhichparticularoperationfailed.Moreover,aswe
haveshownwithoursmallmin()example,accuratelydeterminingand
encodingtheexactrequirementsofthetemplatecanbeadauntingtask.We
exploredebuggingtechniquesthatmakeuseofthesetraitsinSection28.2on
page654.
20.6IntheStandardLibrary
TheC++standardlibraryprovidesiteratortagsforinput,output,forward,
bidirectional,andrandom-accessiteratortags,whichwehaveusedinour
presentation.Theseiteratortagsarepartofthestandarditeratortraits
(std::iterator_traits)andtherequirementsplacedoniterators,sothey
canbesafelyusedfortagdispatchingpurposes.
TheC++11standardlibrarystd::enable_ifclasstemplateprovidesthe
samebehaviorastheEnableIfTclasstemplatepresentedhere.Theonly
differenceisthatthestandardusesalowercasemembertypenamedtyperather
thanouruppercaseType.
AlgorithmspecializationisusedinanumberofplacesintheC++standard
library.Forexample,boththestd::advance()andstd::distance()
haveseveralvariantsbasedontheiteratorcategoryoftheiriteratorarguments.
Moststandardlibraryimplementationstendtousetagdispatching,although,
morerecently,somehaveadoptedstd::enable_iftoimplementthis
algorithmspecialization.Moreover,anumberofC++standardlibrary
implementationsalsousethesetechniquesinternallytoimplementalgorithm
specializationforvariousstandardalgorithms.Forexample,std::copy()
canbespecializedtocallstd::memcpy()orstd::memmove()whenthe
iteratorsrefertocontiguousmemoryandtheirvaluetypeshavetrivialcopy-
assignmentoperators.Similarly,std::fill()canbeoptimizedtocall
std::memset(),andvariousalgorithmscanavoidcallingdestructorswhena
typeisknowntohaveatrivialdestructor.Thesealgorithmspecializationsarenot
mandatedbytheC++standardinthesamewaythattheyarefor
std::advance()orstd::distance(),butimplementershavechosento
providethemforefficiencyreasons.
AsintroducedinSection8.4onpage131,theC++standardlibraryalsohints
stronglyattherequireduseofstd::enable_if<>orsimilarSFINAE-based
techniquesinitsrequirements.Forexample,std::vectorhasaconstructor
templatetoallowavectortobebuiltfromaniteratorsequence:Clickhereto
viewcodeimage
template<typenameInputIterator>
vector(InputIteratorfirst,InputIteratorsecond,
allocator_typeconst&alloc=allocator_type());withtherequirement
that“iftheconstructoriscalledwithatypeInputIteratorthat
doesnotqualifyasaninputiterator,thentheconstructorshallnot
participateinoverloadresolution”(see§23.2.3paragraph14of
[C++11]).Thisphrasingisvagueenoughtoallowthemostefficient
techniquesofthedaytobeusedfortheimplementation,butatthe
timeitwasaddedtothestandard,theuseofstd::enable_if<>was
envisioned.
20.7Afternotes
TagdispatchinghasbeenknowninC++foralongtime.Itwasusedinthe
originalimplementationoftheSTL(see[StepanovLeeSTL]),andisoftenused
alongsidetraits.TheuseofSFINAEandEnableIfismuchnewer:Thefirst
editionofthisbook(see[VandevoordeJosuttisTemplates1st])introducedtheterm
SFINAEanddemonstrateditsusefordetectingthepresenceofmembertypes
(forexample).
The“enableif”techniqueandterminologywasfirstpublishedbyJaakko
J¨arvi,JeremiahWill-cock,HowardHinnant,andAndrewLumsdainein
[OverloadingProperties],whichdescribestheEnableIftemplate,howto
implementfunctionoverloadingwithEnableIf(andDisableIf)pairs,and
howtouseEnableIfwithclasstemplatepartialspecializations.Sincethen,
EnableIfandsimilartechniqueshavebecomeubiquitousinthe
implementationofadvancedtemplatelibraries,includingtheC++standard
library.Moreover,thepopularityofthesetechniquesmotivatedtheextended
SFINAEbehaviorinC++11(seeSection15.7onpage284).PeterDimovwas
thefirsttonotethatdefaulttemplateargumentsforfunctiontemplates(another
C++11feature)madeitpossibletouseEnableIfinconstructortemplates
withoutintroducinganotherfunctionparameter.
Theconceptslanguagefeature(describedinAppendixE)isexpectedinthe
nextC++standardafterC++17.Itisexpectedtomakemanytechniques
involvingEnableIflargelyobsolete.Meanwhile,C++17’sconstexprif
statements(seeSection8.5onpage134andSection20.3.3onpage474)isalso
graduallyerodingtheirpervasivepresenceinmoderntemplatelibraries.
1Anbetteroptionforswap(),specifically,istousestd::move()toavoid
copiesintheprimarytemplate.
2Thecategoriesinthiscasereflectconcepts,andinheritanceofconceptsis
referredtoasrefinement.ConceptsandrefinementaredetailedinAppendixE.
3EnableIfcanalsobeplacedinadefaultedtemplateparameter,whichhas
someadvantagesoverplacementintheresulttype.SeeSection20.3.2onpage
472foradiscussionofEnableIfplacement.
4Usually,algorithmspecializationisusedonlytoprovideefficiencygains,
eitherincomputationtimeorresourceusage.However,somespecializations
ofalgorithmsalsoprovidemorefunctionality,suchas(inthiscase)theability
tomovebackwardinasequence.
5Whileaconversionfunctiontemplatedoeshaveareturntype—thetypeitis
convertingto—thetemplateparametersinthattypeneedtobededucible(see
Chapter15)fortheconversionfunctiontemplatetobehaveproperly.
6InC++17,onecaneliminatetherecursionwithpackexpansionsinthebase
classlistandinausingdeclaration(Section4.4.5onpage65).We
demonstratethistechniqueinSection26.4onpage611.
7Itwouldbeslightlybettertoprovidenoresultinthecaseoffailure,tomake
thisaSFINAE-friendlytrait(seeSection19.4.4onpage424).Moreover,a
robustimplementationwouldwrapthereturntypeinsomethinglike
Identity,becausetherearesometypes—suchasarrayandfunctiontypes
—thatcanbeparametertypesbutnotreturntypes.Weomitthese
improvementsforthesakeofbrevityandreadability.
8C++11introducedthenotionofcontextualconversiontoboolalongwith
explicitconversionoperators.Together,theyreplaceusesofthe“safebool”
idiom([KarlssonSafeBool]),whichtypicallyinvolvesan(implicit)user-
definedconversiontoapointer-to-datamember.Thepointer-to-datamember
isusedbecauseitcanbetreatedasaboolvaluebutdoesn’thaveother,
unwantedconversions,suchasboolbeingpromotedtointaspartof
arithmeticoperations.Forexample,BoolConvertible()+5is
(unfortunately)well-formedcode.
Chapter21
TemplatesandInheritance
Apriori,theremightbenoreasontothinkthattemplatesandinheritanceinteract
ininterestingways.Ifanything,weknowfromChapter13thatderivingfrom
dependentbaseclassesforcesustodealcarefullywithunqualifiednames.
However,itturnsoutthatsomeinterestingtechniquescombinethesetwo
features,includingtheCuriouslyRecurringTemplatePattern(CRTP)and
mixins.Inthischapter,wedescribeafewofthesetechniques.
21.1TheEmptyBaseClassOptimization(EBCO)
C++classesareoften“empty,”whichmeansthattheirinternalrepresentation
doesnotrequireanybitsofmemoryatruntime.Thisisthecasetypicallyfor
classesthatcontainonlytypemembers,nonvirtualfunctionmembers,andstatic
datamembers.Nonstaticdatamembers,virtualfunctions,andvirtualbase
classes,ontheotherhand,dorequiresomememoryatrunningtime.
Evenemptyclasses,however,havenonzerosize.Trythefollowingprogramif
you’dliketoverifythis:Clickheretoviewcodeimage
inherit/empty.cpp
#include<iostream>
classEmptyClass{
};
intmain()
{
std::cout<<"sizeof(EmptyClass):"<<sizeof(EmptyClass)<<’\n’;
}
Formanyplatforms,thisprogramwillprint1assizeofEmptyClass.Afew
systemsimposemorestrictalignmentrequirementsonclasstypesandmayprint
anothersmallinteger(typically,4).
21.1.1LayoutPrinciples
ThedesignersofC++hadvariousreasonstoavoidzero-sizeclasses.For
example,anarrayofzerosizeclasseswouldpresumablyhavesizezerotoo,but
thentheusualpropertiesofpointerarithmeticwouldnolongerapply.For
example,let’sassumeZeroSizedTisazero-sizetype:Clickheretoviewcode
image
ZeroSizedTz[10];
…
&z[i]-&z[j]//computedistancebetweenpointers/addresses
Normally,thedifferenceinthepreviousexampleisobtainedbydividingthe
numberofbytesbetweenthetwoaddressesbythesizeofthetypetowhichitis
pointing,butwhenthatsizeiszerothisisclearlynotsatisfactory.
However,eventhoughtherearenozero-sizetypesinC++,theC++standard
doesspecifythatwhenanemptyclassisusedasabaseclass,nospaceneedsto
beallocatedforitprovidedthatitdoesnotcauseittobeallocatedtothesame
addressasanotherobjectorsubobjectofthesametype.Let’slookatsome
examplestoclarifywhatthisemptybaseclassoptimization(EBCO)meansin
practice.Considerthefollowingprogram:Clickheretoviewcodeimage
inherit/ebco1.cpp
#include<iostream>
classEmpty{
usingInt=int;//typealiasmembersdon’tmakeaclassnonempty
};
classEmptyToo:publicEmpty{
};
classEmptyThree:publicEmptyToo{
};
intmain()
{
std::cout<<"sizeof(Empty):"<<sizeof(Empty)<<’\n’;
std::cout<<"sizeof(EmptyToo):"<<sizeof(EmptyToo)<<’\n’;
std::cout<<"sizeof(EmptyThree):"<<sizeof(EmptyThree)<<’\n’;
}
IfyourcompilerimplementstheEBCO,itwillprintthesamesizeforevery
class,butnoneoftheseclasseshassizezero(seeFigure21.1).Thismeansthat
withinclassEmptyToo,theclassEmptyisnotgivenanyspace.Notealsothat
anemptyclasswithoptimizedemptybases(andnootherbases)isalsoempty.
ThisexplainswhyclassEmptyThreecanalsohavethesamesizeasclass
Empty.IfyourcompilerdoesnotimplementtheEBCO,itwillprintdifferent
sizes(seeFigure21.2).
Clickheretoviewcodeimage
Figure21.1.LayoutofEmptyThreebyacompilerthatimplementstheEBCO
Clickheretoviewcodeimage
Figure21.2.LayoutofEmptyThreebyacompilerthatdoesnotimplementthe
EBCO
ConsideranexamplethatrunsintoaconstraintoftheEBCO:Clickheretoview
codeimage
inherit/ebco2.cpp
#include<iostream>!
classEmpty{
usingInt=int;//typealiasmembersdon’tmakeaclassnonempty
};
classEmptyToo:publicEmpty{
};
classNonEmpty:publicEmpty,publicEmptyToo{
};
intmain()
{
std::cout<<"sizeof(Empty):"<<sizeof(Empty)<<’\n’;
std::cout<<"sizeof(EmptyToo):"<<sizeof(EmptyToo)<<’\n’;
std::cout<<"sizeof(NonEmpty):"<<sizeof(NonEmpty)<<’\n’;
}
ItmaycomeasasurprisethatclassNonEmptyisnotanemptyclass.Afterall,
itdoesnothaveanymembersandneitherdoitsbaseclasses.However,thebase
classesEmptyandEmptyTooofNonEmptycannotbeallocatedtothesame
addressbecausethiswouldcausethebaseclassEmptyofEmptyTootoendup
atthesameaddressasthebaseclassEmptyofclassNonEmpty.Inother
words,twosubobjectsofthesametypewouldendupatthesameoffset,andthis
isnotpermittedbytheobjectlayoutrulesofC++.Itmaybeconceivableto
decidethatoneoftheEmptybasesubobjectsisplacedatoffset“0bytes”and
theotheratoffset“1byte,”butthecompleteNonEmptyobjectstillcannothave
asizeof1bytebecauseinanarrayoftwoNonEmptyobjects,anEmpty
subobjectofthefirstelementcannotendupatthesameaddressasanEmpty
subobjectofthesecondelement(seeFigure21.3).
Clickheretoviewcodeimage
Figure21.3.LayoutofNonEmptybyacompilerthatimplementstheEBCO
TherationalefortheconstraintontheEBCOstemsfromthefactthatitis
desirabletobeabletocomparewhethertwopointerspointtothesameobject.
Becausepointersarenearlyalwaysinternallyrepresentedasjustaddresses,we
mustensurethattwodifferentaddresses(i.e.,pointervalues)correspondtotwo
differentobjects.
Theconstraintmaynotseemverysignificant.However,inpractice,itisoften
encounteredbecausemanyclassestendtoinheritfromasmallsetofempty
classesthatdefinesomecommontypealiases.Whentwosubobjectsofsuch
classesareusedinthesamecompleteobject,theoptimizationisinhibited.
Evenwiththisconstraint,theEBCOisanimportantoptimizationfortemplate
librariesbecauseanumberoftechniquesrelyontheintroductionofbaseclasses
simplyforthepurposeofintroducingnewtypealiasesorprovidingextra
functionalitywithoutaddingnewdata.Severalsuchtechniqueswillbedescribed
inthischapter.
21.1.2MembersasBaseClasses
TheEBCOhasnoequivalentfordatamembersbecause(amongotherthings)it
wouldcreatesomeproblemswiththerepresentationofpointerstomembers.As
aresult,itissometimesdesirabletoimplementasa(private)baseclasswhat
wouldatfirstsightbethoughtofasamembervariable.However,thisisnot
withoutitschallenges.
Theproblemismostinterestinginthecontextoftemplatesbecausetemplate
parametersareoftensubstitutedwithemptyclasstypes,butingeneralwecannot
relyonthisrule.Ifnothingisknownaboutatemplatetypeparameter,theEBCO
cannoteasilybeexploited.Indeed,considerthefollowingtrivialexample:Click
heretoviewcodeimage
template<typenameT1,typenameT2>
classMyClass{
private:
T1a;
T2b;
…
};
Itisentirelypossiblethatoneorbothtemplateparametersaresubstitutedbyan
emptyclasstype.Ifthisisthecase,thentherepresentationof
MyClass<T1,T2>maybesuboptimalandmaywasteawordofmemoryfor
everyinstanceofaMyClass<T1,T2>.
Thiscanbeavoidedbymakingthetemplateargumentsbaseclassesinstead:
Clickheretoviewcodeimage
template<typenameT1,typenameT2>
classMyClass:privateT1,privateT2{
};
However,thisstraightforwardalternativehasitsownsetofproblems:•It
doesn’tworkwhenT1orT2issubstitutedwithanonclasstypeorwithaunion
type.
•Italsodoesn’tworkwhenthetwoparametersaresubstitutedwiththesame
type(althoughthiscanbeaddressedfairlyeasilybyaddinganotherlayerof
inheritance;seepage513).
•Theclassmaybefinal,inwhichcaseattemptstoinheritfromitwillcause
anerror.
Evenifwesatisfactorilyaddressedtheseproblems,averyseriousproblem
persists:Addingabaseclasscanfundamentallymodifytheinterfaceofthegiven
class.ForourMyClassclass,thismaynotseemsignificantbecausethereare
veryfewinterfaceelementstoaffect,butasweseelaterinthischapter,
inheritingfromatemplateparametercanaffectwhetheramemberfunctionis
virtual.Clearly,thisapproachtoexploitingtheEBCOisfraughtwithallkindsof
trouble.
Amorepracticaltoolcanbedevisedforthecommoncasewhenatemplate
parameterisknowntobesubstitutedbyclasstypesonlyandwhenanother
memberoftheclasstemplateisavailable.Themainideaisto“merge”the
potentiallyemptytypeparameterwiththeothermemberusingtheEBCO.For
example,insteadofwritingClickheretoviewcodeimage
template<typenameCustomClass>
classOptimizable{
private:
CustomClassinfo;//mightbeempty
void*storage;
…
};
atemplateimplementerwouldusethefollowing:Clickheretoviewcode
image
template<typenameCustomClass>
classOptimizable{
private:
BaseMemberPair<CustomClass,void*>info_and_storage;
…
};
EvenwithoutseeingtheimplementationofthetemplateBaseMemberPair,it
isclearthatitsusemakestheimplementationofOptimizablemoreverbose.
However,varioustemplatelibraryimplementershavereportedthatthe
performancegains(fortheclientsoftheirlibraries)dojustifytheadded
complexity.Weexplorethisidiomfurtherinourdiscussionoftuplestoragein
Section25.1.1onpage576.
TheimplementationofBaseMemberPaircanbefairlycompact:Clickhere
toviewcodeimage
inherit/basememberpair.hpp
#ifndefBASE_MEMBER_PAIR_HPP
#defineBASE_MEMBER_PAIR_HPP
template<typenameBase,typenameMember>
classBaseMemberPair:privateBase{
private:
Membermem;
public:
//constructor
BaseMemberPair(Baseconst&b,Memberconst&m)
:Base(b),mem(m){
}
//accessbaseclassdataviafirst()
Baseconst&base()const{
returnstatic_cast<Baseconst&>(*this);
}
Base&base(){
returnstatic_cast<Base&>(*this);
}
//accessmemberdataviasecond()
Memberconst&member()const{
returnthis->mem;
}
Member&member(){
returnthis->mem;
}
};
#endif//BASE_MEMBER_PAIR_HPP
Animplementationneedstousethememberfunctionsbase()andmember()
toaccesstheencapsulated(andpossiblystorage-optimized)datamembers.
21.2TheCuriouslyRecurringTemplatePattern
(CRTP)
AnotherpatternistheCuriouslyRecurringTemplatePattern(CRTP).Thisoddly
namedpatternreferstoageneralclassoftechniquesthatconsistsofpassinga
derivedclassasatemplateargumenttooneofitsownbaseclasses.Inits
simplestform,C++codeforsuchapatternlooksasfollows:Clickheretoview
codeimage
template<typenameDerived>
classCuriousBase{
…
};
classCurious:publicCuriousBase<Curious>{
…
};
OurfirstoutlineofCRTPshowsanondependentbaseclass:TheclassCurious
isnotatemplateandisthereforeimmunetosomeofthenamevisibilityissuesof
dependentbaseclasses.However,thisisnotanintrinsiccharacteristicofCRTP.
Indeed,wecouldjustaswellhaveusedthefollowingalternativeoutline:Click
heretoviewcodeimage
template<typenameDerived>
classCuriousBase{
…
};
template<typenameT>
classCuriousTemplate:publicCuriousBase<CuriousTemplate<T>>{
…
};
Bypassingthederivedclassdowntoitsbaseclassviaatemplateparameter,the
baseclasscancustomizeitsownbehaviortothederivedclasswithoutrequiring
theuseofvirtualfunctions.ThismakesCRTPusefultofactorout
implementationsthatcanonlybememberfunctions(e.g.,constructor,
destructors,andsubscriptoperators)oraredependentonthederivedclass’s
identity.
AsimpleapplicationofCRTPconsistsofkeepingtrackofhowmanyobjects
ofacertainclasstypewerecreated.Thisiseasilyachievedbyincrementingan
integralstaticdatamemberineveryconstructoranddecrementingitinthe
destructor.However,havingtoprovidesuchcodeineveryclassistedious,and
implementingthisfunctionalityviaasingle(non-CRTP)baseclasswould
confusetheobjectcountsfordifferentderivedclasses.Instead,wecanwritethe
followingtemplate:Clickheretoviewcodeimage
Clickheretoviewcodeimage
inherit/objectcounter.hpp
#include<cstddef>
template<typenameCountedType>
classObjectCounter{
private:
inlinestaticstd::size_tcount=0;//numberofexistingobjects
protected:
//defaultconstructor
ObjectCounter(){
++count;
}
//copyconstructor
ObjectCounter(ObjectCounter<CountedType>const&){
++count;
}
//moveconstructor
ObjectCounter(ObjectCounter<CountedType>&&){
++count;
}
//destructor
~ObjectCounter(){
--count;
}
public:
//returnnumberofexistingobjects:
staticstd::size_tlive(){
returncount;
}
};
Notethatweuseinlinetobeabletodefineandinitializethecountmember
insidetheclassstructure.BeforeC++17,wehadtodefineitoutsidetheclass
template:Clickheretoviewcodeimage
template<typenameCountedType>
classObjectCounter{
private:
staticstd::size_tcount;//numberofexistingobjects
…
};
//initializecounterwithzero:
template<typenameCountedType>
std::size_tObjectCounter<CountedType>::count=0;
Ifwewanttocountthenumberoflive(i.e.,notyetdestroyed)objectsfora
certainclasstype,itsufficestoderivetheclassfromtheObjectCounter
template.Forexample,wecandefineanduseacountedstringclassalongthe
followinglines:Clickheretoviewcodeimage
inherit/countertest.cpp
#include"objectcounter.hpp"
#include<iostream>
template<typenameCharT>
classMyString:publicObjectCounter<MyString<CharT>>{
…
};
intmain()
{
MyString<char>s1,s2;
MyString<wchar_t>ws;
std::cout<<"numofMyString<char>:"
<<MyString<char>::live()<<'\n';
std::cout<<"numofMyString<wchar_t>:"
<<ws.live()<<'\n';
}
21.2.1TheBarton-NackmanTrick
In1994,JohnJ.BartonandLeeR.Nackmanpresentedatemplatetechniquethat
theycalledrestrictedtemplateexpansion(see[BartonNackman]).Thetechnique
wasmotivatedinpartbythefactthat—atthetime—functiontemplate
overloadingwasseverelylimited1andnamespaceswerenotavailableinmost
compilers.
Toillustratethis,supposewehaveaclasstemplateArrayforwhichwewant
todefinetheequalityoperator==.Onepossibilityistodeclaretheoperatorasa
memberoftheclasstemplate,butthisisnotgoodpracticebecausethefirst
argument(bindingtothethispointer)issubjecttoconversionrulesthatdiffer
fromthesecondargument.Becauseoperator==ismeanttobesymmetricalwith
respecttoitsarguments,itispreferabletodeclareitasanamespacescope
function.Anoutlineofanaturalapproachtoitsimplementationmaylooklike
thefollowing:Clickheretoviewcodeimage
template<typenameT>
classArray{
public:
…
};
Clickheretoviewcodeimage
template<typenameT>
booloperator==(Array<T>const&a,Array<T>const&b)
{
…
}
However,iffunctiontemplatescannotbeoverloaded,thispresentsaproblem:
Nootheroperator==templatecanbedeclaredinthatscope,andyetitislikely
thatsuchatemplatewouldbeneededforotherclasstemplates.Bartonand
Nackmanresolvedthisproblembydefiningtheoperatorintheclassasanormal
friendfunction:Clickheretoviewcodeimage
template<typenameT>
classArray{
staticboolareEqual(Array<T>const&a,Array<T>const&b);
public:
…
friendbooloperator==(Array<T>const&a,Array<T>const&b){
returnareEqual(a,b);
}
};
SupposethisversionofArrayisinstantiatedfortypefloat.Thefriend
operatorfunctionisthendeclaredasaresultofthatinstantiation,butnotethat
thisfunctionitselfisnotaninstantiationofafunctiontemplate.Itisanormal
nontemplatefunctionthatgetsinjectedintheglobalscopeasasideeffectofthe
instantiationprocess.Becauseitisanontemplatefunction,itcouldbe
overloadedwithotherdeclarationsofoperator==evenbeforeoverloadingof
functiontemplateswasaddedtothelanguage.BartonandNackmancalledthis
restrictedtemplateexpansionbecauseitavoidedtheuseofatemplate
operator==(T,T)thatappliedtoalltypesT(inotherwords,unrestricted
expansion).
Because
Clickheretoviewcodeimage
operator==(Array<T>const&,Array<T>const&)isdefinedinsidea
classdefinition,itisimplicitlyconsideredtobeaninline
function,andwethereforedecidedtodelegatetheimplementationto
astaticmemberfunctionareEqual,whichdoesn’tneedtobeinline.
Namelookupforfriendfunctiondefinitionshaschangedsince1994,sothe
Barton-NackmantrickisnotasusefulinstandardC++.Atthetimeofits
invention,frienddeclarationswouldbevisibleintheenclosingscopeofaclass
templatewhenthattemplatewasinstantiatedviaaprocesscalledfriendname
injection.StandardC++insteadfindsfriendfunctiondeclarationsviaargument-
dependentlookup(seeSection13.2.2onpage220forthespecificdetails).This
meansthatatleastoneoftheargumentsofthefunctioncallmustalreadyhave
theclasscontainingthefriendfunctionasanassociatedclass.Thefriend
functionwouldnotbefoundiftheargumentswereofanunrelatedclasstypethat
couldbeconvertedtotheclasscontainingthefriend.Forexample:Clickhereto
viewcodeimage
inherit/wrapper.cpp
classS{
};
template<typenameT>
classWrapper{
private:
Tobject;
public:
Wrapper(Tobj):object(obj){//implicitconversionfromTto
Wrapper<T>
}
friendvoidfoo(Wrapper<T>const&){
}
};
intmain()
{
Ss;
Wrapper<S>w(s);
foo(w);//OK:Wrapper<S>isaclassassociatedwithw
foo(s);//ERROR:Wrapper<S>isnotassociatedwiths
}
Here,thecallfoo(w)isvalidbecausethefunctionfoo()isafrienddeclared
inWrapper<S>whichisaclassassociatedwiththeargumentw.2However,in
thecallfoo(s),thefrienddeclarationoffunctionfoo(Wrapper<S>
const&)isnotvisiblebecausetheclassWrapper<S>inwhichitisdefinedis
notassociatedwiththeargumentsoftypeS.Hence,eventhoughthereisavalid
implicitconversionfromtypeStotypeWrapper<S>(throughtheconstructor
ofWrapper<S>),thisconversionisneverconsideredbecausethecandidate
functionfoo()isnotfoundinthefirstplace.AtthetimeBartonandNackman
inventedtheirtrick,friendnameinjectionwouldhavemadethefriendfoo()
visibleandthecallfoo(s)wouldsucceed.
InmodernC++,theonlyadvantagestodefiningafriendfunctioninaclass
templateoversimplydefininganordinaryfunctiontemplatearesyntactic:friend
functiondefinitionshaveaccesstotheprivateandprotectedmembersoftheir
enclosingclassanddon’tneedtorestateallofthetemplateparametersof
enclosingclasstemplates.However,friendfunctiondefinitionscanbeuseful
whencombinedwiththeCuriouslyRecurringTemplatePattern(CRTP),as
illustratedintheoperatorimplementationsdescribedinthefollowingsection.
21.2.2OperatorImplementations
Whenimplementingaclassthatprovidesoverloadedoperators,itiscommonto
provideoverloadsforanumberofdifferent(butrelated)operators.Forexample,
aclassthatimplementstheequalityoperator(==)willlikelyalsoimplementthe
inequalityoperator(!=),andaclassthatimplementstheless-thanoperator(<)
willlikelyimplementtheotherrelationaloperatorsaswell(>,<=,>=).Inmany
cases,thedefinitionofonlyoneoftheseoperatorsisactuallyinteresting,while
theotherscansimplybedefinedintermsofthatoneoperator.Forexample,the
inequalityoperatorforaclassXislikelytobedefinedintermsoftheequality
operator:Clickheretoviewcodeimage
booloperator!=(Xconst&x1,Xconst&x2){
return!(x1==x2);
}
Giventhelargenumberoftypeswithsimilardefinitionsof!=,itistemptingto
generalizethisintoatemplate:Clickheretoviewcodeimage
template<typenameT>
booloperator!=(Tconst&x1,Tconst&x2){
return!(x1==x2);
}
Infact,theC++standardlibrarycontainssimilarsuchdefinitionsaspartofthe
<utility>header.However,thesedefinitions(for!=,>,<=,and>=)were
relegatedtothenamespacestd::rel_opsduringstandardization,whenit
wasdeterminedthattheycausedproblemswhenmadeavailableinnamespace
std.Indeed,havingthesedefinitionsvisiblemakesitappearthatanytypehas
an!=operator(whichmayfailtoinstantiate),andthatoperatorwillalwaysbe
anexactmatchforbothofitsarguments.Whilethefirstproblemcanbe
overcomethroughtheuseofSFINAEtechniques(seeSection19.4onpage
416),suchthatthis!=definitionwillonlybeinstantiatedfortypeswitha
suitable==operator,thesecondproblemremains:Thegeneral!=definition
abovewillbepreferredoveruser-provideddefinitionsthatrequire,forexample,
aderived-to-baseconversion,whichmaycomeasasurprise.
AnalternativeformulationoftheseoperatortemplatesbasedonCRTPallows
classestooptintothegeneraloperatordefinitions,providingthebenefitsof
increasedcodereusewithoutthesideeffectsofanoverlygeneraloperator:Click
heretoviewcodeimage
inherit/equalitycomparable.cpp
template<typenameDerived>
classEqualityComparable
{
public:
friendbooloperator!=(Derivedconst&x1,Derivedconst&x2){
return!(x1==x2);
}
};
classX:publicEqualityComparable<X>
{
public:
friendbooloperator==(Xconst&x1,Xconst&x2){
//implementlogicforcomparingtwoobjectsoftypeX
}
};
intmain()
{
Xx1,x2;
if(x1!=x2){}
}
Here,wehavecombinedCRTPwiththeBarton-Nackmantrick.
EqualityComparable<>usesCRTPtoprovideanoperator!=forits
derivedclassbasedonthederivedclass’sdefinitionofoperator==.It
actuallyprovidesthatdefinitionviaafriendfunctiondefinition(theBarton-
Nackmantrick),whichgivesthetwoparameterstooperator!=equal
behaviorforconversions.
CRTPcanbeusefulwhenfactoringbehaviorintoabaseclasswhileretaining
theidentityoftheeventualderivedclass.AlongwiththeBarton-Nackmantrick,
CRTPcanprovidegeneraldefinitionsforanumberofoperatorsbasedonafew
canonicaloperators.ThesepropertieshavemadeCRTPwiththeBarton-
NackmantrickafavoritetechniqueforauthorsofC++templatelibraries.
21.2.3Facades
TheuseofCRTPandtheBarton-Nackmantricktodefinesomeoperatorsisa
convenientshortcut.Wecantakethisideafurther,suchthattheCRTPbaseclass
definesmostorallofthepublicinterfaceofaclassintermsofamuchsmaller
(buteasiertoimplement)interfaceexposedbytheCRTPderivedclass.This
pattern,calledthefacadepattern,isparticularlyusefulwhendefiningnewtypes
thatneedtomeettherequirementsofsomeexistinginterface—numerictypes,
iterators,containers,andsoon.
Toillustratethefacadepattern,wewillimplementafacadeforiterators,
whichdrasticallysimplifiestheprocessofwritinganiteratorthatconformsto
therequirementsofthestandardlibrary.Therequiredinterfaceforaniterator
type(particularlyarandomaccessiterator)isquitelarge.Thefollowing
skeletonforclasstemplateIteratorFacadedemonstratestherequirements
foraniteratorinterface:Clickheretoviewcodeimage
Clickheretoviewcodeimage
inherit/iteratorfacadeskel.hpp
template<typenameDerived,typenameValue,typenameCategory,
typenameReference=Value&,typenameDistance=std::ptrdiff_t>
classIteratorFacade
{
public:
usingvalue_type=typenamestd::remove_const<Value>::type;
usingreference=Reference;
usingpointer=Value*;
usingdifference_type=Distance;
usingiterator_category=Category;
//inputiteratorinterface:
referenceoperator*()const{…}
pointeroperator->()const{…}
Derived&operator++(){…}
Derivedoperator++(int){…}
friendbooloperator==(IteratorFacadeconst&lhs,
IteratorFacadeconst&rhs){…}
…
//bidirectionaliteratorinterface:
Derived&operator--(){…}
Derivedoperator--(int){…}
//randomaccessiteratorinterface:
referenceoperator[](difference_typen)const{…}
Derived&operator+=(difference_typen){…}
…
frienddifference_typeoperator-(IteratorFacadeconst&lhs,
IteratorFacadeconst&rhs){…}
friendbooloperator<(IteratorFacadeconst&lhs,
IteratorFacadeconst&rhs){…}
…
};
We’veomittedsomedeclarationsforbrevity,butevenimplementingeveryone
ofthoselistedforeverynewiteratorisquiteachore.Fortunately,thatinterface
canbedistilledintoafewcoreoperations:•Foralliterators:
–dereference():Accessthevaluetowhichtheiteratorrefers(typically
usedviaoperators*and->).
–increment():Movetheiteratortorefertothenextiteminthesequence.
–equals():Determinewhethertwoiteratorsrefertothesameitemina
sequence.
•Forbidirectionaliterators:
–decrement():Movetheiteratortorefertothepreviousiteminthelist.
•Forrandom-accessiterators:
–advance():Movetheiteratornstepsforward(orbackward).
–measureDistance():Determinethenumberofstepstogetfromone
iteratortoanotherinthesequence.
Theroleofthefacadeistoadaptatypethatimplementsonlythosecore
operationstoprovidethefulliteratorinterface.Theimplementation
IteratorFacademostlyinvolvesmappingtheiteratorsyntaxtotheminimal
interface.Inthefollowingexamples,weusethememberfunctions
asDerived()toaccesstheCRTPderivedclass:Clickheretoviewcode
image
Derived&asDerived(){return*static_cast<Derived*>(this);}
Derivedconst&asDerived()const{
return*static_cast<Derivedconst*>(this);
}
Giventhatdefinition,theimplementationofmuchofthefacadeis
straightforward.3Weonlyillustratethedefinitionsforsomeoftheinputiterator
requirements;theothersfollowsimilarly.
Clickheretoviewcodeimage
referenceoperator*()const{
returnasDerived().dereference();
}
Derived&operator++(){
asDerived().increment();
returnasDerived();
}
Derivedoperator++(int){
Derivedresult(asDerived());
asDerived().increment();
returnresult;
}
friendbooloperator==(IteratorFacadeconst&lhs,
IteratorFacadeconst&rhs){
returnlhs.asDerived().equals(rhs.asDerived());
}
DefiningaLinked-ListIterator
WithourdefinitionofIteratorFacade,wecannoweasilydefineaniterator
intoasimplelinked-listclass.Forexample,imaginethatwedefineanodeinthe
linkedlistasfollows:Clickheretoviewcodeimage
inherit/listnode.hpp
template<typenameT>
classListNode
{
public:
Tvalue;
ListNode<T>*next=nullptr;
~ListNode(){deletenext;}
};
UsingIteratorFacade,aniteratorintosuchalistcanbedefinedina
straightforwardmanner:Clickheretoviewcodeimage
inherit/listnodeiterator0.hpp
template<typenameT>
classListNodeIterator
:publicIteratorFacade<ListNodeIterator<T>,T,
std::forward_iterator_tag>
{
ListNode<T>*current=nullptr;
public:
T&dereference()const{
returncurrent->value;
}
voidincrement(){
current=current->next;
}
boolequals(ListNodeIteratorconst&other)const{
returncurrent==other.current;
}
ListNodeIterator(ListNode<T>*current=nullptr):current(current){
}
};
ListNodeIteratorprovidesallofthecorrectoperatorsandnestedtypes
neededtoactasaforwarditerator,andrequiresverylittlecodetoimplement.As
wewillseelater,definingmorecomplicatediterators(e.g.,randomaccess
iterators)requiresonlyasmallamountofextrawork.
Hidingtheinterface
OnedownsidetoourimplementationofListNodeIteratoristhatwewere
requiredtoexpose,asapublicinterface,theoperationsdereference(),
advance(),andequals().Toeliminatethisrequirement,wecanrework
IteratorFacadetoperformallofitsoperationsonthederivedCRTPclass
throughaseparateaccessclass,whichwecallIteratorFacadeAccess:
Clickheretoviewcodeimage
Clickheretoviewcodeimage
inherit/iteratorfacadeaccessskel.hpp
//‘friend’thisclasstoallowIteratorFacadeaccesstocoreiterator
operations:
classIteratorFacadeAccess
{
//onlyIteratorFacadecanusethesedefinitions
template<typenameDerived,typenameValue,typenameCategory,
typenameReference,typenameDistance>
friendclassIteratorFacade;
//requiredofalliterators:
template<typenameReference,typenameIterator>
staticReferencedereference(Iteratorconst&i){
returni.dereference();
}
…
//requiredofbidirectionaliterators:
template<typenameIterator>
staticvoiddecrement(Iterator&i){
returni.decrement();
}
//requiredofrandom-accessiterators:
template<typenameIterator,typenameDistance>
staticvoidadvance(Iterator&i,Distancen){
returni.advance(n);
}
…
};
Thisclassprovidesstaticmemberfunctionsforeachofthecoreiterator
operations,callingthecorresponding(nonstatic)memberfunctionofthe
providediterator.Allofthestaticmemberfunctionsareprivate,withtheaccess
onlygrantedtoIteratorFacadeitself.Therefore,our
ListNodeIteratorcanmakeIteratorFacadeAccessafriendand
keepprivatetheinterfaceneededbythefacade:Clickheretoviewcodeimage
friendclassIteratorFacadeAccess;
IteratorAdapters
OurIteratorFacademakesiteasytobuildaniteratoradapterthattakesan
existingiteratorandexposesanewiteratorthatprovidessometransformedview
oftheunderlyingsequence.Forexample,wemighthaveacontainerofPerson
values:Clickheretoviewcodeimage
inherit/person.hpp
structPerson{
std::stringfirstName;
std::stringlastName;
friendstd::ostream&operator<<(std::ostream&strm,Personconst&p){
returnstrm<<p.lastName<<","<<p.firstName;
}
};
However,ratherthaniteratingoverallofthePersonvaluesinthecontainer,
weonlywanttoseethefirstnames.Inthissection,wedevelopaniterator
adaptercalledProjectionIteratorthatallowsus“project”thevaluesof
anunderlying(base)iteratortosomepointer-to-datamember,forexample,
Person::firstName.
AProjectionIteratorisaniteratordefinedintermsofthebaseiterator
(Iterator)andthetypeofvaluethatwillbeexposedbytheiterator(T):Click
heretoviewcodeimage
inherit/projectioniteratorskel.hpp
template<typenameIterator,typenameT>
classProjectionIterator
:publicIteratorFacade<
ProjectionIterator<Iterator,T>,
T,
typenamestd::iterator_traits<Iterator>::iterator_category,
T&,
typenamestd::iterator_traits<Iterator>::difference_type>
{
usingBase=typenamestd::iterator_traits<Iterator>::value_type;
usingDistance=
typenamestd::iterator_traits<Iterator>::difference_type;
Iteratoriter;
TBase::*member;
friendclassIteratorFacadeAccess
…//implementcoreiteratoroperationsforIteratorFacade
public:
ProjectionIterator(Iteratoriter,TBase::*member)
:iter(iter),member(member){}
};
template<typenameIterator,typenameBase,typenameT>
autoproject(Iteratoriter,TBase::*member){
returnProjectionIterator<Iterator,T>(iter,member);
}
Eachprojectioniteratorstorestwovalues:iter,whichistheiteratorintothe
underlyingsequence(ofBasevalues),andmember,apointer-to-datamember
describingwhichmembertoprojectthrough.Withthatinmind,weconsiderthe
templateargumentsprovidedtotheIteratorFacadebaseclass.Thefirstis
theProjectionIteratoritself(toenableCRTP).Thesecond(T)and
fourth(T&)argumentsarethevalueandreferencetypesofourprojection
iterator,definingthisasasequenceofTvalues.4Thethirdandfiftharguments
merelypassthroughthecategoryanddifferencetypesoftheunderlyingiterator.
Therefore,ourprojectioniteratorwillbeaninputiteratorwhenIteratorisan
inputiterator,abidirectionaliteratorwhenIteratorisabidirectionaliterator,
andsoon.Aproject()functionmakesiteasytobuildprojectioniterators.
Theonlymissingpiecesaretheimplementationsofthecorerequirementsfor
IteratorFacade.Themostinterestingisdereference(),which
dereferencestheunderlyingiteratorandthenprojectsthroughthepointer-to-data
member:Clickheretoviewcodeimage
T&dereference()const{
return(*iter).*member;
}
Theremainingoperationsareimplementedintermsoftheunderlyingiterator:
Clickheretoviewcodeimage
voidincrement(){
++iter;
}
boolequals(ProjectionIteratorconst&other)const{
returniter==other.iter;
}
voiddecrement(){
--iter;
}
Forbrevity,we’veomittedthedefinitionsforrandomaccessiterators,which
followanalogously.
That’sit!Withourprojectioniterator,wecanprintoutthefirstnamesofa
vectorcontainingPersonvalues:Clickheretoviewcodeimage
inherit/projectioniterator.cpp
#include<vector>
#include<algorithm>
#include<iterator>
intmain()
{
std::vector<Person>authors={{"David","Vandevoorde"},
{"Nicolai","Josuttis"},
{"Douglas","Gregor"}};
std::copy(project(authors.begin(),&Person::firstName),
project(authors.end(),&Person::firstName),
std::ostream_iterator<std::string>(std::cout,"\n"));
}
Thisprogramproduces:David
Nicolai
Douglas
Thefacadepatternisparticularlyusefulforcreatingnewtypesthatconformto
somespecificinterface.Newtypesneedonlyexposesomesmallnumberofcore
operations(betweenthreeandsixforouriteratorfacade)tothefacade,andthe
facadetakescareofprovidingacompleteandcorrectpublicinterfaceusinga
combinationofCRTPandtheBarton-Nackmantrick.
21.3Mixins
ConsiderasimplePolygonclassthatconsistsofasequenceofpoints:Click
heretoviewcodeimage
classPoint
{
public:
doublex,y;
Point():x(0.0),y(0.0){}
Point(doublex,doubley):x(x),y(y){}
};
classPolygon
{
private:
std::vector<Point>points;
public:
…//publicoperations
};
ThisPolygonclasswouldbemoreusefuliftheusercouldextendthesetof
informationassociatedwitheachPointtoincludeapplication-specificdata
suchasthecolorofeachpoint,orperhapsassociatealabelwitheachpoint.One
optiontomakethisextensionpossiblewouldbetoparameterizePolygon
basedonthetypeofthepoint:Clickheretoviewcodeimage
template<typenameP>
classPolygon
{
private:
std::vector<P>points;
public:
…//publicoperations
};
UserscouldconceivablycreatetheirownPoint-likedatatypethatprovided
thesameinterfaceasPointbutincludesotherapplication-specificdata,using
inheritance:Clickheretoviewcodeimage
classLabeledPoint:publicPoint
{
public:
std::stringlabel;
LabeledPoint():Point(),label(""){}
LabeledPoint(doublex,doubley):Point(x,y),label(""){}
};
Thisimplementationhasitsshortcomings.Forone,itrequiresthatthetype
Pointbeexposedtotheusersothattheusercanderivefromit.Also,the
authorofLabeledPointneedstobecarefultoprovideexactlythesame
interfaceasPoint(e.g.,inheritingorprovidingallofthesameconstructorsas
Point),orLabeledPointwillnotworkwithPolygon.Thisconstraint
becomesmoreproblematicifPointchangesfromoneversionofthePolygon
templatetoanother:TheadditionofanewPointconstructorcouldrequire
eachderivedclasstobeupdated.
Mixinsprovideanalternativewaytocustomizethebehaviorofatypewithout
inheritingfromit.Mixinsessentiallyinvertthenormaldirectionofinheritance,
becausethenewclassesare“mixedin”totheinheritancehierarchyasbase
classesofaclasstemplateratherthanbeingcreatedasanewderivedclass.This
approachallowstheintroductionofnewdatamembersandotheroperations
withoutrequiringanyduplicationoftheinterface.
Aclasstemplatethatsupportsmixinswilltypicallyacceptanarbitrarynumber
ofextraclassesfromwhichitwillderive:Clickheretoviewcodeimage
template<typename…Mixins>
classPoint:publicMixins…
{
public:
doublex,y;
Point():Mixins()…,x(0.0),y(0.0){}
Point(doublex,doubley):Mixins()…,x(x),y(y){}
};
Now,wecan“mixin”abaseclasscontainingalabeltoproducea
LabeledPoint:Clickheretoviewcodeimage
classLabel
{
public:
std::stringlabel;
Label():label(""){}
};
usingLabeledPoint=Point<Label>;orevenmixinseveralbase
classes:
Clickheretoviewcodeimage
classColor
{
public:
unsignedcharred=0,green=0,blue=0;
};
usingMyPoint=Point<Label,Color>;Withthismixin-basedPoint,it
becomeseasytointroduceadditionalinformationtoPointwithout
changingitsinterface,soPolygonbecomeseasiertouseandevolve.
UsersneedonlyapplytheimplicitconversionfromthePoint
specializationtotheirmixinclass(LabelorColor,above)toaccess
thatdataorinterface.Moreover,thePointclasscanevenbe
entirelyhidden,withthemixinsprovidedtothePolygonclass
templateitself:Clickheretoviewcodeimage
template<typename…Mixins>
classPolygon
{
private:
std::vector<Point<Mixins…>>points;
public:
…//publicoperations
};
Mixinsareusefulincaseswhereatemplateneedssomesmalllevelof
customization—suchasdecoratinginternallystoredobjectswithuser-specified
data—withoutrequiringthelibrarytoexposeanddocumentthoseinternaldata
typesandtheirinterfaces.
21.3.1CuriousMixins
MixinscanbemademorepowerfulbycombiningthemwiththeCuriously
RecurringTemplatePattern(CRTP)describedinSection21.2onpage495.
Here,eachofthemixinsisactuallyaclasstemplatethatwillbeprovidedwith
thetypeofthederivedclass,allowingadditionalcustomizationtothatderived
class.ACRTP-mixinversionofPointwouldbewrittenasfollows:Clickhere
toviewcodeimage
template<template<typename>…Mixins>
classPoint:publicMixins<Point>…
{
public:
doublex,y;
Point():Mixins<Point>()…,x(0.0),y(0.0){}
Point(doublex,doubley):Mixins<Point>()…,x(x),y(y){}
};
Thisformulationrequiressomemoreworkforeachclassthatwillbemixedin,
soclassessuchasLabelandColorwillneedtobecomeclasstemplates.
However,themixed-inclassescannowtailortheirbehaviortothespecific
instanceofthederivedclassthey’vebeenmixedinto.Forexample,wecanmix
thepreviouslydiscussedObjectCountertemplateintoPointtocountthe
numberofpointscreatedbyPolygonandcomposethatmixinwithother
application-specificmixinsaswell.
21.3.2ParameterizedVirtuality
Mixinsalsoallowustoindirectlyparameterizeotherattributesofthederived
class,suchasthevirtualityofamemberfunction.Asimpleexampleshowsthis
rathersurprisingtechnique:Clickheretoviewcodeimage
inherit/virtual.cpp
#include<iostream>
classNotVirtual{
};
classVirtual{
public:
virtualvoidfoo(){
}
};
template<typename…Mixins>
classBase:
publicMixins…{public:
//thevirtualityoffoo()dependsonitsdeclaration
//(ifany)inthebaseclassesMixins…
voidfoo(){
std::cout<<"Base::foo()"<<’\n’;
}
};
template<typename…Mixins>
classDerived:publicBase<Mixins…>{
public:
voidfoo(){
std::cout<<"Derived::foo()"<<’\n’;
}
};
intmain()
{
Base<NotVirtual>*p1=newDerived<NotVirtual>;
p1->foo();//callsBase::foo()
Base<Virtual>*p2=newDerived<Virtual>;
p2->foo();//callsDerived::foo()
}
Thistechniquecanprovideatooltodesignaclasstemplatethatisusablebothto
instantiateconcreteclassesandtoextendusinginheritance.However,itisrarely
sufficientjusttosprinklevirtualityonsomememberfunctionstoobtainaclass
thatmakesagoodbaseclassformorespecializedfunctionality.Thissortof
developmentmethodrequiresmorefundamentaldesigndecisions.Itistherefore
usuallymorepracticaltodesigntwodifferenttools(classorclasstemplate
hierarchies)thantotrytointegratethemallintoonetemplatehierarchy.
21.4NamedTemplateArguments
Varioustemplatetechniquessometimescauseaclasstemplatetoendupwith
manydifferenttemplatetypeparameters.However,manyoftheseparameters
oftenhavereasonabledefaultvalues.Anaturalwaytodefinesuchaclass
templatemaylookasfollows:Clickheretoviewcodeimage
template<typenamePolicy1=DefaultPolicy1,
typenamePolicy2=DefaultPolicy2,
typenamePolicy3=DefaultPolicy3,
typenamePolicy4=DefaultPolicy4>
classBreadSlicer{
…
};
Presumably,suchatemplatecanoftenbeusedwiththedefaulttemplate
argumentvaluesusingthesyntaxBreadSlicer<>.However,ifanondefault
argumentmustbespecified,allprecedingargumentsmustbespecifiedtoo(even
thoughtheymayhavethedefaultvalue).
Clearly,itwouldbeattractivetobeabletouseaconstructakinto
BreadSlicer<Policy3=Custom>ratherthan
BreadSlicer<DefaultPolicy1,DefaultPolicy2,Custom>,as
isthecaserightnow.Inwhatfollowswedevelopatechniquetoenablealmost
exactlythat.5
Ourtechniqueconsistsofplacingthedefaulttypevaluesinabaseclassand
overridingsomeofthemthroughderivation.Insteadofdirectlyspecifyingthe
typearguments,weprovidethemthroughhelperclasses.Forexample,wecould
writeBreadSlicer<Policy3_is<Custom>>.Becauseeachtemplate
argumentcandescribeanyofthepolicies,thedefaultscannotbedifferent.In
otherwords,atahighlevel,everytemplateparameterisequivalent:Clickhere
toviewcodeimage
template<typenamePolicySetter1=DefaultPolicyArgs,
typenamePolicySetter2=DefaultPolicyArgs,
typenamePolicySetter3=DefaultPolicyArgs,
typenamePolicySetter4=DefaultPolicyArgs>
classBreadSlicer{
usingPolicies=PolicySelector<PolicySetter1,PolicySetter2,
PolicySetter3,PolicySetter4>;
//usePolicies::P1,Policies::P2,…torefertothevariouspolicies
…
};
TheremainingchallengeistowritethePolicySelectortemplate.Ithasto
mergethedifferenttemplateargumentsintoasingletypethatoverridesdefault
typealiasmemberswithwhichevernon-defaultswerespecified.Thismerging
canbeachievedusinginheritance:Clickheretoviewcodeimage
//PolicySelector<A,B,C,D>createsA,B,C,Dasbaseclasses
//Discriminator<>allowshavingeventhesamebaseclassmorethan
once
template<typenameBase,intD>
classDiscriminator:publicBase{
};
template<typenameSetter1,typenameSetter2,
typenameSetter3,typenameSetter4>
classPolicySelector:publicDiscriminator<Setter1,1>,
publicDiscriminator<Setter2,2>,
publicDiscriminator<Setter3,3>,
publicDiscriminator<Setter4,4>{
};
NotetheuseofanintermediateDiscriminatortemplate.Itisneededto
allowthevariousSettertypestobeidentical.(Youcannothavemultiple
directbaseclassesofthesametype.Indirectbaseclasses,ontheotherhand,can
havetypesthatareidenticaltothoseofotherbases.)Asannouncedearlier,we’re
collectingthedefaultsinabaseclass:Clickheretoviewcodeimage
//namedefaultpoliciesasP1,P2,P3,P4
classDefaultPolicies{
public:
usingP1=DefaultPolicy1;
usingP2=DefaultPolicy2;
usingP3=DefaultPolicy3;
usingP4=DefaultPolicy4;
};
However,wemustbecarefultoavoidambiguitiesifweendupinheriting
multipletimesfromthisbaseclass.Therefore,weensurethatthebaseclassis
inheritedvirtually:Clickheretoviewcodeimage
//classtodefineauseofthedefaultpolicyvalues
//avoidsambiguitiesifwederivefromDefaultPoliciesmorethan
once
classDefaultPolicyArgs:virtualpublicDefaultPolicies{
};
Finally,wealsoneedsometemplatestooverridethedefaultpolicyvalues:Click
heretoviewcodeimage
template<typenamePolicy>
classPolicy1_is:virtualpublicDefaultPolicies{
public:
usingP1=Policy;//overridingtypealias
};
template<typenamePolicy>
classPolicy2_is:virtualpublicDefaultPolicies{
public:
usingP2=Policy;//overridingtypealias
};
template<typenamePolicy>
classPolicy3_is:virtualpublicDefaultPolicies{
public:
usingP3=Policy;//overridingtypealias
};
template<typenamePolicy>
classPolicy4_is:virtualpublicDefaultPolicies{
public:
usingP4=Policy;//overridingtypealias
};
Withallthisinplace,ourdesiredobjectiveisachieved.Nowlet’slookatwhat
wehavebyexample.Let’sinstantiateaBreadSlicer<>asfollows:Click
heretoviewcodeimage
BreadSlicer<Policy3_is<CustomPolicy>>bc;ForthisBreadSlicer<>the
typePoliciesisdefinedasClickheretoviewcodeimage
PolicySelector<Policy3_is<CustomPolicy>,
DefaultPolicyArgs,
DefaultPolicyArgs,
DefaultPolicyArgs>
WiththehelpoftheDiscriminator<>classtemplates,thisresultsina
hierarchyinwhichalltemplateargumentsarebaseclasses(seeFigure21.4).The
importantpointisthatthesebaseclassesClickheretoviewcodeimage
Figure21.4.ResultingtypehierarchyofBreadSlicer<>::Policies
allhavethesamevirtualbaseclassDefaultPolicies,whichdefinesthe
defaulttypesforP1,P2,P3,andP4.However,P3isredefinedinoneofthe
derivedclasses—namely,inPolicy3_is<>.Accordingtothedomination
rule,thisdefinitionhidesthedefinitionofthebaseclass.Thus,thisisnotan
ambiguity.6
InsidethetemplateBreadSliceryoucanrefertothefourpoliciesbyusing
qualifiednamessuchasPolicies::P3.Forexample:Clickheretoview
codeimage
template<…>
classBreadSlicer{
…
public:
voidprint(){
Policies::P3::doPrint();
}
…
};
Ininherit/namedtmpl.cppyoucanfindtheentireexample.
Wedevelopedthetechniqueforfourtemplatetypeparameters,butit
obviouslyscalestoanyreasonablenumberofsuchparameters.Notethatwe
neveractuallyinstantiateobjectsofthehelperclassthatcontainvirtualbases.
Hence,thefactthattheyarevirtualbasesisnotaperformanceormemory
consumptionissue.
21.5Afternotes
BillGibbonswasthemainsponsorbehindtheintroductionoftheEBCOintothe
C++programminglanguage.NathanMyersmadeitpopularandproposeda
templatesimilartoourBaseMemberPairtotakebetteradvantageofit.The
Boostlibrarycontainsaconsiderablymoresophisticatedtemplate,called
compressed_pair,thatresolvessomeoftheproblemswereportedforthe
MyClasstemplateinthischapter.boost::compressed_paircanalsobe
usedinsteadofourBaseMemberPair.
CRTPhasbeeninusesinceatleast1991.However,JamesCoplienwasfirst
todescribethemformallyasaclassofpatterns(see[CoplienCRTP]).Since
then,manyapplicationsofCRTPhavebeenpublished.Thephrase
parameterizedinheritanceissometimeswronglyequatedwithCRTP.Aswe
haveshown,CRTPdoesnotrequirethederivationtobeparameterizedatall,and
manyformsofparameterizedinheritancedonotconformtoCRTP.CRTPisalso
sometimesconfusedwiththeBarton-Nackmantrick(seeSection21.2.1onpage
497)becauseBartonandNackmanfrequentlyusedCRTPincombinationwith
friendnameinjection(andthelatterisanimportantcomponentoftheBarton-
Nackmantrick).OuruseofCRTPwiththeBarton-Nackmantricktoprovide
operatorimplementationsfollowsthesamebasicapproachasthe
Boost.Operatorslibrary([BoostOperators]),whichprovidesanextensivesetof
operatordefinitions.Similarly,ourtreatmentofiteratorfacadesfollowsthatof
theBoost.Iteratorlibrary([BoostIterator])whichprovidesarich,standard-
librarycompliantiteratorinterfaceforaderivedtypethatprovidesafewcore
iteratoroperations(equality,dereference,movement),andalsoaddressestricky
issuesinvolvingproxyiterators(whichwedidnotaddressforthesakeof
brevity).OurObjectCounterexampleisalmostidenticaltoatechnique
developedbyScottMeyersin[MeyersCounting].
ThenotionofmixinshasbeenaroundinObject-Orientedprogrammingsince
atleast1986([Moon-Flavors])asawaytointroducesmallpiecesof
functionalityintoanOOclass.TheuseoftemplatesformixinsinC++became
popularshortlyafterthefirstC++standardwaspublished,withtwopapers
([SmaragdakisBatoryMixins]and[EiseneckerBlinnCzarnecki])describingthe
approachescommonlyusedtodayformixins.Sincethen,it’sbecomeapopular
techniqueinC++librarydesign.
Namedtemplateargumentsareusedtosimplifycertainclasstemplatesinthe
Boostlibrary.Boostusesmetaprogrammingtocreateatypewithproperties
similartoourPolicySelector(butwithoutusingvirtualinheritance).The
simpleralternativepresentedherewasdevelopedbyoneofus(Vandevoorde).
1ItmaybeworthwhiletoreadSection16.2onpage326tounderstandhow
functiontemplateoverloadingworksinmodernC++.
2NotethatSisalsoaclassassociatedwithwbecauseitisatemplateargument
forthetypeofw.ThespecificrulesforADLarediscussedinSection13.2.1
onpage219.
3Tosimplifythepresentation,weignorethepresenceofproxyiterators,whose
dereferenceoperationdoesnotreturnatruereference.Acomplete
implementationofaniteratorfacade,suchastheonein[BoostIterator],would
adjusttheresulttypesofoperator->andoperator[]toaccountfor
proxies.
4Again,weassumethattheunderlyingiteratorreturnsareference,ratherthana
proxy,tosimplifythepresentation.
5Notethatasimilarlanguageextensionforfunctioncallargumentswas
proposed(andrejected)earlierintheC++standardizationprocess(seeSection
17.4onpage358fordetails).
6YoucanfindthedominationruleinSection10.2/6inthefirstC++standard
(see[C++98])andadiscussionaboutitinSection10.1.1of
[EllisStroustrupARM].
Chapter22
BridgingStaticandDynamicPolymorphism
Chapter18describedthenatureofstaticpolymorphism(viatemplates)and
dynamicpolymorphism(viainheritanceandvirtualfunctions)inC++.Both
kindsofpolymorphismprovidepowerfulabstractionsforwritingprograms,yet
eachhastradeoffs:Staticpolymorphismprovidesthesameperformanceas
nonpolymorphiccode,butthesetoftypesthatcanbeusedatruntimeisfixedat
compiletime.Ontheotherhand,dynamicpolymorphismviainheritanceallows
asingleversionofthepolymorphicfunctiontoworkwithtypesnotknownatthe
timeitiscompiled,butitislessflexiblebecausetypesmustinheritfromthe
commonbaseclass.
Thischapterdescribeshowtobridgebetweenstaticanddynamic
polymorphisminC++,providingsomeofthebenefitsdiscussedinSection18.3
onpage375fromeachmodel:thesmallerexecutablecodesizeand(almost)
entirelycompilednatureofdynamicpolymorphism,alongwiththeinterface
flexibilityofstaticpolymorphismthatallows,forexample,built-intypesto
workseamlessly.Asanexample,wewillbuildasimplifiedversionofthe
standardlibrary’sfunction<>template.
22.1FunctionObjects,Pointers,andstd::function<>
Functionobjectsareusefulforprovidingcustomizablebehaviortotemplates.
Forexample,thefollowingfunctiontemplateenumeratesintegervaluesfrom0
uptosomevalue,providingeachvaluetothegivenfunctionobjectf:Clickhere
toviewcodeimage
bridge/forupto1.cpp
#include<vector>
#include<iostream>
template<typenameF>
voidforUpTo(intn,Ff)
{
for(inti=0;i!=n;++i)
{
f(i);//callpassedfunctionffori
}
}
voidprintInt(inti)
{
std::cout<<i<<’’;
}
intmain()
{
std::vector<int>values;
//insertvaluesfrom0to4:
forUpTo(5,
[&values](inti){
values.push_back(i);
});
//printelements:
forUpTo(5,
printInt);//prints01234
std::cout<<’\n’;
}
TheforUpTo()functiontemplatecanbeusedwithanyfunctionobject,
includingalambda,functionpointer,oranyclassthateitherimplementsa
suitableoperator()oraconversiontoafunctionpointerorreference,and
eachuseofforUpTo()willlikelyproduceadifferentinstantiationofthe
functiontemplate.Ourexamplefunctiontemplateisfairlysmall,butifthe
templatewerelarge,itispossiblethattheseinstantiationscouldincreasecode
size.
Oneapproachtolimitthisincreaseincodesizeistoturnthefunctiontemplate
intoanontemplate,whichneedsnoinstantiation.Forexample,wemightattempt
todothiswithafunctionpointer:Clickheretoviewcodeimage
bridge/forupto2.hpp
voidforUpTo(intn,void(*f)(int))
{
for(inti=0;i!=n;++i)
{
f(i);//callpassedfunctionffori
}
}
However,whilethisimplementationwillworkwhenpassedprintInt(),it
willproduceanerrorwhenpassedthelambda:Clickheretoviewcodeimage
forUpTo(5,
printInt);//OK:prints01234
forUpTo(5,
[&values](inti){//ERROR:lambdanotconvertibletoafunction
pointer
values.push_back(i);
});
Thestandardlibrary’sclasstemplatestd::function<>permitsan
alternativeformulationofforUpTo():Clickheretoviewcodeimage
bridge/forupto3.hpp
#include<functional>
voidforUpTo(intn,std::function<void(int)>f)
{
for(inti=0;i!=n;++i)
{
f(i)//callpassedfunctionffori
}
}
Thetemplateargumenttostd::function<>isafunctiontypethatdescribes
theparametertypesthefunctionobjectwillreceiveandthereturntypethatit
shouldproduce,muchlikeafunctionpointerdescribestheparameterandresult
types.
ThisformulationofforUpTo()providessomeaspectsofstatic
polymorphism—theabilitytoworkwithanunboundedsetoftypesincluding
functionpointers,lambdas,andarbitraryclasseswithasuitableoperator()
—whileitselfremaininganontemplatefunctionwithasingleimplementation.It
doessousingatechniquecalledtypeerasure,whichbridgesthegapbetween
staticanddynamicpolymorphism.
22.2GeneralizedFunctionPointers
Thestd::function<>typeiseffectivelyageneralizedformofaC++
functionpointer,providingthesamefundamentaloperations:•Itcanbeusedto
invokeafunctionwithoutthecallerknowinganythingaboutthefunctionitself.
•Itcanbecopied,moved,andassigned.
•Itcanbeinitializedorassignedfromanotherfunction(withacompatible
signature).
•Ithasa“null”statethatindicateswhennofunctionisboundtoit.
However,unlikeaC++functionpointer,astd::function<>canalsostore
alambdaoranyotherfunctionobjectwithasuitableoperator(),allof
whichmayhavedifferenttypes.
Intheremainderofthischapter,wewillbuildourowngeneralizedfunction
pointerclasstemplate,FunctionPtr,toprovidethesesamecoreoperations
andcapabilitiesandthatcanbeusedinplaceofstd::function:Clickhere
toviewcodeimage
bridge/forupto4.cpp
#include"functionptr.hpp"
#include<vector>
#include<iostream>
voidforUpTo(intn,FunctionPtr<void(int)>f)
{
for(inti=0;i!=n;++i)
{
f(i);//callpassedfunctionffori
}
}
voidprintInt(inti)
{
std::cout<<i<<’’;
}
intmain()
{
std::vector<int>values;
//insertvaluesfrom0to4:
forUpTo(5,
[&values](inti){
values.push_back(i);
});
//printelements:
forUpTo(5,
printInt);//prints01234
std::cout<<’\n’;
}
TheinterfacetoFunctionPtrisfairlystraightforward,providing
construction,copy,move,destruction,initialization,andassignmentfrom
arbitraryfunctionobjectsandinvocationoftheunderlyingfunctionobject.The
mostinterestingpartoftheinterfaceishowitisdescribedentirelywithinaclass
templatepartialspecialization,whichservestobreakthetemplateargument(a
functiontype)intoitscomponentpieces(resultandargumenttypes):Clickhere
toviewcodeimage
bridge/functionptr.hpp
//primarytemplate:
template<typenameSignature>
classFunctionPtr;
//partialspecialization:
template<typenameR,typename…Args>
classFunctionPtr<R(Args…)>
{
private:
FunctorBridge<R,Args…>*bridge;
public:
//constructors:
FunctionPtr():bridge(nullptr){
}
FunctionPtr(FunctionPtrconst&other);//seefunctionptr-cpinv.hpp
FunctionPtr(FunctionPtr&other)
:FunctionPtr(static_cast<FunctionPtrconst&>(other)){
}
FunctionPtr(FunctionPtr&&other):bridge(other.bridge){
other.bridge=nullptr;
}
//constructionfromarbitraryfunctionobjects:
template<typenameF>FunctionPtr(F&&f);//seefunctionptr-init.hpp
//assignmentoperators:
FunctionPtr&operator=(FunctionPtrconst&other){
FunctionPtrtmp(other);
swap(*this,tmp);
return*this;
}
FunctionPtr&operator=(FunctionPtr&&other){
deletebridge;
bridge=other.bridge;
other.bridge=nullptr;
return*this;
}
//constructionandassignmentfromarbitraryfunctionobjects:
template<typenameF>FunctionPtr&operator=(F&&f){
FunctionPtrtmp(std::forward<F>(f));
swap(*this,tmp);
return*this;
}
//destructor:
~FunctionPtr(){
deletebridge;
}
friendvoidswap(FunctionPtr&fp1,FunctionPtr&fp2){
std::swap(fp1.bridge,fp2.bridge);
}
explicitoperatorbool()const{
returnbridge==nullptr;
}
//invocation:
Roperator()(Args…args)const;//seefunctionptr-cpinv.hpp
};
Theimplementationcontainsasinglenonstaticmembervariable,bridge,
whichwillberesponsibleforbothstorageandmanipulationofthestored
functionobject.OwnershipofthispointeristiedtotheFunctionPtrobject,
somostoftheimplementationprovidedmerelymanagesthispointer.The
unimplementedfunctionscontaintheinterestingpartsoftheimplementationand
isdescribedinthefollowingsubsections.
22.3BridgeInterface
TheFunctorBridgeclasstemplateisresponsiblefortheownershipand
manipulationoftheunderlyingfunctionobject.Itisimplementedasanabstract
baseclass,formingthefoundationforthedynamicpolymorphismof
FunctionPtr:Clickheretoviewcodeimage
bridge/functorbridge.hpp
template<typenameR,typename…Args>
classFunctorBridge
{
public:
virtual~FunctorBridge(){
}
virtualFunctorBridge*clone()const=0;
virtualRinvoke(Args…args)const=0;
};
FunctorBridgeprovidestheessentialoperationsneededtomanipulatea
storedfunctionobjectthroughvirtualfunctions:adestructor,aclone()
operationtoperformcopies,andaninvoke.operationtocalltheunderlying
functionobject.Don’tforgettodefineclone()andinvoke()tobeconst
memberfunctions.1
Usingthesevirtualfunctions,wecanimplementFunctionPtr’scopy
constructorandfunctioncalloperator:Clickheretoviewcodeimage
bridge/functionptr-cpinv.hpp
template<typenameR,typename…Args>
FunctionPtr<R(Args…)>::FunctionPtr(FunctionPtrconst&other)
:bridge(nullptr)
{
if(other.bridge){
bridge=other.bridge->clone();
}
}
template<typenameR,typename…Args>
RFunctionPtr<R(Args…)>::operator()(Args…args)const
{
returnbridge->invoke(std::forward<Args>(args)…);
}
22.4TypeErasure
EachinstanceofFunctorBridgeisanabstractclass,soitsderivedclasses
areresponsibleforprovidingactualimplementationsofitsvirtualfunctions.To
supportthecompleterangeofpotentialfunctionobjects—anunboundedset—
wewouldneedanunboundednumberofderivedclasses.Fortunately,wecan
accomplishthisbyparameterizingthederivedclassonthetypeofthefunction
objectitstores:Clickheretoviewcodeimage
bridge/specificfunctorbridge.hpp
template<typenameFunctor,typenameR,typename…Args>
classSpecificFunctorBridge:publicFunctorBridge<R,Args…>{
Functorfunctor;
public:
template<typenameFunctorFwd>
SpecificFunctorBridge(FunctorFwd&&functor)
:functor(std::forward<FunctorFwd>(functor)){
}
virtualSpecificFunctorBridge*clone()constoverride{
returnnewSpecificFunctorBridge(functor);
}
virtualRinvoke(Args…args)constoverride{
returnfunctor(std::forward<Args>(args)…);
}
};
EachinstanceofSpecificFunctorBridgestoresacopyofthefunction
object(whosetypeisFunctor),whichcanbeinvoked,copied(bycloningthe
SpecificFunctorBridge),ordestroyed(implicitlyinthedestructor).
SpecificFunctorBridgeinstancesarecreatedwhenevera
FunctionPtrisinitializedtoanewfunctionobject,completingthe
FunctionPtrexample:Clickheretoviewcodeimage
bridge/functionptr-init.hpp
template<typenameR,typename…Args>
template<typenameF>
FunctionPtr<R(Args…)>::FunctionPtr(F&&f)
:bridge(nullptr)
{
usingFunctor=std::decay_t<F>;
usingBridge=SpecificFunctorBridge<Functor,R,Args…>;
bridge=newBridge(std::forward<F>(f));
}
NotethatwhiletheFunctionPtrconstructoritselfistemplatedonthe
functionobjecttypeF,thattypeisknownonlytotheparticularspecializationof
SpecificFunctorBridge(describedbytheBridgetypealias).Oncethe
newlyallocatedBridgeinstanceisassignedtothedatamemberbridge,the
extrainformationaboutthespecifictypeFislostduetothederived-to-based
conversionfromBridge*toFunctorBridge<R,Args…>*.2Thisloss
oftypeinformationexplainswhythetermtypeerasureisoftenusedtodescribe
thetechniqueofbridgingbetweenstaticanddynamicpolymorphism.
Onepeculiarityoftheimplementationistheuseofstd::decay(see
SectionD.4onpage731)toproducetheFunctortype,whichmakesthe
inferredtypeFsuitableforstorage,forexample,byturningreferencesto
functiontypesintofunctionpointertypesandremovingtop-levelconst,
volatile,andreferencetypes.
22.5OptionalBridging
OurFunctionPtrtemplateisnearlyadrop-inreplacementforafunction
pointer.However,itdoesnotyetsupportoneoperationprovidedbyfunction
pointers:testingwhethertwoFunctionPtrobjectswillinvokethesame
function.AddingsuchanoperationrequiresupdatingtheFunctorBridge
withanequalsoperation:Clickheretoviewcodeimage
virtualboolequals(FunctorBridgeconst*fb)const=0;alongwithan
implementationwithinSpecificFunctorBridgethatcomparesthestored
functionobjectswhentheyhavethesametype:Clickheretoview
codeimage
virtualboolequals(FunctorBridge<R,Args…>const*fb)constoverride
{
if(autospecFb=dynamic_cast<SpecificFunctorBridgeconst*>(fb)){
returnfunctor==specFb->functor;
}
//functorswithdifferenttypesareneverequal:
returnfalse;
}
Finally,weimplementoperator==forFunctionPtr,whichfirstchecks
fornullfunctorsandthendelegatestoFunctorBridge:Clickheretoview
codeimage
friendbool
operator==(FunctionPtrconst&f1,FunctionPtrconst&f2){
if(!f1||!f2){
return!f1&&!f2;
}
returnf1.bridge->equals(f2.bridge);
}
friendbool
operator!=(FunctionPtrconst&f1,FunctionPtrconst&f2){
return!(f1==f2);
}
Thisimplementationiscorrect.However,ithasanunfortunatedrawback:Ifthe
FunctionPtrisassignedorinitializedwithafunctionobjectthatdoesnot
haveasuitableoperator==(whichincludeslambdas,forexample),the
programwillfailtocompile.Thismaycomeasasurprise,because
FunctionPtrsoperator==hasn’tevenbeenusedyet,andmanyother
classtemplates—suchasstd::vector—canbeinstantiatedwithtypesthat
don’thaveanoperator==solongastheirownoperator==isnotused.
Thisproblemwithoperator==isduetotypeerasure:Becauseweare
effectivelylosingthetypeofthefunctionobjectoncetheFunctionPtrhas
beenassignedorinitialized,weneedtocapturealloftheinformationweneedto
knowaboutthetypebeforethatassignmentorinitializationiscomplete.This
informationincludesformingacalltothefunctionobject’soperator==,
becausewecan’tbesurewhenitwillbeneeded.3
Fortunately,wecanuseSFINAE-basedtraits(discussedinSection19.4on
page416)toquerywhetheroperator==isavailablebeforecallingit,witha
fairlyelaboratetrait:Clickheretoviewcodeimage
bridge/isequalitycomparable.hpp
#include<utility>//fordeclval()
#include<type_traits>//fortrue_typeandfalse_type
template<typenameT>
classIsEqualityComparable
{
private:
//testconvertibilityof==and!==tobool:
staticvoid*conv(bool);//tocheckconvertibilitytobool
template<typenameU>
staticstd::true_typetest(decltype(conv(std::declval<Uconst&>()==
std::declval<Uconst&>())),
decltype(conv(!(std::declval<Uconst&>()==
std::declval<Uconst&>())))
);
//fallback:
template<typenameU>
staticstd::false_typetest(…);
public:
staticconstexprboolvalue=decltype(test<T>(nullptr,
nullptr))::value;
};
TheIsEqualityComparabletraitappliesthetypicalformforexpression-
testingtraitsasintroducedinSection19.4.1onpage416:twotest()
overloads,oneofwhichcontainstheexpressionstotestwrappedindecltype,
andtheotherthatacceptsarbitraryargumentsviaanellipsis.Thefirsttest()
functionattemptstocomparetwoobjectsoftypeTconstusing==andthen
ensuresthattheresultcanbebothimplicitlyconvertedtobool(forthefirst
parameter)andpassedtothelogicalnegationoperatoroperator!)witha
resultconvertibletobool.Ifbothoperationsarewellformed,theparameter
typesthemselveswillbothbevoid*.
UsingtheIsEqualityComparabletrait,wecanconstructa
TryEqualsclasstemplatethatcaneitherinvoke==onthegiventype(when
it’savailable)orthrowanexceptionifnosuitable==exists:Clickheretoview
codeimage
bridge/tryequals.hpp
#include<exception>
#include"isequalitycomparable.hpp"
template<typenameT,
boolEqComparable=IsEqualityComparable<T>::value>
structTryEquals
{
staticboolequals(Tconst&x1,Tconst&x2){
returnx1==x2;
}
};
classNotEqualityComparable:publicstd::exception
{
};
template<typenameT>
structTryEquals<T,false>
{
staticboolequals(Tconst&x1,Tconst&x2){
throwNotEqualityComparable();
}
};
Finally,byusingTryEqualswithinourimplementationof
SpecificFunctorBridge,weareabletoprovidesupportfor==within
FunctionPtrwheneverthestoredfunctionobjecttypesmatchandthe
functionobjectsupports==:Clickheretoviewcodeimage
virtualboolequals(FunctorBridge<R,Args…>const*fb)constoverride
{
if(autospecFb=dynamic_cast<SpecificFunctorBridgeconst*>(fb)){
returnTryEquals<Functor>::equals(functor,specFb->functor);
}
//functorswithdifferenttypesareneverequal:
returnfalse;
}
22.6PerformanceConsiderations
Typeerasureprovidessomeoftheadvantagesofbothstaticpolymorphismand
dynamicpolymorphism,butnotall.Inparticular,theperformanceofgenerated
codeusingtypeerasurehewsmorecloselytothatofdynamicpolymorphism,
becausebothusedynamicdispatchviavirtualfunctions.Thus,someofthe
traditionaladvantagesofstaticpolymorphism,suchastheabilityofthecompiler
toinlinecalls,maybelost.Whetherthislossofperformancewillbeperceptible
isapplication-dependent,butit’softeneasytotellbyconsideringhowmuch
workisperformedinthefunctionbeingcalledrelativetothecostofavirtual
functioncall:Ifthetwoareclose(e.g.,usingFunctionPtrtosimplyaddtwo
integers),typeerasureislikelytoexecutefarmoreslowlythanastatic-
polymorphicversion.If,ontheotherhand,thefunctioncallperformsa
significantamountofwork—queryingadatabase,sortingacontainer,or
updatingauserinterface—theoverheadoftypeerasureisunlikelytobe
measurable.
22.7Afternotes
KevlinHenneypopularizedtypeerasureinC++withtheintroductionoftheany
type[HenneyValued-Conversions],whichlaterbecameapopularBoostlibrary
[BoostAny]andpartoftheC++standardlibrarywithC++17.Thetechniquewas
refinedsomewhatintheBoost.Functionlibrary[Boost-Function],whichapplied
variousperformanceandcode-sizeoptimizationsandeventuallybecame
std::function<>.However,eachoftheearlylibrariesaddressedonlya
singlesetofoperations:anywasasimplevaluetypewithonlyacopyandacast
operation;functionaddedinvocationtothat.
Laterefforts,suchastheBoost.TypeErasurelibrary[BoostTypeErasure]and
Adobe’sPolylibrary[AdobePoly],applytemplatemetaprogrammingtechniques
toallowuserstoformatype-erasedvaluewithsomespecifiedlistof
capabilities.Forexample,thefollowingtype(constructedusingthe
Boost.TypeErasurelibrary)handlescopyconstruction,atypeid-likeoperation,
andoutputstreamingforprinting:Clickheretoviewcodeimage
usingAnyPrintable=any<mpl::vector<copy_constructible<>,
typeid_<>,
ostreamable<>
>>;
1Makinginvoke()constisasafetybeltagainstinvokingnon-constoperator()
overloadsthroughconst
FunctionPtrobjects,whichwouldviolatetheexpectationsofprogrammers.
2Althoughthetypecouldbequeriedwithdynamic_cast(amongother
things),theFunctionPtrclassmakesthebridgepointerprivate,so
clientsofFunctionPtrhavenoaccesstothetypeitself.
3Mechanically,thecodeforthecalltooperator==isinstantiatedbecause
allofthevirtualfunctionsofaclasstemplate(inthiscase,
SpecificFunctorBridge)aretypicallyinstantiatedwhentheclass
templateitselfisinstantiated.
Chapter23
Metaprogramming
Metaprogrammingconsistsof“programmingaprogram.”Inotherwords,welay
outcodethattheprogrammingsystemexecutestogeneratenewcodethat
implementsthefunctionalitywereallywant.Usually,theterm
metaprogrammingimpliesareflexiveattribute:Themetaprogramming
componentispartoftheprogramforwhichitgeneratesabitofcode(i.e.,an
additionalordifferentbitoftheprogram).
Whywouldmetaprogrammingbedesirable?Aswithmostotherprogramming
techniques,thegoalistoachievemorefunctionalitywithlesseffort,whereeffort
canbemeasuredascodesize,maintenancecost,andsoforth.What
characterizesmetaprogrammingisthatsomeuser-definedcomputationhappens
attranslationtime.Theunderlyingmotivationisoftenperformance(things
computedattranslationtimecanfrequentlybeoptimizedaway)orinterface
simplicity(ametapro-gramisgenerallyshorterthanwhatitexpandsto)orboth.
Metaprogrammingoftenreliesontheconceptsoftraitsandtypefunctions,as
developedinChapter19.Wethereforerecommendbecomingfamiliarwiththat
chapterpriortodelvingintothisone.
23.1TheStateofModernC++Metaprogramming
C++metaprogrammingtechniquesevolvedovertime(theAfternotesattheend
ofthischaptersurveysomemilestonesinthisarea).Let’sdiscussandcategorize
thevariousapproachestometaprogrammingthatareincommonuseinmodern
C++.
23.1.1ValueMetaprogramming
Inthefirsteditionofthisbook,wewerelimitedtothefeaturesintroducedinthe
originalC++standard(publishedin1998,withminorcorrectionsin2003).In
thatworld,composingsimplecompile-time(“meta-”)computationswasaminor
challenge.Wethereforedevotedagoodchunkofthischaptertodoingjustthat;
onefairlyadvancedexamplecomputedthesquarerootofanintegervalueat
compiletimeusingrecursivetemplateinstantiations.AsintroducedinSection
8.2onpage125,C++11and,especially,C++14removedmostofthatchallenge
withtheintroductionofconstexprfunctions.1Forexample,sinceC++14,a
compile-timefunctiontocomputeasquarerootiseasilywrittenasfollows:
Clickheretoviewcodeimage
meta/sqrtconstexpr.hpp
template<typenameT>
constexprTsqrt(Tx)
{
//handlecaseswherexanditssquarerootareequalasaspecial
casetosimplify
//theiterationcriterionforlargerx:
if(x<=1){
returnx;
}
//repeatedlydetermineinwhichhalfofa[lo,hi]intervalthe
squarerootofxislocated,
//untiltheintervalisreducedtojustonevalue:
Tlo=0,hi=x;
for(;;){
automid=(hi+lo)/2,midSquared=mid*mid;
if(lo+1>=hi||midSquared==x){
//midmustbethesquareroot:
returnmid;
}
//continuewiththehigher/lowerhalf-interval:
if(midSquared<x){
lo=mid;
}
else{
hi=mid;
}
}
}
Thisalgorithmsearchesfortheanswerbyrepeatedlyhalvinganintervalknown
tocontainthesquarerootofx(therootsof0and1aretreatedasspecialcasesto
keeptheconvergencecriterionsimple).Thissqrt()functioncanbeevaluated
atcompileorruntime:Clickheretoviewcodeimage
static_assert(sqrt(25)==5,"");//OK(evaluatedatcompiletime)
static_assert(sqrt(40)==6,"");//OK(evaluatedatcompiletime)
std::array<int,sqrt(40)+1>arr;//declaresarrayof7elements
(compiletime)
Clickheretoviewcodeimage
longlongl=53478;
std::cout<<sqrt(l)<<’\n’;//prints231(evaluatedatruntime)
Thisfunction’simplementationmaynotbethemostefficientatruntime(where
exploitingpeculiaritiesofthemachineoftenpaysoff),butbecauseitismeantto
performcompile-timecomputations,absoluteefficiencyislessimportantthan
portability.Notethatnoadvanced“templatemagic”isinsightinthatsquareroot
example,onlytheusualtemplateargumentdeductionforafunctiontemplate.
Thecodeis“plainC++”andisnotparticularlychallengingtoread.
Valuemetaprogramming(i.e.,programmingthecomputationofcompile-time
values)aswedidaboveisoccasionallyquiteuseful,buttherearetwoadditional
kindsofmetaprogrammingthatcanbeperformedwithmodernC++(say,C++14
andC++17):typemetaprogrammingandhybridmetaprogramming.
23.1.2TypeMetaprogramming
Wealreadyencounteredaformoftypecomputationinourdiscussionofcertain
traitstemplatesinChapter19,whichtakeatypeasinputandproduceanewtype
fromit.Forexample,ourRemoveReferenceTclasstemplatecomputesthe
underlyingtypeofareferencetype.However,theexampleswedevelopedin
Chapter19computedonlyfairlyelementarytypeoperations.Byrelyingon
recursivetemplateinstantiation—amainstayoftemplate-based
metaprogramming—wecanperformtypecomputationsthatareconsiderably
morecomplex.
Considerthefollowingsmallexample:
Clickheretoviewcodeimage
meta/removeallextents.hpp
//primarytemplate:ingeneralweyieldthegiventype:
template<typenameT>
structRemoveAllExtentsT{
usingType=T;
};
//partialspecializationsforarraytypes(withandwithoutbounds):
template<typenameT,std::size_tSZ>
structRemoveAllExtentsT<T[SZ]>{
usingType=typenameRemoveAllExtentsT<T>::Type;
};
template<typenameT>
structRemoveAllExtentsT<T[]>{
usingType=typenameRemoveAllExtentsT<T>::Type;
};
template<typenameT>
usingRemoveAllExtents=typenameRemoveAllExtentsT<T>::Type;Here,
RemoveAllExtentsisatypemetafunction(i.e.,acomputationaldevice
thatproducesaresulttype)thatwillremoveanarbitrarynumberof
top-level“arraylayers”fromatype.2Youcanuseitasfollows:
Clickheretoviewcodeimage
RemoveAllExtents<int[]>//yieldsint
RemoveAllExtents<int[5][10]>//yieldsint
RemoveAllExtents<int[][10]>//yieldsint
RemoveAllExtents<int(*)[5]>//yieldsint(*)[5]
Themetafunctionperformsitstaskbyhavingthepartialspecializationthat
matchesthetop-levelarraycaserecursively“invoke”themetafunctionitself.
Computingwithvalueswouldbeverylimitedifallthatwasavailabletous
werescalarvalues.Fortunately,justaboutanyprogramminglanguagehasat
leastonecontainerofvaluesconstructthatgreatlymagnifiesthepowerofthat
language(andmostlanguageshaveavarietyofcontainerkinds,suchas
arrays/vectors,hashtables,etc.).Thesameistrueoftypemetaprogramming:
Addinga“containeroftypes”constructgreatlyincreasestheapplicabilityofthe
discipline.Fortunately,modernC++includesmechanismsthatenablethe
developmentofsuchacontainer.Chapter24developsaTypelist<…>class
template,whichisexactlysuchacontaineroftypes,ingreatdetail.
23.1.3HybridMetaprogramming
Withvaluemetaprogrammingandtypemetaprogrammingwecancompute
valuesandtypesatcompiletime.Ultimately,however,we’reinterestedinrun-
timeeffects,soweusethesemetaprogramsinruntimecodeinplaceswhere
typesandconstantsareexpected.Metaprogrammingcandomorethanthat,
however:Wecanprogrammaticallyassembleatcompiletimebitsofcodewitha
run-timeeffect.Wecallthathybridmetaprogramming.
Toillustratethisprinciple,let’sstartwithasimpleexample:computingthe
dot-productoftwostd::arrayvalues.Recallthatstd::arrayisafixed-
lengthcontainertemplatedeclaredasfollows:Clickheretoviewcodeimage
namespacestd{
template<typenameT,size_tN>structarray;
}
whereNisthenumberofelements(oftypeT)inthearray.Giventwoobjectsof
thesamearraytype,theirdot-productcanbecomputedasfollows:Clickhereto
viewcodeimage
template<typenameT,std::size_tN>
autodotProduct(std::array<T,N>const&x,std::array<T,N>const&y)
{
Tresult{};
for(std::size_tk=0;k<N;++k){
result+=x[k]*y[k];
}
returnresult;
}
Astraightforwardcompilationofthefor-loopwillproducebranching
instructionsthatonsomemachinesmaycausesomeoverheadcomparedtoa
straight-lineexecutionofresult+=x[0]*y[0];
result+=x[1]*y[1];
result+=x[2]*y[2];
result+=x[3]*y[3];
…
Fortunately,moderncompilerswilloptimizetheloopintowhicheverformis
mostefficientforthetargetplatform.Forthesakeofdiscussion,however,let’s
rewriteourdotProduct()implementationinawaythatavoidstheloop:3
Clickheretoviewcodeimage
template<typenameT,std::size_tN>
structDotProductT{
staticinlineTresult(T*a,T*b){
return*a**b+DotProduct<T,N-1>::result(a+1,b+1);
}
};
//partialspecializationasendcriteria
template<typenameT>
structDotProductT<T,0>{
staticinlineTresult(T*,T*){
returnT{};
}
};
template<typenameT,std::size_tN>
autodotProduct(std::array<T,N>const&x,
std::array<T,N>const&y){
returnDotProductT<T,N>::result(x.begin(),y.begin());
}
Thisnewimplementationdelegatestheworktoaclasstemplate
DotProductT.Thatenablesustouserecursivetemplateinstantiationwith
classtemplatepartialspecializationtoendtherecursion.Notehoweach
instantiationofDotProductTproducesthesumofonetermofthedot-product
andthedot-productoftheremainingcomponentsofthearray.Forvaluesoftype
std::array<T,N>therewillthereforebeNinstancesoftheprimary
templateandoneinstanceoftheterminatingpartialspecialization.Forthistobe
efficient,itiscriticalthatthecompilerinlineseveryinvocationofthestatic
memberfunctionsresult().Fortunately,thatisgenerallytruewhenevena
moderatelevelofcompileroptimizationsisenabled.4
Thecentralobservationaboutthiscodeisthatitblendsacompile-time
computation(achievedherethroughrecursivetemplateinstantiation)that
determinestheoverallstructureofthecodewitharun-timecomputation(calling
result())thatdeterminesthespecificrun-timeeffect.
Wementionedearlierthattypemetaprogrammingisgreatlyenhancedbythe
availabilityofa“containeroftypes.”We’vealreadyseenthatinhybrid
metaprogrammingafixed-lengtharraytypecanbeuseful.Nonetheless,thetrue
“herocontainer”ofhybridmetaprogrammingisthetuple.Atupleisasequence
ofvalues,eachwithaselectabletype.TheC++standardlibraryincludesa
std::tupleclasstemplatethatsupportsthatnotion.Forexample,Clickhere
toviewcodeimage
std::tuple<int,std::string,bool>tVal{42,"Answer",true};
definesavariabletValthataggregatesthreevaluesoftypesint,
std::string,andbool(inthatspecificorder).Becauseofthetremendous
importanceoftuple-likecontainersformodernC++programming,wedevelop
oneindetailinChapter25.ThetypeoftValaboveisverysimilartoasimple
structtypelike:Clickheretoviewcodeimage
structMyTriple{
intv1;
std::stringv2;
boolv3;
};
Giventhatinstd::arrayandstd::tuplewehaveflexiblecounterpartsto
arraytypesand(simple)structtypes,itisnaturaltowonderwhethera
counterparttosimpleuniontypeswouldalsobeusefulforhybridcomputation.
Theansweris“yes.”TheC++standardlibraryintroducedastd::variant
templateforthispurposeinC++17,andwedevelopasimilarcomponentin
Chapter26.
Becausestd::tupleandstd::variant,likestructtypes,are
heterogeneoustypes,hybridmetaprogrammingthatusessuchtypesissometimes
calledheterogeneousmetaprogramming.
23.1.4HybridMetaprogrammingforUnitTypes
Anotherexampledemonstratingthepowerofhybridcomputingislibrariesthat
areabletocomputeresultsofvaluesofdifferentunittypes.Thevalue
computationisperformedatruntime,butthecomputationoftheresultingunits
itdeterminedatcompiletime.
Let’sillustratethiswithahighlysimplifiedexample.Wearegoingtokeep
trackofunitsintermsoftheirratio(fraction)ofaprincipalunit.Forexample,if
theprincipalunitfortimeisasecond,amillisecondisrepresentedwithratio
1/1000andaminutewithratio60/1.Thekey,then,istodefinearatiotype
whereeachvaluehasitsowntype:Clickheretoviewcodeimage
meta/ratio.hpp
template<unsignedN,unsignedD=1>
structRatio{
staticconstexprunsignednum=N;//numerator
staticconstexprunsignedden=D;//denominator
usingType=Ratio<num,den>;
};
Nowwecandefinecompile-timecomputationssuchasaddingtwounits:Click
heretoviewcodeimage
meta/ratioadd.hpp
//implementationofaddingtworatios:
template<typenameR1,typenameR2>
structRatioAddImpl
{
private:
staticconstexprunsignedden=R1::den*R2::den;
staticconstexprunsignednum=R1::num*R2::den+R2::num*R1::den;
public:
typedefRatio<num,den>Type;
};
//usingdeclarationforconvenientusage:
template<typenameR1,typenameR2>
usingRatioAdd=typenameRatioAddImpl<R1,R2>::Type;Thisallowsus
tocomputethesumoftworatiosatcompiletime:Clickheretoview
codeimage
usingR1=Ratio<1,1000>;
usingR2=Ratio<2,3>;
usingRS=RatioAdd<R1,R2>;//RShastypeRatio<2003,2000>
std::cout<<RS::num<<’/’<<RS::den<<’\n’;//prints2003/3000
usingRA=RatioAdd<Ratio<2,3>,Ratio<5,7>>;//RAhastype
Ratio<29,21>
std::cout<<RA::num<<’/’<<RA::den<<’\n’;//prints29/21
Wecannowdefineaclasstemplatefordurations,parameterizedwithan
arbitraryvaluetypeandaunittypethatisaninstanceofRatio<>:Clickhere
toviewcodeimage
meta/duration.hpp
//durationtypeforvaluesoftypeTwithunittypeU:
template<typenameT,typenameU=Ratio<1>>
classDuration{
public:
usingValueType=T;
usingUnitType=typenameU::Type;
private:
ValueTypeval;
public:
constexprDuration(ValueTypev=0)
:val(v){
}
constexprValueTypevalue()const{
returnval;
}
};
Theinterestingpartisthedefinitionofanoperator+toaddtwoDurations:
Clickheretoviewcodeimage
meta/durationadd.hpp
//addingtwodurationswhereunittypemightdiffer:
template<typenameT1,typenameU1,typenameT2,typenameU2>
autoconstexproperator+(Duration<T1,U1>const&lhs,
Duration<T2,U2>const&rhs)
{
//resultingtypeisaunitwith1anominatorand
//theresultingdenominatorofaddingbothunittypefractions
usingVT=Ratio<1,RatioAdd<U1,U2>::den>;
//resultingvalueisthesumofbothvalues
//convertedtotheresultingunittype:
autoval=lhs.value()*VT::den/U1::den*U1::num+
rhs.value()*VT::den/U2::den*U2::num;
returnDuration<decltype(val),VT>(val);
}
Weallowtheargumentstohavedifferentunittypes,U1andU2.Andweuse
theseunittypestocomputetheresultingdurationtohaveaunittypethatisthe
correspondingunitfraction(afractionwherethenumeratoris1).Withallthatin
place,wecancompilethefollowingcode:Clickheretoviewcodeimage
intx=42;
inty=77;
autoa=Duration<int,Ratio<1,1000>>(x);//xmilliseconds
autob=Duration<int,Ratio<2,3>>(y);//y2/3seconds
autoc=a+b;//computesresultingunittype1/3000seconds
//andgeneratesrun-timecodeforc=a*3+b*2000
Thekey“hybrid”effectisthatforthesumcthecompilerdeterminesthe
resultingunittypeRatio<1,3000>atcompiletimeandgeneratescodeto
computeatruntimetheresultingvalue,whichisadjustedfortheresultingunit
type.
Becausethevaluetypeisatemplateparameter,wecanuseclassDuration
withvaluetypesotherthanintorevenuseheterogeneousvaluetypes(aslong
asaddingthevaluesofthesetypesisdefined):Clickheretoviewcodeimage
autod=Duration<double,Ratio<1,3>>(7.5);//7.51/3seconds
autoe=Duration<int,Ratio<1>>(4);//4seconds
autof=d+e;//computesresultingunittype1/3seconds
//andgeneratescodeforf=d+e*3
Inaddition,thecompilercanevenperformthevaluecomputationatcompile-
timeifthevaluesareknownatcompiletime,becauseoperator+for
durationsisconstexpr.
TheC++standardlibraryclasstemplatestd::chronousesthisapproach
withseveralrefinements,suchasusingpredefinedunits(e.g.,
std::chrono::milliseconds),supportingdurationliterals(e.g.,10ms),
anddealingwithoverflow.
23.2TheDimensionsofReflectiveMetaprogramming
Previously,wedescribedvaluemetaprogrammingbasedonconstexpr
evaluationandtypemetaprogrammingbasedonrecursivetemplateinstantiation.
Thesetwooptions,bothavailableinmodernC++,clearlyinvolvedifferent
methodsdrivingthecomputation.Itturnsoutthatvaluemetaprogrammingcan
alsobedrivenintermsofrecursivetemplateinstantiation,and,priortothe
introductionofconstexprfunctionsinC++11,thatwasthemechanismof
choice.Forexample,thefollowingcodecomputesasquarerootofaninteger
usingrecursiveinstantiation:Clickheretoviewcodeimage
meta/sqrt1.hpp
//primarytemplatetocomputesqrt(N)
template<intN,intLO=1,intHI=N>
structSqrt{
//computethemidpoint,roundedup
staticconstexprautomid=(LO+HI+1)/2;
//searchanottoolargevalueinahalvedinterval
staticconstexprautovalue=(N<mid*mid)?Sqrt<N,LO,mid-1>::value
:Sqrt<N,mid,HI>::value;
};
//partialspecializationforthecasewhenLOequalsHI
template<intN,intM>
structSqrt<N,M,M>{
staticconstexprautovalue=M;
};
Thismetaprogramusesmuchthesamealgorithmasourintegersquareroot
constexprfunctioninSection23.1.1onpage529,successivelyhalvingan
intervalknowntocontainthesquareroot.However,theinputtothe
metafunctionisanontypetemplateargumentinsteadofafunctionargument,and
the“localvariables”trackingtheboundstotheintervalarealsorecastas
nontypetemplatearguments.Clearly,thisisafarlessfriendlyapproachthanthe
constexprfunction,butwewillneverthelessanalyzethiscodelateronto
examinehowitconsumescompilerresources.
Inanycase,wecanseethatthecomputationalengineofmetaprogramming
couldpotentiallyhavemanyoptions.Thatis,however,nottheonlydimensionin
whichsuchoptionsmaybeconsidered.Instead,weliketothinkthata
comprehensivemetaprogrammingsolutionforC++mustmakechoicesalong
threedimensions:•Computation
•Reflection
•Generation
Reflectionistheabilitytoprogrammaticallyinspectfeaturesoftheprogram.
Generationreferstotheabilitytogenerateadditionalcodefortheprogram.
Wehaveseentwooptionsforcomputation:recursiveinstantiationand
constexprevaluation.Forreflection,wehavefoundapartialsolutionintype
traits(seeSection19.6.1onpage431).Althoughavailabletraitsenablequitea
fewadvancedtemplatetechniques,theyarefarfromcoveringallthatiswanted
fromareflectionfacilityinthelanguage.Forexample,givenaclasstype,many
applicationswouldliketoprogrammaticallyexplorethemembersofthatclass.
Ourcurrenttraitsarebasedontemplateinstantiation,andC++could
conceivablyprovideadditionallanguagefacilitiesor“intrinsic”library
components5toproduceclasstemplateinstancesthatcontainthereflected
informationatcompiletime.Suchanapproachisagoodmatchforcomputations
basedonrecursivetemplateinstantiations.Unfortunately,classtemplate
instancesconsumealotofcompilerstorageandthatstoragecannotbereleased
untiltheendofthecompilation(tryingtodootherwisewouldresultintaking
significantmorecompilationtime).Analternativeoption,whichisexpectedto
pairwellwiththeconstexprevaluationoptionforthe“computation”
dimension,istointroduceanewstandardtypetorepresentreflectedinformation.
Section17.9onpage363discussesthisoption(whichisnowunderactive
investigationbytheC++standardizationcommittee).
Section17.9onpage363alsoshowsapotentialfutureapproachtopowerful
codegeneration.Creatingaflexible,general,andprogrammer-friendlycode
generationmechanismwithintheexistingC++languageremainsachallenge
thatisbeinginvestigatedbyvariousparties.However,itisalsotruethat
instantiatingtemplateshasalwaysbeena“codegeneration”mechanismofsorts.
Inaddition,compilershavebecomereliableenoughatexpandingcallstosmall
functionsin-linethatthatmechanismcanbeusedasavehicleforcode
generation.ThoseobservationsareexactlywhatunderliesourDotProductT
exampleaboveand,combinedwithmorepowerfulreflectionfacilities,existing
techniquescanalreadyachieveremarkablemetaprogrammingeffects.
23.3TheCostofRecursiveInstantiation
Let’sanalyzetheSqrt<>templateintroducedinSection23.2onpage537.The
primarytemplateisthegeneralrecursivecomputationthatisinvokedwiththe
templateparameterN(thevalueforwhichtocomputethesquareroot)andtwo
otheroptionalparameters.Theseoptionalparametersrepresenttheminimumand
maximumvaluestheresultcanhave.Ifthetemplateiscalledwithonlyone
argument,weknowthatthesquarerootisatleast1andatmostthevalueitself.
Therecursionthenproceedsusingabinarysearchtechnique(oftencalled
methodofbisectioninthiscontext).Insidethetemplate,wecomputewhether
valueisinthefirstorthesecondhalfoftherangebetweenLOandHI.This
casedifferentiationisdoneusingtheconditionaloperator?:.Ifmid2isgreater
thanN,wecontinuethesearchinthefirsthalf.Ifmid2islessthanorequaltoN,
weusethesametemplateforthesecondhalfagain.
ThepartialspecializationendstherecursiveprocesswhenLOandHIhavethe
samevalueM,whichisourfinalvalue.
Templateinstantiationsarenotcheap:Evenrelativelymodestclasstemplates
canallocateoverakilobyteofstorageforeveryinstance,andthatstoragecannot
bereclaimeduntilcompilationascompleted.Let’sthereforeexaminethedetails
ofasimpleprogramthatusesourSqrttemplate:Clickheretoviewcodeimage
meta/sqrt1.cpp
#include<iostream>
#include"sqrt1.hpp"
intmain()
{
std::cout<<"Sqrt<16>::value="<<Sqrt<16>::value<<’\n’;
std::cout<<"Sqrt<25>::value="<<Sqrt<25>::value<<’\n’;
std::cout<<"Sqrt<42>::value="<<Sqrt<42>::value<<’\n’;
std::cout<<"Sqrt<1>::value="<<Sqrt<1>::value<<’\n’;
}
Theexpression
Sqrt<16>::value
isexpandedto
Sqrt<16,1,16>::value
Insidethetemplate,themetaprogramcomputesSqrt<16,1,16>::valueas
follows:mid=(1+16+1)/2
=9
Clickheretoviewcodeimage
value=(16<9*9)?Sqrt<16,1,8>::value
:Sqrt<16,9,16>::value
=(16<81)?Sqrt<16,1,8>::value
:Sqrt<16,9,16>::value
=Sqrt<16,1,8>::value
Thus,theresultiscomputedasSqrt<16,1,8>::value,whichisexpanded
asfollows:Clickheretoviewcodeimage
mid=(1+8+1)/2
=5
value=(16<5*5)?Sqrt<16,1,4>::value
:Sqrt<16,5,8>::value
=(16<25)?Sqrt<16,1,4>::value
:Sqrt<16,5,8>::value
=Sqrt<16,1,4>::value
Similarly,Sqrt<16,1,4>::valueisdecomposedasfollows:Clickhereto
viewcodeimage
mid=(1+4+1)/2
=3
value=(16<3*3)?Sqrt<16,1,2>::value
:Sqrt<16,3,4>::value
=(16<9)?Sqrt<16,1,2>::value
:Sqrt<16,3,4>::value
=Sqrt<16,3,4>::value
Finally,Sqrt<16,3,4>::valueresultsinthefollowing:Clickheretoview
codeimage
mid=(3+4+1)/2
=4
value=(16<4*4)?Sqrt<16,3,3>::value
:Sqrt<16,4,4>::value
=(16<16)?Sqrt<16,3,3>::value
:Sqrt<16,4,4>::value
=Sqrt<16,4,4>::value
andSqrt<16,4,4>::valueendstherecursiveprocessbecauseitmatches
theexplicitspecializationthatcatchesequalhighandlowbounds.Thefinal
resultisthereforevalue=4
23.3.1TrackingAllInstantiations
Ouranalysisabovefollowedthesignificantinstantiationsthatcomputethe
squarerootof16.However,whenacompilerevaluatestheexpressionClickhere
toviewcodeimage
(16<=8*8)?Sqrt<16,1,8>::value
:Sqrt<16,9,16>::value
itinstantiatesnotonlythetemplatesinthepositivebranchbutalsothoseinthe
negativebranch(Sqrt<16,9,16>).Furthermore,becausethecodeattempts
toaccessamemberoftheresultingclasstypeusingthe::operator,allthe
membersinsidethatclasstypearealsoinstantiated.Thismeansthatthefull
instantiationofSqrt<16,9,16>resultsinthefullinstantiationof
Sqrt<16,9,12>andSqrt<16,13,16>.Whenthewholeprocessis
examinedindetail,wefindthatdozensofinstantiationsendupbeinggenerated.
ThetotalnumberisalmosttwicethevalueofN.
Fortunately,therearetechniquestoreducethisexplosioninthenumberof
instantiations.Toillustrateonesuchimportanttechnique,werewriteourSqrt
metaprogramasfollows:Clickheretoviewcodeimage
meta/sqrt2.hpp
#include"ifthenelse.hpp"
//primarytemplateformainrecursivestep
template<intN,intLO=1,intHI=N>
structSqrt{
//computethemidpoint,roundedup
staticconstexprautomid=(LO+HI+1)/2;
//searchanottoolargevalueinahalvedinterval
usingSubT=IfThenElse<(N<mid*mid),
Sqrt<N,LO,mid-1>,
Sqrt<N,mid,HI>>;
staticconstexprautovalue=SubT::value;
};
//partialspecializationforendofrecursioncriterion
template<intN,intS>
structSqrt<N,S,S>{
staticconstexprautovalue=S;
};
ThekeychangehereistheuseoftheIfThenElsetemplate,whichwas
introducedinSection19.7.1onpage440.Remember,theIfThenElse
templateisadevicethatselectsbetweentwotypesbasedonagivenBoolean
constant.Iftheconstantistrue,thefirsttypeistype-aliasedtoType;
otherwise,Typestandsforthesecondtype.Atthispointitisimportantto
rememberthatdefiningatypealiasforaclasstemplateinstancedoesnotcausea
C++compilertoinstantiatethebodyofthatinstance.Therefore,whenwewrite
Clickheretoviewcodeimage
usingSubT=IfThenElse<(N<mid*mid),
Sqrt<N,LO,mid-1>,
Sqrt<N,mid,HI>>;
neitherSqrt<N,LO,mid-1>norSqrt<N,mid,HI>isfullyinstantiated.
WhicheverofthesetwotypesendsupbeingasynonymforSubTisfully
instantiatedwhenlookingupSubT::value.Incontrasttoourfirstapproach,
thisstrategyleadstoanumberofinstantiationsthatisproportionaltolog2(N):a
verysignificantreductioninthecostofmetaprogrammingwhenNgets
moderatelylarge.
23.4ComputationalCompleteness
OurSqrt<>exampledemonstratesthatatemplatemetaprogramcancontain:•
Statevariables:Thetemplateparameters
•Loopconstructs:Throughrecursion
•Executionpathselection:Byusingconditionalexpressionsorspecializations•
Integerarithmetic
Iftherearenolimitstotheamountofrecursiveinstantiationsandthenumberof
statevariablesthatareallowed,itcanbeshownthatthisissufficienttocompute
anythingthatiscomputable.However,itmaynotbeconvenienttodosousing
templates.Furthermore,becausetemplateinstantiationrequiressubstantial
compilerresources,extensiverecursiveinstantiationquicklyslowsdowna
compilerorevenexhauststheresourcesavailable.TheC++standard
recommendsbutdoesnotmandatethat1024levelsofrecursiveinstantiationsbe
allowedasaminimum,whichissufficientformost(butcertainlynotall)
templatemetaprogrammingtasks.
Hence,inpractice,templatemetaprogramsshouldbeusedsparingly.There
areafewsituations,however,whentheyareirreplaceableasatooltoimplement
convenienttemplates.Inparticular,theycansometimesbehiddenintheinnards
ofmoreconventionaltemplatestosqueezemoreperformanceoutofcritical
algorithmimplementations.
23.5RecursiveInstantiationversusRecursive
TemplateArguments
Considerthefollowingrecursivetemplate:Clickheretoviewcodeimage
template<typenameT,typenameU>
structDoublify{
};
template<intN>
structTrouble{
usingLongType=Doublify<typenameTrouble<N-1>::LongType,
typenameTrouble<N-1>::LongType>;
};
template<>
structTrouble<0>{
usingLongType=double;
};
Trouble<10>::LongTypeouch;
TheuseofTrouble<10>::LongTypenotonlytriggerstherecursive
instantiationofTrouble<9>,Trouble<8>,…,Trouble<0>,butitalso
instantiatesDoublifyoverincreasinglycomplextypes.Table23.1illustrates
howquicklyitgrows.
TypeAlias UnderlyingType
Trouble<0>::LongType double
Trouble<1>::LongType Doublify<double,double>
Trouble<2>::LongType Doublify<Doublify<double,double>,
Doublify<double,double>>
Trouble<3>::LongType Doublify<Doublify<Doublify<double,double>,
Doublify<double,double>>,
<Doublify<double,double>,
Doublify<double,double>>>
Table23.1.GrowthofTrouble<N>::LongType
AscanbeseenfromTable23.1,thecomplexityofthetypedescriptionofthe
expressionTrouble<N>::LongTypegrowsexponentiallywithN.In
general,suchasituationstressesaC++compilerevenmorethandorecursive
instantiationsthatdonotinvolverecursivetemplatearguments.Oneofthe
problemshereisthatacompilerkeepsarepresentationofthemanglednamefor
thetype.Thismanglednameencodestheexacttemplatespecializationinsome
way,andearlyC++implementationsusedanencodingthatisroughly
proportionaltothelengthofthetemplate-id.Thesecompilersthenusedwell
over10,000charactersforTrouble<10>::LongType.
NewerC++implementationstakeintoaccountthefactthatnestedtemplate-
idsarefairlycommoninmodernC++programsanduseclevercompression
techniquestoreduceconsiderablythegrowthinnameencoding(e.g.,afew
hundredcharactersforTrouble<10>::LongType).Thesenewercompilers
alsoavoidgeneratingamanglednameifnoneisactuallyneededbecauseno
low-levelcodeisactuallygeneratedforthetemplateinstance.Still,allother
thingsbeingequal,itisprobablypreferabletoorganizerecursiveinstantiationin
suchawaythattemplateargumentsneednotalsobenestedrecursively.
23.6EnumerationValuesversusStaticConstants
IntheearlydaysofC++,enumerationvaluesweretheonlymechanismtocreate
“trueconstants”(calledconstant-expressions)asnamedmembersinclass
declarations.Withthem,wecould,forexample,defineaPow3metaprogramto
computepowersof3asfollows:Clickheretoviewcodeimage
meta/pow3enum.hpp
//primarytemplatetocompute3totheNth
template<intN>
structPow3{
enum{value=3*Pow3<N-1>::value};
};
//fullspecializationtoendtherecursion
template<>
structPow3<0>{
enum{value=1};
};
ThestandardizationofC++98introducedtheconceptofin-classstaticconstant
initializers,sothatourPow3metaprogramcouldlookasfollows:Clickhereto
viewcodeimage
meta/pow3const.hpp
//primarytemplatetocompute3totheNth
template<intN
structPow3{
staticintconstvalue=3*Pow3<N-1>::value;
};
//fullspecializationtoendtherecursion
template<>
structPow3<0>{
staticintconstvalue=1;
};
However,thereisadrawbackwiththisversion:Staticconstantmembersare
lvalues(seeAppendixB).So,ifwehaveadeclarationsuchasvoidfoo(int
const&);andwepassittheresultofametaprogram:foo(Pow3<7>::value);
acompilermustpasstheaddressofPow3<7>::value,andthatforcesthe
compilertoinstantiateandallocatethedefinitionforthestaticmember.Asa
result,thecomputationisnolongerlimitedtoapure“compile-time”effect.
Enumerationvaluesaren’tlvalues(i.e.,theydon’thaveanaddress).So,when
wepassthembyreference,nostaticmemoryisused.It’salmostexactlyasif
youpassedthecomputedvalueasaliteral.Thefirsteditionofthisbook
thereforepreferredtheuseofenumeratorconstantsforthiskindofapplications.
C++11,however,introducedconstexprstaticdatamembers,andthoseare
notlimitedtointegraltypes.Theydonotsolvetheaddressissueraisedabove,
butinspiteofthatshortcomingtheyarenowacommonwaytoproduceresults
ofmetaprograms.Theyhavetheadvantageofhavingacorrecttype(asopposed
toanartificialenumtype),andthattypecanbededucedwhenthestaticmember
isdeclaredwiththeautotypespecifier.C++17addedinlinestaticdata
members,whichdosolvetheaddressissueraisedabove,andcanbeusedin
conjunctionwithconstexpr.
23.7Afternotes
TheearliestdocumentedexampleofametaprogramwasbyErwinUnruh,then
representingSiemensontheC++standardizationcommittee.Henotedthe
computationalcompletenessofthetemplateinstantiationprocessand
demonstratedhispointbydevelopingthefirstmetaprogram.Heusedthe
Metawarecompilerandcoaxeditintoissuingerrormessagesthatwouldcontain
successiveprimenumbers.HereisthecodethatwascirculatedataC++
committeemeetingin1994(modifiedsothatitnowcompilesonstandard
conformingcompilers):6
Clickheretoviewcodeimage
meta/unruh.cpp
//primenumbercomputation//(modifiedfromoriginalfrom1994byErwin
Unruh)
//primenumbercomputation
//(modiedfromoriginalfrom1994byErwinUnruh)
template<intp,inti>
structis_prime{
enum{pri=(p==2)||((p%i)&&is_prime<(i>2?p:0),i-1>::pri)};
};
template<>
structis_prime<0,0>{
enum{pri=1};
};
template<>
structis_prime<0,1>{
enum{pri=1};
};
template<inti>
structD{
D(void*);
};
template<inti>
structCondNull{
staticintconstvalue=i;
};
template<>
structCondNull<0>{
staticvoid*value;
};
void*CondNull<0>::value=0;
template<inti>
structPrime_print{//primarytemplateforlooptoprintprime
numbers
Prime_print<i-1>a;
enum{pri=is_prime<i,i-1>::pri};
voidf(){
D<i>d=CondNull<pri?1:0>::value;//1isanerror,0isne
a.f();
}
};
template<>
structPrime_print<1>{//fullspecializationtoendtheloop
enum{pri=0};
voidf(){
D<1>d=0;
};
};
#ifndefLAST
#defineLAST18
#endif
intmain()
{
Prime_print<LAST>a;
a.f();
}
Ifyoucompilethisprogram,thecompilerwillprinterrormessageswhen,in
Prime_print::f(),theinitializationofdfails.Thishappenswhenthe
initialvalueis1becausethereisonlyaconstructorforvoid*,andonly0hasa
validconversiontovoid*.Forexample,ononecompiler,weget(among
severalothermessages)thefollowingerrors:7
Clickheretoviewcodeimage
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<17>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<13>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<11>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<7>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<5>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<3>’
unruh.cpp:39:14:error:noviableconversionfrom’constint’to
’D<2>’
TheconceptofC++templatemetaprogrammingasaseriousprogrammingtool
wasfirstmadepopular(andsomewhatformalized)byToddVeldhuizeninhis
paperUsingC++TemplateMetaprograms(see[VeldhuizenMeta95]).Todd’s
workonBlitz++(anumericarraylibraryforC++,see[Blitz++])alsointroduced
manyrefinementsandextensionstometaprogramming(andtoexpression
templatetechniques,introducedinChapter27).
BoththefirsteditionofthisbookandAndreiAlexandrescu’s“ModernC++
Design”(see[AlexandrescuDesign])contributedtoanexplosionofC++libraries
exploitingtemplate-basedmetaprogrammingbycatalogingsomeofthebasic
techniquesthatarestillinusetoday.TheBoostproject(see[Boost])was
instrumentalinbringingordertothisexplosion.Earlyon,itintroducedtheMPL
(meta-programminglibrary),whichdefinedaconsistentframeworkfortype
metaprogrammingmadepopularalsothroughAbrahamsandGurtovoy’sbook
“C++TemplateMetaprogramming”(see[Boost-MPL]).
AdditionalimportantadvanceshavebeenmadebyLouisDionneinmaking
metaprogrammingsyntacticallymoreaccessible,particularlythroughhis
Boost.Hanalibrary(see[BoostHana]).Louis,alongwithAndrewSutton,Herb
Sutter,DavidVandevoorde,andothersarenowspearheadingeffortsinthe
standardizationcommitteetogivemetaprogrammingfirst-classsupportinthe
language.Animportantbasisforthatworkistheexplorationofwhatprogram
propertiesshouldbeavailablethroughreflection;MatúšChochlík,Axel
Naumann,andDavidSankelareprincipalcontributorsinthatarea.
In[BartonNackman]JohnJ.BartonandLeeR.Nackmanillustratedhowto
keeptrackofdimensionalunitswhenperformingcomputations.TheSIunits
librarywasamorecomprehensivelibraryfordealingwithphysicalunits
developedbyWalterBrown([BrownSIunits]).Thestd::chronocomponent
inthestandardlibrary,whichweusedasaninspirationforSection23.1.4on
page534,onlydealswithtimeanddates,andwascontributedbyHoward
Hinnant.
1TheC++11constexprcapabilitiesweresufficienttosolvemanycommon
challenges,buttheprogrammingmodelwasnotalwayspleasant(e.g.,noloop
statementswereavailable,soiterativecomputationhadtoexploitrecursive
functioncalls;seeSection23.2onpage537).C++14enabledloopstatements
andvariousotherconstructs.
2TheC++standardlibraryprovidesacorrespondingtypetrait
std::remove_all_extents.SeeSectionD.4onpage730fordetails.
3Thisisknownasloopunrolling.Wegenerallyrecommendagainstexplicitly
unrollingloopsinportablecodesincethedetailsthatdeterminethebest
unrollingstrategydependhighlyonthetargetplatformandtheloopbody;the
compilerusuallydoesamuchbetterjoboftakingthoseconsiderationsinto
account.
4Wespecifytheinlinekeywordexplicitlyherebecausesomecompilers
(notablyClang)takethisashinttotryalittlehardertoinlinecalls.Froma
languagepointofview,thesefunctionsareimplicitlyinlinebecausethey
aredefinedinthebodyoftheirenclosingclass.
5SomeofthetraitsprovidedintheC++standardlibraryalreadyrelyonsome
cooperationfromthecompiler(vianonstandard“intrinsic”operators).See
Section19.10onpage461.
6ThankstoErwinUnruhforprovidingthecodeforthisbook.Youcanfindthe
Chapter24
Typelists
Effectiveprogrammingtypicallyrequirestheuseofvariousdatastructures,and
metaprogrammingisnodifferent.Fortypemetaprogramming,thecentraldata
structureisthetypelist,which,asitsnameimplies,isalistcontainingtypes.
Templatemetaprogramscanoperateontheselistsoftypes,manipulatingthemto
eventuallyproduceapartoftheexecutableprogram.Inthischapter,wediscuss
techniquesforworkingwithtypelists.Sincemostoperationsinvolvingtypelists
makeuseoftemplatemetaprogramming,werecommendthatyoufamiliarize
yourselfwithmetaprogramming,asdiscussedinChapter23.
24.1AnatomyofaTypelist
Atypelistisatypethatrepresentsalistoftypesandcanbemanipulatedbya
templatemetapro-gram.Itprovidestheoperationstypicallyassociatedwitha
list:iteratingovertheelements(types)inthelist,addingelements,orremoving
elements.However,typelistsdifferfrommostrun-timedatastructures,suchas
std::list,inthattheydon’tallowmutation.Forexample,addinganelement
toanstd::listchangesthelistitself,andthatchangecanbeobservedby
anyotherpartoftheprogramthathasaccesstothatlist.Addinganelementtoa
typelist,ontheotherhand,doesnotchangetheoriginaltypelist:Rather,adding
anelementtoanexistingtypelistcreatesanewtypelistwithoutmodifyingthe
original.Readersfamiliarwithfunctionalprogramminglanguages,suchas
Scheme,ML,andHaskell,willlikelyrecognizetheparallelsbetweenworking
withtypelistsinC++andlistsinthoselanguages.
Atypelististypicallyimplementedasaclasstemplatespecializationthat
encodesthecontentsofthetypelist—thatis,thetypesitcontainsandtheirorder
—withinitstemplatearguments.Adirectimplementationofatypelistencodes
theelementsinaparameterpack:Clickheretoviewcodeimage
typelist/typelist.hpp
template<typename…Elements>
classTypelist
{
};
TheelementsofaTypelistarewrittendirectlyasitstemplatearguments.An
emptytypelistiswrittenasTypelist<>,atypelistcontainingjustintis
writtenasTypelist<int>,andsoon.Hereisatypelistcontainingallofthe
signedintegraltypes:Clickheretoviewcodeimage
usingSignedIntegralTypes=
Typelist<signedchar,short,int,long,longlong>;Manipulatingthis
typelisttypicallyrequiresbreakingthetypelistintoparts,
generallybyseparatingthefirstelementinthelist(thehead)from
theremainingelementsinthelist(thetail).Forexample,theFront
metafunctionextractsthefirstelementfromthetypelist:Clickhere
toviewcodeimage
typelist/typelistfront.hpp
template<typenameList>
classFrontT;
template<typenameHead,typename…Tail>
classFrontT<Typelist<Head,Tail…>>
{
public:
usingType=Head;
};
template<typenameList>
usingFront=typenameFrontT<List>::Type;
Therefore,FrontT<SignedIntegralTypes>::Type(writtenmore
succinctlyasFront<SignedIntegralTypes>)willproducesigned
char.Similarly,thePopFrontmetafunctionremovesthefirstelementfrom
thetypelist.It’simplementationsplitsthetypelistelementsintotheheadandtail,
thenformsanewTypelistspecializationfromtheelementsinthetail.
Clickheretoviewcodeimage
typelist/typelistpopfront.hpp
template<typenameList>
classPopFrontT;
template<typenameHead,typename…Tail>
classPopFrontT<Typelist<Head,Tail…>>{
public:
usingType=Typelist<Tail…>;
};
template<typenameList>
usingPopFront=typenamePopFrontT<List>::Type;
PopFront<SignedIntegralTypes>producesthetypelistClickhereto
viewcodeimage
Typelist<short,int,long,longlong>
Onecanalsoinsertelementsontothefrontofthetypelistbycapturingallofthe
existingelementsintoatemplateparameterpack,thencreatinganew
Typelistspecializationcontainingallofthoseelements:Clickheretoview
codeimage
typelist/typelistpushfront.hpp
template<typenameList,typenameNewElement>
classPushFrontT;
template<typename…Elements,typenameNewElement>
classPushFrontT<Typelist<Elements…>,NewElement>{
public:
usingType=Typelist<NewElement,Elements…>;
};
template<typenameList,typenameNewElement>
usingPushFront=typenamePushFrontT<List,NewElement>::Type;
Asonemightexpect,
Clickheretoviewcodeimage
PushFront<SignedIntegralTypes,bool>
produces:
Clickheretoviewcodeimage
Typelist<bool,signedchar,short,int,long,longlong>
24.2TypelistAlgorithms
ThefundamentaltypelistoperationsFront,PopFront,andPushFrontcan
becomposedtocreatemoreinterestingtypelistmanipulations.Forexample,we
canreplacethefirstelementinatypelistbyapplyingPushFronttotheresult
ofPopFront:Clickheretoviewcodeimage
usingType=PushFront<PopFront<SignedIntegralTypes>,bool>;
//equivalenttoTypelist<bool,short,int,long,longlong>
Goingfurther,wecanimplementalgorithms—searches,transformations,
reversals—astemplatemetafunctionsoperatingontypelists.
24.2.1Indexing
Oneofthemostfundamentaloperationsonatypelististoextractaspecific
elementfromthelist.Section24.1illustratedhowtoimplementanoperation
thatextractsthefirstelement.Here,wegeneralizethisoperationtoextractthe
Nthelement.Forexample,toextractthetypeatindex2ofthegiventypelistwe
canwrite:Clickheretoviewcodeimage
usingTL=NthElement<Typelist<short,int,long>,2>;whichmakesTL
analiasforlong.TheNthElementoperationisimplementedwitha
recursivemetaprogramthatwalksthroughthetypelistuntilitfinds
therequestedelement:Clickheretoviewcodeimage
typelist/nthelement.hpp
//recursivecase:
template<typenameList,unsignedN>
classNthElementT:publicNthElementT<PopFront<List>,N-1>
{
};
//basiscase:
template<typenameList>
classNthElementT<List,0>:publicFrontT<List>
{
};
template<typenameList,unsignedN>
usingNthElement=typenameNthElementT<List,N>::Type;
First,considerthebasiscase,coveredbythepartialspecializationwhereNis0.
Thisspecializationterminatesourrecursionbyprovidingtheelementatthefront
ofthelist.ItdoessobyinheritingpubliclyfromFrontT<List>,which
(indirectly)providestheTypetypealiasthatisboththefrontofthislistand,
therefore,theresultoftheNthElementmetafunction,usingmetafunction
forwarding(discussedinSection19.3.2onpage404).
Therecursivecase,whichisalsotheprimarydefinitionofthetemplate,walks
throughthetypelist.BecausethepartialspecializationguaranteesthatN>0,the
recursivecaseremovesthefrontelementfromthelistandrequeststhe(N−1)st
elementfromtheremaininglist.Inourexample,Clickheretoviewcodeimage
NthElementT<Typelist<short,int,long>,2>inheritsfrom
Clickheretoviewcodeimage
NthElementT<Typelist<int,long>,1>whichtheninheritsfrom
Clickheretoviewcodeimage
NthElementT<Typelist<long>,0>Here,wehitthebasiscase,and
inheritancefromFrontT<Typelist<long>>providestheresultviathe
nestedtypeType.
24.2.2FindingtheBestMatch
Manytypelistalgorithmssearchfordatawithinthetypelist.Forexample,one
maywanttofindthelargesttypewithinthetypelist(e.g.,toallocateenough
storageforanyofthetypesinthelist).Thistoocanbeaccomplishedwitha
recursivetemplatemetaprogram:Clickheretoviewcodeimage
typelist/largesttype.hpp
template<typenameList>
classLargestTypeT;
//recursivecase:
template<typenameList>
classLargestTypeT
{
private:
usingFirst=Front<List>;
usingRest=typenameLargestTypeT<PopFront<List>>::Type;
public:
usingType=IfThenElse<(sizeof(First)>=sizeof(Rest)),First,Rest>;
};
//basiscase:
template<>
classLargestTypeT<Typelist<>>
{
public:
usingType=char;
};
template<typenameList>
usingLargestType=typenameLargestTypeT<List>::Type;
TheLargestTypealgorithmwillreturnthefirst,largesttypewithinthe
typelist.Forexample,ifgiventhetypelistTypelist<bool,int,long,
short>,thisalgorithmwillreturnthefirsttypethatisthesamesizeaslong,
whichislikelytobeeitherintorlong,dependingonyourplatform.1
TheprimarytemplateforLargestTypeTdoublesastherecursivecasefor
thealgorithm.Itemploysthecommonfirst/restidiom,whichhasthreesteps.In
thefirststep,itcomputesapartialresultbasedonjustthefirstelement,whichin
thiscaseisthefrontelementofthelist,andplacesitinFirst.Next,itrecurses
tocomputetheresultfortherestoftheelementsinthelist,andplacesthatresult
inRest.Forexample,inthefirststepofrecursionforthetypelist
Typelist<bool,int,long,short>,Firstisbool,whileRest
istheresultofapplyingthealgorithmtoTypelist<int,long,short>.
Finally,thethirdstepcombinestheFirstandRestresultstoproducethe
solution.Here,theIfThenElsepicksthelargerofeitherthefirstelementin
thelist(First)orthebestcandidatesofar(Rest),andreturnsthewinner.2
The>=breakstiesinfavoroftheelementthatcomesearlierinthelist.
Therecursionterminateswhenthelistisempty.Bydefault,weusecharasa
sentineltypetoinitializethealgorithm,becauseeverytypeisaslargeaschar.
NotethatthebasiscaseexplicitlymentionstheemptytypelistTypelist<>.
Thisissomewhatunfortunate,becauseitprecludestheuseofotherformsof
typelists,whichwe’llreturntoinlatersections(includingSection24.3onpage
566,Section24.5onpage571,andChapter25).Toaddressthisproblem,we
introduceanIsEmptymetafunctionthatdetermineswhetherthegiventypelist
hasnoelements:Clickheretoviewcodeimage
typelist/typelistisempty.hpp
template<typenameList>
classIsEmpty
{
public:
staticconstexprboolvalue=false;
};
template<>
classIsEmpty<Typelist<>>{
public:
staticconstexprboolvalue=true;
};
UsingIsEmpty,wecanimplementLargestTypesothatitworksequally
wellforanytypelistthatimplementsFront,PopFront,andIsEmpty,as
follows:Clickheretoviewcodeimage
typelist/genericlargesttype.hpp
template<typenameList,boolEmpty=IsEmpty<List>::value>
classLargestTypeT;
//recursivecase:
template<typenameList>
classLargestTypeT<List,false>
{
private:
usingContender=Front<List>;
usingBest=typenameLargestTypeT<PopFront<List>>::Type;
public:
usingType=IfThenElse<(sizeof(Contender)>=sizeof(Best)),
Contender,Best>;
};
//basiscase:
template<typenameList>
classLargestTypeT<List,true>
{
public:
usingType=char;
};
template<typenameList>
usingLargestType=typenameLargestTypeT<List>::Type;
ThedefaultedsecondtemplateparametertoLargestTypeT,Empty,checks
whetherthelistisempty.Ifnot,therecursivecase(whichfixesthisargumentto
false)continuesexploringthelist.Otherwise,thebasiscase(whichfixesthis
argumenttotrue)terminatestherecursionandprovidestheinitialresult
(char).
24.2.3AppendingtoaTypelist
ThePushFrontprimitiveoperationallowsustoaddanewelementtothe
frontofatypelist,producinganewtypelist.Supposethat,instead,wewantto
addanewelementattheendofthelist,asweoftendowithrun-timecontainers
suchasstd::listandstd::vector.ForourTypelisttemplate,this
operationrequiresonlyasmallmodificationtothePushFrontimplementation
fromSection24.1onpage549toproducePushBack:Clickheretoviewcode
image
typelist/typelistpushback.hpp
template<typenameList,typenameNewElement>
classPushBackT;
template<typename…Elements,typenameNewElement>
classPushBackT<Typelist<Elements…>,NewElement>
{
public:
usingType=Typelist<Elements…,NewElement>;
};
template<typenameList,typenameNewElement>
usingPushBack=typenamePushBackT<List,NewElement>::Type;
However,aswiththeLargestTypealgorithm,wecanimplementageneral
algorithmforPushBackthatusesonlytheprimitiveoperationsFront,
PushFront,PopFront,andIsEmpty:3
Clickheretoviewcodeimage
typelist/genericpushback.hpp
template<typenameList,typenameNewElement,bool=
IsEmpty<List>::value>
classPushBackRecT;
//recursivecase:
template<typenameList,typenameNewElement>
classPushBackRecT<List,NewElement,false>
{
usingHead=Front<List>;
usingTail=PopFront<List>;
usingNewTail=typenamePushBackRecT<Tail,NewElement>::Type;
public:
usingType=PushFront<Head,NewTail>;
};
//basiscase:
template<typenameList,typenameNewElement>
classPushBackRecT<List,NewElement,true>
{
public:
usingType=PushFront<List,NewElement>;
};
//genericpush-backoperation:
template<typenameList,typenameNewElement>
classPushBackT:publicPushBackRecT<List,NewElement>{};
template<typenameList,typenameNewElement>
usingPushBack=typenamePushBackT<List,NewElement>::Type;
ThePushBackRecTtemplatemanagestherecursion.Inthebasiscase,weuse
PushFronttoaddNewElementtotheemptylist,becausePushFrontis
equivalenttoPushBackonanemptylist.Therecursivecaseisfarmore
interesting:Itsplitsthelistintoitsfirstelement(Head)andatypelistcontaining
theremainingelements(Tail).ThenewelementisthenappendedtotheTail,
recursively,toproduceNewTail.WethenusePushFrontagaintoaddHead
tothefrontoflistNewTailtoformthefinallist.
Let’sunwraptherecursionforasimpleexample:Clickheretoviewcode
image
PushBackRecT<Typelist<short,int>,long>
Inouroutermoststep,HeadisshortandTailisTypelist<int>.We
recursetoClickheretoviewcodeimage
PushBackRecT<Typelist<int>,long>
whereHeadisintandTailisTypelist<>.
WerecurseagaintocomputeClickheretoviewcodeimage
PushBackRecT<Typelist<>,long>
whichtriggersthebasiscaseandreturnsPushFront<Typelist<>,
long>,whichitselfevaluatestoTypelist<long>.Therecursionthen
unwinds,pushingthepreviousHeadonthefrontofthelist:Clickheretoview
codeimage
PushFront<int,Typelist<long>>
ThisproducesTypelist<int,long>.Therecursionunwrapsagain,
pushingtheoutermostHead(short)ontothislist:Clickheretoviewcode
image
PushFront<short,Typelist<int,long>>
Thisproducesthefinalresult:Clickheretoviewcodeimage
Typelist<short,int,long>
ThegeneralPushBackRecTimplementationworksonanykindoftypelist.
Likethepreviousalgorithmsdevelopedinthissection,itrequiresalinear
numberoftemplateinstantiationstoevaluate,becauseforatypelistoflengthN,
therewillbeN+1instantiationsofPushBackRecTandPushFrontT,as
wellasNinstantiationsofFrontTandPopFrontT.Countingthenumberof
templateinstantiationscanprovideroughestimatesofthetimeitwilltaketo
compileaparticularmetaprogram,becausetemplateinstantiationitselfisafairly
involvedprocessforthecompiler.
Compiletimecanbeaproblemforlargetemplatemetaprograms,soitis
reasonabletotrytoreducethenumberoftemplateinstantiationsperformedby
thesealgorithms.4Infact,ourfirstimplementationofPushBack—which
employedapartialspecializationonTypelist—requiresonlyaconstant
numberoftemplateinstantiations,makingitfarmoreefficient(atcompiletime)
thanthegenericversion.Moreover,becauseitisdescribedasapartial
specializationofPushBackT,thisefficientimplementationwillautomatically
beselectedwhenperformingPushBackonaTypelistinstance,bringing
thenotionofalgorithmspecialization(asdiscussedinSection20.1onpage465)
totemplatemetaprograms.Manyofthetechniquesdiscussedinthatsectioncan
beappliedtotemplatemetaprogramstoreducethenumberoftemplate
instantiationsthealgorithmperforms.
24.2.4ReversingaTypelist
Whentypelistshavesomeorderingamongtheirelements,itisconvenienttobe
abletoreversetheorderingoftheelementsinthetypelistwhenapplyingsome
algorithms.Forexample,theSignedIntegralTypestypelistintroducedin
Section24.1onpage549isorderedintermsofincreasingintegerrank.
However,itmaybemoreusefultoreversethislisttoproducethetypelist
Typelist<longlong,long,int,short,signedchar>
orderedbydecreasingrank.TheReversealgorithmimplementsthis
metafunction:Clickheretoviewcodeimage
typelist/typelistreverse.hpp
template<typenameList,boolEmpty=IsEmpty<List>::value>
classReverseT;
template<typenameList>
usingReverse=typenameReverseT<List>::Type;
//recursivecase:
template<typenameList>
classReverseT<List,false>
:publicPushBackT<Reverse<PopFront<List>>,Front<List>>{};
//basiscase:
template<typenameList>
classReverseT<List,true>
{
public:
usingType=List;
};
Thebasiscasefortherecursionofthismetafunctionistheidentityfunctionon
anemptytypelist.Therecursivecasesplitsthelistintoitsfirstelementandthe
remainingelementsinthelist.Forexample,ifgiventhetypelist
Typelist<short,int,long>,therecursivestepseparatesthefirst
element(short)fromtheremainingelements(Typelist<int,long>).It
thenrecursivelyreversesthelistofremainingelements(producing
Typelist<long,int>)and,finally,appendsthefirstelementtothat
reversedlistwithPushBackT(producingTypelist<long,int,
short>).
TheReversealgorithmmakesitpossibletoimplementaPopBackT
operationfortypeliststoremovethelastelementfromatypelist:Clickhereto
viewcodeimage
typelist/typelistpopback.hpp
template<typenameList>
classPopBackT{
public:
usingType=Reverse<PopFront<Reverse<List>>>;
};
template<typenameList>
usingPopBack=typenamePopBackT<List>::Type;
Thealgorithmreversesthelist,removesthefirstelementfromthereversedlist
(usingPopFront),andthenreversestheresultinglistagain.
24.2.5TransformingaTypelist
Ourprevioustypelistalgorithmshaveallowedustoextractarbitraryelements
fromatypelist,searchwithinthelist,constructnewlists,andreverselists.
However,wehaveyettoperformanyoperationsontheelementswithinthe
typelist.Forexample,wemaywantto“transform”allofthetypesinthetypelist
insomeway,5suchasbyturningeachtypeintoitsconst-qualifiedvariant
usingtheAddConstmetafunction:Clickheretoviewcodeimage
typelist/addconst.hpp
template<typenameT>
structAddConstT
{
usingType=Tconst;
};
template<typenameT>
usingAddConst=typenameAddConstT<T>::Type;
Tothatend,wewillimplementaTransformalgorithmthattakesatypelist
andametafunctionandproducesanothertypelistcontainingtheresultof
applyingthemetafunctiontoeachtype.Forexample,thetype
Transform<SignedIntegralTypes,AddConstT>willbeatypelistcontaining
signedcharconst,shortconst,intconst,longconst,and
longlongconst.Themetafunctionisprovidedthroughatemplate
templateparameter,whichmapsaninputtypetoanoutputtype.The
Transformalgorithmitselfis,aswemightexpect,arecursivealgorithm:
Clickheretoviewcodeimage
typelist/transform.hpp
template<typenameList,template<typenameT>classMetaFun,
boolEmpty=IsEmpty<List>::value>
classTransformT;
//recursivecase:
template<typenameList,template<typenameT>classMetaFun>
classTransformT<List,MetaFun,false>
:publicPushFrontT<typenameTransformT<PopFront<List>,
MetaFun>::Type,
typenameMetaFun<Front<List>>::Type>
{
};
//basiscase:
template<typenameList,template<typenameT>classMetaFun>
classTransformT<List,MetaFun,true>
{
public:
usingType=List;
};
template<typenameList,template<typenameT>classMetaFun>
usingTransform=typenameTransformT<List,MetaFun>::Type;
Therecursivecasehere,whilesyntacticallyheavy,isstraightforward.Theresult
ofatransformistheresultoftransformingthefirstelementinthetypelist
(secondargumenttoPushFront)andaddingittothebeginningofthe
sequenceproducedbyrecursivelytransformingtherestoftheelementsinthe
typelist(firstargumenttoPushFront).
SeealsoSection24.4onpage569whichshowshowamoreefficient
implementationofTransformcanbedeveloped.
24.2.6AccumulatingTypelists
Transformisausefulalgorithmfortransformingeachoftheelementsofthe
sequence.ItisoftenusedinconjunctionwithAccumulate,whichcombines
alloftheelementsofasequenceintoasingleresultingvalue.6The
AccumulatealgorithmtakesatypelistTwithelementsT1,T2,…,TN,an
initialtypeI,andametafunctionF,whichacceptstwotypesandreturnsatype.
ItreturnsF(F(F(…F(I,T1),T2),…,TN−1),TN),whereattheithstepinthe
accumulationFisappliedtotheresultofthepreviousi−1stepsandTi.
Dependingonthetypelist,choiceofF,andinitialtype,wecanuse
Accumulatetoproduceanumberofdifferentoutcomes.Forexample,ifF
selectsthelargestoftwotypes,Accumulatewillbehavelikethe
LargestTypealgorithm.Ontheotherhand,ifFacceptsatypelistandatype
andpushesthetypeonthebackofthetypelist,Accumulatewillbehavelike
theReversealgorithm.
TheimplementationofAccumulatefollowsourstandardrecursive-
metaprogrambreakdown:Clickheretoviewcodeimage
typelist/accumulate.hpp
template<typenameList,
template<typenameX,typenameY>classF,
typenameI,
bool=IsEmpty<List>::value>
classAccumulateT;
//recursivecase:
template<typenameList,
template<typenameX,typenameY>classF,
typenameI>
classAccumulateT<List,F,I,false>
:publicAccumulateT<PopFront<List>,F,
typenameF<I,Front<List>>::Type>
{
};
//basiscase:
template<typenameList,
template<typenameX,typenameY>classF,
typenameI>
classAccumulateT<List,F,I,true>
{
public:
usingType=I;
};
template<typenameList,
template<typenameX,typenameY>classF,
typenameI>
usingAccumulate=typenameAccumulateT<List,F,I>::Type;
Here,theinitialtypeIisalsousedastheaccumulator,capturingthecurrent
result.Therefore,thebasiscasereturnsthisresultwhenitreachestheendofthe
typelist.7Intherecursivecase,thealgorithmappliesFtothepreviousresult(I)
andthefrontofthelist,passingtheresultofapplyingFonastheinitialtypefor
theaccumulationoftheremainderofthelist.
WithAccumulateinhand,wecanreverseatypelistusingPushFrontTas
themetafunctionFandanemptytypelist(TypeList<T>)astheinitialtypeI:
Clickheretoviewcodeimage
usingResult=Accumulate<SignedIntegralTypes,PushFrontT,
Typelist<>>;
//producesTypeList<longlong,long,int,short,signedchar>
ImplementinganAccumulator-basedversionofLargestType,
LargestTypeAccrequiresslightlymoreeffort,becauseweneedtoproducea
metafunctionthatreturnsthelargeroftwotypes:Clickheretoviewcodeimage
typelist/largesttypeacc0.hpp
template<typenameT,typenameU>
classLargerTypeT
:publicIfThenElseT<sizeof(T)>=sizeof(U),T,U>
{
};
template<typenameTypelist>
classLargestTypeAccT
:publicAccumulateT<PopFront<Typelist>,LargerTypeT,
Front<Typelist>>
{
};
template<typenameTypelist>
usingLargestTypeAcc=typenameLargestTypeAccT<Typelist>::Type;
NotethatthisformulationofLargestTyperequiresanonemptytypelist,
becauseitprovidesthefirstelementofthetypelistastheinitialtype.Wecould
handletheempty-listcaseexplicitly,eitherbyreturningsomesentineltype
(charorvoid)orbymakingthealgorithmitselfSFINAE-friendly,as
discussedinSection19.4.4onpage424:Clickheretoviewcodeimage
typelist/largesttypeacc.hpp
template<typenameT,typenameU>
classLargerTypeT
:publicIfThenElseT<sizeof(T)>=sizeof(U),T,U>
{
};
template<typenameTypelist,bool=IsEmpty<Typelist>::value>
classLargestTypeAccT;
template<typenameTypelist>
classLargestTypeAccT<Typelist,false>
:publicAccumulateT<PopFront<Typelist>,LargerTypeT,
Front<Typelist>>
{
};
template<typenameTypelist>
classLargestTypeAccT<Typelist,true>
{
};
template<typenameTypelist>
usingLargestTypeAcc=typenameLargestTypeAccT<Typelist>::Type;
Accumulateisapowerfultypelistalgorithmbecauseitallowsustoexpress
manydifferentoperations,andthereforecanbeconsideredoneofthe
foundationalalgorithmsfortypelistmanipulation.
24.2.7InsertionSort
Forourfinaltypelistalgorithm,wewillimplementaninsertionsort.Aswith
otheralgorithms,therecursivestepsplitsthelistintoitsfirstelement(thehead)
andtheremainingelements(thetail).Thetailisthensorted(recursively),and
theheadisinsertedintothecorrectpositionwithinthesortedlist.Theshellof
thisalgorithmisexpressedasatypelistalgorithm:Clickheretoviewcode
image
typelist/insertionsort.hpp
template<typenameList,
template<typenameT,typenameU>classCompare,
bool=IsEmpty<List>::value>
classInsertionSortT;
template<typenameList,
template<typenameT,typenameU>classCompare>
usingInsertionSort=typenameInsertionSortT<List,Compare>::Type;
//recursivecase(insertfirstelementintosortedlist):
template<typenameList,
template<typenameT,typenameU>classCompare>
classInsertionSortT<List,Compare,false>
:publicInsertSortedT<InsertionSort<PopFront<List>,Compare>,
Front<List>,Compare>
{
};
//basiscase(anemptylistissorted):
template<typenameList,
template<typenameT,typenameU>classCompare>
classInsertionSortT<List,Compare,true>
{
public:
usingType=List;
};
TheCompareparameteristhecomparisonusedtoorderelementsinthe
typelist.ItacceptstwotypesandevaluatestoaBooleanviaitsvaluemember.
Thebasiscase,anemptytypelist,istrivial.
ThecoreofinsertionsortistheInsertSortedTmetafunction,which
insertsavalueintoanalready-sortedlistatthefirstpointthatwillkeepthelist
sorted:Clickheretoviewcodeimage
typelist/insertsorted.hpp
#include"identity.hpp"
template<typenameList,typenameElement,
template<typenameT,typenameU>classCompare,
bool=IsEmpty<List>::value>
classInsertSortedT;
//recursivecase:
template<typenameList,typenameElement,
template<typenameT,typenameU>classCompare>
classInsertSortedT<List,Element,Compare,false>
{
//computethetailoftheresultinglist:
usingNewTail=
typenameIfThenElse<Compare<Element,Front<List>>::value,
IdentityT<List>,
InsertSortedT<PopFront<List>,Element,Compare>
>::Type;
//computetheheadoftheresultinglist:
usingNewHead=IfThenElse<Compare<Element,Front<List>>::value,
Element,
Front<List>>;
public:
usingType=PushFront<NewTail,NewHead>;
};
//basiscase:
template<typenameList,typenameElement,
template<typenameT,typenameU>classCompare>
classInsertSortedT<List,Element,Compare,true>
:publicPushFrontT<List,Element>
{
};
template<typenameList,typenameElement,
template<typenameT,typenameU>classCompare>
usingInsertSorted=typenameInsertSortedT<List,Element,
Compare>::Type;
Thebasiscaseisagaintrivial,becauseasingle-elementlistisalwayssorted.The
recursivecasediffersbasedonwhethertheelementtobeinsertedshouldgoat
thebeginningofthelistorlaterinthelist.Iftheelementbeinginsertedprecedes
thefirstelementinthelist(whichisalreadysorted),theresultisthatelement
prependedtothelistwithPushFront.Otherwise,wesplitthelistintoitshead
andtail,recursetoinsertthatelementintothetail,thenprependtheheadtothe
resultofinsertingtheelementintothetail.
Thisimplementationincludesacompile-timeoptimizationtoavoid
instantiatingtypesthatwillnotbeused,atechniquediscussedinSection19.7.1
onpage440.Thefollowingimplementationwouldtechnicallyalsobecorrect:
Clickheretoviewcodeimage
template<typenameList,typenameElement,
template<typenameT,typenameU>classCompare>
classInsertSortedT<List,Element,Compare,false>
:publicIfThenElseT<Compare<Element,Front<List>>::value,
PushFront<List,Element>,
PushFront<InsertSorted<PopFront<List>,
Element,Compare>,
Front<List>>>
{
};
However,suchaformulationoftherecursivecasewouldbeunnecessarily
inefficient,becauseitevaluatesthetemplateargumentsinbothbranchesofthe
IfThenElseTeventhoughonlyonebranchwillbeused.Inourcase,the
PushFrontinthethenbranchistypicallyfairlycheap,buttherecursive
InsertSortedinvocationintheelsebranchisnot.
Inouroptimizedimplementation,thefirstIfThenElsecomputesthetailof
theresultinglist,NewTail.ThesecondandthirdargumentstoIfThenElse
arebothmetafunctionsthatwillcomputetheresultforthatbranch.Thesecond
argument(the“then”branch)usesIdentityT(showninSection19.7.1on
page440)toproducetheunmodifiedList.Thethirdargument(the“else”
branch)usesInsertSortedTtocomputetheresultofinsertingtheelement
laterinthesortedlist.Atthetoplevel,onlyoneofIdentityTor
InsertSortedTwillbeinstantiated,soverylittleextraworkisperformed
(thePopFront,intheworsecase).ThesecondIfThenElsethencomputes
theheadoftheresultinglist;thebranchesareevaluatedimmediatelybecause
bothareassumedtobecheap.Thefinallistisconstructedfromthecomputed
NewHeadandNewTail.Thisformulationhasthedesirablepropertythatthe
numberofinstantiationsrequiredtoinsertanelementintoasortedlistis
proportionatetoitspositionintheresultinglist.Thismanifestsasamuch
higher-levelpropertyofinsertionsortinthatthenumberofinstantiationstosort
analready-sortedlistislinearinthelengthofthelist.(Insertionsortremains
quadraticinthenumberofinstantiationsforinputssortedinreverse.)The
followingprogramdemonstratestheuseofinsertionsorttoorderalistoftypes
basedontheirsize.Thecomparisonoperationusesthesizeofoperatorand
comparestheresult:Clickheretoviewcodeimage
typelist/insertionsorttest.hpp
template<typenameT,typenameU>
structSmallerThanT{
staticconstexprboolvalue=sizeof(T)<sizeof(U);
};
voidtestInsertionSort()
{
usingTypes=Typelist<int,char,short,double>;
usingST=InsertionSort<Types,SmallerThanT>;
std::cout<<std::is_same<ST,Typelist<char,short,int,
double>>::value
<<’\n’;
}
24.3NontypeTypelists
Typelistsprovidetheabilitytodescribeandmanipulateasequenceoftypes
usingarichsetofalgorithmsandoperations.Insomecases,itisalsousefulto
workwithsequencesofcompile-timevalues,suchastheboundsofa
multidimensionalarrayorindicesintoanothertypelist.
Thereareseveralwaystoproduceatypelistofcompile-timevalues.One
simpleapproachinvolvesdefiningaCTValueclasstemplate(namedfora
compile-timevalue)thatrepresentsavalueofaspecifictypewithinatypelist:8
Clickheretoviewcodeimage
typelist/ctvalue.hpp
template<typenameT,TValue>
structCTValue
{
staticconstexprTvalue=Value;
};
UsingtheCTValuetemplate,wecannowexpressatypelistcontaininginteger
valuesforthefirstfewprimenumbers:Clickheretoviewcodeimage
usingPrimes=Typelist<CTValue<int,2>,CTValue<int,3>,
CTValue<int,5>,CTValue<int,7>,
CTValue<int,11>>;
Withthisrepresentation,wecanperformnumericcomputationsonatypelistof
values,suchascomputingtheproductoftheseprimes.
First,aMultiplyTtemplateacceptstwocompile-timevalueswiththesame
typeandproducesanewcompile-timevalueofthatsametype,multiplyingthe
inputvalues:Clickheretoviewcodeimage
typelist/multiply.hpp
template<typenameT,typenameU>
structMultiplyT;
template<typenameT,TValue1,TValue2>
structMultiplyT<CTValue<T,Value1>,CTValue<T,Value2>>{
public:
usingType=CTValue<T,Value1*Value2>;
};
template<typenameT,typenameU>
usingMultiply=typenameMultiplyT<T,U>::Type;
Then,byusingMultiplyT,thefollowingexpressionyieldstheproductofall
primenumbers:Accumulate<Primes,MultiplyT,CTValue<int,1>>::value
Unfortunately,thisusageofTypelistandCTValueisfairlyverbose,
especiallyforthecasewhereallofthevaluesareofthesametype.Wecan
optimizethisparticularcasebyintroducinganaliastemplateCTTypelistthat
providesahomogeneouslistofvalues,describedasaTypelistof
CTValues:Clickheretoviewcodeimage
typelist/cttypelist.hpp
template<typenameT,T…Values>
usingCTTypelist=Typelist<CTValue<T,Values>…>;
Wecannowwriteanequivalent(butfarmoreconcise)definitionofPrimes
usingCTTypelistasfollows:Clickheretoviewcodeimage
usingPrimes=CTTypelist<int,2,3,5,7,11>;Theonlydownsideto
thisapproachisthataliastemplatesarejustaliases,soerror
messagesmayendupprintingtheunderlyingTypelistofCTValueTypes,
causingthemtobemoreverbosethanwewouldlike.Toaddressthis
issue,wecancreateanentirelynewtypelistclass,Valuelist,that
storesthevaluesdirectly:Clickheretoviewcodeimage
typelist/valuelist.hpp
template<typenameT,T…Values>
structValuelist{
};
template<typenameT,T…Values>
structIsEmpty<Valuelist<T,Values…>>{
staticconstexprboolvalue=sizeof…(Values)==0;
};
template<typenameT,THead,T…Tail>
structFrontT<Valuelist<T,Head,Tail…>>{
usingType=CTValue<T,Head>;
staticconstexprTvalue=Head;
};
template<typenameT,THead,T…Tail>
structPopFrontT<Valuelist<T,Head,Tail…>>{
usingType=Valuelist<T,Tail…>;
};
template<typenameT,T…Values,TNew>
structPushFrontT<Valuelist<T,Values…>,CTValue<T,New>>{
usingType=Valuelist<T,New,Values…>;
};
template<typenameT,T…Values,TNew>
structPushBackT<Valuelist<T,Values…>,CTValue<T,New>>{
usingType=Valuelist<T,Values…,New>;
};
ByprovidingIsEmpty,FrontT,PopFrontT,andPushFrontT,wehave
madeValuelistapropertypelistthatcanbeusedwiththealgorithmsdefined
inthischapter.PushBackTisprovidedasanalgorithmspecializationtoreduce
thecostofthisoperationatcompiletime.Forexample,Valuelistcanbe
usedwiththeInsertionSortalgorithmdefinedpreviously:Clickhereto
viewcodeimage
typelist/valuelisttest.hpp
template<typenameT,typenameU>
structGreaterThanT;
template<typenameT,TFirst,TSecond>
structGreaterThanT<CTValue<T,First>,CTValue<T,Second>>{
staticconstexprboolvalue=First>Second;
};
voidvaluelisttest()
{
usingIntegers=Valuelist<int,6,2,4,9,5,2,1,7>;
usingSortedIntegers=InsertionSort<Integers,GreaterThanT>;
static_assert(std::is_same_v<SortedIntegers,
Valuelist<int,9,7,6,5,4,2,2,1>>,
"insertionsortfailed");
NotethatyoucanprovidetheabilitytoinitializeCTValuebyusingtheliteral
operator,e.g.,Clickheretoviewcodeimage
autoa=42_c;//initializesaasCTValue<int,42>
SeeSection25.6onpage599fordetails.
24.3.1DeducibleNontypeParameters
InC++17,CTValuecanbeimprovedbyusingasingle,deduciblenontype
parameter(spelledwithauto):Clickheretoviewcodeimage
typelist/ctvalue17.hpp
template<autoValue>
structCTValue
{
staticconstexprautovalue=Value;
};
ThiseliminatestheneedtospecifythetypeforeachuseofCTValue,makingit
easiertouse:Clickheretoviewcodeimage
usingPrimes=Typelist<CTValue<2>,CTValue<3>,CTValue<5>,
CTValue<7>,CTValue<11>>;
ThesamecanbedoneforaC++17Valuelist,buttheresultisn’tnecessarily
better.AsnotedinSection15.10.1onpage296,anontypeparameterpackwith
deducedtypeallowsthetypesofeachargumenttodiffer:Clickheretoview
codeimage
template<auto…Values>
classValuelist{};
intx;
usingMyValueList=Valuelist<1,’a’,true,&x>;
Whilesuchaheterogeneousvaluelistmaybeuseful,itisnotthesameasour
previousValuelistthatrequiredalloftheelementstohavethesametype.
Althoughonecouldrequirethatalloftheelementshavethesametype(whichis
alsodiscussedinSection15.10.1onpage296),anemptyValuelist<>will
necessarilyhavenoknownelementtypes.
24.4OptimizingAlgorithmswithPackExpansions
Packexpansions(describedindepthinSection12.4.1onpage201)canbea
usefulmechanismforoffloadingtheworkoftypelistiterationtothecompiler.
TheTransformalgorithmdevelopedinSection24.2.5onpage559isonethat
naturallylendsitselftotheuseofapackexpansion,becauseitisapplyingthe
sameoperationtoeachoftheelementsinthelist.Thisenablesanalgorithm
specialization(bywayofapartialspecialization)foraTransformofa
Typelist:Clickheretoviewcodeimage
typelist/variadictransform.hpp
template<typename…Elements,template<typenameT>classMetaFun>
classTransformT<Typelist<Elements…>,MetaFun,false>
{
public:
usingType=Typelist<typenameMetaFun<Elements>::Type…>;
};
Thisimplementationcapturesthetypelistelementsintoaparameterpack
Elements.Itthenemploysapackexpansionwiththepatterntypename
MetaFun<Elements>::Typetoapplythemetafunctiontoeachofthetypes
inElementsandformsatypelistfromtheresults.Thisimplementationis
arguablysimpler,becauseitdoesn’trequirerecursionanduseslanguagefeatures
inafairlystraightforwardway.Moreover,itrequiresfewertemplate
instantiations,becauseonlyoneinstanceoftheTransformtemplateneedsto
beinstantiated.Thealgorithmstillrequiresalinearnumberofinstantiationsof
MetaFun,butthoseinstantiationsarefundamentaltothealgorithm.
Otheralgorithmsbenefitindirectlyfromusesofpackexpansions.For
example,theReversealgorithmdescribedinSection24.2.4onpage557
requiresalinearnumberofinstantiationsofPushBack.Withthepack-
expansionformofPushBackonTypelistdescribedinSection24.2.3on
page555(whichrequiresasingleinstantiation),Reverseislinear.However,
themore-generalrecursiveimplementationofReversealsodescribedinthat
sectionisitselflinearinthenumberofinstantiations,makingReverse
quadratic!
Packexpansionscanalsobeusefultoselecttheelementsinagivenlistof
indicestoproduceanewtypelist.TheSelectmetafunctiontakesinatypelist
andaValuelistcontainingindicesintothattypelist,thenproducesanew
typelistcontainingtheelementsspecifiedbytheValuelist:Clickhereto
viewcodeimage
typelist/select.hpp
template<typenameTypes,typenameIndices>
classSelectT;
template<typenameTypes,unsigned…Indices>
classSelectT<Types,Valuelist<unsigned,Indices…>>
{
public:
usingType=Typelist<NthElement<Types,Indices>…>;
};
template<typenameTypes,typenameIndices>
usingSelect=typenameSelectT<Types,Indices>::Type;
TheindicesarecapturedinaparameterpackIndices,whichisexpandedto
produceasequenceofNthElementtypestoindexintothegiventypelist,
capturingtheresultinanewTypelist.Thefollowingexampleillustrateshow
wecanuseSelecttoreverseatypelist:Clickheretoviewcodeimage
usingSignedIntegralTypes=
Typelist<signedchar,short,int,long,longlong>;
usingReversedSignedIntegralTypes=
Select<SignedIntegralTypes,Valuelist<unsigned,4,3,2,1,0>>;
//producesTypelist<longlong,long,int,short,signedchar>
Anontypetypelistcontainingindicesintoanotherlistisoftencalledanindexlist
(orindexsequence),andcanallowonetosimplifyoreliminaterecursive
computations.IndexlistsaredescribedindetailinSection25.3.4onpage585.
24.5Cons-styleTypelists
Priortotheintroductionofvariadictemplates,typelistsweregenerally
formulatedwitharecursivedatastructuremodeledafterLISP’sconscell.Each
conscellcontainsavalue(theheadofthelist)andanestedlist,thelatterof
whichcouldeitherbeanotherconscellortheemptylist,nil.Thisnotioncan
bedirectlyexpressedinC++:Clickheretoviewcodeimage
typelist/cons.hpp
classNil{};
template<typenameHeadT,typenameTailT=Nil>
classCons{
public:
usingHead=HeadT;
usingTail=TailT;
};
AnemptytypelistiswrittenNil,whileasingle-elementlistcontainingintis
writtenasCons<int,Nil>or,moresuccinctly,Cons<int>.Longerlists
requirenesting:Clickheretoviewcodeimage
usingTwoShort=Cons<short,Cons<unsignedshort>>;Arbitrarilylong
typelistscanbeconstructedbydeeprecursivenesting,although
writingsuchlonglistsbyhandcanbecomeratherunwieldy:Click
heretoviewcodeimage
usingSignedIntegralTypes=Cons<signedchar,Cons<short,Cons<int,
Cons<long,Cons<longlong,Nil>>>>>;Extractingthefirstelementin
acons-stylelistrefersdirectlytotheheadofthelist:Clickhere
toviewcodeimage
typelist/consfront.hpp
template<typenameList>
classFrontT{
public:
usingType=typenameList::Head;
};
template<typenameList>
usingFront=typenameFrontT<List>::Type;
AddinganelementtothefrontwrapsanotherConsaroundtheexistinglist:
Clickheretoviewcodeimage
typelist/conspushfront.hpp
template<typenameList,typenameElement>
classPushFrontT{
public:
usingType=Cons<Element,List>;
};
template<typenameList,typenameElement>
usingPushFront=typenamePushFrontT<List,Element>::Type;
Finally,removingthefirstelementfromarecursivetypelistextractsthetailof
thelist:Clickheretoviewcodeimage
typelist/conspopfront.hpp
template<typenameList>
classPopFrontT{
public:
usingType=typenameList::Tail;
};
template<typenameList>
usingPopFront=typenamePopFrontT<List>::Type;
AnIsEmptyspecializationforNilcompletesthesetofcoretypelist
operations:Clickheretoviewcodeimage
typelist/consisempty.hpp
template<typenameList>
structIsEmpty{
staticconstexprboolvalue=false;
};
template<>
structIsEmpty<Nil>{
staticconstexprboolvalue=true;
};
Withthesetypelistoperations,wecannowusetheInsertionSortalgorithm
definedinSection24.2.7onpage563,thistimewithcons-stylelists:Clickhere
toviewcodeimage
typelist/conslisttest.hpp
template<typenameT,typenameU>
structSmallerThanT{
staticconstexprboolvalue=sizeof(T)<sizeof(U);
};
voidconslisttest()
{
usingConsList=Cons<int,Cons<char,Cons<short,Cons<double>>>>;
usingSortedTypes=InsertionSort<ConsList,SmallerThanT>;
usingExpected=Cons<char,Cons<short,Cons<int,Cons<double>>>>;
std::cout<<std::is_same<SortedTypes,Expected>::value<<’\n’;
}
Aswe’veseenwiththeinsertionsort,cons-styletypelistscanexpressallofthe
samealgorithmsasthevariadictypelistsdescribedthroughoutthischapter.
Indeed,manyofthealgorithmsdescribedarewritteninpreciselythesamestyle
usedtomanipulatecons-styletypelists.However,theyhaveafewdownsides
thatleadustopreferthevariadicversions:First,thenestingmakeslongcons-
styletypelistsveryhardtobothwriteandread,insourcecodeandincompiler
diagnostics.Second,severalalgorithms(includingPushBackand
Transform)canbespecializedforvariadictypeliststoprovidemoreefficient
implementations(asmeasuredbythenumberofinstantiations).Finally,theuse
ofvariadictemplatesfortypelistsfitswellwiththeuseofvariadictemplatesfor
heterogeneouscontainers,discussedinChapter25andChapter26.
24.6Afternotes
TypelistsseemtohavesprungforthshortlyafterthefirstC++standardwas
publishedin1998.KrysztofCzarneckiandUlrichEiseneckerintroducedaLISP-
inspiredConsstylelistofintegralconstantsin[CzarneckiEiseneckerGenProg],
althoughtheydidnotmaketheleaptogeneraltypelists.
AlexandrescupopularizedtypelistsinhisinfluentialbookModernC++
Design([AlexandrescuDesign]).Aboveall,Alexandrescudemonstratedthe
versatilityoftypelistsforsolvinginterestingdesignproblemswithtemplate
metaprogrammingandtypelists,makingthesetechniquesaccessibletoC++
programmers.
AbrahamsandGurtovoyprovidedmuch-neededstructurefor
metaprogrammingin[AbrahamsGurtovoyMeta],describingabstractionsfor
typelists,typelistalgorithms,andrelatedcomponentsinfamiliartermsdrawn
fromtheC++standardlibrary:sequences,iterators,algorithms,and
(meta)functions.Theaccompanyinglibrary,Boost.MPL([BoostMPL]),iswidely
usedformanipulatingtypelists.
1Thereareevensomeplatformswhereboolisaslargeasalong!
2Notethatthetypelistcouldcontaintypestowhichsizeofdoesnotapply,
suchasvoid.Inthiscase,thecompilerwillproduceanerror,when
attemptingtocomputethelargesttypeofthetypelist.
3Toexperimentwiththisversionofthealgorithm,notethatyouwillneedto
removethepartialspecializationofPushBackforTypelist,oritwillbe
usedinlieuofthegenericversion.
4AbrahamsandGurtovoy([AbrahamsGurtovoyMeta])provideamuchmore
in-depthdiscussionofcompilationtimefortemplatemetaprograms,including
anumberoftechniquesforreducingcompiletime.Weonlyscratchthe
surfacehere.
5Withinthefunctionallanguagecommunity,thisoperationistypicallyknown
asmap.However,weusethetermtransformtobetteralignwiththeC++
standardlibrary’sownalgorithmnames.
6Withinthefunctionallanguagecommunity,thisoperationistypicallyknown
asreduce.However,weusethetermaccumulatetobetteralignwiththe
C++standardlibrary’sownalgorithmnames.
7Thisalsoensuresthattheresultofaccumulatinganemptylistwillbethe
initialvalue.
8Thestandardlibrarydefinesthestd::integral_constanttemplate,
whichismorefeaturefulversionofCTValue.
Chapter25
Tuples
Throughoutthisbookweoftenusehomogeneouscontainersandarray-liketypes
toillustratethepoweroftemplates.Suchhomogeneousstructuresextendthe
conceptofaC/C++arrayandarepervasiveinmostapplications.C++(andC)
alsohasanonhomogeneouscontainmentfacility:theclass(orstruct).This
chapterexplorestuples,whichaggregatedatainamannersimilartoclassesand
structs.Forexample,atuplecontaininganint,adouble,anda
std::stringissimilartoastructwithint,double,andstd::string
members,exceptthattheelementsofthetuplearereferencedpositionally(as0,
1,2)ratherthanthroughnames.Thepositionalinterfaceandtheabilitytoeasily
constructatuplefromatypelistmaketuplesmoresuitablethanstructsforuse
withtemplatemetaprogrammingtechniques.
Analternativeviewontuplesisasamanifestationofatypelistinthe
executableprogram.Forexample,whileatypelistTypelist<int,
double,std::string>describesthesequenceoftypescontainingint,
double,andstd::stringthatcanbemanipulatedatcompiletime,a
Tuple<int,double,std::string>describesstorageforanint,a
double,andastd::stringthatcanbemanipulatedatruntime.For
example,thefollowingprogramcreatesaninstanceofsuchatuple:Clickhereto
viewcodeimage
template<typename…Types>
classTuple{
…//implementationdiscussedbelow
};
Tuple<int,double,std::string>t(17,3.14,"Hello,World!");
Itiscommontousetemplatemetaprogrammingwithtypeliststogeneratetuples
thatcanbeusedtostoredata.Forexample,eventhoughwe’vearbitrarily
selectedint,double,andstd::stringastheelementtypesinthe
exampleabove,wecouldhavecreatedthesetoftypesstoredbythetuplewitha
metaprogram.
Intheremainderofthischapter,wewillexploretheimplementationand
manipulationoftheTupleclasstemplate,whichisasimplifiedversionofthe
classtemplatestd::tuple.
25.1BasicTupleDesign
25.1.1Storage
Tuplescontainstorageforeachofthetypesinthetemplateargumentlist.That
storagecanbeaccessedthroughafunctiontemplateget,usedasget<I>(t)
foratuplet.Forexample,get<0>(t)onthetinthepreviousexample
wouldreturnareferencetotheint17,whileget<1>(t)returnsareference
tothedouble3.14.
Therecursiveformulationoftuplestorageisbasedontheideathatatuple
containingN>0elementscanbestoredasbothasingleelement(thefirst
element,orheaderofthelist)andatuplecontainingN−1elements(thetail),
withaseparatespecialcaseforazero-elementtuple.Thus,athree-elementtuple
Tuple<int,double,std::string>canbestoredasanintanda
Tuple<double,std::string>.Thattwo-elementtuplecanthenbe
storedasadoubleandaTuple<std::string>,whichitselfcanbestored
asastd::stringandaTuple<>.Infact,thisisthesamekindofrecursive
decompositionusedinthegenericversionsoftypelistalgorithms,andtheactual
implementationofrecursivetuplestorageunfoldssimilarly:Clickheretoview
codeimage
template<typename…Types>
classTuple;
//recursivecase:
template<typenameHead,typename…Tail>
classTuple<Head,Tail…>
{
private:
Headhead;
Tuple<Tail…>tail;
public:
//constructors:
Tuple(){
}
Tuple(Headconst&head,Tuple<Tail…>const&tail)
:head(head),tail(tail){
}
…
Head&getHead(){returnhead;}
Headconst&getHead()const{returnhead;}
Tuple<Tail…>&getTail(){returntail;}
Tuple<Tail…>const&getTail()const{returntail;}
};
//basiscase:
template<>
classTuple<>{
//nostoragerequired
};
Intherecursivecase,eachTupleinstancecontainsadatamemberheadthat
storesthefirstelementinthelist,alongwithadatamembertailthatstoresthe
remainingelementsinthelist.Thebasiscaseissimplytheemptytuple,which
hasnoassociatedstorage.
Thegetfunctiontemplatewalksthisrecursivestructuretoextractthe
requestedelement:1
Clickheretoviewcodeimage
tuples/tupleget.hpp
//recursivecase:
template<unsignedN>
structTupleGet{
template<typenameHead,typename…Tail>
staticautoapply(Tuple<Head,Tail…>const&t){
returnTupleGet<N-1>::apply(t.getTail());
}
};
//basiscase:
template<>
structTupleGet<0>{
template<typenameHead,typename…Tail>
staticHeadconst&apply(Tuple<Head,Tail…>const&t){
returnt.getHead();
}
};
template<unsignedN,typename…Types>
autoget(Tuple<Types…>const&t){
returnTupleGet<N>::apply(t);
}
Notethatthefunctiontemplategetissimplyathinwrapperoveracalltoa
staticmemberfunctionofTupleGet.Thistechniqueiseffectivelya
workaroundforthelackofpartialspecializationoffunctiontemplates(discussed
inSection17.3onpage356),whichweusetospecializeonthevalueofN.In
therecursivecase(N>0),thestaticmemberfunctionapply()extractsthetail
ofthecurrenttupleanddecrementsNtokeeplookingfortherequestedelement
laterinthetuple.Thebasiscase(N=0)returnstheheadofthecurrenttuple,
completingtheimplementation.
578Chapter25:Tuples
25.1.2Construction
Besidestheconstructorsdefinedsofar:Clickheretoviewcodeimage
Tuple(){
}
Tuple(Headconst&head,Tuple<Tail…>const&tail)
:head(head),tail(tail){
}
foratupletobeuseful,weneedtobeabletoconstructitbothfromasetof
independentvalues(oneforeachelement)andfromanothertuple.Copy-
constructingfromasetofindependentvaluespassesthefirstofthosevaluesto
initializetheheadelement(viaitsbaseclass),thenpassestheremainingvalues
tothebaseclassrepresentingthetail:Clickheretoviewcodeimage
Tuple(Headconst&head,Tailconst&…tail)
:head(head),tail(tail…){
}
ThisenablesourinitialTupleexample:Clickheretoviewcodeimage
Tuple<int,double,std::string>t(17,3.14,"Hello,World!");
However,thisisn’tthemostgeneralinterface:Usersmaywishto
move-constructtoinitializesome(butperhapsnotall)ofthe
elementsortohaveanelementconstructedfromavalueofa
differenttype.Therefore,weshoulduseperfectforwarding(Section
15.6.3onpage280)toinitializethetuple:Clickheretoviewcode
image
template<typenameVHead,typename…VTail>
Tuple(VHead&&vhead,VTail&&…vtail)
:head(std::forward<VHead>(vhead)),
tail(std::forward<VTail>(vtail)…){
}
Next,weimplementsupportforconstructingatuplefromanothertuple:Click
heretoviewcodeimage
template<typenameVHead,typename…VTail>
Tuple(Tuple<VHead,VTail…>const&other)
:head(other.getHead()),tail(other.getTail()){}
However,theintroductionofthisconstructordoesnotsufficetoallowtuple
conversions:Giventhetupletabove,anattempttocreateanothertuplewith
compatibletypeswillfail:Clickheretoviewcodeimage
//ERROR:noconversionfromTuple<int,double,string>tolong
Tuple<longint,longdouble,std::string>t2(t);
Theproblemhereisthattheconstructortemplatemeanttoinitializefromasetof
independentvaluesisabettermatchthantheconstructortemplatethatacceptsa
tuple.Toaddressthisproblem,wehavetousestd::enable_if<>(see
Section6.3onpage98andSection20.3onpage469)todisablebothmember
functiontemplateswhenthetaildoesnothavetheexpectedlength:Clickhereto
viewcodeimage
template<typenameVHead,typename…VTail,
typename=std::enable_if_t<sizeof…(VTail)==sizeof…(Tail)>>
Tuple(VHead&&vhead,VTail&&…vtail)
:head(std::forward<VHead>(vhead)),
tail(std::forward<VTail>(vtail)…){}
template<typenameVHead,typename…VTail,
typename=std::enable_if_t<sizeof…(VTail)==sizeof…(Tail)>>
Tuple(Tuple<VHead,VTail…>const&other)
:head(other.getHead()),tail(other.getTail()){}
Youcanfindallconstructordeclarationsintuples/tuple.hpp.
AmakeTuple()functiontemplateusesdeductiontodeterminetheelement
typesoftheTupleitreturns,makingitfareasiertocreateatuplefromagiven
setofelements:Clickheretoviewcodeimage
tuples/maketuple.hpp
template<typename…Types>
automakeTuple(Types&&…elems)
{
returnTuple<std::decay_t<Types>…>(std::forward<Types>(elems)…);
}
Again,weuseperfectforwardingcombinedwiththestd::decay<>traitto
convertstringliteralsandotherrawarraysintopointersandremoveconstand
references.Forexample:Clickheretoviewcodeimage
makeTuple(17,3.14,"Hello,World!")initializesa
Clickheretoviewcodeimage
Tuple<int,double,charconst*>
25.2BasicTupleOperations
25.2.1Comparison
Tuplesarestructuraltypesthatcontainothervalues.Tocomparetwotuples,it
sufficestocomparetheirelements.Therefore,wecanwriteadefinitionof
operator==tocomparetwodefinitionselement-wise:Clickheretoview
codeimage
tuples/tupleeq.hpp
//basiscase:
booloperator==(Tuple<>const&,Tuple<>const&)
{
//emptytuplesarealwaysequivalent
returntrue;
}
//recursivecase:
template<typenameHead1,typename…Tail1,
typenameHead2,typename…Tail2,
typename=std::enable_if_t<sizeof…(Tail1)==sizeof…(Tail2)>>
booloperator==(Tuple<Head1,Tail1…>const&lhs,
Tuple<Head2,Tail2…>const&rhs)
{
returnlhs.getHead()==rhs.getHead()&&
lhs.getTail()==rhs.getTail();
}
Likemanyalgorithmsontypelistsandtuples,theelement-wisecomparison
visitstheheadelementandthenrecursestovisitthetail,eventuallyhittingthe
basiscase.The!=,<,>,<=,and>=operatorsfollowanalogously.
25.2.2Output
Throughoutthischapter,wewillbecreatingnewtupletypes,soitisusefultobe
abletoseethosetuplesinanexecutingprogram.Thefollowingoperator<<
printsanytuplewhoseelementtypescanbeprinted:Clickheretoviewcode
image
tuples/tupleio.hpp
#include<iostream>
voidprintTuple(std::ostream&strm,Tuple<>const&,boolisFirst=
true)
{
strm<<(isFirst?’(’:’)’);
}
template<typenameHead,typename…Tail>
voidprintTuple(std::ostream&strm,Tuple<Head,Tail…>const&t,
boolisFirst=true)
{
strm<<(isFirst?"(":",");
strm<<t.getHead();
printTuple(strm,t.getTail(),false);
}
template<typename…Types>
std::ostream&operator<<(std::ostream&strm,Tuple<Types…>const&t)
{
printTuple(strm,t);
returnstrm;
}
Now,itiseasytocreateanddisplaytuples.Forexample:Clickheretoview
codeimage
std::cout<<makeTuple(1,2.5,std::string("hello"))<<’\n’;prints
(1,2.5,hello)
25.3TupleAlgorithms
Atuplesisacontainerthatprovidestheabilitytoaccessandmodifyeachofits
elements(throughget)aswellastocreatenewtuples(directlyorwith
makeTuple())andtobreakatupleintoitsheadandtail(getHead()and
getTail()).Thesefundamentalbuildingblocksaresufficienttobuildasuite
oftuplealgorithms,suchasaddingorremovingelementsfromatuple,
reorderingtheelementsinatuple,orselectingsomesubsetoftheelementsin
thetuple.
Tuplealgorithmsareparticularlyinterestingbecausetheyrequireboth
compile-timeandrun-timecomputation.LikethetypelistalgorithmsofChapter
24,applyinganalgorithmtoatuplemayresultinatuplewithacompletely
differenttype,whichrequirescompile-timecomputation.Forexample,reversing
aTuple<int,double,string>producesaTuple<string,
double,int>.However,likeanalgorithmforahomogeneouscontainer
(e.g.,std::reverse()onastd::vector),tuplealgorithmsactually
requirecodetoexecuteatruntime,andweneedtobemindfuloftheefficiency
ofthegeneratedcode.
25.3.1TuplesasTypelists
Ifweignoretheactualrun-timecomponentofourTupletemplate,weseethat
ithaspreciselythesamestructureastheTypelisttemplatedevelopedin
Chapter24:Itacceptsanynumberoftemplatetypeparameters.Infact,witha
fewpartialspecializations,wecanturnTupleintoafull-featuredtypelist:Click
heretoviewcodeimage
tuples/tupletypelist.hpp
//determinewhetherthetupleisempty:
template<>
structIsEmpty<Tuple<>>{
staticconstexprboolvalue=true;
};
//extractfrontelement:
template<typenameHead,typename…Tail>
classFrontT<Tuple<Head,Tail…>>{
public:
usingType=Head;
};
//removefrontelement:
template<typenameHead,typename…Tail>
classPopFrontT<Tuple<Head,Tail…>>{
public:
usingType=Tuple<Tail…>;
};
//addelementtothefront:
template<typename…Types,typenameElement>
classPushFrontT<Tuple<Types…>,Element>{
public:
usingType=Tuple<Element,Types…>;
};
//addelementtotheback:
template<typename…Types,typenameElement>
classPushBackT<Tuple<Types…>,Element>{
public:
usingType=Tuple<Types…,Element>;
};
Now,allofthetypelistalgorithmsdevelopedinChapter24workequallywell
withTupleandTypelist,sothatweeasilycandealwiththetypeoftuples.
Forexample:Clickheretoviewcodeimage
Tuple<int,double,std::string>t1(17,3.14,"Hello,World!");
using
T2=PopFront<PushBack<decltype(t1),bool>>;
T2t2(get<1>(t1),get<2>(t1),true);
std::cout<<t2;
whichprints:
(3.14,Hello,World!,1)
Aswewillseeshortly,typelistalgorithmsappliedtotupletypesareoftenusedto
helpdeterminetheresulttypeofatuplealgorithm.
25.3.2AddingtoandRemovingfromaTuple
Forthevaluesoftuples,theabilitytoaddanelementtothebeginningorendof
atupleisimportantforbuildingmoreadvancedalgorithms.Aswithtypelists,
insertionatthefrontofatupleisfareasierthaninsertionattheback,sowestart
withpushFront:Clickheretoviewcodeimage
tuples/pushfront.hpp
template<typename…Types,typenameV>
PushFront<Tuple<Types…>,V>
pushFront(Tuple<Types…>const&tuple,Vconst&value)
{
returnPushFront<Tuple<Types…>,V>(value,tuple);
}
Addinganewelement(calledvalue)ontothefrontofanexistingtuple
requiresustoformanewtuplewithvalueasitsheadandtheexistingtuple
asitstail.TheresultingtupletypeisTuple<V,Types…>.However,wehave
optedtousethetypelistalgorithmPushFronttodemonstratethetight
couplingbetweenthecompile-timeandrun-timeaspectsoftuplealgorithms:
Thecompile-timePushFrontcomputesthetypethatweneedtoconstructto
producetheappropriaterun-timevalue.
Addinganewelementtotheendofanexistingtupleismorecomplicated,
becauseitrequiresarecursivewalkofthetuple,buildingupthemodifiedtuple
aswego.NotehowthestructureofthepushBack()implementationfollows
therecursiveformulationofthetypelistPushBack()fromSection24.2.3on
page555:Clickheretoviewcodeimage
tuples/pushback.hpp
//basiscase
template<typenameV>
Tuple<V>pushBack(Tuple<>const&,Vconst&value)
{
returnTuple<V>(value);
}
//recursivecase
template<typenameHead,typename…Tail,typenameV>
Tuple<Head,Tail…,V>
pushBack(Tuple<Head,Tail…>const&tuple,Vconst&value)
{
returnTuple<Head,Tail…,V>(tuple.getHead(),
pushBack(tuple.getTail(),value));
Thebasiscase,asexpected,appendsavaluetoazero-lengthtuplebyproducing
atuplecontainingjustthatvalue.Intherecursivecase,weformanewtuple
fromthecurrentelementatthebeginningofthelist(tuple.getHead())and
theresultofaddingthenewelementtothetailofthelist(therecursive
pushBackcall).Whilewehaveoptedtoexpresstheconstructedtypeas
Tuple<Head,Tail…,V>,wenotethatthisisequivalenttousingthe
compile-timePushBack<Tuple<Head,Tail…>,V>.
Also,popFront()iseasytoimplement:Clickheretoviewcodeimage
tuples/popfront.hpp
template<typename…Types>
PopFront<Tuple<Types…>>
popFront(Tuple<Types…>const&tuple)
{
returntuple.getTail();
}
NowwecanprogramtheexamplefromSection25.3.1onpage582asfollows:
Clickheretoviewcodeimage
Tuple<int,double,std::string>t1(17,3.14,"Hello,World!");
autot2=popFront(pushBack(t1,true));
std::cout<<std::boolalpha<<t2<<’\n’;
whichprints
(3.14,Hello,World!,true)
25.3.3ReversingaTuple
Theelementsofatuplecanbereversedwithanotherrecursivetuplealgorithm
whosestructurefollowsthatofthetypelistreverseofSection24.2.4onpage
557:Clickheretoviewcodeimage
tuples/reverse.hpp
//basiscase
Tuple<>reverse(Tuple<>const&t)
{
returnt;
}
//recursivecase
template<typenameHead,typename…Tail>
Reverse<Tuple<Head,Tail…>>reverse(Tuple<Head,Tail…>const&t)
{
returnpushBack(reverse(t.getTail()),t.getHead());
}
Thebasiscaseistrivial,whiletherecursivecasereversesthetailofthelistand
appendsthecurrentheadtothereversedlist.Thismeans,forexample,thatClick
heretoviewcodeimage
reverse(makeTuple(1,2.5,std::string("hello")))willproducea
Tuple<string,double,int>withthevaluesstring("hello"),2.5,and
1,respectively.
Aswithtypelists,thatwaywecannoweasilyprovidepopBack()bycalling
popFront()forthetemporarilyreversedlistusingPopBackfromSection
24.2.4onpage558:Clickheretoviewcodeimage
tuples/popback.hpp
template<typename…Types>
PopBack<Tuple<Types…>>
popBack(Tuple<Types…>const&tuple)
{
returnreverse(popFront(reverse(tuple)));
}
25.3.4IndexLists
Therecursiveformulationoftuplereversalintheprevioussectioniscorrect,but
itisunnecessarilyinefficientatruntime.Toseetheproblem,weintroducea
simpleclassthatcountsthenumberoftimesitiscopied:2
Clickheretoviewcodeimage
tuples/copycounter.hpp
template<intN>
structCopyCounter
{
inlinestaticunsignednumCopies=0;
CopyCounter(){
}
CopyCounter(CopyCounterconst&){
++numCopies;
}
};
Then,wecreateandreverseatupleofCopyCounterinstances:Clickhereto
viewcodeimage
tuples/copycountertest.hpp
voidcopycountertest()
{
Tuple<CopyCounter<0>,CopyCounter<1>,CopyCounter<2>,
CopyCounter<3>,CopyCounter<4>>copies;
autoreversed=reverse(copies);
std::cout<<"0:"<<CopyCounter<0>::numCopies<<"copies\n";
std::cout<<"1:"<<CopyCounter<1>::numCopies<<"copies\n";
std::cout<<"2:"<<CopyCounter<2>::numCopies<<"copies\n";
std::cout<<"3:"<<CopyCounter<3>::numCopies<<"copies\n";
std::cout<<"4:"<<CopyCounter<4>::numCopies<<"copies\n";
}
Thisprogramwilloutput:
Clickheretoviewcodeimage
0:5copies
1:8copies
2:9copies
3:8copies
4:5copies
That’salotofcopies!Intheidealimplementationoftuplereverse,eachelement
wouldonlybecopiedasingletime,fromthesourcetupledirectlytothecorrect
positionintheresulttuple.Wecouldachievethisgoalwithcarefuluseof
references,includingusingreferencesforthetypesoftheintermediate
arguments,butdoingsocomplicatesourimplementationconsiderably.
Toeliminateextraneouscopiesintuplereverse,considerhowwemight
implementaone-offtuplereverseoperationforasingletupleofknownlength
(say,5elements,asinourexample).WecouldsimplyusemakeTuple()and
get():Clickheretoviewcodeimage
autoreversed=makeTuple(get<4>(copies),get<3>(copies),
get<2>(copies),get<1>(copies),
get<0>(copies));
Thisprogramproducestheexactoutputthatwewant,withasinglecopyofeach
tupleelement:Clickheretoviewcodeimage
0:1copies
1:1copies
2:1copies
3:1copies
4:1copies
Indexlists(alsocalledindexsequences;seeSection24.4onpage570)
generalizethisnotionbycapturingthesetoftupleindices—inthiscase4,3,2,
1,0—intoaparameterpack,whichallowsthesequenceofgetcallstobe
producedviaapackexpansion.Thisallowstheseparationoftheindex
computation,whichcanbeanarbitrarilycomplicatedtemplatemetaprogram,
fromtheactualapplicationofthatindexlist,whererun-timeefficiencyismost
important.Thestandardtypestd::integer_sequence(introducedin
C++14)isoftenusedtorepresentindexlists.
25.3.5ReversalwithIndexLists
Toperformtuplereversalwithindexlists,wefirstneedarepresentationofindex
lists.Anindexlistisatypelistcontainingvaluesmeanttobeusedasindicesinto
eitheratypelistoraheterogeneousdatastructure(seeSection24.4onpage570).
Forourindexlist,wewillusetheValuelisttypedevelopedinSection24.3
onpage566.Theindexlistcorrespondingtothetuplereversalexampleabove
wouldbeClickheretoviewcodeimage
Valuelist<unsigned,4,3,2,1,0>Howdoweproducethisindexlist?
Oneapproachwouldbetostartbygeneratinganindexlistcounting
upwardfrom0toN−1(inclusive),whereNisthelengthofatuple,
usingasimpletemplatemetaprogramMakeIndexList:3
Clickheretoviewcodeimage
tuples/makeindexlist.hpp
//recursivecase
template<unsignedN,typenameResult=Valuelist<unsigned>>
structMakeIndexListT
:MakeIndexListT<N-1,PushFront<Result,CTValue<unsigned,N-1>>>
{
};
//basiscase
template<typenameResult>
structMakeIndexListT<0,Result>
{
usingType=Result;
};
template<unsignedN>
usingMakeIndexList=typenameMakeIndexListT<N>::Type;Wecanthen
composethisoperationwiththetypelistReversetoproducethe
appropriateindexlist:Clickheretoviewcodeimage
usingMyIndexList=Reverse<MakeIndexList<5>>;
//equivalenttoValuelist<unsigned,4,3,2,1,0>
Toactuallyperformthereversal,theindicesintheindexlistneedtobecaptured
intoanontypeparameterpack.Thisishandledbysplittingtheimplementation
oftheindex-settuplereverse()algorithmintotwoparts:Clickheretoview
codeimage
tuples/indexlistreverse.hpp
template<typename…Elements,unsigned…Indices>
autoreverseImpl(Tuple<Elements…>const&t,
Valuelist<unsigned,Indices…>)
{
returnmakeTuple(get<Indices>(t)…);
}
template<typename…Elements>
autoreverse(Tuple<Elements…>const&t)
{
returnreverseImpl(t,
Reverse<MakeIndexList<sizeof…(Elements)>>());
}
InC++11,thereturntypeshavetobedeclaredasClickheretoviewcodeimage
->decltype(makeTuple(get<Indices>(t)…))and
Clickheretoviewcodeimage
->decltype(reverseImpl(t,Reverse<MakeIndexList<sizeof…(Elements)>>
()))ThereverseImpl()functiontemplatecapturestheindicesfrom
itsValuelistparameterintoaparameterpackIndices.Itthen
returnstheresultofcallingmakeTuple()withargumentsformedby
callingget()onthetuplewiththesetofcapturedindices.
Thereverse()algorithmitselfmerelyformstheappropriateindexset,as
discussedearlier,andprovidesthattothereverseImplalgorithm.The
indicesaremanipulatedasatemplatemetaprogramandthereforeproduceno
run-timecode.Theonlyrun-timecodeisinreverseImpl,whichuses
makeTuple()toconstructtheresultingtupleinonestepandthereforecopies
thetupleelementsonlyasingletime.
25.3.6ShuffleandSelect
ThereverseImpl()functiontemplateusedintheprevioussectiontoform
thereversedtupleactuallycontainsnocodespecifictothereverse()
operation.Rather,itsimplyselectsaparticularsetofindicesfromanexisting
tupleandusesthemtoformanewtuple.reverse()providesareversedsetof
indices,butmanyalgorithmscanbuildonthiscoretupleselect()
algorithm:4
Clickheretoviewcodeimage
tuples/select.hpp
template<typename…Elements,unsigned…Indices>
autoselect(Tuple<Elements…>const&t,
Valuelist<unsigned,Indices…>)
{
returnmakeTuple(get<Indices>(t)…);
}
Onesimplealgorithmthatbuildsonselect()isatuple“splat”operation,
whichtakesasingleelementinatupleandreplicatesittocreateanothertuple
withsomenumberofcopiesofthatelement.Forexample:Clickheretoview
codeimage
Tuple<int,double,std::string>t1(42,7.7,"hello"};
autoa=splat<1,4>(t);
std::cout<<a<<’\n’;wouldproduceaTuple<double,double,double,
double>whereeachofthevaluesisacopyofget<1>(t),soitwill
print(7.7,7.7,7.7,7.7)
Givenametaprogramtoproducea“replicated”indexsetconsistingofNcopies
ofthevalueI,splat()isadirectapplicationofselect():5
Clickheretoviewcodeimage
tuples/splat.hpp
template<unsignedI,unsignedN,typenameIndexList=
Valuelist<unsigned>>
classReplicatedIndexListT;
template<unsignedI,unsignedN,unsigned…Indices>
classReplicatedIndexListT<I,N,Valuelist<unsigned,Indices…>>
:publicReplicatedIndexListT<I,N-1,
Valuelist<unsigned,Indices…,I>>{
};
template<unsignedI,unsigned…Indices>
classReplicatedIndexListT<I,0,Valuelist<unsigned,Indices…>>{
public:
usingType=Valuelist<unsigned,Indices…>;
};
template<unsignedI,unsignedN>
usingReplicatedIndexList=typenameReplicatedIndexListT<I,N>::Type;
template<unsignedI,unsignedN,typename…Elements>
autosplat(Tuple<Elements…>const&t)
{
returnselect(t,ReplicatedIndexList<I,N>());
}
Evencomplicatedtuplealgorithmscanalsobeimplementedintermsofa
templatemetaprogramontheindexlistfollowedbyanapplicationof
select().Forexample,wecanusetheinsertionsortdevelopedinSection
24.2.7onpage563tosortatuplebasedonthesizesoftheelementtypes.Given
suchasort()function,whichacceptsatemplatemetafunctioncomparing
tupleelementtypesasthecomparisonoperation,wecouldsorttupleelementsby
sizewithcodelikethefollowing:Clickheretoviewcodeimage
tuples/tuplesorttest.hpp
#include<complex>
template<typenameT,typenameU>
classSmallerThanT
{
public:
staticconstexprboolvalue=sizeof(T)<sizeof(U);
};
voidtestTupleSort()
{
autoT1=makeTuple(17LL,std::complex<double>(42,77),’c’,42,7.7);
std::cout<<t1<<’\n’;
autoT2=sort<SmallerThanT>(t1);//t2isTuple<int,long,
std::string>
std::cout<<"sortedbysize:"<<t2<<’\n’;
}
Theoutputmight,forexample,beasfollows:6
Clickheretoviewcodeimage
(17,(42,77),c,42,7.7)
sortedbysize:(c,42,7.7,17,(42,77))
Theactualsort()implementationinvolvestheuseofInsertionSortwith
atupleselect():7
Clickheretoviewcodeimage
tuples/tuplesort.hpp
//metafunctionwrapperthatcomparestheelementsinatuple:
template<typenameList,template<typenameT,typenameU>classF>
classMetafunOfNthElementT{
public:
template<typenameT,typenameU>classApply;
template<unsignedN,unsignedM>
classApply<CTValue<unsigned,M>,CTValue<unsigned,N>>
:publicF<NthElement<List,M>,NthElement<List,N>>{};
};
//sortatuplebasedoncomparingtheelementtypes:
template<template<typenameT,typenameU>classCompare,
typename…Elements>
autosort(Tuple<Elements…>const&t)
{
returnselect(t,
InsertionSort<MakeIndexList<sizeof…(Elements)>,
MetafunOfNthElementT<
Tuple<Elements…>,
Compare>::templateApply>());
}
LookcarefullyattheuseofInsertionSort:Theactualtypelisttobesorted
isalistofindicesintothetypelist,constructedwithMakeIndexList<>.
Therefore,theresultoftheinsertionsortisasetofindicesintothetuple,which
isthenprovidedtoselect().However,becausetheInsertionSortis
operatingonindices,itexpectsitscomparisonoperationtocomparetwoindices.
Theprincipleiseasiertounderstandwhenconsideringasortoftheindicesofa
std::vector,asinthefollowing(non-metaprogramming)example:Click
heretoviewcodeimage
tuples/indexsort.cpp
#include<vector>
#include<algorithm>
#include<string>
intmain()
{
std::vector<std::string>strings={"banana","apple","cherry"};
std::vector<unsigned>indices={0,1,2};
std::sort(indices.begin(),indices.end(),
[&strings](unsignedi,unsignedj){
returnstrings[i]<strings[j];
});
}
Here,indicescontainsindicesintothevectorstrings.Thesort()
operationsortstheactualindices,sothelambdaprovidedasthecomparison
operationacceptstwounsignedvalues(ratherthanstringvalues).
However,thebodyofthelambdausesthoseunsignedvaluesasindicesinto
thestringsvector,sotheorderingisactuallyaccordingtothecontentsof
strings.Attheendofthesort,indicesprovidesindicesintostrings,
sortedbasedonthevaluesinstrings.
OuruseofInsertionSortforthetuplesort()employsthesame
approach.TheadaptertemplateMetafunOfNthElementTprovidesa
templatemetafunction(itsnestedApply)thatacceptstwoindices(CTValue
specializations)andusesNthElementtoextractthecorrespondingelements
fromitsTypelistargument.Inasense,themembertemplateApplyhas
“captured”thetypelistprovidedtoitsenclosingtemplate
(MetafunOfNthElementT)inthesamewaythatthelambdacapturedthe
stringsvectorfromitsenclosingscope.Applythenforwardstheextracted
elementtypestotheunderlyingmetafunctionF,completingtheadaptation.
Notethatallofthecomputationforthesortisperformedatcompiletime,and
theresultingtupleisformeddirectly,withnoextraneouscopyingofvaluesat
runtime.
25.4ExpandingTuples
Tuplesareusefulforstoringasetofrelatedvaluestogetherintoasinglevalue,
regardlessofwhattypesorhowmanyofthoserelatedvaluesthereare.Atsome
point,itmaybenecessarytounpacksuchatuple,forexample,topassits
elementsasseparateargumentstoafunction.Asasimpleexample,wemay
wanttotakeatupleandpassitselementstothevariadicprint()operation
describedinSection12.4onpage200:Clickheretoviewcodeimage
Tuple<std::string,charconst*,int,char>t("Pi","isroughly",
3,’\n’);
print(t…);//ERROR:cannotexpandatuple;itisn’taparameterpack
Asnotedintheexample,the“obvious”attempttounpackatuplewillnot
succeed,becauseitisnotaparameterpack.Wecanachievethesamemeans
usinganindexlist.Thefollowingfunctiontemplateapply()acceptsa
functionandatuple,thencallsthefunctionwiththeunpackedtupleelements:
Clickheretoviewcodeimage
tuples/apply.hpp
template<typenameF,typename…Elements,unsigned…Indices>
autoapplyImpl(Ff,Tuple<Elements…>const&t,
Valuelist<unsigned,Indices…>)
->decltype(f(get<Indices>(t)…))
{
returnf(get<Indices>(t)…);
}
template<typenameF,typename…Elements,
unsignedN=sizeof…(Elements)>
autoapply(Ff,Tuple<Elements…>const&t)
->decltype(applyImpl(f,t,MakeIndexList<N>()))
{
returnapplyImpl(f,t,MakeIndexList<N>());
}
TheapplyImpl()functiontemplatetakesagivenindexlistandusesitto
expandtheelementsofatupleintotheargumentlistforitsfunctionobject
argumentf.Theuser-facingapply()isresponsibleonlyforconstructingthe
initialindexlist.Together,theyallowustoexpandatupleintotheargumentsof
print():Clickheretoviewcodeimage
Tuple<std::string,charconst*,int,char>t("Pi","isroughly",
3,’\n’);
apply(print,t);//OK:printsPiisroughly3
C++17providesasimilarfunctionthatworksforanytuple-liketype.
25.5OptimizingTuple
Atupleisafundamentalheterogeneouscontainerwithalargenumberof
potentialuses.Assuch,itisworthwhiletoconsiderwhatcanbedoneto
optimizetheuseoftuplesbothatruntime(storage,executiontime)andat
compiletime(numberoftemplatesinstantiations).Thissectiondiscussesafew
specificoptimizationstoourTupleimplementation.
25.5.1TuplesandtheEBCO
OurformulationforTuplestorageusesmorestoragethanisstrictlynecessary.
Oneproblemisthatthetailmemberwilleventuallybeanemptytuple,
becauseeverynonemptytupleterminateswithanemptytuple,anddata
membersmustalwayshaveatleastonebyteofstorage.
ToimproveTuple’sstorageefficiency,wecanapplytheemptybaseclass
optimization(EBCO)discussedinSection21.1onpage489byinheritingfrom
thetailtupleratherthanmakingitamember.Forexample:Clickheretoview
codeimage
tuples/tuplestorage1.hpp
//recursivecase:
template<typenameHead,typename…Tail>
classTuple<Head,Tail…>:privateTuple<Tail…>
{
private:
Headhead;
public:
Head&getHead(){returnhead;}
Headconst&getHead()const{returnhead;}
Tuple<Tail…>&getTail(){return*this;}
Tuple<Tail…>const&getTail()const{return*this;}
};
ThisisthesameapproachwetookwithBaseMemberPairinSection21.1.2
onpage494.Unfortunately,ithasthepracticalsideeffectofreversingtheorder
inwhichthetupleelementsareinitializedinconstructors.Previously,because
theheadmemberprecededthetailmember,theheadwouldbeinitialized
first.InthisnewformulationofTuplestorage,thetailisinabaseclass,soit
willbeinitializedbeforethememberhead.8
Thisproblemcanbeaddressedbysinkingtheheadmemberintoitsownbase
classthatprecedesthetailinthebaseclasslist.Adirectimplementationofthis
wouldintroduceaTupleElttemplatethatisusedtowrapeachelementtype
sothatTuplecaninheritfromit:Clickheretoviewcodeimage
tuples/tuplestorage2.hpp
template<typename...Types>
classTuple;
template<typenameT>
classTupleElt
{
Tvalue;
public:
TupleElt()=default;
template<typenameU>
TupleElt(U&&other):value(std::forward<U>(other){}
T&get(){returnvalue;}
Tconst&get()const{returnvalue;}
};
//recursivecase:
template<typenameHead,typename...Tail>
classTuple<Head,Tail...>
:privateTupleElt<Head>,privateTuple<Tail...>
{
public:
Head&getHead(){
//potentiallyambiguous
returnstatic_cast<TupleElt<Head>*>(this)->get();
}
Headconst&getHead()const{
//potentiallyambiguous
returnstatic_cast<TupleElt<Head>const*>(this)->get();
}
Tuple<Tail...>&getTail(){return*this;}
Tuple<Tail...>const&getTail()const{return*this;}
};
//basiscase:
template<>
classTuple<>{
//nostoragerequired
};
Whilethisapproachhassolvedtheinitialization-orderingproblem,ithas
introducedanew(worse)problem:Wecannolongerextractelementsfroma
tuplethathastwoelementsofthesametype,suchasTuple<int,int>,
becausethederived-to-baseconversionfromatupletoTupleEltofthattype
(e.g.,TupleElt<int>)willbeambiguous.
Tobreaktheambiguity,weneedtoensurethateachTupleEltbaseclassis
uniquewithinagivenTuple.Oneapproachistoencodethe“height”ofthis
valuewithinitstuple,thatis,thelengthofthetailtuple.Thelastelementinthe
tuplewillbestoredwithheight0,thenext-to-last-elementwillbestoredwith
height1,andsoon:9
Clickheretoviewcodeimage
tuples/tupleelt1.hpp
template<unsignedHeight,typenameT>
classTupleElt{
Tvalue;
public:
TupleElt()=default;
template<typenameU>
TupleElt(U&&other):value(std::forward<U>(other)){}
T&get(){returnvalue;}
Tconst&get()const{returnvalue;}
};
Withthissolution,wecanproduceaTuplethatappliestheEBCOwhile
maintaininginitializationorderandsupportformultipleelementsofthesame
type:Clickheretoviewcodeimage
tuples/tuplestorage3.hpp
template<typename...Types>
classTuple;
//recursivecase:
template<typenameHead,typename...Tail>
classTuple<Head,Tail...>
:privateTupleElt<sizeof...(Tail),Head>,privateTuple<Tail...>
{
usingHeadElt=TupleElt<sizeof...(Tail),Head>;
public:
Head&getHead(){
returnstatic_cast<HeadElt*>(this)->get();
}
Headconst&getHead()const{
returnstatic_cast<HeadEltconst*>(this)->get();
}
Tuple<Tail...>&getTail(){return*this;}
Tuple<Tail...>const&getTail()const{return*this;}
};
//basiscase:
template<>
classTuple<>{
//nostoragerequired
};
Withthisimplementation,thefollowingprogram:Clickheretoviewcodeimage
tuples/compressedtuple1.cpp
#include<algorithm>
#include"tupleelt1.hpp"
#include"tuplestorage3.hpp"
#include<iostream>
structA{
A(){
std::cout<<"A()"<<’\n’;
}
};
structB{
B(){
std::cout<<"B()"<<’\n’;
}
};
intmain()
{
Tuple<A,char,A,char,B>t1;
std::cout<<sizeof(t1)<<"bytes"<<’\n’;
}
prints
A()
A()
B()
5bytes
TheEBCOhaseliminatedonebyte(fortheemptytuple,Tuple<>).However,
notethatbothAandBareemptyclasses,whichhintsatonemoreopportunity
forapplyingtheEBCOinTuple.TupleEltcanbeextendedslightlyto
inheritfromtheelementtypewhenitissafetodoso,withoutrequiringchanges
toTuple:Clickheretoviewcodeimage
tuples/tupleelt2.hpp
#include<type_traits>
template<unsignedHeight,typenameT,
bool=std::is_class<T>::value&&!std::is_final<T>::value>
classTupleElt;
template<unsignedHeight,typenameT>
classTupleElt<Height,T,false>
{
Tvalue;
public:
TupleElt()=default;
template<typenameU>
TupleElt(U&&other):value(std::forward<U>(other)){}
T&get(){returnvalue;}
Tconst&get()const{returnvalue;}
};
template<unsignedHeight,typenameT>
classTupleElt<Height,T,true>:privateT
{
public:
TupleElt()=default;
template<typenameU>
TupleElt(U&&other):T(std::forward<U>(other)){}
T&get(){return*this;}
Tconst&get()const{return*this;}
};
WhenTupleEltisprovidedwithanon-finalclass,itinheritsfromtheclass
privatelytoallowtheEBCOtoapplytothestoredvalue,too.Withthischange,
thepreviousprogramnowprintsA()
A()
B()
2bytes
25.5.2Constant-timeget()
Theget()operationisextremelycommonwhenworkingwithtuples,butit’s
recursiveimplementationrequiresalinearnumberoftemplateinstantiationsthat
canaffectcompiletime.Fortunately,theEBCOoptimizationsintroducedinthe
previoussectionhaveenabledamoreefficientimplementationofgetthatwe
willdescribehere.
Thekeyinsightisthattemplateargumentdeduction(Chapter15)deduces
templateargumentsforabaseclasswhenmatchingaparameter(ofthebase
classtype)toanargument(ofthederivedclasstype).Thus,ifwecancompute
theheightHoftheelementwewishtoextract,wecanrelyontheconversion
fromtheTuplespecializationtoTupleElt<H,T>(whereTisdeduced)to
extractthatelementwithoutmanuallywalkingthroughalloftheindices:Click
heretoviewcodeimage
tuples/constantget.hpp
template<unsignedH,typenameT>
T&getHeight(TupleElt<H,T>&te)
{
returnte.get();
}
template<typename...Types>
classTuple;
template<unsignedI,typename...Elements>
autoget(Tuple<Elements...>&t)
->decltype(getHeight<sizeof...(Elements)-I-1>(t))
{
returngetHeight<sizeof...(Elements)-I-1>(t);
}
Becauseget<I>(t)receivestheindexIofthedesiredelement(whichcounts
fromthebeginningofthetuple)whilethetuple’sactualstorageisintermsof
heightH(whichcountsfromtheendofthetuple),wecomputeHfromI.
TemplateargumentdeductionforthecalltogetHeight()performstheactual
search:TheheightHisfixedbecauseitisexplicitlyprovidedinthecall,soonly
oneTupleEltbaseclasswillmatch,fromwhichthetypeTwillbededuced.
NotethatgetHeight()mustbedeclaredafriendofTupletoallowthe
conversiontotheprivatebaseclass.Forexample:Clickheretoviewcodeimage
//insidetherecursivecaseforclasstemplateTuple:
template<unsignedI,typename…Elements>
friendautoget(Tuple<Elements…>&t)
->decltype(getHeight<sizeof…(Elements)-I-1>(t));Notethatthis
implementationrequiresonlyaconstantnumberoftemplate
instantiations,becausewehaveoffloadedthehardworkofmatching
uptheindextothecompiler’stemplateargumentdeductionengine.
25.6TupleSubscript
Inprinciple,itisalsopossibletodefineanoperator[]toaccesstheelements
ofatuple,similarlytothewaystd::vectordefinesoperator[].10
However,unlikestd::vector,atuple’selementscaneachhaveadifferent
type,soatuple’soperator[]mustbeatemplatewheretheresulttypediffers
dependingontheindexoftheelement.That,inturn,requireseachindextohave
adifferenttype,sotheindex’stypecanbeusedtodeterminetheelementtype.
TheclasstemplateCTValue,introducedinSection24.3onpage566,allows
ustoencodethenumericindexwithinatype.Wecanusethistodefinea
subscriptoperatorasamemberofTuple:Clickheretoviewcodeimage
template<typenameT,TIndex>
auto&operator[](CTValue<T,Index>){
returnget<Index>(*this);
}
Here,weusethevalueofthepassedindexwithinthetypeoftheCTValue
argumenttomakeacorrespondingget<>()call.
Nowwecanusethisclassasfollows:Clickheretoviewcodeimage
autot=makeTuple(0,’1’,2.2f,std::string{"hello"});
autoa=t[CTValue<unsigned,2>{}];
autob=t[CTValue<unsigned,3>{}];aandbwillbeinitializedby
thetypeandvalueofthethirdandfourthvaluesintheTuplet.
Tomaketheusageofconstantindicesmoreconvenient,wecanimplementthe
literaloperatorwithconstexprtocomputethenumericcompile-timeliterals
directlyfromordinaryliteralswiththesuffix_c:Clickheretoviewcodeimage
tuples/literals.hpp
#include"ctvalue.hpp"
#include<cassert>
#include<cstddef>
//convertsinglechartocorrespondingintvalueatcompiletime:
constexprinttoInt(charc){
//hexadecimalletters:
if(c>=’A’&&c<=’F’){
returnstatic_cast<int>(c)-static_cast<int>(’A’)+10;
}
if(c>=’a’&&c<=’f’){
returnstatic_cast<int>(c)-static_cast<int>(’a’)+10;
}
//other(disable’.’forfloating-pointliterals):
assert(c>=’0’&&c<=’9’);
returnstatic_cast<int>(c)-static_cast<int>(’0’);
}
//parsearrayofcharstocorrespondingintvalueatcompiletime:
template<std::size_tN>
constexprintparseInt(charconst(&arr)[N]){
intbase=10;//tohandlebase(default:decimal)
intoffset=0;//toskipprefixeslike0x
if(N>2&&arr[0]==’0’){
switch(arr[1]){
case’x’://prefix0xor0X,sohexadecimal
case’X’:
base=16;
offset=2;
break;
case’b’://prefix0bor0B(sinceC++14),sobinary
case’B’:
base=2;
offset=2;
break;
default://prefix0,sooctal
base=8;
offset=1;
break;
}
}
//iterateoveralldigitsandcomputeresultingvalue:
intvalue=0;
intmultiplier=1;
for(std::size_ti=0;i<N-offset;++i){
if(arr[N-1-i]!=’\’’){//ignoreseparatingsinglequotes(e.g.in
1’000)
value+=toInt(arr[N-1-i])*multiplier;
multiplier*=base;
}
}
returnvalue;
}
//literaloperator:parseintegralliteralswithsuffix_cas
sequenceofchars:
template<char…cs>
constexprautooperator""_c(){
returnCTValue<int,parseInt<sizeof…(cs)>({cs…})>{};
}
Herewetaketheadvantageofthefactthat,fornumericliterals,wecanusethe
literaloperatortodeduceeachcharacteroftheliteralasitsowntemplate
parameter(seeSection15.5.1onpage277fordetails).Wepassthecharactersto
aconstexprhelperfunctionparseInt()thatcomputesthevalueofthe
charactersequenceatcompiletimeandyieldsitasCTValue.Forexample:•
42_cyieldsCTValue<int,42>
•0x815_cyieldsCTValue<int,2069>
•0b1111’1111_cyieldsCTValue<int,255>11
Notethattheparserdoesnothandlefloating-pointliterals.Forthem,the
assertionresultsinacompile-timeerrorbecauseitisarun-timefeaturethatcan’t
beusedincompile-timecontexts.
Withthis,wecanusetuplesasfollows:Clickheretoviewcodeimage
autot=makeTuple(0,’1’,2.2f,std::string{"hello"});
autoc=t[2_c];
autod=t[3_c];ThisapproachisusedbyBoost.Hana(see
[BoostHana]),ametaprogramminglibrarysuitedforcomputationson
bothtypesandvalues.
25.7Afternotes
Tupleconstructionisoneofthosetemplateapplicationsthatappearstohave
beenindependentlyattemptedbymanyprogrammers.TheBoost.TupleLibrary
[BoostTuple]becameoneofthemostpopularformulationsoftuplesinC++,and
eventuallygrewintotheC++11std::tuple.
PriortoC++11,manytupleimplementationswerebasedontheideaofa
recursivepairstructure;thefirsteditionofthisbook,
[VandevoordeJosuttisTemplates1st],illustratedonesuchapproachviaits
“recursiveduos.”OneinterestingalternativewasdevelopedbyAndrei
Alexandrescuin[AlexandrescuDesign].Hecleanlyseparatedthelistoftypes
fromthelistoffieldsinthetuple,usingtheconceptoftypelists(asdiscussedin
Chapter24)asafoundationfortuples.
C++11broughtvariadictemplates,whereparameterpackscouldclearly
capturethelistoftypesforatuple,eliminatingtheneedforrecursivepairs.Pack
expansionsandthenotionofindexlists[GregorJarviPowellVariadicTemplates]
collapsedrecursivetemplateinstantiationsintosimpler,moreefficienttemplate
instantiations,makingtuplesmorewidelypractical.Indexlistshavebecomeso
criticaltotheperformanceoftupleandtypelistalgorithms,thatcompilers
includeanintrinsicaliastemplatesuchas__make_integer_seq<S,T,
N>thatexpandstoS<T,0,1,…,N>withoutadditionaltemplate
instantiations,therebyacceleratingapplicationsof
std::make_index_sequenceandmake_integer_sequence.
Tupleisthemostwidelyusedheterogeneouscontainer,butitisn’ttheonly
one.TheBoost.FusionLibrary[BoostFusion]providesotherheterogeneous
counterpartstocommoncontainers,suchasheterogeneouslist,deque,set,
andmap.Moreimportant,itprovidesaframeworkforwritingalgorithmsfor
heterogeneouscollections,usingthesamekindsofabstractionsandterminology
astheC++standardlibraryitself(e.g.,iterators,sequences,andcontainers).
Boost.Hana[BoostHana]takesmanyoftheideaspresentinBoost.MPL
[BoostMPL]andBoost.Fusion,bothdesignedandimplementedlongbefore
C++11cametofruition,andreimaginesthemwiththenewC++11(andC++14)
languagefeatures.Theresultisanelegantlibrarythatprovidespowerful,
composablecomponentsforheterogeneouscomputation.
1Acompleteimplementationofget()shouldalsohandlenon-constand
rvalue-referencetuplesappropriately.
2BeforeC++17,inlinestaticmemberswerenotsupported.So,wehadto
initializenumCopiesoutsidetheclassstructureinonetranslationunit.
3C++14providesasimilartemplatemake_index_sequencethatyieldsa
listofindicesoftypestd::size_t,aswellasamoregeneral
make_integer_sequencethatallowsthespecifictypetobeselected.
4InC++11,thereturntypehastobedeclaredas->
decltype(makeTuple(get<Indices>(t)…)).
5InC++11,thereturntypeofsplat()hastobedeclaredas->
decltype(return-expression).
6Notethattheresultingorderdependsontheplatform-specificsize.For
example,thesizeofadoublemightbeless,thesame,orgreaterthanthe
sizeofalonglong.
7InC++11,thereturntypeofsort()hastobedeclaredas->
decltype(return-expression).
8Anotherpracticalimpactofthischangeisthattheelementsofthetuplewill
endupbeingstoredinreverseorder,becausebaseclassesaretypicallystored
beforemembers.
9Itwouldbemoreintuitivetosimplyusetheindexofthetupleelementrather
thanitsheight.However,thatinformationisnotreadilyavailableinTuple,
becauseagiventuplemayappearbothasastandalonetupleandasthetailof
anothertuple.AgivenTupledoesknow,however,howmanyelementsare
initsowntail.
10ThankstoLouisDionneforpointingoutthefeaturesdescribedinthissection.
11Theprefix0bforbinaryliteralsandthesinglequotecharactertoseparate
digitsaresupportedsinceC++14.
Chapter26
DiscriminatedUnions
Thetuplesdevelopedinthepreviouschapteraggregatevaluesofsomelistof
typesintoasinglevalue,givingthemroughlythesamefunctionalityasasimple
struct.Giventhisanalogy,itisnaturaltowonderwhatthecorrespondingtype
wouldbeforaunion:Itwouldcontainasinglevalue,butthatvaluewouldhave
atypeselectedfromsomesetofpossibletypes.Forexample,adatabasefield
mightcontainaninteger,floating-pointvalue,string,orbinaryblob,butitcan
onlycontainavalueofoneofthosetypesatanygiventime.
Inthischapter,wedevelopaclasstemplateVariantthatdynamicallystores
avalueofoneofagivensetofpossiblevaluetypes,similartotheC++17
standardlibrary’sstd::variant<>.Variantisadiscriminatedunion,
meaningthatavariantknowswhichofitspossiblevaluetypesiscurrently
active,providingbettertypesafetythantheequivalentC++union.Variant
itselfisavariadictemplate,whichacceptsthelistoftypestheactivevaluemay
have.Forexample,thevariableClickheretoviewcodeimage
Variant<int,double,string>field;canstoreanint,double,or
string,butonlyoneofthesevaluesatatime.1Thefollowing
programillustratesthebehaviorofVariant:Clickheretoviewcode
image
variant/variant.cpp
#include"variant.hpp"
#include<iostream>
#include<string>
intmain()
{
Variant<int,double,std::string>field(17);
if(field.is<int>()){
std::cout<<"Fieldstorestheinteger"
<<field.get<int>()<<’\n’;
}
field=42;//assignvalueofsametype
field="hello";//assignvalueofdifferenttype
std::cout<<"Fieldnowstoresthestring’"
<<field.get<std::string>()<<"’\n";
}
Itproducesthefollowingoutput:
Clickheretoviewcodeimage
Fieldstorestheinteger17
Fieldnowstoresthestring"hello"
Thevariantcanbeassignedtoavalueofanyofitstypes.Wecantestwhether
thevariantcurrentlycontainsavalueoftypeTusingthememberfunction
is<T>(),thenextractthatstoredvaluewiththememberfunctionget<T>().
26.1Storage
ThefirstmajordesignaspectofourVarianttypeishowtomanagethe
storageoftheactivevalue,thatis,thevaluethatiscurrentlystoredwithinthe
variant.Thedifferenttypeslikelyhavedifferentsizesandalignmentsto
consider.Additionally,thevariantwillneedtostoreadiscriminatortoindicate
whichofthepossibletypesisthetypeoftheactivevalue.Onesimple(albeit
inefficient)storagemechanismusesatuple(seeChapter25)directly:Clickhere
toviewcodeimage
variant/variantstorageastuple.hpp
template<typename…Types>
classVariant{
public:
Tuple<Types…>storage;
unsignedchardiscriminator;
};
Here,thediscriminatoractsasadynamicindexintothetuple.Onlythetuple
elementwhosestaticindexisequaltothecurrentdiscriminatorvaluehasavalid
value,sowhendiscriminatoris0,get<0>(storage)providesaccess
totheactivevalue;whenthediscriminatoris1,get<1>(storage)
providesaccesstotheactivevalue,andsoon.
Wecouldbuildthecorevariantoperationsis<T>()andget<T>()ontop
ofthetuple.However,doingsoisquiteinefficient,becausethevariantitselfnow
requiresstorageequaltothesumofthesizesofallofthepossiblevaluetypes,
eventhoughonlyonewillbeactiveatatime.2Abetterapproachoverlapsthe
storageofeachofthepossibletypes.Wecouldimplementthisbyrecursively
unwrappingthevariantintoitsheadandtail,aswedidwithtuplesinSection
25.1.1onpage576,butwithaunionratherthanaclass:Clickheretoviewcode
image
variant/variantstorageasunion.hpp
template<typename…Types>
unionVariantStorage;
template<typenameHead,typename…Tail>
unionVariantStorage<Head,Tail…>{
Headhead;
VariantStorage<Tail…>tail;
};
template<>
unionVariantStorage<>{
};
Here,theunionisguaranteedtohavesufficientsizeandalignmenttoallowany
oneofthetypesinTypestobestoredatanygiventime.Unfortunately,this
unionitselfisfairlyhardtoworkwith,becausemostofthetechniqueswewill
usetoimplementVariantwilluseinheritance,whichisnotpermittedfora
union.
Instead,weoptforalow-levelrepresentationofthevariantstorage:a
characterarraylargeenoughtoholdanyofthetypesandwithsuitablealignment
foranyofthetypes,whichweuseasabuffertostoretheactivevalue.The
VariantStorageclasstemplateimplementsthisbufferalongwitha
discriminator:Clickheretoviewcodeimage
variant/variantstorage.hpp
#include<new>//forstd::launder()
template<typename…Types>
classVariantStorage{
usingLargestT=LargestType<Typelist<Types…>>;
alignas(Types…)unsignedcharbuffer[sizeof(LargestT)];
unsignedchardiscriminator=0;
public:
unsignedchargetDiscriminator()const{returndiscriminator;}
voidsetDiscriminator(unsignedchard){discriminator=d;}void*
getRawBuffer(){returnbuffer;}
constvoid*getRawBuffer()const{returnbuffer;}
template<typenameT>
T*getBufferAs(){returnstd::launder(reinterpret_cast<T*>(buffer));
}
template<typenameT>
Tconst*getBufferAs()const{
returnstd::launder(reinterpret_cast<Tconst*>(buffer));
}
};
Here,weusetheLargestTypemetaprogramdevelopedinSection24.2.2on
page552tocomputethesizeofthebuffer,ensuringitislargeenoughforanyof
thevaluetypes.Similarly,thealignaspackexpansionensuresthatthebuffer
willhaveanalignmentsuitableforanyofthevaluetypes.3Thebufferwehave
computedisessentiallythemachinerepresentationoftheunionshownabove.
WecanaccessapointertothebufferusinggetBuffer()andmanipulatethe
storagethroughtheuseofexplicitcasts,placementnew(tocreatenewvalues),
andexplicitdestruction(todestroythevalueswecreated).Ifyouarenotfamiliar
withstd::launder()asusedingetBufferAs(),itsufficientfornowto
knowthatitreturnsitsargumentunmodified;wewillexplainitsrolewhenwe
talkaboutassignmentoperatorsforourVarianttemplate(seeSection26.4.3
onpage617).
26.2Design
Nowthatwehaveasolutiontothestorageproblemforvariants,wedesignthe
Varianttypeitself.AswiththeTupletype,weuseinheritancetoprovide
behaviorforeachtypeinthelistofTypes.UnlikewithTuple,however,these
baseclassesdonothavestorage.Rather,eachofthebaseclassesusesthe
CuriouslyRecurringTemplatePattern(CRTP)discussedinSection21.2onpage
495toaccessthesharedvariantstoragethroughthemost-derivedtype.
TheclasstemplateVariantChoice,definedbelow,providesthecore
operationsneededtooperateonthebufferwhenthevariant’sactivevalueis(or
willbe)oftypeT:Clickheretoviewcodeimage
variant/variantchoice.hpp
#include"findindexof.hpp"
template<typenameT,typename…Types>
classVariantChoice{
usingDerived=Variant<Types…>;
Derived&getDerived(){return*static_cast<Derived*>(this);}
Derivedconst&getDerived()const{
return*static_cast<Derivedconst*>(this);
}
protected:
//computethediscriminatortobeusedforthistype
constexprstaticunsignedDiscriminator=
FindIndexOfT<Typelist<Types…>,T>::value+1;
public:
VariantChoice(){}
VariantChoice(Tconst&value);//seevariantchoiceinit.hpp
VariantChoice(T&&value);//seevariantchoiceinit.hpp
booldestroy();//seevariantchoicedestroy.hpp
Derived&operator=(Tconst&value);//seevariantchoiceassign.hpp
Derived&operator=(T&&value);//seevariantchoiceassign.hpp
};
ThetemplateparameterpackTypeswillcontainallofthetypesinthe
Variant.ItallowsustoformtheDerivedtype(forCRTP)andthereforeto
providethedowncastoperationgetDerived().Thesecondinterestinguseof
TypesistofindthelocationoftheparticulartypeTinthelistofTypes,which
weaccomplishwiththemetafunctionFindIndexOfT:Clickheretoviewcode
image
variant/findindexof.hpp
template<typenameList,typenameT,unsignedN=0,
boolEmpty=IsEmpty<List>::value>
structFindIndexOfT;
//recursivecase:
template<typenameList,typenameT,unsignedN>
structFindIndexOfT<List,T,N,false>
:publicIfThenElse<std::is_same<Front<List>,T>::value,
std::integral_constant<unsigned,N>,
FindIndexOfT<PopFront<List>,T,N+1>>
{
};
//basiscase:
template<typenameList,typenameT,unsignedN>
structFindIndexOfT<List,T,N,true>
{
};
ThisindexvalueisusedtocomputethediscriminatorvaluecorrespondingtoT;
wewillreturntothespecificdiscriminatorvalueslater.
TheskeletonofVariantfollows,illustratingtherelationshipamong
Variant,VariantStorage,andVariantChoice:Clickheretoview
codeimage
variant/variant-skel.hpp
template<typename…Types>
classVariant
:privateVariantStorage<Types…>,
privateVariantChoice<Types,Types…>…
{
template<typenameT,typename…OtherTypes>
friendclassVariantChoice;//enableCRTP
…
};
Aspreviouslynoted,eachVarianthasasingle,sharedVariantStorage
baseclass.4Additionally,ithassomenumberofVariantChoicebase
classes,whichareproducedfromthefollowingnestedpackexpansion(see
Section12.4.4onpage205):Clickheretoviewcodeimage
VariantChoice<Types,Types…>…
Inthisinstancewehavetwoexpansions:Theouterexpansionproducesa
VariantChoicebaseclassforeachtypeTinTypesbyexpandingthefirst
referencetoTypes.Theinnerexpansion,whichexpandsthesecondoccurrence
ofTypes,additionallypassesallofthetypesinTypesalongtoeach
VariantChoicebaseclass.ForaClickheretoviewcodeimage
Variant<int,double,std::string>thisproducesthefollowingsetof
VariantChoicebaseclasses:5
Clickheretoviewcodeimage
VariantChoice<int,int,double,std::string>,
VariantChoice<double,int,double,std::string>,
VariantChoice<std::string,int,double,std::string>The
discriminatorvaluesforthesethreebaseclasseswillbe1,2,and
3,respectively.Whenthediscriminatormemberofthevariant’s
storagematchesthediscriminatorofaparticularVariantChoicebase
class,thatbaseclassisresponsibleformanagingtheactivevalue.
Thediscriminatorvalue0isreservedforcaseswherethevariantcontainsno
value,whichisanoddstatethatcanonlybeobservedwhenanexceptionis
thrownduringassignment.ThroughoutthediscussionofVariant,wewillbe
carefultocopewithadiscriminatorvalueof0(andsetitwhenappropriate),but
weleavethediscussionofthiscasetoSection26.4.3onpage613.
ThecompletedefinitionofVariantislistedonthefollowingpage.The
followingsectionswilldescribetheimplementationofeachofthemembersof
Variant.
Clickheretoviewcodeimage
Clickheretoviewcodeimage
variant/variant.hpp
template<typename…Types>
classVariant
:privateVariantStorage<Types…>,
privateVariantChoice<Types,Types…>…
{
template<typenameT,typename…OtherTypes>
friendclassVariantChoice;
public:
template<typenameT>boolis()const;//seevariantis.hpp
template<typenameT>T&get()&;//seevariantget.hpp
template<typenameT>Tconst&get()const&;//seevariantget.hpp
template<typenameT>T&&get()&&;//seevariantget.hpp
//seevariantvisit.hpp:
template<typenameR=ComputedResultType,typenameVisitor>
VisitResult<R,Visitor,Types&…>visit(Visitor&&vis)&;
template<typenameR=ComputedResultType,typenameVisitor>
VisitResult<R,Visitor,Typesconst&…>visit(Visitor&&vis)const&;
template<typenameR=ComputedResultType,typenameVisitor>
VisitResult<R,Visitor,Types&&…>visit(Visitor&&vis)&&;
usingVariantChoice<Types,Types…>::VariantChoice…;
Variant();//seevariantdefaultctor.hpp
Variant(Variantconst&source);//seevariantcopyctor.hpp
Variant(Variant&&source);//seevariantmovector.hpp
template<typename…SourceTypes>
Variant(Variant<SourceTypes…>const&source);//
variantcopyctortmpl.hpp
template<typename…SourceTypes>
Variant(Variant<SourceTypes…>&&source);
usingVariantChoice<Types,Types…>::operator=…;
Variant&operator=(Variantconst&source);//see
variantcopyassign.hpp
Variant&operator=(Variant&&source);
template<typename…SourceTypes>
Variant&operator=(Variant<SourceTypes…>const&source);
template<typename…SourceTypes>
Variant&operator=(Variant<SourceTypes…>&&source);
boolempty()const;
~Variant(){destroy();}
voiddestroy();//seevariantdestroy.hpp
};
26.3ValueQueryandExtraction
ThemostbasicqueriesforaVarianttypearetoaskitwhetheritsactivevalue
isofaparticulartypeTandtoaccesstheactivevaluewhenitstypeisknown.
Theis()memberfunction,definedbelow,determineswhetherthevariant
currentlystoresavalueoftypeT:Clickheretoviewcodeimage
variant/variantis.hpp
template<typename…Types>
template<typenameT>
boolVariant<Types…>::is()const
{
returnthis->getDiscriminator()==
VariantChoice<T,Types…>::Discriminator;
}
Givenavariantv,v.is<int>()willdeterminewhetherv’sactivevalueisof
typeint.Thecheckisstraightforward,comparingthediscriminatorinthe
variant’sstorageagainsttheDiscriminatorvalueofthecorresponding
VariantChoicebaseclass.
Ifthetypewe’relookingfor(T)isnotfoundinthelist,the
VariantChoicebaseclasswillfailtoinstantiatebecauseFindIndexOfT
willnotcontainavaluemember,causingan(intentional)compilationfailure
inis<T>().Thispreventsusererrorswheretheuserisaskingforatypethat
cannotpossiblybestoredinthevariant.
Theget()memberfunctionextractsareferencetothestoredvalue.Itmust
beprovidedwiththetypetoextract(e.g.,v.get<int>()),andisonlyvalid
whenthevariant’sactivevalueisofthattype:Clickheretoviewcodeimage
variant/variantget.hpp
#include<exception>
classEmptyVariant:publicstd::exception{
};
template<typename…Types>
template<typenameT>
T&Variant<Types…>::get()&{
if(empty()){
throwEmptyVariant();
}
assert(is<T>());
return*this->templategetBufferAs<T>();
}
Whenthevariantdoesnotstoreavalue(itsdiscriminatoris0),get()throws
anEmptyVariantexception.Theconditionsunderwhichthediscriminator
canbe0arethemselvesduetoexceptions,andaredescribedinSection26.4.3
onpage613.Otherattemptstogetavaluefromthevariantwiththewrongtype
areprogrammererrorsdetectedbyafailedassertion.
26.4ElementInitialization,Assignmentand
Destruction
EachVariantChoicebaseclassisresponsibleforhandlingtheinitialization,
assignment,anddestructionwhentheactivevaluehastypeT.Thissection
developsthesecoreoperationsbyfillinginthedetailsoftheVariantChoice
classtemplate.
26.4.1Initialization
Webeginwithinitializationofavariantfromavalueofoneofthetypesit
stores.Forexample,initializingaVariant<int,double,string>
fromadoublevalue.ThisisaccomplishedwithVariantChoice
constructorsthatacceptavalueoftypeT:Clickheretoviewcodeimage
variant/variantchoiceinit.hpp
#include<utility>//forstd::move()
template<typenameT,typename…Types>
VariantChoice<T,Types…>::VariantChoice(Tconst&value){
//placevalueinbufferandsettypediscriminator:
new(getDerived().getRawBuffer())T(value);
getDerived().setDiscriminator(Discriminator);
}
template<typenameT,typename…Types>
VariantChoice<T,Types…>::VariantChoice(T&&value){
//placemovedvalueinbufferandsettypediscriminator:
new(getDerived().getRawBuffer())T(std::move(value));
getDerived().setDiscriminator(Discriminator);
}
Ineachcase,theconstructorusestheCRTPoperationgetDerived()to
accessthesharedbuffer,thenperformsaplacementnewtoinitializethestorage
withanewvalueoftypeT.Thefirstconstructorcopy-constructstheincoming
value,whilethesecondconstructormove-constructstheincomingvalue.6
Afterward,theconstructorssetthediscriminatorvaluetoindicatethe(dynamic)
typeofthevariant’sstorage.
Oureventualgoalistobeabletoinitializeavariantfromavalueofanyofits
types,evenaccountingforimplicitconversions.Forexample:Clickheretoview
codeimage
Variant<int,double,string>v("hello");//implicitlyconvertedto
string
Toaccomplishthis,weinherittheVariantChoiceconstructorsinto
Variantitselfbyintroducingtheusingdeclaration7
Clickheretoviewcodeimage
usingVariantChoice<Types,Types…>::VariantChoice…;Ineffect,this
usingdeclarationproducesVariantconstructorsthatcopyormove
fromeachtypeTinTypes.ForaVariant<int,double,string>,the
constructorsare,effectively:Variant(intconst&);
Variant(int&&);
Variant(doubleconst&);
Variant(double&&);
Variant(stringconst&);
Variant(string&&);
26.4.2Destruction
WhenVariantisinitialized,avalueisconstructedintoitsbuffer.The
destroyoperationhandlesthedestructionofthatvalue:Clickheretoview
codeimage
variant/variantchoicedestroy.hpp
template<typenameT,typename…Types>
boolVariantChoice<T,Types…>::destroy(){
if(getDerived().getDiscriminator()==Discriminator){
//iftypematches,callplacementdelete:
getDerived().templategetBufferAs<T>()->~T();
returntrue;
}
returnfalse;
}
Whenthediscriminatormatches,weexplicitlydestroythecontentsofthebuffer
bycallingtheappropriatedestructorusing->~T().
TheVariantChoice::destroy()operationisonlyusefulwhenthe
discriminatormatches.However,wegenerallywanttodestroythevaluestored
inthevariantwithoutregardtowhichtypeiscurrentlyactive.Therefore,
Variant::destroy()callsalloftheVariantChoice::destroy()
operationsinitsbaseclasses:Clickheretoviewcodeimage
variant/variantdestroy.hpp
template<typename…Types>
voidVariant<Types…>::destroy(){
//calldestroy()oneachVariantChoicebaseclass;atmostonewill
succeed:
boolresults[]={
VariantChoice<Types,Types…>::destroy()…
};
//indicatethatthevariantdoesnotstoreavalue
this->setDiscriminator(0);
}
Thepackexpansionintheinitializerofresultsensuresthatdestroyis
calledoneachoftheVariantChoicebaseclasses.Atmostoneofthesecalls
willactuallysucceed(theonewiththematchingdiscriminator),leavingthe
variantempty.Theemptystateisindicatedbysettingthediscriminatorvalueto
0.
Thearrayresultsitselfisthereonlytoprovideacontexttousean
initializerlist;itsactualvaluesareignored.InC++17,wecanuseafold
expression(discussedinSection12.4.6onpage207)toeliminatetheneedfor
thisextraneousvariable:Clickheretoviewcodeimage
variant/variantdestroy17.hpp
template<typename…Types>
voidVariant<Types…>::destroy()
{
//calldestroy()oneachVariantChoicebaseclass;atmostonewill
succeed:
(VariantChoice<Types,Types…>::destroy(),…);
//indicatethatthevariantdoesnotstoreavalue
this->setDiscriminator(0);
}
26.4.3Assignment
Assignmentbuildsoninitializationanddestruction,asillustratedbythe
assignmentoperators:Clickheretoviewcodeimage
variant/variantchoiceassign.hpp
template<typenameT,typename…Types>
autoVariantChoice<T,Types…>::operator=(Tconst&value)->Derived&
{
if(getDerived().getDiscriminator()==Discriminator){
//assignnewvalueofsametype:
*getDerived().templategetBufferAs<T>()=value;
}
else{
//assignnewvalueofdifferenttype:
getDerived().destroy();//trydestroy()foralltypes
new(getDerived().getRawBuffer())T(value);//placenewvalue
getDerived().setDiscriminator(Discriminator);
}
returngetDerived();
}
template<typenameT,typename…Types>
autoVariantChoice<T,Types…>::operator=(T&&value)->Derived&{
if(getDerived().getDiscriminator()==Discriminator){
//assignnewvalueofsametype:
*getDerived().templategetBufferAs<T>()=std::move(value);
}
else{
//assignnewvalueofdifferenttype:
getDerived().destroy();//trydestroy()foralltypes
new(getDerived().getRawBuffer())T(std::move(value));//placenew
value
getDerived().setDiscriminator(Discriminator);
}
returngetDerived();
}
Aswithinitializationfromoneofthestoredvaluetypes,each
VariantChoiceprovidesanassignmentoperatorthatcopies(ormoves)from
itsstoredvaluetypeintothevariant’sstorage.Theseassignmentoperatorsare
inheritedbyVariantviathefollowingusingdeclaration;Clickheretoview
codeimage
usingVariantChoice<Types,Types…>::operator=…;Theimplementationof
theassignmentoperatorhastwopaths.Ifthevariantalreadystores
avalueofthegiventypeT(identifiedbyadiscriminatormatch),
thentheassignmentoperatorwillcopy-assignormove-assignthe
valueoftypeTdirectlyintothebuffer,asappropriate.The
discriminatorisunchanged.
IfthevariantdoesnotstoreavalueoftypeT,assignmentrequiresatwo-step
process:DestroythecurrentvalueusingVariant::destroy(),then
initializeanewvalueoftypeTusingplacementnew,settingthediscriminator
appropriately.
Therearethreecommonproblemswithsuchatwo-stepassignmentusing
placementnew,whichwehavetotakeintoaccount:•Self-assignment
•Exceptions
•std::launder()
Self-Assignment
Self-assignmentcanoccurforavariantvduetoanexpressionlikethe
following:v=v.get<T>()
Withthetwo-stepprocessimplementedabove,thesourcevaluewouldbe
destroyedbeforeitcouldbecopied,potentiallyleadingtomemorycorruption.
Fortunately,self-assignmentalwaysimpliesthatthediscriminatormatches,so
suchcodewillinvoketheassignmentoperatorforTratherthanthistwo-step
process.
Exceptions
Ifthedestructionoftheexistingvaluecompletesbuttheinitializationofthenew
valuethrowsanexception,whatisthestateofthevariant?Inour
implementation,Variant::destroy()resetsthediscriminatorvalueto0.
Innonexceptionalcases,thediscriminatorwillbesetappropriatelyafter
initializationcompletes.Whenanexceptionoccursduringinitializationofthe
newvalue,thediscriminatorremains0toindicatethatthevariantdoesnotstore
avalue.Inourdesign,thisistheonlywaytoproduceavariantwithoutavalue.
Thefollowingprogramillustrateshowtotriggeravariantwithnostorageby
attemptingtocopyavalueofatypewhosecopyconstructorthrows:Clickhere
toviewcodeimage
Clickheretoviewcodeimage
variant/variantexception.cpp
#include"variant.hpp"
#include<exception>
#include<iostream>
#include<string>
classCopiedNonCopyable:publicstd::exception
{
};
classNonCopyable
{
public:
NonCopyable(){
}
NonCopyable(NonCopyableconst&){
throwCopiedNonCopyable();
}
NonCopyable(NonCopyable&&)=default;
NonCopyable&operator=(NonCopyableconst&){
throwCopiedNonCopyable();
}
NonCopyable&operator=(NonCopyable&&)=default;
};
intmain()
{
Variant<int,NonCopyable>v(17);
try{
NonCopyablenc;
v=nc;
}
catch(CopiedNonCopyable){
std::cout<<"CopyassignmentofNonCopyablefailed."<<’\n’;
if(!v.is<int>()&&!v.is<NonCopyable>()){
std::cout<<"Varianthasnovalue."<<’\n’;
}
}
}
Theoutputofthisprogramis:
CopyassignmentofNonCopyablefailed.
Varianthasnovalue.
Accessestoavariantthathasnovalue,whethertheyarethroughget()or
throughthevisitormechanismdescribedinthefollowingsection,throwthe
EmptyVariantexceptiontoallowprogramstorecoverfromthisexceptional
condition.Theempty()memberfunctioncheckswhetherthevariantisinthis
emptystate:Clickheretoviewcodeimage
variant/variantempty.hpp
template<typename…Types>
boolVariant<Types…>::empty()const{
returnthis->getDiscriminator()==0;
}
Thethirdproblemwithourtwo-stepassignmentisasubtleonethattheC++
standardizationcommitteehasonlybecomeawareofattheendoftheC++17
standardizationprocess.Webrieflyexplainitnext.
std::launder()
C++compilersgenerallyaimatproducinghigh-performancecode,andperhaps
theprimarymechanismtoimprovetheperformanceofgeneratedcodeisto
avoidrepeatedlycopyingdatafrommemorytoregisters.Todothiswell,a
compilerhastomakesomeassumptionsandoneofthoseassumptionsisthat
certainkindsofdataareimmutableduringtheirlifetime.Thatincludesconst
data,references(whichcanbeinitialized,butnotthereaftermodified),andsome
bookkeepingdatastoredinpolymorphicobjectsthatisusedtodispatchvirtual
functions,locatevirtualbasesclasses,andhandletypeidand
dynamic_castoperators.
Theproblemwithourtwo-stepassignmentprocedureaboveisthatitsneakily
endsthelifetimeofoneobjectandstartsthelifetimeofanotherinthesame
placeinawaythatthecompilermaynotbeabletorecognize.Consequently,a
compilermightassumethatavalueitacquiredfromthepreviousstateofa
Variantobjectisstillvalid,when,infact,aninitializationwithplacement
newinvalidatedit.Withoutmitigation,thenetresultwouldbethataprogram
usingVariantoftypeswithimmutabledatamembersmayoccasionally
produceinvalidresultswhencompiledforgoodperformance.Suchbugsare
usuallyveryhardtotrackdown(inpartbecausetheyoccurrarelyandinpart
becausetheyarenotreallyvisibleinthesourcecode).
SinceC++17,thesolutionforthisissueistoaccesstheaddressofthenew
objectthroughstd::launder(),whichjustreturnsitsargument,butwhich
causesthecompilertorecognizethattheresultingaddresspointstoanobject
thatmaydifferfromwhatthecompilerassumesabouttheargumentpassedto
std::launder().However,notethatstd::launder()onlyfixesthe
addressitreturns,nottheargumentpassedtostd::launder(),becausethe
compilerreasonsintermsofexpressions,notactualaddresses(sincetheydonot
existuntilruntime).Therefore,afterconstructinganewvaluewithplacement
new,wehavetoensurethateachfollowingaccessusesthe“laundered”data.
Thatiswhywealways“launder”thepointertoourVariantbuffer.Thereare
waystodoalittlebetter(suchasaddinganadditionalpointermemberthatrefers
tothebufferandgetsthe“laundered”addressaftereachassignmentofanew
valuewithplacementnew),buttheycomplicatethecodeinwaysthatarehardto
maintain.Ourapproachissimpleandcorrect,aslongasweaccessthebuffer
exclusivelythroughthegetBufferAs()members.
Thesituationwithstd::launder()isnotwhollysatisfying:Itisvery
subtle,hardtoperceive(e.g.,wedidn’tnoticeituntiljustbeforethebookwent
topress),andhardtoalleviate(i.e.,std::launder()isnotveryeasytouse).
Severalmembersofthecommitteehavethereforeaskedthatmoreworkbedone
tofindamoresatisfactorysolution.See[JosuttisLaunder]foramoredetailed
descriptionoftheissue.
26.5Visitors
Theis()andget()memberfunctionsallowustocheckwhethertheactive
valueisofaspecifictypeandaccessavaluewiththattype.However,inspecting
allofthepossibletypeswithinavariantquicklydevolvesintoaredundantchain
ofifstatements.Forexample,thefollowingprintsthevalueofa
Variant<int,double,string>namedv:Clickheretoviewcode
image
if(v.is<int>()){
std::cout<<v.get<int>();
}
elseif(v.is<double>()){
std::cout<<v.get<double>();
}
else{
std::cout<<v.get<string>();
}
Togeneralizethistoprintthevaluestoredinanarbitraryvariantrequiresa
recursivelyinstantiatedfunctiontemplatealongwithahelper.Forexample:
Clickheretoviewcodeimage
variant/printrec.cpp
#include"variant.hpp"
#include<iostream>
template<typenameV,typenameHead,typename…Tail>
voidprintImpl(Vconst&v)
{
if(v.templateis<Head>()){
std::cout<<v.templateget<Head>();
}
elseifconstexpr(sizeof…(Tail)>0){
printImpl<V,Tail…>(v);
}
}
template<typename…Types>
voidprint(Variant<Types…>const&v)
{
printImpl<Variant<Types…>,Types…>(v);
}
intmain(){
Variant<int,short,float,double>v(1.5);
print(v);
}
Thisisasignificantamountofcodeforarelativelysimpleoperation.To
simplifythis,weturntheproblemaroundbyextendingVariantwitha
visit()operation.Theclientthenpassesinavisitorfunctionobjectwhose
operator()willbeinvokedwiththeactivevalue.Becausetheactivevalue
couldbeanyoneofthevariant’spotentialtypes,thisoperator()islikely
eithertobeoverloadedoritselfafunctiontemplate.Forexample,ageneric
lambdaprovidesatemplatedoperator(),allowingustoconciselyrepresent
theprintoperationforavariantv:Clickheretoviewcodeimage
v.visit([](autoconst&value){
std::cout<<value;
});
Thisgenericlambdaisroughlyequivalenttothefollowingfunctionobject,
whichcanalsobeusefulforcompilersthatdonotyetsupportgenericlambdas:
Clickheretoviewcodeimage
classVariantPrinter{
public:
template<typenameT>
voidoperator()(Tconst&value)const
{
std::cout<<value;
}
};
Thecoreofthevisit()operationissimilartotherecursiveprintoperation:
ItstepsthroughthetypesoftheVariant,checkingwhethertheactivevalue
hasthegiventype(withis<T>()),andthenactswhenithasfoundthe
appropriatetype:Clickheretoviewcodeimage
variant/variantvisitimpl.hpp
template<typenameR,typenameV,typenameVisitor,
typenameHead,typename…Tail>
RvariantVisitImpl(V&&variant,Visitor&&vis,Typelist<Head,Tail…>)
{
if(variant.templateis<Head>()){
returnstatic_cast<R>(
std::forward<Visitor>(vis)(
std::forward<V>(variant).templateget<Head>()));
}
elseifconstexpr(sizeof…(Tail)>0){
returnvariantVisitImpl<R>(std::forward<V>(variant),
std::forward<Visitor>(vis),
Typelist<Tail…>());
}
else{
throwEmptyVariant();
}
}
variantVisitImpl()isanonmemberfunctiontemplatewithanumberof
templateparameters.ThetemplateparameterRdescribestheresulttypeofthe
visitationoperation,whichwewillreturntolater.Visthetypeofthevariantand
Visitoristhetypeofthevisitor.HeadandTailareusedtodecomposethe
typesintheVarianttoeffectrecursion.
Thefirstifperformsa(run-time)checktodeterminewhethertheactive
valueofthegivenvariantisoftypeHead:Ifso,thevalueisextractedfromthe
variantviaget<Head>()andpassedalongtothevisitor,terminatingthe
recursion.Thesecondifperformsrecursionwhentherearemoreelementsto
consider.Ifnoneofthetypeshavematched,thevariantdoesnotcontaina
value,8inwhichcasetheimplementationthrowstheEmptyVariant
exception.
AsidefromtheresulttypecomputationprovidedbyVisitResult(which
willbediscussedinthenextsection),thevisit()implementationis
straightforward:Clickheretoviewcodeimage
Clickheretoviewcodeimage
variant/variantvisit.hpp
template<typename…Types>
template<typenameR,typenameVisitor>
VisitResult<R,Visitor,Types&…>
Variant<Types…>::visit(Visitor&&vis)&{
usingResult=VisitResult<R,Visitor,Types&…>;
returnvariantVisitImpl<Result>(*this,std::forward<Visitor>(vis),
Typelist<Types…>());
}
template<typename…Types>
template<typenameR,typenameVisitor>
VisitResult<R,Visitor,Typesconst&…>
Variant<Types…>::visit(Visitor&&vis)const&{
usingResult=VisitResult<R,Visitor,Typesconst&…>;
returnvariantVisitImpl<Result>(*this,std::forward<Visitor>(vis),
Typelist<Types…>());
}
template<typename…Types>
template<typenameR,typenameVisitor>
VisitResult<R,Visitor,Types&&…>
Variant<Types…>::visit(Visitor&&vis)&&{
usingResult=VisitResult<R,Visitor,Types&&…>;
returnvariantVisitImpl<Result>(std::move(*this),
std::forward<Visitor>(vis),
Typelist<Types…>());
}
TheimplementationsdelegatetovariantVisitImpldirectly,passingalong
thevariantitself,forwardingthevisitor,andsupplyingthecompletelistoftypes.
Theonlydifferencesbetweenthethreeimplementationsarewhethertheypass
thevariantitselfasVariant&,Variantconst&,orVariant&&.
26.5.1VisitResultType
Theresulttypeofvisit()remainsamystery.Agivenvisitormighthave
differentoperator()overloadsthatproducedifferentresulttypes,a
templatedoperator()whoseresulttypeisdependentonitsparametertype,
orsomecombinationthereof.Forexample,considerthefollowinggeneric
lambda:[](autoconst&value){
returnvalue+1;
}
Theresulttypeofthislambdadependsontheinputtype:givenanint,itwill
produceaint,butgivenadouble,itwillproduceadouble.Ifthisgeneric
lambdawerepassedtothevisit()operationofaVariant<int,
double>,whatshouldtheresultbe?
Thereisnosinglecorrectanswer,soourvisit()operationallowstheresult
typetobeexplicitlyprovided.Forexample,onemightwanttocapturethe
resultsinanotherVariant<int,double>.Onecanexplicitlyspecifythe
resulttypetovisit()asthefirsttemplateargument:Clickheretoviewcode
image
v.visit<Variant<int,double>>([](autoconst&value){returnvalue+
1;});
Theabilitytoexplicitlyspecifytheresulttypeisimportantwhenthereisnoone-
size-fits-allsolution.However,requiringthattheresulttypebeexplicitly
specifiedinallcasescanbeverbose.Therefore,visit()providesbothoptions
usingthecombinationofadefaulttemplateargumentandasimple
metaprogram.Recallthedeclarationofvisit():Clickheretoviewcode
image
template<typenameR=ComputedResultType,typenameVisitor>
VisitResult<R,Visitor,Types&…>visit(Visitor&&vis)&;
ThetemplateparameterR,whichweexplicitlyspecifiedintheexampleabove,
alsohasadefaultargumentsothatitneednotalwaysbeexplicitlyspecified.
ThatdefaultargumentisanincompletesentineltypeComputedResultType:
classComputedResultType;Tocomputeitsresulttype,visitpassesallofits
templateparametersalongtoVisitResult,analiastemplatethatprovides
accesstoanewtypetraitVisitResultT:Clickheretoviewcodeimage
variant/variantvisitresult.hpp
//anexplicitly-providedvisitorresulttype:
template<typenameR,typenameVisitor,typename…ElementTypes>
classVisitResultT
{
public:
usingType=R;
};
template<typenameR,typenameVisitor,typename…ElementTypes>
usingVisitResult=
typenameVisitResultT<R,Visitor,ElementTypes…>::Type;Theprimary
definitionofVisitResultThandlescaseswheretheargumentforRhas
beenexplicitlyspecified,soTypeisdefinedtoR.Aseparatepartial
specializationapplieswhenRreceivesitsdefaultargument,
ComputedResultType:Clickheretoviewcodeimage
template<typenameVisitor,typename…ElementTypes>
classVisitResultT<ComputedResultType,Visitor,ElementTypes…>
{
…
}
Thispartialspecializationisresponsibleforcomputinganappropriateresulttype
forthecommoncase,andisthesubjectofthenextsection.
26.5.2CommonResultType
Whencallingavisitorthatmayproducedifferenttypesforeachofthevariant’s
elementtypes,howcanwecombinethosetypesintoasingleresulttypefor
visit()?Therearesomeobviouscases—ifthevisitorreturnsthesametype
foreachelementtype,thatshouldbetheresulttypeofvisit().
C++alreadyhasanotionofareasonableresulttype,whichwasintroducedin
Section1.3.3onpage12:Intheternaryexpressionb?x:y,thetypeofthe
expressionisthecommontypebetweenthetypesofxandy.Forexample,ifx
hastypeintandyhastypedouble,thecommontypeisdoublebecause
intpromotestodouble.Wecancapturethisnotionofthecommontypeina
typetrait:Clickheretoviewcodeimage
variant/commontype.hpp
usingstd::declval;
template<typenameT,typenameU>
classCommonTypeT
{
public:
usingType=decltype(true?declval<T>():declval<U>());
};
template<typenameT,typenameU>
usingCommonType=typenameCommonTypeT<T,U>::Type;Thenotionofa
commontypeextendstoasetoftypes:Thecommontypeisatypeto
whichallofthetypesinthesetcanpromote.Forourvisitor,we
wanttocomputethecommontypeoftheresulttypesthatthevisitor
willproducewhencalledwitheachofthetypesinthevariant:Click
heretoviewcodeimage
variant/variantvisitresultcommon.hpp
#include"accumulate.hpp"
#include"commontype.hpp"
//theresulttypeproducedwhencallingavisitorwithavalueof
typeT:
template<typenameVisitor,typenameT>
usingVisitElementResult=decltype(declval<Visitor>()(declval<T>()));
//thecommonresulttypeforavisitorcalledwitheachofthegiven
elementtypes:
template<typenameVisitor,typename…ElementTypes>
classVisitResultT<ComputedResultType,Visitor,ElementTypes…>
{
usingResultTypes=
Typelist<VisitElementResult<Visitor,ElementTypes>…>;
public:
usingType=
Accumulate<PopFront<ResultTypes>,CommonTypeT,Front<ResultTypes>>;
};
TheVisitResultcomputationoccursintwostages.First,
VisitElementResultcomputestheresulttypeproducedwhencallingthe
visitorwithavalueoftypeT.Thismetafunctionisappliedtoeachofthegiven
elementtypestodeterminealloftheresulttypesthatthevisitorcouldproduce,
capturingtheresultinthetypelistResultTypes.
Next,thecomputationusestheAccumulatealgorithmdescribedinSection
24.2.6onpage560toapplythecommon-typecomputationtothetypelistof
resulttypes.Itsinitialvalue(thethirdargumenttoAccumulate)isthefirst
resulttype,whichiscombinedviaCommonTypeTwithsuccessivevaluesfrom
theremainderoftheResultTypestypelist.Theendresultisthecommontype
towhichallofthevisitor’sresulttypescanbeconverted,oranerroriftheresult
typesareincompatible.
SinceC++11,thestandardlibraryprovidesacorrespondingtypetrait,
std::common_type<>,whichusesthisapproachtoyieldthecommontype
ofanarbitrarynumberofpassedtypes(seeSectionD.5onpage732),effectively
combiningCommonTypeTandAccumulate.Byusing
std::common_type<>,theimplementationofVisitResultTissimpler:
Clickheretoviewcodeimage
variant/variantvisitresultstd.hpp
template<typenameVisitor,typename…ElementTypes>
classVisitResultT<ComputedResultType,Visitor,ElementTypes…>
{
public:
usingType=
std::common_type_t<VisitElementResult<Visitor,ElementTypes>…>;
};
Thefollowingexampleprogramprintsoutthetypeproducedbypassingina
genericlambdathatadds1tothevalueitgets:Clickheretoviewcodeimage
variant/visit.cpp
#include"variant.hpp"
#include<iostream>
#include<typeinfo>
intmain()
{
Variant<int,short,double,float>v(1.5);
autoresult=v.visit([](autoconst&value){
returnvalue+1;
});
std::cout<<typeid(result).name()<<’\n’;
}
Theoutputofthisprogramwillbethetype_infonamefordouble,because
thatisthetypetowhichalloftheresulttypescanbeconverted.
26.6VariantInitializationandAssignment
Variantscanbeinitializedandassignedinavarietyofways,includingdefault
construction,copy-andmove-construction,andcopy-andmove-assignment.
ThissectiondetailstheseVariantoperations.
DefaultInitialization
Shouldvariantsprovideadefaultconstructor?Ifitdoesnot,variantsmaybe
unnecessarilyhardtousebecauseonewillalwayshavetoconjureaninitial
value(evenwhenonedoesnotmakesenseprogrammatically).Ifitdoesprovide
adefaultconstructor,whatshouldthesemanticsbe?
Onepossiblesemanticswouldbefordefaultinitializationtohavenostored
value,representedbythediscriminator0.However,suchemptyvariantsaren’t
generallyuseful(e.g.,onecannotvisitthemorfindanyvaluetoextract),and
makingthisthedefaultinitializationbehaviorwouldpromotetheexceptional
stateofanemptyvariant(describedinSection26.4.3onpage613)toacommon
one.
Alternatively,thedefaultconstructorcouldconstructavalueofsometype.For
ourvariant,wefollowthesemanticsofC++17’sstd::variant<>and
default-constructavalueofthefirsttypeinthelistoftypes:Clickheretoview
codeimage
variant/variantdefaultctor.hpp
template<typename…Types>
Variant<Types…>::Variant(){
*this=Front<Typelist<Types…>>();
}
Thisapproachissimpleandpredictableandavoidstheintroductionofempty
variantsinmostuses.Thebehaviorcanbeseeninthisprogram:Clickhereto
viewcodeimage
variant/variantdefaultctor.cpp
#include"variant.hpp"
#include<iostream>
intmain()
{
Variant<int,double>v;
if(v.is<int>()){
std::cout<<"Default-constructedvstorestheint"
<<v.get<int>()<<’\n’;
}
Variant<double,int>v2;
if(v2.is<double>()){
std::cout<<"Default-constructedv2storesthedouble"
<<v2.get<double>()<<’\n’;
}
}
whichproducesthefollowingoutput:Clickheretoviewcodeimage
Default-constructedvstorestheint0
Default-constructedv2storesthedouble0
Copy/MoveInitialization
Copyandmoveinitializationaremoreinteresting.Tocopyasourcevariant,we
needtodeterminewhichtypeitiscurrentlystoring,copy-constructthatvalue
intothebuffer,andsetthatdiscriminator.Fortunately,visit()handles
decodingtheactivevalueofthesourcevariant,andthecopy-assignment
operatorinheritedfromVariantChoicewillcopy-constructavalueintothe
buffer,leadingtoacompactimplementation:9
Clickheretoviewcodeimage
variant/variantcopyctor.hpp
template<typename…Types>
Variant<Types…>::Variant(Variantconst&source){
if(!source.empty()){
source.visit([&](autoconst&value){
*this=value;
});
}
}
Themoveconstructorissimilar,differingonlyinitsuseofstd::movewhen
visitingthesourcevariantandmove-assigningfromthesourcevalue:Clickhere
toviewcodeimage
variant/variantmovector.hpp
template<typename…Types>
Variant<Types…>::Variant(Variant&&source){
if(!source.empty()){
std::move(source).visit([&](auto&&value){
*this=std::move(value);
});
}
}
Oneparticularlyinterestingaspectofthevisitor-basedimplementationisthatit
alsoworksforthetemplatedformsofthecopyandmoveoperations.For
example,thetemplatedcopyconstructorcanbedefinedasfollows:Clickhereto
viewcodeimage
variant/variantcopyctortmpl.hpp
template<typename…Types>
template<typename…SourceTypes>
Variant<Types…>::Variant(Variant<SourceTypes…>const&source){
if(!source.empty()){
source.visit([&](autoconst&value){
*this=value;
});
}
}
Becausethiscodevisitsthesource,theassignmentto*thiswilloccurforeach
ofthetypesofthesourcevariant.Overloadresolutionforthisassignmentwill
findthemostappropriatedestinationtypeforeachsourcetype,performing
implicitconversionsasnecessary.Thefollowingexampleillustratesconstruction
andassignmentfromdifferentvarianttypes:Clickheretoviewcodeimage
variant/variantpromote.cpp
#include"variant.hpp"
#include<iostream>
#include<string>
intmain()
{
Variant<short,float,charconst*>v1((short)123);
Variant<int,std::string,double>v2(v1);
std::cout<<"v2containstheinteger"<<v2.get<int>()<<’\n’;
v1=3.14f;
Variant<double,int,std::string>v3(std::move(v1));
std::cout<<"v3containsthedouble"<<v3.get<double>()<<’\n’;
v1="hello";
Variant<double,int,std::string>v4(std::move(v1));
std::cout<<"v4containsthestring"<<v4.get<std::string>()<<
’\n’;
}
Constructingorassigningfromv1toeitherv2orv3involvesintegral
promotions(shorttoint),floating-pointpromotions(floattodouble),
anduser-definedconversions(charconst*tostd::string).Theoutput
ofthisprogramisasfollows:v2containstheinteger123
v3containsthedouble3.14
v4containsthestringhello
Assignment
TheVariantassignmentoperatorsaresimilartothecopyandmove
constructorsabove.Here,weillustrateonlythecopyassignmentoperator:Click
heretoviewcodeimage
variant/variantcopyassign.hpp
template<typename…Types>
Variant<Types…>&Variant<Types…>::operator=(Variantconst&source){
if(!source.empty()){
source.visit([&](autoconst&value){
*this=value;
});
}
else{
destroy();
}
return*this;
}
Theonlyinterestingadditionisintheelsebranch:Whenthesourcevariant
containsnovalue(indicatedbyadiscriminator0),wedestroythevalueofthe
destination,implicitlysettingitsdiscriminatorto0.
26.7Afternotes
AndreiAlexandrescucovereddiscriminatedunionsindetailinaseriesof
articles[AlexandrescuDiscriminatedUnions].OurtreatmentofVariantrelies
onsomeofthesametechniquessuchasalignedbuffersforin-placestorageand
visitationtoextractvalues.Someofthedifferencesareduetothebaselanguage:
AndreiwasworkingwithC++98,so,forexample,itcannotmakeuseofvariadic
templatesorinheritingconstructors.Andreialsodevotesconsiderabletimetothe
computationofalignment,whichC++11madetrivialwiththeintroductionof
alignas.Themostinterestingdesigndifferenceisinthehandlingofthe
discriminator:Whileweoptedtouseanintegraldiscriminatortoindicatewhich
typewascurrentlystoredinthevariant,Andreiemploysa“staticvtable”
approachusingfunctionpointerstoconstruct,copy,query,anddestroythe
underlyingelementtype.Interestingly,thisstaticvtableapproachhasbeenmore
influentialasanoptimizationtechniqueforopendiscriminatedunionslikethe
FunctionPtrtemplate,developedinSection22.2onpage519,andisa
commonoptimizationforimplementationsofstd::functiontoeliminate
theuseofvirtualfunctions.Boost’sanytype([BoostAny])isanotheropen
discriminateduniontype,whichwasadoptedbythestandardlibraryas
std::anyinC++17.
Later,theBoostlibraries([Boost])introducedseveraldiscriminatedunion
types,includingavarianttype([BoostVariant])thatinfluencedtheone
developedinthischapter.ThedesigndocumentationforBoost.Variant
([BoostVariant])includesafascinatingdiscussionoftheexception-safetyissue
withvariantassignment(referredasthe“never-emptyguarantee”)andthe
variousnot-entirely-satisfyingsolutionstotheproblem.Whenadoptedbythe
standardlibraryasstd::variantwithC++17thenever-emptyguarantee
wasgivenup:Theneedtoallocateheapstorageforbackupswasremovedby
allowingthatthestd::variantstatecanbecome
valueless_by_exceptionprovidedassigninganewvaluetoitthrows,a
behaviorwemodelwithouremptyvariants.
UnlikeourVarianttemplate,std::variantallowsmultipleidentical
templatearguments(e.g.,std::variant<int,int>).Enablingthat
functionalityinVariantwouldrequiresignificantchangesinourdesign,
includingaddingamethodtodisambiguatetheVariantChoicebaseclasses
andanalternativetothenestedpackexpansiondescribedinSection26.2on
page608.
Thevariantvisit()operationdescribedinthischapterisstructurally
identicaltotheadhocvisitorpatterndescribedbyAndreiAlexandrescuin
[AlexandrescuAdHocVisitor].Alexandrescu’sadhocvisitorisintendedto
simplifytheprocessofcheckingapointertosomecommonbaseclassagainst
somesetofknownderivedclasses(describedasatypelist).Theimplementation
usesdynamic_casttotestthepointeragainsteachderivedclassinthe
typelist,callingthevisitorwiththederivedclasspointerwhenitfindsamatch.
1NotethatthelistofpotentialtypesisfixedatthetimetheVariantis
declared,whichmeansthatVariantisacloseddiscriminatedunion.An
opendiscriminatedunionwouldallowvaluesofadditionaltypes,notknown
atthetimethediscriminatedunionwascreated,tobestoredwithintheunion.
TheFunctionPtrclassdiscussedinChapter22canbeviewedasaformof
anopendiscriminatedunion.
2Therearemanyotherproblemswiththisapproachaswell,suchastheimplied
requirementthatallofthetypesinTypeshaveadefaultconstructor.
3Althoughweoptednotto,wecouldhaveusedatemplatemetaprogramto
computethemaximalalignmentratherthanusingthepackexpansionof
alignas.Theresultisthesameeitherway,buttheformulationabove
movesthealignmentcomputationworkintothecompiler.
4Thebaseclassesareprivatebecausetheirpresenceisnotpartofthepublic
interface.ThefriendtemplateisrequiredtoallowtheasDerived()
functionsinVariantChoicetoperformthedowncasttoVariant.
5OneinterestingeffectofdistinguishingtheVariantChoicebaseclassesof
agivenVariantonlybythetypeTisthatitpreventsduplicatetypes.A
Variant<double,int,double>willproduceacompilererror
indicatingthataclasscannotdirectlyinheritfromthesamebaseclass(inthis
case,VariantChoice<double,double,int,double>,twice).
6Theuseofconstructionherepreventstheuseofreferencetypeswithour
Variantdesign.Thislimitationcouldbeaddressedbywrappingreferences
inaclasssuchasstd::reference_wrapper.
7Theuseofapackexpansioninausingdeclaration(Section4.4.5onpage65)
wasintroducedinC++17.PriortoC++17,inheritingtheseconstructorswould
haverequiredarecursiveinheritancepatternsimilartotheformulationof
TupleshowninChapter25.
8ThiscaseisdiscussedindetailinSection26.4.3onpage613.
9Despitethesyntacticuseoftheassignmentoperator(=)withinthelambda,
theactualimplementationsoftheassignmentoperatorinVariantChoice
willperformacopy-constructionbecausethevariantinitiallystoresnovalue.
Chapter27
ExpressionTemplates
Inthischapterweexploreatemplateprogrammingtechniquecalledexpression
templates.Itwasoriginallyinventedinsupportofnumericarrayclasses,andthat
isalsothecontextinwhichweintroduceithere.
Anumericarrayclasssupportsnumericoperationsonwholearrayobjects.For
example,itispossibletoaddtwoarrays,andtheresultcontainselementsthat
arethesumsofthecorrespondingvaluesintheargumentarrays.Similarly,a
wholearraycanbemultipliedbyascalar,meaningthateachelementofthearray
isscaled.Naturally,itisdesirabletokeeptheoperatornotationthatissofamiliar
forbuilt-inscalartypes:Clickheretoviewcodeimage
Array<double>x(1000),y(1000);
…
x=1.2*x+x*y;
Fortheseriousnumbercruncher,itiscrucialthatsuchexpressionsbeevaluated
asefficientlyascanbeexpectedfromtheplatformonwhichthecodeisrun.
Achievingthiswiththecompactoperatornotationofthisexampleisnotrivial
task,butexpressiontemplateswillcometoourrescue.
Expressiontemplatesarereminiscentoftemplatemetaprogramming,inpart
becauseexpressiontemplatesrelyonsometimesdeeplynestedtemplate
instantiations,whicharenotunliketherecursiveinstantiationsencounteredin
templatemetaprograms.Thefactthatbothtechniqueswereoriginallydeveloped
tosupporthigh-performancearrayoperations(seeourexampleusingtemplates
tounrollloopsinSection23.1.3onpage533)probablyalsocontributestoa
sensethattheyarerelated.Certainlythetechniquesarecomplementary.For
example,metaprogrammingisconvenientforsmallfixed-sizearrays,whereas
expressiontemplatesareveryeffectiveforoperationsonmediumtolargearrays
sizedatruntime.
27.1TemporariesandSplitLoops
Tomotivateexpressiontemplates,let’sstartwithastraightforward(ormaybe
naive)approachtoimplementtemplatesthatenablenumericarrayoperations.A
basicarraytemplatemightlookasfollows(SArraystandsforsimplearray):
Clickheretoviewcodeimage
exprtmpl/sarray1.hpp
#include<cstddef>
#include<cassert>
template<typenameT>
classSArray{
public:
//createarraywithinitialsize
explicitSArray(std::size_ts)
:storage(newT[s]),storage_size(s)
{init();
}
//copyconstructor
SArray(SArray<T>const&orig)
:storage(newT[orig.size()]),storage_size(orig.size()){
copy(orig);
}
//destructor:freememory
~SArray(){
delete[]storage;
}
//assignmentoperator
SArray<T>&operator=(SArray<T>const&orig){
if(&orig!=this){
copy(orig);
}return*this;
}
//returnsize
std::size_tsize()const{
returnstorage_size;
}
//indexoperatorforconstantsandvariables
Tconst&operator[](std::size_tidx)const{
returnstorage[idx];
}
T&operator[](std::size_tidx){
returnstorage[idx];
}
protected:
//initvalueswithdefaultconstructor
voidinit(){
for(std::size_tidx=0;idx<size();++idx){
storage[idx]=T();
}
}
//copyvaluesofanotherarray
voidcopy(SArray<T>const&orig){
assert(size()==orig.size());
for(std::size_tidx=0;idx<size();++idx){
storage[idx]=orig.storage[idx];
}
}
private:
T*storage;//storageoftheelements
std::size_tstorage_size;//numberofelements
};
Thenumericoperatorscanbecodedasfollows:Clickheretoviewcodeimage
exprtmpl/sarrayops1.hpp
//additionoftwoSArrays
template<typenameT>
SArray<T>operator+(SArray<T>const&a,SArray<T>const&b)
{
assert(a.size()==b.size());
SArray<T>result(a.size());
for(std::size_tk=0;k<a.size();++k){
result[k]=a[k]+b[k];
}
returnresult;
}
//multiplicationoftwoSArrays
template<typenameT>
SArray<T>operator*(SArray<T>const&a,SArray<T>const&b)
{
assert(a.size()==b.size());
SArray<T>result(a.size());
for(std::size_tk=0;k<a.size();++k){
result[k]=a[k]*b[k];
}
returnresult;
}
//multiplicationofscalarandSArray
template<typenameT>
SArray<T>operator*(Tconst&s,SArray<T>const&a)
{
SArray<T>result(a.size());
for(std::size_tk=0;k<a.size();++k){
result[k]=s*a[k];
}
returnresult;
}
//multiplicationofSArrayandscalar
//additionofscalarandSArray
//additionofSArrayandscalar…
Manyotherversionsoftheseandotheroperatorscanbewritten,butthese
sufficetoallowourexampleexpression:Clickheretoviewcodeimage
exprtmpl/sarray1.cpp
#include"sarray1.hpp"
#include"sarrayops1.hpp"
intmain()
{
SArray<double>x(1000),y(1000);
…
x=1.2*x+x*y;
}
Thisimplementationturnsouttobeveryinefficientfortworeasons:1.Every
applicationofanoperator(exceptassignment)createsatleastonetemporary
array(i.e.,atleastthreetemporaryarraysofsize1,000eachinourexample,
assumingacompilerperformsalltheallowabletemporarycopyeliminations).
2.Everyapplicationofanoperatorrequiresadditionaltraversalsoftheargument
andresultarrays(approximately6,000doublesareread,andapproximately
4,000doublesarewritteninourexample,assumingonlythreetemporary
SArrayobjectsaregenerated).
Whathappensconcretelyisasequenceofloopsthatoperateswithtemporaries:
Clickheretoviewcodeimage
tmp1=1.2*x;//loopof1,000operations
//pluscreationanddestructionoftmp1
tmp2=x*y//loopof1,000operations
//pluscreationanddestructionoftmp2
tmp3=tmp1+tmp2;//loopof1,000operations
//pluscreationanddestructionoftmp3
x=tmp3;//1,000readoperationsand1,000writeoperations
Thecreationofunneededtemporariesoftendominatesthetimeneededfor
operationsonsmallarraysunlessspecialfastallocatorsareused.Fortrulylarge
arrays,temporariesaretotallyunacceptablebecausethereisnostoragetohold
them.(Challengingnumericsimulationsoftentrytousealltheavailable
memoryformorerealisticresults.Ifthememoryisusedtoholdunneeded
temporariesinstead,thequalityofthesimulationwillsuffer.)Early
implementationsofnumericarraylibrariesfacedthisproblemandencouraged
userstousecomputedassignments(suchas+=,*=,andsoforth)instead.The
advantageoftheseassignmentsisthatboththeargumentandthedestinationare
providedbythecaller,andhencenotemporariesareneeded.Forexample,we
couldaddSArraymembersasfollows:Clickheretoviewcodeimage
exprtmpl/sarrayops2.hpp
//additiveassignmentofSArray
template<typenameT>
SArray<T>&SArray<T>::operator+=(SArray<T>const&b)
{
assert(size()==orig.size());
for(std::size_tk=0;k<size();++k){
(*this)[k]+=b[k];
}
return*this;
}
//multiplicativeassignmentofSArray
template<typenameT>
SArray<T>&SArray<T>::operator*=(SArray<T>const&b)
{
assert(size()==orig.size());
for(std::size_tk=0;k<size();++k){
(*this)[k]*=b[k];
}
return*this;
}
//multiplicativeassignmentofscalar
template<typenameT>
SArray<T>&SArray<T>::operator*=(Tconst&s)
{
for(std::size_tk=0;k<size();++k){
(*this)[k]*=s;
}
return*this;
}
Withoperatorssuchasthese,ourexamplecomputationcouldberewrittenas
Clickheretoviewcodeimage
exprtmpl/sarray2.cpp
#include"sarray2.hpp"
#include"sarrayops1.hpp"
#include"sarrayops2.hpp"
intmain()
{
SArray<double>x(1000),y(1000);
…
//processx=1.2*x+x*y
SArray<double>tmp(x);
tmp*=y;
x*=1.2;
x+=tmp;
}
Clearly,thetechniqueusingcomputedassignmentsstillfallsshort:•The
notationhasbecomeclumsy.
•Wearestillleftwithanunneededtemporarytmp.
•Theloopissplitovermultipleoperations,requiringatotalofapproximately
6,000doubleelementstobereadfrommemoryand4,000doublestobe
writtentomemory.
Whatwereallywantisone“idealloop”thatprocessesthewholeexpressionfor
eachindex:Clickheretoviewcodeimage
intmain()
{
SArray<double>x(1000),y(1000);
…
for(intidx=0;idx<x.size();++idx){
x[idx]=1.2*x[idx]+x[idx]*y[idx];
}
}
Nowweneednotemporaryarray,andwehaveonlytwomemoryreads
(x[idx]andy[idx])andonememorywrite(x[k])periteration.Asa
result,themanuallooprequiresonlyapproximately2,000memoryreadsand
1,000memorywrites.
Giventhatonmodern,high-performancecomputerarchitectures,memory
bandwidthisthelimitingfactorforthespeedofthesesortsofarrayoperations,it
isnotsurprisingthatinpracticetheperformanceofthesimpleoperator
overloadingapproachesshownhereisoneortwoordersofmagnitudeslower
thanthemanuallycodedloop.However,wewouldliketogettheperformanceof
themanuallycodedloopwithoutthecumbersomeanderror-proneeffortof
writingtheseloopsbyhandandwithoutusingaclumsynotation.
27.2EncodingExpressionsinTemplateArguments
Thekeytoresolvingourproblemisnottoattempttoevaluatepartofan
expressionuntilthewholeexpressionhasbeenseen(inourexample,untilthe
assignmentoperatorisinvoked).Thus,beforetheevaluation,wemustrecord
whichoperationsarebeingappliedtowhichobjects.Theoperationsare
determinedatcompiletimeandcanthereforebeencodedintemplatearguments.
Forourexampleexpression,
1.2*x+x*y;
thismeansthattheresultof1.2*xisnotanewarraybutanobjectthat
representseachvalueofxmultipliedby1.2.Similarly,x*ymustyieldeach
elementofxmultipliedbyeachcorrespondingelementofy.Finally,whenwe
needthevaluesoftheresultingarray,wedothecomputationthatwestoredfor
laterevaluation.
Let’sdesignaconcreteimplementation.Ourimplementationwillevaluatethe
expression1.2*x+x*y;
intoanobjectwiththefollowingtype:Clickheretoviewcodeimage
A_Add<A_Mult<A_Scalar<double>,Array<double>>,
A_Mult<Array<double>,Array<double>>>Wecombineanewfundamental
ArrayclasstemplatewithclasstemplatesA_Scalar,A_Add,and
A_Mult.Youmayrecognizeaprefixrepresentationforthesyntaxtree
correspondingtothisexpression(seeFigure27.1).Thisnested
template-idrepresentstheoperationsinvolvedandthetypesofthe
objectstowhichtheoperationsshouldbeapplied.A_Scalaris
presentedlaterbutisessentiallyjustaplaceholderforascalarin
anarrayexpression.
Figure27.1.Treerepresentationofexpression1.2*x+x*y
27.2.1OperandsoftheExpressionTemplates
Tocompletetherepresentationoftheexpression,wemuststorereferencestothe
argumentsineachoftheA_AddandA_Multobjectsandrecordthevalueof
thescalarintheA_Scalarobject(orareferencethereto).Herearepossible
definitionsforthecorrespondingoperands:Clickheretoviewcodeimage
exprtmpl/exprops1.hpp
#include<cstddef>
#include<cassert>
//includehelperclasstraitstemplatetoselectwhethertoreferto
an
//expressiontemplatenodeeitherbyvalueorbyreference
#include"exprops1a.hpp"
//classforobjectsthatrepresenttheadditionoftwooperands
template<typenameT,typenameOP1,typenameOP2>
classA_Add{
private:
typenameA_Traits<OP1>::ExprRefop1;//firstoperand
typenameA_Traits<OP2>::ExprRefop2;//secondoperand
public:
//constructorinitializesreferencestooperands
A_Add(OP1const&a,OP2const&b)
:op1(a),op2(b){
}
//computesumwhenvaluerequested
Toperator[](std::size_tidx)const{
returnop1[idx]+op2[idx];
}
//sizeismaximumsize
std::size_tsize()const{
assert(op1.size()==0||op2.size()==0
||op1.size()==op2.size());
returnop1.size()!=0?op1.size():op2.size();
}
};
//classforobjectsthatrepresentthemultiplicationoftwooperands
template<typenameT,typenameOP1,typenameOP2>
classA_Mult{
private:
typenameA_Traits<OP1>::ExprRefop1;//firstoperand
typenameA_Traits<OP2>::ExprRefop2;//secondoperand
public:
//constructorinitializesreferencestooperands
A_Mult(OP1const&a,OP2const&b)
:op1(a),op2(b){
}
//computeproductwhenvaluerequested
Toperator[](std::size_tidx)const{
returnop1[idx]*op2[idx];
}
//sizeismaximumsize
std::size_tsize()const{
assert(op1.size()==0||op2.size()==0
||op1.size()==op2.size());
returnop1.size()!=0?op1.size():op2.size();
}
};
Asyoucansee,weaddedsubscriptingandsize-queryingoperationsthatallow
ustocomputethesizeandthevaluesoftheelementsforthearrayresultingfrom
theoperationsrepresentedbythesubtreeofnodesrootedatthegivenobject.
Foroperationsinvolvingarraysonly,thesizeoftheresultisthesizeofeither
operand.However,foroperationsinvolvingbothanarrayandascalar,thesize
oftheresultisthesizeofthearrayoperand.Todistinguisharrayoperandsfrom
scalaroperands,wedefineasizeofzeroforscalars.TheA_Scalartemplateis
thereforedefinedasfollows:Clickheretoviewcodeimage
exprtmpl/exprscalar.hpp
//classforobjectsthatrepresentscalars:
template<typenameT>
classA_Scalar{
private:
Tconst&s;//valueofthescalar
public:
//constructorinitializesvalue
constexprA_Scalar(Tconst&v)
:s(v){
}
//forindexoperations,thescalaristhevalueofeachelement
constexprTconst&operator[](std::size_t)const{
returns;
}
//scalarshavezeroassize
constexprstd::size_tsize()const{
return0;
};
};
(Wehavedeclaredtheconstructorandmemberfunctionsconstexprsothis
classcanbeusedatcompiletime.Thisis,however,notstrictlyneededforour
purposes.)Notethatscalarsalsoprovideanindexoperator.Insidethe
expression,theyrepresentanarraywiththesamescalarvalueforeachindex.
YouprobablysawthattheoperatorclassesusedahelperclassA_Traitsto
definethemembersfortheoperands:Clickheretoviewcodeimage
typenameA_Traits<OP1>::ExprRefop1;//firstoperand
typenameA_Traits<OP2>::ExprRefop2;//secondoperand
Thisisnecessarybecause,ingeneral,wecandeclarethemtobereferences,
sincemosttemporarynodesareboundinthetop-levelexpressionandtherefore
liveuntiltheendoftheevaluationofthatcompleteexpression.Theone
exceptionaretheA_Scalarnodes.Theyareboundwithintheoperator
functionsandmightnotliveuntiltheendoftheevaluationofthecomplete
expression.Thus,toavoidhavingmembersrefertoscalarsthatnolongerexist,
theA_Scalaroperandshavetobecopiedbyvalue.
Inotherwords,weneedmembersthatare•constantreferencesingeneral:
Clickheretoviewcodeimage
OP1const&op1;//refertofirstoperandbyreference
OP2const&op2;//refertosecondoperandbyreference
•butordinaryvaluesforscalars:
Clickheretoviewcodeimage
OP1op1;//refertofirstoperandbyvalue
OP2op2;//refertosecondoperandbyvalue
Thisisaperfectapplicationoftraitsclasses.Thetraitsclassdefinesatypetobe
aconstantreferenceingeneralbutanordinaryvalueforscalars:Clickhereto
viewcodeimage
exprtmpl/exprops1a.hpp
//helpertraitsclasstoselecthowtorefertoanexpression
templatenode
//-ingeneralbyreference
//-forscalarsbyvalue
template<typenameT>classA_Scalar;
//primarytemplate
template<typenameT>
classA_Traits{
public:
usingExprRef=Tconst&;//typetorefertoisconstantreference
};
//partialspecializationforscalars
template<typenameT>
classA_Traits<A_Scalar<T>>{
public:
usingExprRef=A_Scalar<T>;//typetorefertoisordinaryvalue
};
NotethatsinceA_Scalarobjectsrefertoscalarsinthetop-levelexpression,
thosescalarscanusereferencetypes.Thatis,A_Scalar<T>::sisareference
member.
27.2.2TheArrayType
Withourabilitytoencodeexpressionsusinglightweightexpressiontemplates,
wemustnowcreateanArraytypethatcontrolsactualstorageandthatknows
abouttheexpressiontemplates.However,itisalsousefulforengineering
purposestokeepassimilaraspossibletheinterfaceforarealarraywithstorage
andoneforarepresentationofanexpressionthatresultsinanarray.Tothisend,
wedeclaretheArraytemplateasfollows:Clickheretoviewcodeimage
template<typenameT,typenameRep=SArray<T>>
classArray;ThetypeRepcanbeSArrayifArrayisarealarrayof
storage,1oritcanbethenestedtemplate-idsuchasA_AddorA_Mult
thatencodesanexpression.EitherwaywearehandlingArray
instantiations,whichconsiderablysimplifyourlaterdealings.In
fact,eventhedefinitionoftheArraytemplateneedsno
specializationstodistinguishthetwocases,althoughsomeofthe
memberscannotbeinstantiatedfortypeslikeA_Multsubstitutedfor
Rep.
Hereisthedefinition.Thefunctionalityislimitedroughlytowhatwas
providedbyourSArraytemplate,althoughoncethecodeisunderstood,itis
nothardtoaddtothatfunctionality:Clickheretoviewcodeimage
#include<cstddef>
#include<cassert>
#include"sarray1.hpp"
template<typenameT,typenameRep=SArray<T>>
classArray{
private:
Repexpr_rep;//(accessto)thedataofthearray
public:
//createarraywithinitialsize
explicitArray(std::size_ts)
:expr_rep(s){
}
//createarrayfrompossiblerepresentation
Array(Repconst&rb)
:expr_rep(rb){
}
//assignmentoperatorforsametype
Array&operator=(Arrayconst&b){
assert(size()==b.size());
for(std::size_tidx=0;idx<b.size();++idx){
expr_rep[idx]=b[idx];
}
return*this;
}
//assignmentoperatorforarraysofdifferenttype
template<typenameT2,typenameRep2>
Array&operator=(Array<T2,Rep2>const&b){
assert(size()==b.size());
for(std::size_tidx=0;idx<b.size();++idx){
expr_rep[idx]=b[idx];
}
return*this;
}
//sizeissizeofrepresenteddata
std::size_tsize()const{
returnexpr_rep.size();
}
//indexoperatorforconstantsandvariables
decltype(auto)operator[](std::size_tidx)const{
assert(idx<size());
returnexpr_rep[idx];
}
T&operator[](std::size_tidx){
assert(idx<size());
returnexpr_rep[idx];
}
//returnwhatthearraycurrentlyrepresents
Repconst&rep()const{
returnexpr_rep;
}
Rep&rep(){
returnexpr_rep;
}
};
Asyoucansee,manyoperationsaresimplyforwardedtotheunderlyingRep
object.However,whencopyinganotherarray,wemusttakeintoaccountthe
possibilitythattheotherarrayisreallybuiltonanexpressiontemplate.Thus,we
parameterizethesecopyoperationsintermsoftheunderlyingRep
representation.
Thesubscriptingoperatordeservesalittlediscussion.Notethattheconst
versionofthatoperatorusesadeducedreturntyperatherthanthemore
traditionaltypeTconst&.WedothatbecauseifReprepresentsisA_Multor
A_Add,itssubscriptingoperatorreturnsatemporaryvalue(i.e.,aprvalue),
whichcannotbereturnedbyreference(anddecltype(auto)willdeducea
nonreferencetypefortheprvaluecase).Ontheotherhand,ifRepis
SArray<T>thentheunderlyingsubscriptoperatorproducesaconstlvalue,
andthededucedreturntypewillbeamatchingconstreferenceforthatcase.
27.2.3TheOperators
Wehavemostofthemachineryinplacetohaveefficientnumericoperatorsfor
ournumericArraytemplate,excepttheoperatorsthemselves.Asimplied
earlier,theseoperatorsonlyassembletheexpressiontemplateobjects—they
don’tactuallyevaluatetheresultingarrays.
Foreachordinarybinaryoperator,wemustimplementthreeversions:array-
array,array-scalar,andscalar-array.Tobeabletocomputeourinitialvalue,we
need,forexample,thefollowingoperators:Clickheretoviewcodeimage
exprtmpl/exprops2.hpp
//additionoftwoArrays:
template<typenameT,typenameR1,typenameR2>
Array<T,A_Add<T,R1,R2>>
operator+(Array<T,R1>const&a,Array<T,R2>const&b){
returnArray<T,A_Add<T,R1,R2>>
(A_Add<T,R1,R2>(a.rep(),b.rep()));
}
//multiplicationoftwoArrays:
template<typenameT,typenameR1,typenameR2>
Array<T,A_Mult<T,R1,R2>>
operator*(Array<T,R1>const&a,Array<T,R2>const&b){
returnArray<T,A_Mult<T,R1,R2>>
(A_Mult<T,R1,R2>(a.rep(),b.rep()));
}
//multiplicationofscalarandArray:
template<typenameT,typenameR2>
Array<T,A_Mult<T,A_Scalar<T>,R2>>
operator*(Tconst&s,Array<T,R2>const&b){
returnArray<T,A_Mult<T,A_Scalar<T>,R2>>
(A_Mult<T,A_Scalar<T>,R2>(A_Scalar<T>(s),b.rep()));
}
//multiplicationofArrayandscalar,additionofscalarandArray
//additionofArrayandscalar:
…
Thedeclarationoftheseoperatorsissomewhatcumbersome(ascanbeseen
fromtheseexamples),butthefunctionsreallydon’tdomuch.Forexample,the
plusoperatorfortwoarraysfirstcreatesanA_Add<>objectthatrepresentsthe
operatorandtheoperandsClickheretoviewcodeimage
A_Add<T,R1,R2>(a.rep(),b.rep())
andwrapsthisobjectinanArrayobjectsothatwecanusetheresultasany
otherobjectthatrepresentsdataofanarray:Clickheretoviewcodeimage
returnArray<T,A_Add<T,R1,R2>>(…);Forscalarmultiplication,weuse
theA_ScalartemplatetocreatetheA_MultobjectClickheretoview
codeimage
A_Mult<T,A_Scalar<T>,R2>(A_Scalar<T>(s),b.rep())
andwrapagain:
Clickheretoviewcodeimage
returnArray<T,A_Mult<T,A_Scalar<T>,R2>>(…);Othernonmemberbinary
operatorsaresosimilarthatmacroscanbeusedtocovermost
operatorswithrelativelylittlesourcecode.Another(smaller)macro
couldbeusedfornonmemberunaryoperators.
27.2.4Review
Onfirstdiscoveryoftheexpressiontemplateidea,theinteractionofthevarious
declarationsanddefinitionscanbedaunting.Hence,atop-downreviewofwhat
happenswithourexamplecodemayhelpcrystallizeunderstanding.Thecodewe
willanalyzeisthefollowing(youcanfinditaspartof
meta/exprmain.cpp):Clickheretoviewcodeimage
intmain()
{
Array<double>x(1000),y(1000);
…
x=1.2*x+x*y;
}
BecausetheRepargumentisomittedinthedefinitionofxandy,itissettothe
default,whichisSArray<double>.So,xandyarearrayswith“real”storage
andnotjustrecordingsofoperations.
Whenparsingtheexpression
1.2*x+x*y
thecompilerfirstappliestheleftmost*operation,whichisascalar-array
operator.Overloadresolutionthusselectsthescalar-arrayformofoperator*:
Clickheretoviewcodeimage
template<typenameT,typenameR2>
Array<T,A_Mult<T,A_Scalar<T>,R2>>
operator*(Tconst&s,Array<T,R2>const&b){
returnArray<T,A_Mult<T,A_Scalar<T>,R2>>
(A_Mult<T,A_Scalar<T>,R2>(A_Scalar<T>(s),b.rep()));
}
TheoperandtypesaredoubleandArray<double,SArray<double>>.
Thus,thetypeoftheresultisClickheretoviewcodeimage
Array<double,A_Mult<double,A_Scalar<double>,SArray<double>>>The
resultvalueisconstructedtoreferenceanA_Scalar<double>object
constructedfromthedoublevalue1.2andtheSArray<double>
representationoftheobjectx.
Next,thesecondmultiplicationisevaluated:Itisanarray-arrayoperation
x*y.Thistimeweusetheappropriateoperator*:Clickheretoviewcode
image
template<typenameT,typenameR1,typenameR2>
Array<T,A_Mult<T,R1,R2>>
operator*(Array<T,R1>const&a,Array<T,R2>const&b){
returnArray<T,A_Mult<T,R1,R2>>
(A_Mult<T,R1,R2>(a.rep(),b.rep()));
}
TheoperandtypesarebothArray<double,SArray<double>>,sothe
resulttypeisClickheretoviewcodeimage
Array<double,A_Mult<double,SArray<double>,SArray<double>>>This
timethewrappedA_MultobjectreferstotwoSArray<double>
representations:theoneofxandtheoneofy.
Finally,the+operationisevaluated.Itisagainanarray-arrayoperation,and
theoperandtypesaretheresulttypesthatwejustdeduced.So,weinvokethe
array-arrayoperator+:Clickheretoviewcodeimage
template<typenameT,typenameR1,typenameR2>
Array<T,A_Add<T,R1,R2>>
operator+(Array<T,R1>const&a,Array<T,R2>const&b){
returnArray<T,A_Add<T,R1,R2>>
(A_Add<T,R1,R2>(a.rep(),b.rep()));
}
Tissubstitutedwithdouble,whereasR1issubstitutedwithClickheretoview
codeimage
A_Mult<double,A_Scalar<double>,SArray<double>>andR2is
substitutedwithClickheretoviewcodeimage
A_Mult<double,SArray<double>,SArray<double>>Hence,thetypeofthe
expressiontotherightoftheassignmenttokenisClickheretoview
codeimage
Array<double,
A_Add<double,
A_Mult<double,A_Scalar<double>,SArray<double>>,
A_Mult<double,SArray<double>,SArray<double>>>>Thistypeismatched
totheassignmentoperatortemplateoftheArraytemplate:Clickhere
toviewcodeimage
template<typenameT,typenameRep=SArray<T>>
classArray{
public:
…
//assignmentoperatorforarraysofdifferenttype
template<typenameT2,typenameRep2>
Array&operator=(Array<T2,Rep2>const&b){
assert(size()==b.size());
for(std::size_tidx=0;idx<b.size();++idx){
expr_rep[idx]=b[idx];
}
return*this;
}
…
};
Theassignmentoperatorcomputeseachelementofthedestinationxbyapplying
thesubscriptoperatortotherepresentationoftherightside,thetypeofwhichis
Clickheretoviewcodeimage
A_Add<double,
A_Mult<double,A_Scalar<double>,SArray<double>>,
A_Mult<double,SArray<double>,SArray<double>>>>Carefullytracing
thissubscriptoperatorshowsthatforagivensubscriptidx,it
computesClickheretoviewcodeimage
(1.2*x[idx])+(x[idx]*y[idx])
whichisexactlywhatwewant.
27.2.5ExpressionTemplatesAssignments
ItisnotpossibletoinstantiatewriteoperationsforanarraywithaRepargument
thatisbuiltonourexampleA_MultandA_Addexpressiontemplates.(Indeed,
itmakesnosensetowritea+b=c.)However,itisentirelyreasonabletowrite
otherexpressiontemplatesforwhichassignmenttotheresultispossible.For
example,indexingwithanarrayofintegralvalueswouldintuitivelycorrespond
tosubsetselection.Inotherwords,theexpressionx[y]=2*x[y];
shouldmeanthesameas
Clickheretoviewcodeimage
for(std::size_tidx=0;idx<y.size();++idx){
x[y[idx]]=2*x[y[idx]];
}
Enablingthisimpliesthatanarraybuiltonanexpressiontemplatebehaveslike
anlvalue(i.e.,iswritable).Theexpressiontemplatecomponentforthisisnot
fundamentallydifferentfrom,say,A_Mult,exceptthatbothconstandnon-
constversionsofthesubscriptoperatorsareprovided,andtheymayreturn
lvalues(references):Clickheretoviewcodeimage
exprtmpl/exprops3.hpp
template<typenameT,typenameA1,typenameA2>
classA_Subscript{
public:
//constructorinitializesreferencestooperands
A_Subscript(A1const&a,A2const&b)
:a1(a),a2(b){
}
//processsubscriptionwhenvaluerequested
decltype(auto)operator[](std::size_tidx)const{
returna1[a2[idx]];
}
T&operator[](std::size_tidx){
returna1[a2[idx]];
}
//sizeissizeofinnerarray
std::size_tsize()const{
returna2.size();
}
private:
A1const&a1;//referencetofirstoperand
A2const&a2;//referencetosecondoperand
};
Again,decltype(auto)comesinhandytohandlesubscriptingofarrays
independentlyofwhethertheunderlyingrepresentationproducesprvaluesor
lvalues.
Theextendedsubscriptoperatorwithsubsetsemanticsthatwassuggested
earlierwouldrequirethatadditionalsubscriptoperatorsbeaddedtotheArray
template.Oneoftheseoperatorscouldbedefinedasfollows(acorresponding
constversionwouldpresumablyalsobeneeded):Clickheretoviewcode
image
exprtmpl/exprops4.hpp
template<typenameT,typenameR>
template<typenameT2,typenameR2>
Array<T,A_Subscript<T,R,R2>>
Array<T,R>::operator[](Array<T2,R2>const&b){
returnArray<T,A_Subscript<T,R,R2>>
(A_Subscript<T,R,R2>(*this,b));
}
27.3PerformanceandLimitationsofExpression
Templates
Tojustifythecomplexityoftheexpressiontemplateidea,wehavealready
invokedgreatlyenhancedperformanceonarray-wiseoperations.Asyoutrace
whathappenswiththeexpressiontemplates,you’llfindthatmanysmallinline
functionscalleachotherandthatmanysmallexpressiontemplateobjectsare
allocatedonthecallstack.Theoptimizermustperformcompleteinliningand
eliminationofthesmallobjectstoproducecodethatperformsaswellas
manuallycodedloops.Inthefirsteditionofthisbook,wereportedthatfew
compilerscouldachievesuchoptimizations.Sincethen,thesituationhas
improvedconsiderably,nodoubtdueinparttothefactthatthetechniquehas
provenpopular.
Theexpressiontemplatestechniquedoesnotresolvealltheproblematic
situationsinvolvingnumericoperationsonarrays.Forexample,itdoesnotwork
formatrix-vectormultiplicationsoftheformx=A*x;
wherexisacolumnvectorofsizenandAisann-by-nmatrix.Theproblem
hereisthatatemporarymustbeusedbecauseeachelementoftheresultcan
dependoneachelementoftheoriginalx.Unfortunately,theexpressiontemplate
loopupdatesthefirstelementofxrightawayandthenusesthatnewly
computedelementtocomputethesecondelement,whichiswrong.Theslightly
differentexpressionx=A*y;
ontheotherhand,doesnotneedatemporaryifxandyaren’taliasesforeach
other,whichimpliesthatasolutionwouldhavetoknowtherelationshipofthe
operandsatruntime.Thisinturnsuggestscreatingarun-timestructurethat
representstheexpressiontreeinsteadofencodingthetreeinthetypeofthe
expressiontemplate.ThisapproachwaspioneeredbytheNewMatlibraryof
RobertDavies(see[NewMat]).Itwasknownlongbeforeexpressiontemplates
weredeveloped.
27.4Afternotes
ExpressiontemplatesweredevelopedindependentlybyToddVeldhuizenand
DavidVandevoorde(Toddcoinedtheterm)atatimewhenmembertemplates
werenotyetpartoftheC++programminglanguage(anditseemedatthetime
thattheywouldneverbeaddedtoC++).Thiscausedsomeproblemsin
implementingtheassignmentoperator:Itcouldnotbeparameterizedforthe
expressiontemplate.Onetechniquetoworkaroundthisconsistedofintroducing
intheexpressiontemplatesaconversionoperatortoaCopierclass
parameterizedwiththeexpressiontemplatebutinheritingfromabaseclassthat
wasparameterizedonlyintheelementtype.Thisbaseclassthenprovideda
(virtual)copy_tointerfacetowhichtheassignmentoperatorcouldrefer.
Hereisasketchofthemechanism(withthetemplatenamesusedinthis
chapter):Clickheretoviewcodeimage
template<typenameT>
classCopierInterface{
public:
virtualvoidcopy_to(Array<T,SArray<T>>&)const;
};
template<typenameT,typenameX>
classCopier:publicCopierInterface<T>{
public:
Copier(Xconst&x):expr(x){
}
virtualvoidcopy_to(Array<T,SArray<T>>&)const{
//implementationofassignmentloop
…
}
private:
Xconst&expr;
};
template<typenameT,typenameRep=SArray<T>>
classArray{
public:
//delegatedassignmentoperator
Array<T,Rep>&operator=(CopierInterface<T>const&b){
b.copy_to(rep);
};
…
};
template<typenameT,typenameA1,typenameA2>
classA_mult{
public:
operatorCopier<T,A_Mult<T,A1,A2>>();
…
};
Thisaddsanotherlevelofcomplexityandsomeadditionalrun-timecostto
expressiontemplates,butevenso,theresultingperformancebenefitswere
impressiveatthetime.
TheC++standardlibrarycontainsaclasstemplatevalarraythatwas
meanttobeusedforapplicationsthatwouldjustifythetechniquesusedforthe
Arraytemplatedevelopedinthischapter.Aprecursorofvalarrayhadbeen
designedwiththeintentionthatcompilersaimingatthemarketforscientific
computationwouldrecognizethearraytypeandusehighlyoptimizedinternal
codefortheiroperations.Suchcompilerswouldhave“understood”thetypesin
somesense.However,thisneverhappened(inpartbecausethemarketin
questionisrelativelysmallandinpartbecausetheproblemgrewincomplexity
asvalarraybecameatemplate).Sometimeaftertheexpressiontemplate
techniquewasdiscovered,oneofus(Vandevoorde)submittedtotheC++
committeeaproposalthatturnedvalarrayessentiallyintotheArray
templatewedeveloped(withmanybellsandwhistlesinspiredbytheexisting
valarrayfunctionality).Theproposalrepresentsthefirsttimethattheconcept
oftheRepparameterwasdocumented.Priortothis,thearrayswithactual
storageandtheexpressiontemplatepseudo-arraysweredifferenttemplates.
Whenclientcodeintroducedafunctionfoo()acceptinganarray—for
example,Clickheretoviewcodeimage
doublefoo(Array<double>const&);callingfoo(1.2*x)forcedthe
conversionfortheexpressiontemplatetoanarraywithactual
storage,evenwhentheoperationsappliedtothatargumentdidnot
requireatemporary.WithexpresssiontemplatesembeddedintheRep
argument,itispossibleinsteadtodeclareClickheretoviewcode
image
template<typenameRep>
doublefoo(Array<double,Rep>const&);andnoconversionhappens
unlessoneisactuallyneeded.
ThevalarrayproposalcamelateintheC++standardizationprocessand
practicallyrewroteallthetextregardingvalarrayinthestandard.Itwas
rejectedasaresult,andinstead,afewtweakswereaddedtotheexistingtextto
allowimplementationsbasedonexpressiontemplates.However,theexploitation
ofthisallowanceremainsmuchmorecumbersomethanwhatwasdiscussed
here.Atthetimeofthiswriting,nosuchimplementationisknown,andstandard
valarraysare,generallyspeaking,quiteinefficientatperformingthe
operationsforwhichtheyweredesigned.
Finally,itisworthobservingherethatmanyofthepioneeringtechniques
presentedinthischapter,aswellaswhatlaterbecameknownastheSTL,2were
alloriginallyimplementedonthesamecompiler:version4oftheBorlandC++
compiler.Thiswasperhapsthefirstcompilerthatmadetemplateprogramming
broadlypopularintheC++programmingcommunity.
Expressiontemplateswerefirstappliedprimarilyforoperationsonarray-like
types.However,afterafewyears,newapplicationswerefound.Amongthe
mostground-breakingwereJaakkoJ¨arvi’sandGaryPowell’sBoost.Lambda
library(see[LambdaLib]),whichprovidedausablelambdaexpressionfacility
yearsbeforelambdaexpressionsbecameacorelanguagefeature3,andEric
Niebler’sBoost.Protolibrary,whichisalibrarytometa-programexpression
templates,withthegoalofcreatingembeddeddomain-specificlanguagesin
C++.OtherBoostlibraries,likeBoost.FusionandBoost.Hana,alsomake
advanceduseofexpressiontemplates.
1ItisconvenienttoreusethepreviouslydevelopedSArrayhere,butinan
industrial-strengthlibrary,aspecial-purposeimplementationmaybe
preferablebecausewewon’tuseallthefeaturesofSArray.
2TheStandardTemplateLibrary(STL)revolutionizedtheworldofC++
librariesandwaslatermadepartoftheC++standardlibrary(see
[JosuttisStdLib]).
3Jaakkowasalsoinstrumentalindevelopingthecorelanguagefeature.
Chapter28
DebuggingTemplates
Templatesraisetwoclassesofchallengeswhenitcomestodebuggingthem.One
setofchallengesisdefinitelyaproblemforwritersoftemplates:Howcanwe
ensurethatthetemplateswewritewillfunctionforanytemplateargumentsthat
satisfytheconditionswedocument?Theotherclassofproblemsisalmost
exactlytheopposite:Howcanauserofatemplatefindoutwhichofthe
templateparameterrequirementsitviolatedwhenthetemplatedoesnotbehave
asdocumented?
Beforewediscusstheseissuesindepth,itisusefultocontemplatethekinds
ofconstraintsthatmaybeimposedontemplateparameters.Inthischapter,we
dealmostlywiththeconstraintsthatleadtocompilationerrorswhenviolated,
andwecalltheseconstraintssyntacticconstraints.Syntacticconstraintscan
includetheneedforacertainkindofconstructortoexist,foraparticular
functioncalltobeunambiguous,andsoforth.Theotherkindofconstraintwe
callsemanticconstraints.Theseconstraintsaremuchhardertoverify
mechanically.Inthegeneralcase,itmaynotevenbepracticaltodoso.For
example,wemayrequirethattherebea<operatordefinedonatemplatetype
parameter(whichisasyntacticconstraint),butusuallywe’llalsorequirethatthe
operatoractuallydefinessomesortoforderingonitsdomain(whichisa
semanticconstraint).
Thetermconceptisoftenusedtodenoteasetofconstraintsthatisrepeatedly
requiredinatemplatelibrary.Forexample,theC++standardlibraryrelieson
suchconceptsasrandomaccessiteratoranddefaultconstructible.Withthis
terminologyinplace,wecansaythatdebuggingtemplatecodeincludesa
significantamountofdetermininghowconceptsareviolatedinthetemplate
implementationandintheiruse.Thischapterdelvesintobothdesignand
debuggingtechniquesthatcanmaketemplateseasiertoworkwith,bothfor
templateauthorsandfortemplateusers.
28.1ShallowInstantiation
Whentemplateerrorsoccur,theproblemsareoftenfoundafteralongchainof
instantiations,leadingtolengthyerrormessageslikethosediscussedinSection
9.4onpage1431Toillustratethis,considerthefollowingsomewhatcontrived
code:Clickheretoviewcodeimage
template<typenameT>
voidclear(T&p)
{
*p=0;//assumesTisapointer-liketype
}
template<typenameT>
voidcore(T&p)
{
clear(p);
}
template<typenameT>
voidmiddle(typenameT::Indexp)
{
core(p);
}
template<typenameT>
voidshell(Tconst&env)
{
typenameT::Indexi;
middle<T>(i);
}
Thisexampleillustratesthetypicallayeringofsoftwaredevelopment:High-
levelfunctiontemplateslikeshell()relyoncomponentslikemiddle(),
whichthemselvesmakeuseofbasicfacilitieslikecore().Whenweinstantiate
shell(),allthelayersbelowitalsoneedtobeinstantiated.Inthisexample,a
problemisrevealedinthedeepestlayer:core()isinstantiatedwithtypeint
(fromtheuseofClient::Indexinmiddle())andattemptstodereference
avalueofthattype,whichisanerror.
Theerrorisonlydetectableatinstantiationtime.Forexample:Clickhereto
viewcodeimage
classClient
{
public:
usingIndex=int;
};
intmain()
{
ClientmainClient;
shell(mainClient);
}
Agoodgenericdiagnosticincludesatraceofallthelayersthatledtothe
problems,butweobservethatsomuchinformationcanappearunwieldy.
Anexcellentdiscussionofthecoreideassurroundingthisproblemcanbe
foundin[StroustrupDnE],inwhichBjarneStroustrupidentifiestwoclassesof
approachestodetermineearlierwhethertemplateargumentssatisfyasetof
constraints:throughalanguageextensionorthroughearlierparameteruse.We
covertheformeroptioninSection17.8onpage361andAppendixE.Thelatter
alternativeconsistsofforcinganyerrorsinshallowinstantiations.Thisis
achievedbyinsertingunusedcodewithnootherpurposethantotriggeranerror
ifthatcodeisinstantiatedwithtemplateargumentsthatdonotmeetthe
requirementsofdeeperlevelsoftemplates.
Inourpreviousexample,wecouldaddcodeinshell()thatattemptsto
dereferenceavalueoftypeT::Index.Forexample:Clickheretoviewcode
image
template<typenameT>
voidignore(Tconst&)
{
}
template<typenameT>
voidshell(Tconst&env)
{
classShallowChecks
{
voidderef(typenameT::Indexptr){
ignore(*ptr);
}
};
typenameT::Indexi;
middle(i);
}
IfTisatypesuchthatT::Indexcannotbedereferenced,anerrorisnow
diagnosedonthelocalclassShallowChecks.Notethatbecausethelocal
classisnotactuallyused,theaddedcodedoesnotimpacttherunningtimeofthe
shell()function.Unfortunately,manycompilerswillwarnthat
ShallowChecksisnotused(andneitherareitsmembers).Trickssuchasthe
useoftheignore()templatecanbeusedtoinhibitsuchwarnings,butthey
addtothecomplexityofthecode.
ConceptChecking
Clearly,thedevelopmentofthedummycodeinourexamplecanbecomeas
complexasthecodethatimplementstheactualfunctionalityofthetemplate.To
controlthiscomplexity,itisnaturaltoattempttocollectvarioussnippetsof
dummycodeinsomesortoflibrary.Forexample,suchalibrarycouldcontain
macrosthatexpandtocodethattriggerstheappropriateerrorwhenatemplate
parametersubstitutionviolatestheconceptunderlyingthatparticularparameter.
ThemostpopularsuchlibraryistheConceptCheckLibrary,whichispartofthe
Boostdistribution(see[BCCL]).
Unfortunately,thetechniqueisn’tparticularlyportable(thewayerrorsare
diagnoseddiffersconsiderablyfromonecompilertoanother)andsometimes
masksissuesthatcannotbecapturedatahigherlevel.
OncewehaveconceptsinC++(seeAppendixE),wehaveotherwaysto
supportthedefinitionofrequirementsandexpectedbehavior.
28.2StaticAssertions
Theassert()macroisoftenusedinC++codetocheckthatsomeparticular
conditionholdsatacertainpointwithintheprogram’sexecution.Iftheassertion
fails,theprogramis(noisily)haltedsotheprogrammercanfixtheproblem.
TheC++static_assertkeyword,introducedwithC++11,servesthe
samepurposebutisevaluatedatcompiletime:Ifthecondition(whichmustbea
constantexpression)evaluatestofalse,thecompilerwillissueanerror
message.Thaterrormessagewillincludeastring(thatispartofthe
static_assertitself)indicatingtotheprogrammerwhathasgonewrong.
Forexample,thefollowingstaticassertionensuresthatwearecompilingona
platformwith64-bitpointers:Clickheretoviewcodeimage
static_assert(sizeof(void*)*CHAR_BIT==64,"Nota64-bit
platform");Staticassertionscanbeusedtoprovideusefulerror
messageswhenatemplateargumentdoesnotsatisfytheconstraintsof
atemplate.Forexample,usingthetechniquesdescribedinSection
19.4onpage416,wecancreateatypetraittodeterminewhethera
giventypeisdereferenceable:Clickheretoviewcodeimage
debugging/hasderef.hpp
#include<utility>//fordeclval()
#include<type_traits>//fortrue_typeandfalse_type
template<typenameT>
classHasDereference{
private:
template<typenameU>structIdentity;
template<typenameU>staticstd::true_type
test(Identity<decltype(*std::declval<U>())>*);
template<typenameU>staticstd::false_type
test(…);
public:
staticconstexprboolvalue=decltype(test<T>(nullptr))::value;
};
Now,wecanintroduceastaticassertionintoshell()thatprovidesabetter
diagnosticiftheshell()templatefromtheprevioussectionisinstantiated
withatypethatisnotdereferencable:Clickheretoviewcodeimage
template<typenameT>
voidshell(Tconst&env)
{
static_assert(HasDereference<T>::value,"Tisnotdereferenceable");
typenameT::Indexi;
middle(i);
}
Withthischange,thecompilerproducesasignificantlymoreconcisediagnostic
indicatingthatthetypeTisnotdereferenceable.
Staticassertionscandrasticallyimprovetheuserexperiencewhenworking
withtemplatelibraries,bymakingerrormessagesbothshorterandmoredirect.
Notethatyoucanalsoapplythemtoclasstemplatesandusealltypetraits
discussedinAppendixD:Clickheretoviewcodeimage
template<typenameT>
classC{
static_assert(HasDereference<T>::value,"Tisnotdereferenceable");
static_assert(std::is_default_constructible<T>::value,
"Tisnotdefaultconstructible");
…
};
28.3Archetypes
Whenwritingatemplate,itischallengingtoensurethatthetemplatedefinition
willcompileforanytemplateargumentsthatmeetthespecifiedconstraintsfor
thattemplate.Considerasimplefind()algorithmthatlooksforavaluewithin
anarray,alongwithitsdocumentedconstraints:Clickheretoviewcodeimage
//TmustbeEqualityComparable,meaning:
//twoobjectsoftypeTcanbecomparedwith==andtheresult
convertedtobool
template<typenameT>
intfind(Tconst*array,intn,Tconst&value);Wecouldimaginethe
followingstraightforwardimplementationofthisfunctiontemplate:
Clickheretoviewcodeimage
template<typenameT>
intfind(Tconst*array,intn,Tconst&value){
inti=0;
while(i!=n&&array[i]!=value)
++i;
returni;
}
Therearetwoproblemswiththistemplatedefinition,bothofwhichwill
manifestascompilationerrorswhengivencertaintemplateargumentsthat
technicallymeettherequirementsofthetemplateyetbehaveslightlydifferently
thanthetemplateauthorexpected.Wewillusethenotionofarchetypestotest
ourimplementation’suseofitstemplateparametersagainsttherequirements
specifiedbythefind()template.
Archetypesareuser-definedclassesthatcanbeusedastemplateargumentsto
testatemplatedefinition’sadherencetotheconstraintsitimposesonits
correspondingtemplateparameter.Anarchetypeisspecificallycraftedtosatisfy
therequirementsofthetemplateinthemostminimalwaypossible,without
providinganyextraneousoperations.Ifinstantiationofatemplatedefinition
withthearchetypeasitstemplateargumentsucceeds,thenweknowthatthe
templatedefinitiondoesnottrytouseanyoperationsnotexplicitlyrequiredby
thetemplate.
Forexample,hereisanarchetypeintendedtomeettherequirementsofthe
EqualityComparableconceptdescribedinthedocumentationofour
find()algorithm:Clickheretoviewcodeimage
classEqualityComparableArchetype
{
};
classConvertibleToBoolArchetype
{
public:
operatorbool()const;
};
ConvertibleToBoolArchetype
operator==(EqualityComparableArchetypeconst&,
EqualityComparableArchetypeconst&);TheEqualityComparableArchetype
hasnomemberfunctionsordata,andtheonlyoperationitprovides
isanoverloadedoperator==tosatisfytheequalityrequirementfor
find().Thatoperator==isitselfratherminimal,returninganother
archetype,ConvertibleToBoolArchetype,whoseonlydefinedoperation
isauser-definedconversiontobool.
TheEqualityComparableArchetypeclearlymeetsthestated
requirementsofthefind()template,sowecancheckwhetherthe
implementationoffind()heldupitsendofthecontractbyattemptingto
instantiatefind()withEqualityComparableArchetype:Clickhereto
viewcodeimage
templateintfind(EqualityComparableArchetypeconst*,int,
EqualityComparableArchetypeconst&);Theinstantiationof
find<EqualityComparableArchetype>willfail,indicatingthatwehave
foundourfirstproblem:theEqualityComparabledescriptionrequires
only==,buttheimplementationoffind()reliesoncomparingT
objectswith!=.Ourimplementationwouldhaveworkedwithmostuser-
definedtypes,whichimplement==and!=asapair,butitwas
actuallyincorrect.Archetypesareintendedtofindsuchproblems
earlyinthedevelopmentoftemplatelibraries.
Alteringtheimplementationoffind()touseequalityratherthaninequality
solvesthisfirstproblem,andthefind()templatewillsuccessfullycompile
withthearchetype:2
Clickheretoviewcodeimage
template<typenameT>
intfind(Tconst*array,intn,Tconst&value){
inti=0;
while(i!=n&&!(array[i]==value))
++i;
returni;
}
Uncoveringthesecondprobleminfind()usingarchetypesrequiresabitmore
ingenuity.Notethatthenewdefinitionoffind()nowappliesthe!operator
directlytotheresultof==.Inthecaseofourarchetype,thisreliesontheuser-
definedconversiontoboolandthebuilt-inlogicalnegationoperator!.A
morecarefulimplementationofConvertibleToBoolArchetypepoisons
operator!sothatitcannotbeusedimproperly:Clickheretoviewcode
image
classConvertibleToBoolArchetype
{
public:
operatorbool()const;
booloperator!()=delete;//logicalnegationwasnotexplicitly
required
};
Wecouldextendthisarchetypefurther,usingdeletedfunctions3toalsopoison
theoperators&&and||tohelpfindproblemsinothertemplatedefinitions.
Typically,atemplateimplementerwillwanttodevelopanarchetypeforevery
conceptidentifiedinthetemplatelibraryandthenusethesearchetypestotest
eachtemplatedefinitionagainstitsstatedrequirements.
28.4Tracers
Sofar,wehavediscussedbugsthatarisewhencompilingorlinkingprograms
thatcontaintemplates.However,themostchallengingtaskofensuringthata
programbehavescorrectlyatruntimeoftenfollowsasuccessfulbuild.
Templatescansometimesmakethistaskalittlemoredifficultbecausethe
behaviorofgenericcoderepresentedbyatemplatedependsuniquelyonthe
clientofthattemplate(certainlymuchmoresothanordinaryclassesand
functions).Atracerisasoftwaredevicethatcanalleviatethataspectof
debuggingbydetectingproblemsintemplatedefinitionsearlyinthe
developmentcycle.
Atracerisauser-definedclassthatcanbeusedasanargumentforatemplate
tobetested.Often,atracerisalsoanarchetype,writtenjusttomeetthe
requirementsofthetemplate.Moreimportant,however,atracershouldgenerate
atraceoftheoperationsthatareinvokedonit.Thisallows,forexample,to
verifyexperimentallytheefficiencyofalgorithmsaswellasthesequenceof
operations.
Hereisanexampleofatracerthatmightbeusedtotestasortingalgorithm:4
Clickheretoviewcodeimage
debugging/tracer.hpp
#include<iostream>
classSortTracer{
private:
intvalue;//integervaluetobesorted
intgeneration;//generationofthistracer
inlinestaticlongn_created=0;//numberofconstructorcalls
inlinestaticlongn_destroyed=0;//numberofdestructorcalls
inlinestaticlongn_assigned=0;//numberofassignments
inlinestaticlongn_compared=0;//numberofcomparisons
inlinestaticlongn_max_live=0;//maximumofexistingobjects
//recomputemaximumofexistingobjects
staticvoidupdate_max_live(){
if(n_created-n_destroyed>n_max_live){
n_max_live=n_created-n_destroyed;
}
}
public:
staticlongcreations(){
returnn_created;
}
staticlongdestructions(){
returnn_destroyed;
}
staticlongassignments(){
returnn_assigned;
}
staticlongcomparisons(){
returnn_compared;
}
staticlongmax_live(){
returnn_max_live;
}
public:
//constructor
SortTracer(intv=0):value(v),generation(1){
++n_created;
update_max_live();
std::cerr<<"SortTracer#"<<n_created
<<",createdgeneration"<<generation
<<"(total:"<<n_created-n_destroyed
<<")\n";
}
//copyconstructor
SortTracer(SortTracerconst&b)
:value(b.value),generation(b.generation+1){
++n_created;
update_max_live();
std::cerr<<"SortTracer#"<<n_created
<<",copiedasgeneration"<<generation
<<"(total:"<<n_created-n_destroyed
<<")\n";
}
//destructor
~SortTracer(){
++n_destroyed;
update_max_live();
std::cerr<<"SortTracergeneration"<<generation
<<"destroyed(total:"
<<n_created-n_destroyed<<")\n";
}
//assignment
SortTracer&operator=(SortTracerconst&b){
++n_assigned;
std::cerr<<"SortTracerassignment#"<<n_assigned
<<"(generation"<<generation
<<"="<<b.generation
<<")\n";
value=b.value;
return*this;
}
//comparison
friendbooloperator<(SortTracerconst&a,
SortTracerconst&b){
++n_compared;
std::cerr<<"SortTracercomparison#"<<n_compared
<<"(generation"<<a.generation
<<"<"<<b.generation
<<")\n";
returna.value<b.value;
}
intval()const{
returnvalue;
}
};
Inadditiontothevaluetosort,value,thetracerprovidesseveralmembersto
traceanactualsort:Foreachobject,generationtracesbyhowmanycopy
operationsitisremovedfromtheoriginal.Thatis,anoriginalhas
generation==1,adirectcopyofanoriginalhasgeneration==2,a
copyofacopyhasgeneration==3,andsoon.Theotherstaticmembers
tracethenumberofcreations(constructorcalls),destructions,assignment
comparisons,andthemaximumnumberofobjectsthateverexisted.
Thisparticulartracerallowsustotrackthepatternofentitycreationand
destructionaswellasassignmentsandcomparisonsperformedbyagiven
template.Thefollowingtestprogramillustratesthisforthestd::sort()
algorithmoftheC++standardlibrary:Clickheretoviewcodeimage
debugging/tracertest.cpp
#include<iostream>
#include<algorithm>
#include"tracer.hpp"
intmain()
{
//preparesampleinput:
SortTracerinput[]={7,3,5,6,4,2,0,1,9,8};
//printinitialvalues:
for(inti=0;i<10;++i){
std::cerr<<input[i].val()<<’’;
}
std::cerr<<’\n’;
//rememberinitialconditions:
longcreated_at_start=SortTracer::creations();
longmax_live_at_start=SortTracer::max_live();
longassigned_at_start=SortTracer::assignments();
longcompared_at_start=SortTracer::comparisons();
//executealgorithm:
std::cerr<<"---[Startstd::sort()]--------------------\n";
std::sort<>(&input[0],&input[9]+1);
std::cerr<<"---[Endstd::sort()]----------------------\n";
//verifyresult:
for(inti=0;i<10;++i){
std::cerr<<input[i].val()<<’’;
}
std::cerr<<"\n\n";
//finalreport:
std::cerr<<"std::sort()of10SortTracer’s"
<<"wasperformedby:\n"
<<SortTracer::creations()-created_at_start
<<"temporarytracers\n"
<<"upto"
<<SortTracer::max_live()
<<"tracersatthesametime("
<<max_live_at_start<<"before)\n"
<<SortTracer::assignments()-assigned_at_start
<<"assignments\n"
<<SortTracer::comparisons()-compared_at_start
<<"comparisons\n\n";
}
Runningthisprogramcreatesaconsiderableamountofoutput,butmuchcanbe
concludedfromthefinalreport.Foroneimplementationofthestd::sort()
function,wefindthefollowing:Clickheretoviewcodeimage
std::sort()of10SortTracer’swasperformedby:
9temporarytracers
upto11tracersatthesametime(10before)
33assignments
27comparisons
Forexample,weseethatalthoughninetemporarytracerswerecreatedinour
programwhilesorting,atmosttwoadditionaltracersexistedatanyonetime.
Ourtracerthusfulfillstworoles:Itprovesthatthestandardsort()
algorithmrequiresnomorefunctionalitythanourtracer(e.g.,operators==and
>werenotneeded),anditgivesusasenseofthecostofthealgorithm.Itdoes
not,however,revealmuchaboutthecorrectnessofthesortingtemplate.
28.5Oracles
Tracersarerelativelysimpleandeffective,buttheyallowustotracethe
executionoftemplatesonlyforspecificinputdataandforaspecificbehaviorof
itsrelatedfunctionality.Wemaywonder,forexample,whatconditionsmustbe
metbythecomparisonoperatorforthesortingalgorithmtobemeaningful(or
correct),butinourexample,wehaveonlytestedacomparisonoperatorthat
behavesexactlylikeless-thanforintegers.
Anextensionoftracersisknowninsomecirclesasoracles(orrun-time
analysisoracles).Theyaretracersthatareconnectedtoaninferenceengine—a
programthatcanrememberassertionsandreasonsaboutthemtoinfercertain
conclusions.
Oraclesallowus,insomecases,toverifytemplatealgorithmsdynamically
withoutfullyspecifyingthesubstitutingtemplatearguments(theoraclesarethe
arguments)ortheinputdata(theinferenceenginemayrequestsomesortof
inputassumptionwhenitgetsstuck).However,thecomplexityofthealgorithms
thatcanbeanalyzedinthiswayisstillmodest(becauseofthelimitationsofthe
inferenceengines),andtheamountofworkisconsiderable.Forthesereasons,
wedonotdelveintothedevelopmentoforacles,buttheinterestedreadershould
examinethepublicationmentionedintheafternotes(andthereferences
containedtherein).
28.6Afternotes
AfairlysystematicattempttoimproveC++compilerdiagnosticsbyadding
dummycodeinhigh-leveltemplatescanbefoundinJeremySiek’sConcept
CheckLibrary(see[BCCL]).ItispartoftheBoostlibrary(see[Boost]).
RobertKlarerandJohnMaddockproposedthestatic_assertfeatureto
helpprogrammerscheckconditionsatcompiletime.Itwasamongtheearliest
featuresaddedtowhatwouldlaterbecomeC++11.Priortothat,itwas
commonlyexpressedasalibraryormacrousingtechniquessimilartothose
describedinSection28.1onpage652.TheBoost.StaticAssertlibraryisone
suchimplementation.
TheMELASsystemprovidedoraclesforcertainpartsoftheC++standard
library,allowingverificationofsomeofitsalgorithms.Thissystemisdiscussed
in[MusserWangDynaVeri].5
1Andifyou’vemadeitthisfarinthebook,nodoubtyou’veencounterederror
messagesthatmakethatinitialexamplelooktame!
2Theprogramwillcompilebutitwillnotlink,becauseweneverdefinedthe
overloadedoperator==.That’stypicalforarchetypes,whicharegenerally
meantonlyascompile-timecheckingaids.
3Deletedfunctionsarefunctionsthatparticipateinoverloadresolutionas
normalfunctions.Iftheyareselectedbyoverloadresolution,however,the
compilerproducesanerror.
4BeforeC++17,wehadtoinitializethestaticmembersoutsidetheclass
declarationinonetranslationunit.
5Oneauthor,DavidMusser,wasalsoakeyfigureinthedevelopmentofthe
C++standardlibrary.Amongotherthings,hedesignedandimplementedthe
firstassociativecontainers.
AppendixA
TheOne-DefinitionRule
AffectionatelyknownastheODR,theone-definitionruleisacornerstoneforthe
well-formedstructuringofC++programs.Themostcommonconsequencesof
theODRaresimpleenoughtorememberandapply:Definenoninlinefunctions
orobjectsexactlyonceacrossallfiles,anddefineclasses,inlinefunctions,and
inlinevariablesatmostoncepertranslationunit,makingsurethatalldefinitions
forthesameentityareidentical.
However,thedevilisinthedetails,andwhencombinedwithtemplate
instantiation,thesedetailscanbedaunting.Thisappendixismeanttoprovidea
comprehensiveoverviewoftheODRfortheinterestedreader.Wealsoindicate
whenspecificrelatedissuesareexpoundedoninthemaintext.
A.1TranslationUnits
InpracticewewriteC++programsbyfillingfileswith“code.”However,the
boundarysetbyafileisnotterriblyimportantinthecontextoftheODR.
Instead,whatmattersaretranslationunits.Essentially,atranslationunitisthe
resultofapplyingthepreprocessortoafileyoufeedtoyourcompiler.The
preprocessordropssectionsofcodenotselectedbyconditionalcompilation
directives(#if,#ifdef,andfriends),dropscomments,inserts#included
files(recursively),andexpandsmacros.
Hence,asfarastheODRisconcerned,havingthefollowingtwofilesClick
heretoviewcodeimage
//══header.hpp:
#ifdefDO_DEBUG
#definedebug(x)std::cout<<x<<’\n’
#else
#definedebug(x)
#endif
voiddebugInit();
//══myprog.cpp:
#include"header.hpp"
intmain()
{
debugInit();
debug("main()");
}
isequivalenttothefollowingsinglefile://══myprog.cpp:
voiddebugInit();
intmain()
{
debugInit();
}
Connectionsacrosstranslationunitboundariesareestablishedbyhaving
correspondingdeclarationswithexternallinkageintwotranslationunits(e.g.,
twodeclarationsoftheglobalfunctiondebugInit()).
Notethattheconceptofatranslationunitisalittlemoreabstractthanjusta
“preprocessedfile.”Forexample,ifweweretofeedapreprocessedfiletwiceto
acompilertoformasingleprogram,itwouldbringintotheprogramtwodistinct
translationunits(thereisnopointindoingso,however).
A.2DeclarationsandDefinitions
Thetermsdeclarationanddefinitionareoftenusedinterchangeablyincommon
“programmertalk.”InthecontextoftheODR,however,theexactmeaningof
thesewordsisimportant.1
AdeclarationisaC++constructthat(usually)2introducesorreintroducesa
nameinyourprogram.Adeclarationcanalsobeadefinition,dependingon
whichentityitintroducesandhowitintroducesit:•Namespacesand
namespacealiases:Thedeclarationsofnamespacesandtheiraliasesarealways
alsodefinitions,althoughthetermdefinitionisunusualinthiscontextbecause
thelistofmembersofanamespacecanbe“extended”atalatertime(unlike
classesandenumerationtypes,forexample).
•Classes,classtemplates,functions,functiontemplates,memberfunctions,
andmemberfunctiontemplates:Thedeclarationisadefinitionifandonlyif
thedeclarationincludesabrace-enclosedbodyassociatedwiththename.This
ruleincludesunions,operators,memberoperators,staticmemberfunctions,
constructorsanddestructors,andexplicitspecializationsoftemplateversions
ofsuchthings(i.e.,anyclass-likeandfunction-likeentity).
•Enumerations:Thedeclarationisadefinitionifandonlyifitincludesthe
brace-enclosedlistofenumerators.
•Localvariablesandnonstaticdatamembers:Theseentitiescanalwaysbe
treatedasdefinitions,althoughthedistinctionrarelymatters.Notethatthe
declarationofafunctionparameterinafunctiondefinitionisitselfadefinition
becauseitdenotesalocalvariable,butafunctionparameterinafunction
declarationthatisnotadefinitionisnotadefinition.
•Globalvariables:Ifthedeclarationisnotdirectlyprecededbyakeyword
externorifithasaninitializer,thedeclarationofaglobalvariableisalsoa
definitionofthatvariable.Otherwise,itisnotadefinition.
•Staticdatamembers:Thedeclarationisadefinitionifandonlyifitappears
outsidetheclassorclasstemplateofwhichitisamemberoritisdeclared
inlineorconstexprintheclassorclasstemplate.
•Explicitandpartialspecializations:Thedeclarationisadefinitionifthe
declarationfollowingthetemplate<>ortemplate<…>isitselfa
definition,exceptthattheexplicitspecializationofastaticdatamemberor
staticdatamembertemplateisadefinitiononlyifitincludesaninitializer.
Otherdeclarationsarenotdefinitions.Thatincludestypealiases(withtypedef
orusing),usingdeclarations,usingdirectives,templateparameterdeclarations,
explicitinstantiationdirective,static_assertdeclarations,andsoon.
A.3TheOne-DefinitionRuleinDetail
Asweimpliedintheintroductiontothisappendix,therearemanydetailstothe
actualODR.Weorganizetherule’sconstraintsbytheirscope.
A.3.1One-per-ProgramConstraints
Therecanbeatmostonedefinitionofthefollowingitemsperprogram:•
Noninlinefunctionsandnoninlinememberfunctions(includingfull
specializationsoffunctiontemplates)•Noninlinevariables(essentially,
variablesdeclaredinanamespacescopeorintheglobalscope,andwithoutthe
staticspecifier)•NoninlinestaticdatamembersForexample,aC++
programconsistingofthefollowingtwotranslationunitsisinvalid:Clickhereto
viewcodeimage
//══translationunit1:
intcounter;
//══translationunit2:
intcounter;//ERROR:definedtwice(ODRviolation)
Thisruledoesnotapplytoentitieswithinternallinkage(essentially,entities
declaredwiththestaticspecifierintheglobalscopeorinanamespacescope)
becauseevenwhentwosuchentitieshavethesamename,theyareconsidered
distinct.Inthesamevein,entitiesdeclaredinunnamednamespacesare
considereddistinctiftheyappearindistincttranslationunits;inC++11andlater,
suchentitiesalsohaveinternallinkagebydefault,butpriortoC++11theyhad
externallinkagebydefault.Forexample,thefollowingtwotranslationunitscan
becombinedintoavalidC++program:Clickheretoviewcodeimage
//══translationunit1:
staticintcounter=2;//unrelatedtoothertranslationunits
namespace{
voidunique()//unrelatedtoothertranslationunits
{
}
}
//══translationunit1:
staticintcounter=0;//unrelatedtoothertranslationunits
namespace{
voidunique()//unrelatedtoothertranslationunits
{
++counter;
}
}
intmain()
{
unique();
}
Furthermore,theremustbeexactlyoneofthepreviouslymentioneditemsinthe
programiftheyareusedinacontextotherthanthediscardedbranchofa
constexprifstatement(afeatureonlyavailableinC++17;seeSection14.6on
page263).Thetermusedinthiscontexthasaprecisemeaning.Itindicatesthat
thereissomesortofreferencetotheentitysomewhereintheprogramthat
causestheentitytobeneededforstraightforwardcodegeneration.3This
referencecanbeanaccesstothevalueofavariable,acalltoafunction,orthe
addressofsuchanentity.Thisreferencecanbeexplicitinthesource,oritcanbe
implicit.Forexample,anewexpressionmaycreateanimplicitcalltothe
associateddeleteoperatortohandlesituationswhenaconstructorthrowsan
exceptionrequiringtheunused(butallocated)memorytobecleanedup.
Anotherexampleconsistsofcopyconstructors,whichmustbedefinedevenif
theyendupbeingoptimizedaway(unlessthelanguagerequiresthemtobe
optimizedaway,whichisfrequentlythecaseinC++17).Virtualfunctionsare
alsoimplicitlyused(bytheinternalstructuresthatenablevirtualfunctioncalls),
unlesstheyarepurevirtualfunctions.Severalotherkindsofimplicitusesexist,
butweomitthemforthesakeofconciseness.
Somereferencesdonotconstituteauseintheprevioussense:Thosethat
appearinanunevaluatedoperand(e.g.,theoperandofasizeofor
decltypeoperator).Theoperandofatypeidoperator(seeSection9.1.1on
page138)isunevaluatedonlyinsomecases.Specifically,ifareferenceappears
aspartofatypeidoperator,itisnotauseintheprevioussense,unlessthe
argumentofthetypeidoperatorendsupdesignatingapolymorphicobject(an
objectwith—possiblyinherited—virtualfunctions).Forexample,considerthe
followingsingle-fileprogram:Clickheretoviewcodeimage
#include<typeinfo>
classDecider{
#ifdefined(DYNAMIC)
virtual~Decider(){
}
#endif
};
externDeciderd;
intmain()
{
charconst*name=typeid(d).name();
return(int)sizeof(d);
}
ThisisavalidprogramifandonlyifthepreprocessorsymbolDYNAMICisnot
defined.Indeed,thevariabledisnotdefined,butthereferencetodin
sizeof(d)doesnotconstituteause,andthereferenceintypeid(d)isa
useonlyifdisanobjectofapolymorphictype(because,ingeneral,itisnot
alwayspossibletodeterminetheresultofapolymorphictypeidoperation
untilruntime).
AccordingtotheC++standard,theconstraintsdescribedinthissectiondonot
requireadiagnosticfromaC++implementation.Inpractice,theyareusually
reportedbylinkersasduplicateormissingdefinitions.
A.3.2One-per-TranslationUnitConstraints
Noentitycanbedefinedmorethanonceinatranslationunit.Sothefollowing
exampleisinvalidC++:Clickheretoviewcodeimage
inlinevoidf(){}
inlinevoidf(){}//ERROR:duplicatedefinition
Thisisoneofthemainreasonsforsurroundingthecodeinheaderfileswith
guards://══guarddemo.hpp:
#ifndefGUARDDEMO_HPP
#defineGUARDDEMO_HPP
…
#endif//GUARDDEMO_HPP
Suchguardsensurethatthesecondtimeaheaderfileis#included,its
contentsarediscarded,therebyavoidingtheduplicatedefinitionofaclass,inline
entity,template,andsoon,thatitmaycontain.
TheODRalsospecifiesthatcertainentitiesmustbedefinedincertain
circumstances.Thiscanbethecaseforclasstypes,inlinefunctions,andinlines
variables.Inthefollowingfewparagraphs,wereviewthedetailedrules.
AclasstypeX(includingstructsandunions)mustbedefinedina
translationunitpriortoanyofthefollowingkindsofusesinthattranslation
unit:•ThecreationofanobjectoftypeX(e.g.,asavariabledeclarationor
throughanewexpression).Thecreationcouldbeindirect,forexample,whenan
objectthatitselfcontainsanobjectoftypeXisbeingcreated.
•ThedeclarationofadatamemberoftypeX.
•ApplyingthesizeofortypeidoperatortoanobjectoftypeX.
•ExplicitlyorimplicitlyaccessingmembersoftypeX.
•ConvertinganexpressiontoorfromtypeXusinganykindofconversion,or
convertinganexpressiontoorfromapointerorreferencetoX(exceptvoid*)
usinganimplicitcast,static_cast,ordynamic_cast.
•AssigningavaluetoanobjectoftypeX.
•DefiningorcallingafunctionwithanargumentorreturntypeoftypeX.Just
declaringsuchafunctiondoesn’tneedthetypetobedefined,however.
TherulesfortypesalsoapplytotypesXgeneratedfromclasstemplates,which
meansthatthecorrespondingtemplatesmustbedefinedinthosesituationsin
whichsuchatypeXmustbedefined.Thesesituationscreatepointsof
instantiationorPOIs(seeSection14.3.2onpage250).
Inlinefunctionsmustbedefinedineverytranslationunitinwhichtheyare
used(inwhichtheyarecalledortheiraddressistaken).However,unlikeclass
types,theirdefinitioncanfollowthepointofuse:inlineintnotSoFast();
intmain()
{
notSoFast();
}
inlineintnotSoFast()
{
}
AlthoughthisisvalidC++,somecompilersbasedonoldertechnologydonot
actually“inline”thecalltoafunctionwithabodythathasnotbeenseenyet;
hencethedesiredeffectmaynotbeachieved.
Justaswithclasstemplates,theuseofafunctiongeneratedfroma
parameterizedfunctiondeclaration(afunctionormemberfunctiontemplate,ora
memberfunctionofaclasstemplate)createsapointofinstantiation.Unlike
classtemplates,however,thecorrespondingdefinitioncanappearafterthepoint
ofinstantiation.
ThefacetsoftheODRexplainedinthissubsectionaregenerallyeasily
verifiedbyC++compilers;hencetheC++standardrequiresthatcompilersissue
somesortofdiagnosticwhenoneoftheserulesisviolated.Anexceptionisthe
lackofdefinitionofaparameterizedfunction.Suchsituationsaretypicallynot
diagnosed.
A.3.3Cross-TranslationUnitEquivalenceConstraints
Theabilitytodefinecertainkindsofentitiesinmorethanonetranslationunit
bringswithitthepotentialforanewkindoferror:multipledefinitionsthatdon’t
match.Unfortunately,sucherrorsarehardtodetectbytraditionalcompiler
technologyinwhichtranslationunitsareprocessedoneatatime.Consequently,
theC++standarddoesn’tmandatethatdifferencesinmultipledefinitionsbe
detectedordiagnosed(itdoesallowit,ofcourse).Ifthiscross-translationunit
constraintisviolated,however,theC++standardqualifiesthisasleadingto
undefinedbehavior,whichmeansthatanythingreasonableorunreasonablemay
happen.Typically,suchundiagnosederrorsmayleadtoprogramcrashesor
wrongresults,butinprincipletheycanalsoleadtoother,moredirect,kindsof
damage(e.g.,filecorruption).4
Thecross-translationunitconstraintsspecifythatwhenanentityisdefinedin
twodifferentplaces,thetwoplacesmustconsistofexactlythesamesequenceof
tokens(thekeywords,operators,identifiers,andsoforth,remainingafter
preprocessing).Furthermore,thesetokensmustmeanthesamethingintheir
respectivecontext(e.g.,theidentifiersmayneedtorefertothesamevariable).
Considerthefollowingexample://══translationunit1:
staticintcounter=0;
inlinevoidincreaseCounter()
{
++counter;
}
intmain()
{
}
//══translationunit2:
staticintcounter=0;
inlinevoidincreaseCounter()
{
++counter;
}
Thisexampleisinerrorbecauseeventhoughthetokensequencefortheinline
functionincreaseCounter()looksidenticalinbothtranslationunits,they
containatokencounterthatreferstotwodifferententities.Indeed,because
thetwovariablesnamedcounterhaveinternallinkage(staticspecifier),
theyareunrelateddespitehavingthesamename.Notethatthisisanerroreven
thoughneitheroftheinlinefunctionsisactuallyused.
Placingthedefinitionsofentitiesthatcanbedefinedinmultipletranslation
unitsinheaderfilesthatare#includedwheneverthedefinitionsareneeded
ensuresthattokensequencesareidenticalinalmostallsituations.5Withthis
approach,situationsinwhichtwoidenticaltokensrefertodifferentthings
becomefairlyrare,butwhenitdoeshappen,theresultingerrorsareoften
mysteriousandhardtotrack.
Thecross-translationunitconstraintsapplynotonlytoentitiesthatcanbe
definedinmultipleplacesbutalsotodefaultargumentsindeclarations.Inother
words,thefollowingprogramhasundefinedbehavior://══translationunit1:
voidunused(int=3);
intmain()
{
}
//══translationunit2:
voidunused(int=4);Weshouldnoteherethattheequivalenceoftokenstreams
cansometimesinvolvesubtleimpliciteffects.Thefollowingexampleislifted
(inaslightlymodifiedform)fromtheC++standard:Clickheretoviewcode
image
//══translationunit1:
classX{
public:
X(int,int);
X(int,int,int);
};
X::X(int,int=0)
{
}
classD{
Xx=0;
};
Dd1;//X(int,int)calledbyD()
//══translationunit2:
classX{
public:
X(int,int);
X(int,int,int);
};
X::X(int,int=0,int=0)
{
}
classD:publicX{
Xx=0;
};
Dd2;//X(int,int,int)calledbyD()
Inthisexample,theproblemoccursbecausetheimplicitlygenerateddefault
constructorofclassDisdifferentinthetwotranslationunits.OnecallstheX
constructortakingtwoarguments,andtheothercallstheXconstructortaking
threearguments.Ifanything,thisexampleisanadditionalincentivetolimit
defaultargumentstoonelocationintheprogram(ifpossible,thislocation
shouldbeinaheaderfile).Fortunately,placingdefaultargumentsonout-of-
classdefinitionsisararepractice.
Thereisalsoanexceptiontotherulethatsaysthatidenticaltokensmustrefer
toidenticalentities.Ifidenticaltokensrefertounrelatedconstantsthathavethe
samevalueandtheaddressoftheresultingexpressionsisnotused(noteven
implicitlybybindingareferencetoavariableproducingtheconstant),thenthe
tokensareconsideredequivalent.Thisexceptionallowsforprogramstructures
likethefollowing://══header.hpp:
#ifndefHEADER_HPP
#defineHEADER_HPP
intconstlength=10;
classMiniBuffer{
charbuf[length];
…
};
#endif//HEADER_HPP
Inprinciple,whenthisheaderfileisincludedintwodifferenttranslationunits,
twodistinctconstantvariablesnamedlengtharecreatedbecauseconstin
thiscontextimpliesstatic.However,suchconstantvariablesareoftenmeant
todefinecompile-timeconstantvalues,notaparticularstoragelocationatrun
time.Hence,ifwedon’tforcesuchastoragelocationtoexist(byreferringtothe
addressofthevariable),itissufficientforthetwoconstantstohavethesame
value.
Finally,anoteabouttemplates.Thenamesintemplatesbindintwophases.
Nondependentnamesbindatthepointwherethetemplateisdefined.Forthese,
theequivalencerulesarehandledsimilarlytoothernontemplatedefinitions.For
namesthatbindatthepointofinstantiation,theequivalencerulesmustbe
appliedatthatpoint,andthebindingsmustbeequivalent.
1Wealsothinkit’sagoodhabittohandlethetermscarefullywhenexchanging
ideasaboutCandC++.Wedosothroughoutthisbook.
2Someconstructs(suchasstatic_assert)donotintroduceanynamesbut
aresyntacticallytreatedasdeclarations.
3Variousoptimizationtechniquesmaycausethisneedtoberemoved,butthe
languagedoesn’tassumesuchoptimizations.
4Version1ofthegcccompileractuallyjokinglydidthisbystartingthegame
ofRogueinsomesituationslikethis.
5Occasionally,conditionalcompilationdirectivesevaluatedifferentlyin
differenttranslationunits.Usesuchdirectiveswithcare.Otherdifferencesare
possibletoo,buttheyareevenlesscommon.
AppendixB
ValueCategories
ExpressionsareacornerstoneoftheC++language,providingtheprimary
mechanismbywhichitcanexpresscomputations.Everyexpressionhasatype,
whichdescribesthestatictypeofthevaluethatitscomputationproduces.The
expression7hastypeint,asdoestheexpression5+2,andtheexpressionx
ifxisavariableoftypeint.Eachexpressionalsohasavaluecategory,which
describessomethingabouthowthevaluewasformedandaffectshowthe
expressionbehaves.
B.1TraditionalLvaluesandRvalues
Historically,therewereonlytwovaluecategories:lvaluesandrvalues.Lvalues
areexpressionsthatrefertoactualvaluesstoredinmemoryorinamachine
register,suchastheexpressionxwherexisthenameofavariable.These
expressionsmaybemodifiable,allowingonetoupdatethestoredvalue.For
example,ifxisavariableoftypeint,thefollowingassignmentwillreplace
thevalueofxwith7:x=7;
Thetermlvalueisderivedfromtheroletheseexpressionscouldplaywithinan
assignment:Theletter“l”standsfor“left-handside”because(historically,inC)
onlylvaluesmayoccurontheleft-handsideoftheassignment.Conversely,
rvalues(where“r”standsfor“right-handside”)couldoccuronlyontheright-
handsideofanassignmentexpression.
However,whenCwasstandardizedin1989,thingschanged:Whileanint
conststillwasavaluestoredinmemory,itcouldnotoccurontheleft-hand
sideofanassignment:Clickheretoviewcodeimage
intconstx;//xisanonmodifiablelvalue
x=7;//ERROR:modifiablelvaluerequiredontheleft
C++changedthingsevenfurther:Classrvaluescanoccurontheleft-handside
ofassignments.Suchassignmentsareactuallyfunctioncallstotheappropriate
assignmentoperatoroftheclassratherthan“simple”assignmentsforscalar
types,sotheyfollowthe(separate)rulesofmemberfunctioncalls.
Becauseofallthesechanges,thetermlvalueisnowsometimessaidtostand
forlocalizablevalue.Expressionsthatrefertoavariablearenottheonlykindof
lvalueexpression.Anotherclassofexpressionsthatarelvaluesincludepointer
dereferenceoperations(e.g.,*p),whichrefertothevaluestoredattheaddress
thepointerreferences,andexpressionsthatrefertoamemberofaclassobject
(e.g.,p->data).Evencallstofunctionsthatreturnvaluesof“traditional”
lvaluereferencetypedeclaredwith&arelvalues.Forexample(seeSectionB.4
onpage679fordetails):Clickheretoviewcodeimage
std::vector<int>v;
v.front()//yieldsanlvaluebecausethereturntypeisanlvalue
reference
Perhapssurprisingly,stringliteralsarealso(nonmodifiable)lvalues.
Rvaluesarepuremathematicalvalues(suchas7orthecharacter’a’)that
don’tnecessarilyhaveanyassociatedstorage;theycomeintoexistenceforthe
purposeofacomputationbutcannotbereferencedagainoncetheyhavebeen
used.Inparticular,anyliteralvalueexceptstringliterals(e.g.,7,’a’,true,
nullptr)arervalues,asaretheresultsofmanybuilt-inarithmetic
computations(e.g.,x+5forxofintegertype)andcallstofunctionsthat
returnaresultbyvalue.Thatis,alltemporariesarervalues.(Thatdoesn’tapply
tonamedreferencesthatrefertothem,though.)
B.1.1Lvalue-to-RvalueConversions
Duetotheirephemeralnature,rvaluesarenecessarilyrestrictedtotheright-hand
sideofa(“simple”)assignment:Anassignment7=8doesn’tmakesense
becausethemathematical7isn’tallowedtoberedefined.Lvalues,ontheother
hand,don’tappeartohavethesamerestriction:Onecancertainlycomputethe
assignmentx=ywhenxandyarevariablesofcompatibletype,eventhough
theexpressionsxandyarebothlvalues.
Theassignmentx=yworksbecausetheexpressionontheright-handside,
y,undergoesanimplicitconversioncalledthelvalue-to-rvalueconversion.As
itsnameimplies,thelvalue-to-rvalueconversiontakesanlvalueandproducesan
rvalueofthesametypebyreadingfromthestorageorregisterassociatedwith
thelvalue.Thisconversionthereforeaccomplishestwothings:First,itensures
thatanlvaluecanbeusedwhereveranrvalueisexpected(e.g.,astheright-hand
sideofanassignmentorinamathematicalexpressionsuchasx+y).Second,
itidentifieswhereintheprogramthecompiler(priortooptimization)mayemit
a“load”instructiontoreadavaluefrommemory.
B.2ValueCategoriesSinceC++11
WhenrvaluereferenceswereintroducedinC++11insupportofmove
semantics,thetraditionalpartitioningofexpressionsintolvaluesandrvalueswas
nolongersufficienttodescribealltheC++11languagebehaviors.TheC++
standardizationcommitteethereforeredesignedthevaluecategorysystembased
onthreecoreandtwocompositecategories(seeFigureB.1).Thecorecategories
are:lvalue,prvalue(“purervalue”),andxvalue.Thecompositecategoriesare:
glvalue(“generalizedlvalue,”whichistheunionoflvalueandxvalue)and
rvalue(theunionofxvalueandprvalue).
Notethatallexpressionsarestilleitherlvaluesorrvalues,butthervalues
categoryisnowfurthersubdivided.
Clickheretoviewcodeimage
FigureB.1.ValueCategoriessinceC++11
ThisC++11categorizationhasremainedineffect,butinC++17the
characterizationofthecategorieswerereformulatedasfollows:•Aglvalueisan
expressionwhoseevaluationdeterminestheidentityofanobject,bit-field,or
function(i.e.,anentitythathasstorage).
•Aprvalueisanexpressionwhoseevaluationinitializesanobjectorabit-field,
orcomputesthevalueoftheoperandofanoperator.
•Anxvalueisaglvaluedesignatinganobjectorbit-fieldwhoseresourcescan
bereused(usuallybecauseitisaboutto“expire”—the“x”inxvalueoriginally
camefrom“eXpiringvalue”).
•Anlvalueisaglvaluethatisnotanxvalue.
•Anrvalueisanexpressionthatiseitheraprvalueoranxvalue.
NotethatinC++17(andtosomeextent,inC++11andC++14),theglvaluevs.
prvaluedichotomyisarguablymorefundamentalthanthetraditionallvaluevs.
rvaluedistinction.
AlthoughthisdescribesthecharacterizationintroducedinC++17,those
descriptionsalsoapplytoC++11andC++14(thepriordescriptionswere
equivalentbuthardertoreasonabout).
Exceptforbitfields,glvaluesproduceentitieswithanaddress.Thataddress
maybethatofasubobjectofalargerenclosingobject.Inthecaseofabaseclass
subobject,thetypeoftheglvalue(expression)iscalleditsstatictype,andthe
typeofthemostderivedobjectthatbaseclassispartofiscalledthedynamic
typeoftheglvalue.Iftheglvaluedoesnotproduceabaseclasssubobject,its
staticanddynamictypesareidentical(i.e.,thetypeoftheexpression).
Examplesoflvaluesare:•Expressionsthatdesignatevariablesorfunctions•
Applicationsofthebuilt-inunary*operator(“pointerindirection”)•An
expressionthatisjustastringliteral•Acalltoafunctionwithareturntypethat
isanlvaluereferenceExamplesofprvaluesare:•Expressionsthatconsistofa
literalthatisnotastringliteralorauser-definedliteral1
•Applicationsofthebuilt-inunary&operator(i.e.,takingtheaddressofan
expression)•Applicationsofbuilt-inarithmeticoperators•Acalltoafunction
withareturntypethatisnotareferencetype•Lambdaexpressions
Examplesofxvaluesare:•Acalltoafunctionwithareturntypethatisan
rvaluereferencetoanobjecttype(e.g.,std::move())•Acasttoanrvalue
referencetoanobjecttypeNotethatrvaluereferencestofunctiontypesproduce
lvalues,notxvalues.
It’sworthemphasizingthatglvalues,prvalues,xvalues,andsoon,are
expressions,andnotvalues2orentities.Forexample,avariableisnotanlvalue
eventhoughanexpressiondenotingavariableisanlvalue:Clickheretoview
codeimage
intx=3;//xhereisavariable,notanlvalue.3isaprvalue
initializing
//thevariablex.
inty=x;//xhereisanlvalue.Theevaluationofthatlvalue
expressiondoesnot
//producethevalue3,butadesignationofanobjectcontainingthe
value3.
//Thatlvalueisthenthenconvertedtoaprvalue,whichiswhat
initializesy.
B.2.1TemporaryMaterialization
Wepreviouslymentionedthatlvaluesoftenundergoanlvalue-to-rvalue
conversion3becauseprvaluesarethekindsofexpressionsthatinitializeobjects
(orprovidetheoperandsformostbuilt-inoperators).
InC++17,thereisadualtothisconversion,knownastemporary
materialization(butitcouldjustaswellhavebeencalled“prvalue-to-xvalue
conversion”):Anytimeaprvaluevalidlyappearswhereaglvalue(which
includesthexvaluecase)isexpected,atemporaryobjectiscreatedand
initializedwiththeprvalue(recallthatprvaluesareprimarily“initializing
values”),andtheprvalueisreplacedbyanxvaluedesignatingthetemporary.For
example:intf(intconst&);
intr=f(3);Becausef()inthisexamplehasareferenceparameter,itexpectsa
glvalueargument.However,theexpression3isaprvalue.The“temporary
materialization”rulethereforekicksin,andtheexpression3is“converted”toan
xvaluedesignatingatemporaryobjectinitializedwiththevalue3.
Moregenerally,atemporaryismaterializedtobeinitializedwithaprvaluein
thefollowingsituations:•Aprvalueisboundtoareference(e.g.,thatcallf(3)
above).
•Amemberofaclassprvalueisaccessed.
•Anarrayprvalueissubscripted.
•Anarrayprvalueisconvertedtoapointertoitsfirstelement(i.e.,arraydecay).
•Aprvalueappearsinabracedinitializerlistthat,forsometypeX,initializesan
objectoftypestd::initializer_list<X>.
•Thesizeofortypeidoperatorisappliedtoaprvalue.
•Aprvalueisthetop-levelexpressioninastatementoftheform“expr;”oran
expressioniscasttovoid.
Thus,inC++17,theobjectinitializedbyaprvalueisalwaysdeterminedbythe
context,and,asaresult,temporariesarecreatedonlywhentheyarereally
needed.PriortoC++17,prvalues(particularlyofclasstype)alwaysimplieda
temporary.Copiesofthosetemporariescouldoptionallybeelidedlateron,buta
compilerstillhadtoenforcemostsemanticsconstraintsofthecopyoperation
(e.g.,acopyconstructormayneedtobecallable).Thefollowingexampleshows
aconsequenceoftheC++17revisionoftherules:Clickheretoviewcodeimage
classN{
public:
N();
N(Nconst&)=delete;//thisclassisneithercopyable…
N(N&&)=delete;//…normovable
};
Nmake_N(){
returnN{};//AlwayscreatesaconceptualtemporarypriortoC++17.
}//InC++17,notemporaryiscreatedatthispoint.
auton=make_N();//ERRORpriortoC++17becausetheprvalueneeds
a
//conceptualcopy.OKsinceC++17,becausenis
//initializeddirectlyfromtheprvalue.
PriortoC++17,theprvalueN{}producedatemporaryoftypeN,butcompilers
wereallowedtoelidecopiesandmovesofthattemporary(whichtheyalways
did,inpractice).Inthiscase,thatmeansthatthetemporaryresultofcalling
make_N()canbeconstructeddirectlyinthestorageofn;nocopyormove
operationisneeded.Unfortunately,pre-C++17compilersstillhavetocheckthat
acopyormoveoperationcouldbemade,andinthisexamplethatisnotpossible
becausethecopyconstructorofNisdeleted(andnomoveconstructoris
generated).Hence,C++11andC++14compilersmustissueanerrorforthis
example.
WithC++17theprvalueNitselfdoesnotproduceatemporary.Instead,it
initializesanobjectdeterminedbythecontext:Inourexample,thatobjectisthe
onedenotedbyn.Nocopyormoveoperationiseverconsidered(thisisnotan
optimization,butalanguageguarantee)andthereforethecodeisvalidC++17.
Weconcludewithanexamplethatshowsavarietyofvaluecategory
situations:Clickheretoviewcodeimage
classX{
};
Xv;
Xconstc;
voidf(Xconst&);//acceptsanexpressionofanyvaluecategory
voidf(X&&);//acceptsprvaluesandxvaluesonlybutisabetter
match
//forthosethanthepreviousdeclaration
f(v);//passesamodifiablelvaluetothefirst
f()f(c);//passesanonmodifiablelvaluetothefirstf()
f(X());//passesaprvalue(sinceC++17materializedasxvalue)to
the2ndf()
f(std::move(v));//passesanxvaluetothesecondf()
B.3CheckingValueCategorieswithdecltype
Withthekeyworddecltype(introducedinC++11),itispossibletocheckthe
valuecategoryofanyC++expression.Foranyexpressionx,decltype((x))
(notethedoubleparentheses)yields:•typeifxisaprvalue•type&ifxisan
lvalue•type&&ifxisanxvalueThedoubleparenthesesindecltype((x))
areneededtoavoidproducingthedeclaredtypeofanamedentityincasewhere
theexpressionxdoesindeednameanentity(inothercases,theparentheses
havenoeffect).Forexample,iftheexpressionxsimplynamesavariablev,the
constructwithoutparenthesesbecomesdecltype(v),whichproducesthe
typeofthevariablevratherthanatypereflectingthevaluecategoryofthe
expressionxreferringtothatvariable.
Thus,usingtypetraitsforanyexpressione,wecancheckitsvaluecategoryas
follows:Clickheretoviewcodeimage
ifconstexpr(std::is_lvalue_reference<decltype((e))>::value){
std::cout<<"expressionislvalue\n";
}
elseifconstexpr(std::is_rvalue_reference<decltype((e))>::value){
std::cout<<"expressionisxvalue\n";
}
else{
std::cout<<"expressionisprvalue\n";
}
SeeSection15.10.2onpage298fordetails.
B.4ReferenceTypes
ReferencetypesinC++—suchasint&—interactwithvaluecategoriesintwo
importantways.Thefirstisthatareferencemaylimitthevaluecategoryofan
expressionitcanbindto.Forexample,anon-constlvaluereferenceoftype
int&canonlybeinitializedwithanexpressionthatisanlvalueoftypeint.
Similarly,anrvaluereferenceoftypeint&&canonlybeinitializedwithan
expressionthatisanrvalueoftypeint.
Thesecondwayinwhichvaluecategoriesinteractwithreferencesiswiththe
returntypesoffunctions,wheretheuseofareferencetypeasthereturntype
affectsthevaluecategoryofacalltothatfunction.Inparticular:•Acalltoa
functionwhosereturntypeisanlvaluereferenceyieldsanlvalue.
•Acalltoafunctionwhosereturntypeisanrvaluereferencetoanobjecttype
yieldsanxvalue(rvaluereferencestofunctiontypesalwaysresultinlvalues).
•Acalltoafunctionthatreturnsanonreferencetypeyieldsaprvalue.
Weillustratetheinteractionsbetweenreferencetypesandvaluecategoriesinthe
followingexample.Given:int&lvalue();
int&&xvalue();
intprvalue();boththevaluecategoryandtypeofagivenexpressioncanbe
determinedviadecltype.AsdescribedinSection15.10.2onpage298,ituses
referencetypestodescribewhentheexpressionisanlvalueorxvalue:Click
heretoviewcodeimage
std::is_same_v<decltype(lvalue()),int&//yieldstruebecauseresult
islvalue
std::is_same_v<decltype(xvalue()),int&&>//yieldstruebecause
resultisxvalue
std::is_same_v<decltype(prvalue()),int>//yieldstruebecause
resultisprvalue
Thus,thefollowingcallsarepossible:Clickheretoviewcodeimage
int&lref1=lvalue();//OK:lvaluereferencecanbindtoanlvalue
int&lref3=prvalue();//ERROR:lvaluereferencecannotbindtoa
prvalue
int&lref2=xvalue();//ERROR:lvaluereferencecannotbindtoan
xvalue
int&&rref1=lvalue();//ERROR:rvaluereferencecannotbindtoan
lvalue
int&&rref2=prvalue();//OK:rvaluereferencecanbindtoa
prvalue
int&&rref3=xvalue();//OK:rvaluereferencecanbindtoan
xrvalue
1User-definedliteralscanleadtolvaluesorrvalues,dependingonthereturn
typeoftheassociatedliteraloperator.
2Whichunfortunatelymeansthatthesetermsaremisnomers.
3IntheworldofC++11valuecategories,thephraseglvalue-to-prvalue
conversionwouldbemoreaccurate,butthetraditionaltermremainsmore
common.
AppendixC
OverloadResolution
Overloadresolutionistheprocessthatselectsthefunctiontocallforagivencall
expression.Considerthefollowingsimpleexample:Clickheretoviewcode
image
voiddisplay_num(int);//#1
voiddisplay_num(double);//#2
intmain()
{
display_num(399);//#1matchesbetterthan#2
display_num(3.99);//#2matchesbetterthan#1
}
Inthisexample,thefunctionnamedisplay_num()issaidtobeoverloaded.
Whenthisnameisusedinacall,aC++compilermustthereforedistinguish
betweenthevariouscandidatesusingadditionalinformation;mostly,this
informationisthetypesofthecallarguments.Inourexample,itmakesintuitive
sensetocalltheintversionwhenthefunctioniscalledwithaninteger
argumentandthedoubleversionwhenafloating-pointargumentisprovided.
Theformalprocessthatattemptstomodelthisintuitivechoiceistheoverload
resolutionprocess.
Thegeneralideasbehindtherulesthatguideoverloadresolutionaresimple
enough,butthedetailshavebecomequitecomplexduringtheC++
standardizationprocess.Thiscomplexitywasdrivenmostlybythedesireto
supportvariousreal-worldexamplesthatintuitively(toahuman)seemtohave
an“obviouslybestmatch,”butwhentryingtoformalizethisintuition,various
subtletiesarose.
Inthisappendix,weprovideareasonablydetailedsurveyoftheoverload
resolutionrules.However,thecomplexityofthisprocessissuchthatwedonot
claimtocovereverypartofthetopic.
C.1WhenDoesOverloadResolutionKickIn?
Overloadresolutionisjustonepartofthecompleteprocessingofafunctioncall.
Infact,itisnotpartofeveryfunctioncall.First,callsthroughfunctionpointers
andcallsthroughpointerstomemberfunctionsarenotsubjecttooverload
resolutionbecausethefunctiontocallisentirelydetermined(atruntime)bythe
pointers.Second,function-likemacroscannotbeoverloadedandaretherefore
notsubjecttooverloadresolution.
Ataveryhighlevel,acalltoanamedfunctioncanbeprocessedinthe
followingway:•Thenameislookeduptoformaninitialoverloadset.
•Ifnecessary,thissetisadjustedinvariousways(e.g.,templateargument
deductionandsubstitutionoccurs,whichcancausesomefunctiontemplate
candidatestobediscarded).
•Anycandidatethatdoesn’tmatchthecallatall(evenafterconsideringimplicit
conversionsanddefaultarguments)iseliminatedfromtheoverloadset.This
resultsinasetofviablefunctioncandidates.
•Overloadresolutionisperformedtofindabestcandidate.Ifthereisone,itis
selected;otherwise,thecallisambiguous.
•Theselectedcandidateischecked.Forexample,ifitisadeletedfunction(i.e.,
onedefinedwith=delete)oraninaccessibleprivatememberfunction,a
diagnosticisissued.
Eachofthesestepshasitsownsubtleties,butoverloadresolutionisarguablythe
mostcomplex.Fortunately,afewsimpleprinciplesclarifythemajorityof
situations.Weexaminetheseprinciplesnext.
C.2SimplifiedOverloadResolution
Overloadresolutionrankstheviablecandidatefunctionsbycomparinghoweach
argumentofthecallmatchesthecorrespondingparameterofthecandidates.For
onecandidatetobeconsideredbetterthananother,thebettercandidatecannot
haveanyofitsparametersbeaworsematchthanthecorrespondingparameterin
theothercandidate.Thefollowingexampleillustratesthis:Clickheretoview
codeimage
voidcombine(int,double);
voidcombine(long,int);
intmain()
{
combine(1,2);//ambiguous!
}
Inthisexample,thecalltocombine()isambiguousbecausethefirst
candidatematchesthefirstargument(theliteral1oftypeint)best,whereasthe
secondcandidatematchesthesecondargumentbest.Wecouldarguethatintis
insomesenseclosertolongthantodouble(whichsupportschoosingthe
secondcandidate),butC++doesnotattempttodefineameasureofcloseness
thatinvolvesmultiplecallarguments.
Giventhisfirstprinciple,weareleftwithspecifyinghowwellagiven
argumentmatchesthecorrespondingparameterofaviablecandidate.Asafirst
approximation,wecanrankthepossiblematchesasfollows(frombestto
worst):1.Perfectmatch.Theparameterhasthetypeoftheexpression,orithasa
typethatisareferencetothetypeoftheexpression(possiblywithaddedconst
and/orvolatilequalifiers).
2.Matchwithminoradjustments.Thisincludes,forexample,thedecayofan
arrayvariabletoapointertoitsfirstelementortheadditionofconstto
matchanargumentoftypeint**toaparameteroftypeintconst*
const*.
3.Matchwithpromotion.Promotionisakindofimplicitconversionthat
includestheconversionofsmallintegraltypes(suchasbool,char,short,
andsometimesenumerations)toint,unsignedint,long,or
unsignedlong,andtheconversionoffloattodouble.
4.Matchwithstandardconversionsonly.Thisincludesanysortofstandard
conversion(suchasinttofloat)orconversionfromaderivedclasstoone
ofitspublic,unambiguousbaseclassesbutexcludestheimplicitcalltoa
conversionoperatororaconvertingconstructor.
5.Matchwithuser-definedconversions.Thisallowsanykindofimplicit
conversion.
6.Matchwithellipsis(…).Anellipsisparametercanmatchalmostanytype.
However,thereisoneexception:Classtypeswithanontrivialcopy
constructormayormaynotbevalid(implementationsarefreetoallowor
disallowthis).
Thefollowingcontrivedexampleillustratessomeofthesematches:Clickhereto
viewcodeimage
intf1(int);//#1
intf1(double);//#2
f1(4);//calls#1:perfectmatch(#2requiresastandard
conversion)
intf2(int);//#3
intf2(char);//#4
f2(true);//calls#3:matchwithpromotion
//(#4requiresstrongerstandardconversion)
classX{
public:
X(int);
};
intf3(X);//#5
intf3(…);//#6
f3(7);//calls#5:matchwithuser-definedconversion
//(#6requiresamatchwithellipsis)
Notethatoverloadresolutionoccursaftertemplateargumentdeduction,andthis
deductiondoesnotconsiderallthesesortsofconversions.Forexample:Click
heretoviewcodeimage
template<typenameT>
classMyString{
public:
MyString(Tconst*);//convertingconstructor
…
};
template<typenameT>
MyString<T>truncate(MyString<T>const&,int);
intmain()
{
MyString<char>str1,str2;
str1=truncate<char>("HelloWorld",5);//OK
str2=truncate("HelloWorld",5);//ERROR
}
Theimplicitconversionprovidedthroughtheconvertingconstructorisnot
consideredduringtemplateargumentdeduction.Theassignmenttostr2finds
noviablefunctiontruncate();henceoverloadresolutionisnotperformedat
all.
Inthecontextoftemplateargumentdeduction,recallalsothatanrvalue
referencetoatemplateparametercandeducetoeitheranlvaluereferencetype
(afterreferencecollapsing)ifthecorrespondingargumentisanlvalueortoan
rvaluereferencetypeifthatargumentisanrvalue(seeSection15.6onpage
277).Forexample:Clickheretoviewcodeimage
template<typenameT>voidstrange(T&&,T&&);
template<typenameT>voidbizarre(T&&,double&&);
intmain()
{
strange(1.2,3.4);//OK:withTdeducedtodouble
doubleval=1.2;
strange(val,val);//OK:withTdeducedtodouble&
strange(val,3.4);//ERROR:conflictingdeductions
bizarre(val,val);//ERROR:lvaluevaldoesn’tmatchdouble&&
}
Thepreviousprinciplesareonlyafirstapproximation,buttheycovermany
cases.Yettherearequiteafewcommonsituationsthatarenotadequately
explainedbytheserules.Weproceedwithabriefdiscussionofthemost
importantrefinementsoftheserules.
C.2.1TheImpliedArgumentforMemberFunctions
Callstononstaticmemberfunctionshaveahiddenparameterthatisaccessiblein
thedefinitionofthememberfunctionas*this.Foramemberfunctionofa
classMyClass,thehiddenparameterisusuallyoftypeMyClass&(fornon-
constmemberfunctions)orMyClassconst&(forconstmember
functions).1Thisissomewhatsurprisinggiventhatthishasapointertype.It
wouldhavebeennicertomakethisequivalenttowhatisnow*this.
However,thiswaspartofanearlyversionofC++beforereferencetypeswere
partofthelanguage,andbythetimereferencetypeswereadded,toomuchcode
alreadydependedonthisbeingapointer.
Thehidden*thisparameterparticipatesinoverloadresolutionjustlikethe
explicitparameters.Mostofthetimethisisquitenatural,butoccasionallyit
comesunexpectedly.Thefollowingexampleshowsastring-likeclassthatdoes
notworkasintended(yetwehaveseensuchcodeintherealworld):Clickhere
toviewcodeimage
#include<cstddef>
classBadString{
public:
BadString(charconst*);
…
//characteraccessthroughsubscripting:
char&operator[](std::size_t);//#1
charconst&operator[](std::size_t)const;
//implicitconversiontonull-terminatedbytestring:
operatorchar*();//#2
operatorcharconst*();
…
};
intmain()
{
BadStringstr("correkt");
str[5]=’c’;//possiblyanoverloadresolutionambiguity!
}
Atfirst,nothingseemsambiguousabouttheexpressionstr[5].Thesubscript
operatorat#1seemslikeaperfectmatch.However,itisnotquiteperfect
becausetheargument5hastypeint,andtheoperatorexpectsanunsigned
integertype(size_tandstd::size_tusuallyhavetypeunsignedint
orunsignedlong,butnevertypeint).Still,asimplestandardinteger
conversionmakes#1easilyviable.However,thereisanotherviablecandidate:
thebuilt-insubscriptoperator.Indeed,ifweapplytheimplicitconversion
operatortostr(whichistheimplicitmemberfunctionargument),weobtaina
pointertype,andnowthebuilt-insubscriptoperatorapplies.Thisbuilt-in
operatortakesanargumentoftypeptrdiff_t,whichonmanyplatformsis
equivalenttointandthereforeisaperfectmatchfortheargument5.Soeven
thoughthebuilt-insubscriptoperatorisapoormatch(byuser-defined
conversion)fortheimpliedargument,itisabettermatchthantheoperator
definedat#1fortheactualsubscript!Hencethepotentialambiguity.2Tosolve
thiskindofproblemportably,youcandeclareoperator[]withaptrdiff_t
parameter,oryoucanreplacetheimplicittypeconversiontochar*byan
explicitconversion(whichisusuallyrecommendedanyway).
Itispossibleforasetofviablecandidatestocontainbothstaticandnonstatic
members.Whencomparingastaticmemberwithanonstaticmember,thequality
ofthematchoftheimplicitargumentisignored(onlythenonstaticmemberhas
animplicit*thisparameter).
Bydefault,anonstaticmemberfunctionhasanimplicit*thisparameterthat
isanlvaluereferencetype,butC++11introducedsyntaxtomakeitanrvalue
referencetype.Forexample:Clickheretoviewcodeimage
structS{
voidf1();//implicit*thisparameterisanlvaluereference(see
below)
voidf2()&&;//implicit*thisparameterisanrvaluereference
voidf3()&;//implicit*thisparameterisanlvaluereference
};
Asyoucantellfromthisexample,itispossiblenotonlytomaketheimplicit
parameteranrvaluereference(withthe&&suffix)butalsotoaffirmthelvalue
referencecase(withthe&suffix).Interestingly,specifyingthe&suffixisnot
exactlyequivalenttoleavingitoff:Anoldspecial-casepermitsanrvaluetobe
boundtoanlvaluereferencetonon-consttypewhenthatreferenceisthe
traditionalimplicit*thisparameter,butthat(somewhatdangerous)special
casenolongerappliesifthelvaluereferencetreatmentwasrequestedexplicitly.
So,withthedefinitionofSspecifiedabove:Clickheretoviewcodeimage
intmain()
{
S().f1();//OK:oldruleallowsrvalueS()tomatchimplied
//lvaluereferencetypeS&of*this
S().f2();//OK:rvalueS()matchesrvaluereferencetype
//of*this
S().f3();//ERROR:rvalueS()cannotmatchexplicitlvalue
//referencetypeof*this
}
C.2.2RefiningthePerfectMatch
ForanargumentoftypeX,therearefourcommonparametertypesthat
constituteaperfectmatch:X,X&,Xconst&,andX&&(Xconst&&is
alsoanexactmatch,butitisrarelyused).However,itisrathercommonto
overloadafunctionontwokindsofreferences.PriortoC++11,thismeantcases
likethese:Clickheretoviewcodeimage
voidreport(int&);//#1
voidreport(intconst&);//#2
intmain()
{
for(intk=0;k<10;++k){
report(k);//calls#1
}
report(42);//calls#2
}
Here,theversionwithouttheextraconstispreferredforlvalues,whereasonly
theversionwithconstcanmatchrvalues.
WiththeadditionofrvaluereferencesinC++11,anothercommoncaseoftwo
perfectmatchesneedingtobedistinguishedisillustratedbythefollowing
example:Clickheretoviewcodeimage
structValue{
…
};
voidpass(Valueconst&);//#1
voidpass(Value&&);//#2
voidg(X&&x)
{
pass(x);//calls#1,becausexisanlvalue
pass(X());//calls#2,becauseX()isanrvalue(infact,prvalue)
pass(std::move(x));//calls#2,becausestd::move(x)isanrvalue
(infact,xvalue)
}
Thistime,theversiontakinganrvaluereferenceisconsideredabettermatchfor
rvalues,butitcannotmatchlvalues.
Notethatthisalsoappliestotheimplicitargumentofamemberfunctioncall:
Clickheretoviewcodeimage
classWonder{
public:
voidtick();//#1
voidtick()const;//#2
voidtack()const;//#3
};
voidrun(Wonder&device)
{
device.tick();//calls#1
device.tack();//calls#3,becausethereisnonon-constversion
//ofWonder::tack()
}
Finally,thefollowingmodificationofourearlierexampleillustratesthattwo
perfectmatchescanalsocreateanambiguityifyouoverloadwithandwithout
references:Clickheretoviewcodeimage
voidreport(int);//#1
voidreport(int&);//#2
voidreport(intconst&);//#3
intmain()
{
for(intk=0;k<10;++k){
report(k);//ambiguous:#1and#2matchequallywell
}
report(42);//ambiguous:#1and#3matchequallywell
}
C.3OverloadingDetails
Theprevioussectioncoversmostoftheoverloadingsituationsencounteredin
everydayC++programming.Thereare,unfortunately,manymorerulesand
exceptionstotheserules—morethanisreasonabletopresentinabookthatis
notreallyaboutfunctionoverloadinginC++.Nonetheless,wediscusssomeof
themhereinpartbecausetheyapplysomewhatmoreoftenthanotherrulesand
inparttoprovideasenseforhowdeepthedetailsgo.
C.3.1PreferNontemplatesorMoreSpecialized
Templates
Whenallotheraspectsofoverloadresolutionareequal,anontemplatefunction
ispreferredoveraninstanceofatemplate(itdoesn’tmatterwhetherthat
instanceisgeneratedfromthegenerictemplatedefinitionorwhetheritis
providedasanexplicitspecialization).Forexample:Clickheretoviewcode
image
template<typenameT>intf(T);//#1
voidf(int);//#2
intmain()
{
returnf(7);//ERROR:selects#2,whichdoesn’treturnavalue
}
Thisexamplealsoclearlyillustratesthatoverloadresolutionnormallydoesnot
involvethereturntypeoftheselectedfunction.
However,whenotheraspectsofoverloadresolutionslightlydiffer(suchas
havingdifferentconstandreferencequalifiers),firstthegeneralrulesof
overloadresolutionapply.Thiseffectcaneasilyaccidentallycausesurprising
behavior,whenmemberfunctionsaredefinedthatacceptthesameargumentsas
copyormoveconstructors.SeeSection16.2.4onpage333fordetails.
Ifthechoiceisbetweentwotemplates,thenthemostspecializedofthe
templatesispreferred(providedoneisactuallymorespecializedthantheother).
SeeSection16.2.2onpage330forathoroughexplanationofthisconcept.One
specialcaseofthisdistinctionoccurswhentwotemplatesonlydifferinthatone
addsatrailingparameterpacks:Thetemplatewithoutthepackisconsidered
morespecializedandisthereforepreferredifitmatchesthecall.Section4.1.2on
page57discussesanexampleofthissituation.
C.3.2ConversionSequences
Animplicitconversioncan,ingeneral,beasequenceofelementaryconversions.
Considerthefollowingcodeexample:Clickheretoviewcodeimage
classBase{
public:
operatorshort()const;
};
classDerived:publicBase{
};
voidcount(int);
voidprocess(Derivedconst&object)
{
count(object);//matcheswithuser-definedconversion
}
Thecallcount(object)worksbecauseobjectcanimplicitlybeconverted
toint.However,thisconversionrequiresseveralsteps:1.Aconversionof
objectfromDerivedconsttoBaseconst(thisisaglvalue
conversion;itpreservestheidentityoftheobject)2.Auser-definedconversion
oftheresultingBaseconstobjecttotypeshort3.Apromotionofshort
toint
Thisisthemostgeneralkindofconversionsequence:astandardconversion(a
derived-to-baseconversion,inthiscase),followedbyauser-definedconversion,
followedbyanotherstandardconversion.Althoughtherecanbeatmostone
user-definedconversioninaconversionsequence,itisalsopossibletohaveonly
standardconversions.
Animportantprincipleofoverloadresolutionisthataconversionsequence
thatisasubsequenceofanotherconversionsequenceispreferableoverthelatter
sequence.Iftherewereanadditionalcandidatefunctionvoidcount(short);inthe
example,itwouldbepreferredforthecallcount(object)becauseitdoesn’t
requirethethirdstep(promotion)intheconversionsequence.
C.3.3PointerConversions
Pointersandpointerstomembersundergovariousspecialstandardconversions,
including•Conversionstotypebool
•Conversionsfromanarbitrarypointertypetovoid*
•Derived-to-baseconversionsforpointers•Base-to-derivedconversionsfor
pointerstomembersAlthoughallofthesecancausea“matchwithstandard
conversionsonly,”theyarenotrankedequally.
First,conversionstotypebool(bothfromaregularpointerandfroma
pointertoamember)areconsideredworsethananyotherkindofstandard
conversion.Forexample:Clickheretoviewcodeimage
;voidcheck(void*);//#1
voidcheck(bool);//#2
voidrearrange(Matrix*m)
{
check(m);//calls#1
…
}
Withinthecategoryofregularpointerconversions,aconversiontotypevoid*
isconsideredworsethanaconversionfromaderivedclasspointertoabase
classpointer.Furthermore,ifconversionstodifferentclassesrelatedby
inheritanceexist,aconversiontothemostderivedclassispreferred.Hereis
anothershortexample:Clickheretoviewcodeimage
classInterface{
…
};
classCommonProcesses:publicInterface{
…
};
classMachine:publicCommonProcesses{
…
};
char*serialize(Interface*);//#1
char*serialize(CommonProcesses*);//#2
voiddump(Machine*machine)
{
char*buffer=serialize(machine);//calls#2
…
}
TheconversionfromMachine*toCommonProcesses*ispreferredover
theconversiontoInterface*,whichisfairlyintuitive.
Averysimilarruleappliestopointerstomembers:Betweentwoconversions
ofrelatedpointer-to-membertypes,the“closestbase”intheinheritancegraph
(i.e.,theleastderived)ispreferred.
C.3.4InitializerLists
Initializerlistarguments(initializerspassedwithincurlybraces)canbe
convertedtoseveraldifferentkindsofparameters:initializer_lists,
classtypeswithaninitializer_listconstructor,classtypes
forwhichtheinitializerlistelementscanbetreatedas(separate)parameterstoa
constructor,oraggregateclasstypeswhosememberscanbeinitializedbythe
elementsoftheinitializerlist.Thefollowingprogramillustratesthesecases:
Clickheretoviewcodeimage
overload/initlist.cpp
#include<initializer_list>
#include<string>
#include<vector>
#include<complex>
#include<iostream>
voidf(std::initializer_list<int>){
std::cout<<"#1\n";
}
voidf(std::initializer_list<std::string>){
std::cout<<"#2\n";
}
voidg(std::vector<int>const&vec){
std::cout<<"#3\n";
}
voidh(std::complex<double>const&cmplx){
std::cout<<"#4\n";
}
structPoint{
intx,y;
};
voidi(Pointconst&pt){
std::cout<<"#5\n";
}
intmain()
{
f({1,2,3});//prints#1
f({"hello","initializer","list"});//prints#2
g({1,1,2,3,5});//prints#3
h({1.5,2.5});//prints#4
i({1,2});//prints#5
}
Inthefirsttwocallstof(),theinitializerlistargumentsareconvertedto
std::initializer_listvalues,whichinvolvesconvertingeachofthe
elementsintheinitializerlisttotheelementtypeofthe
std::initializer_list.Inthefirstcall,alloftheelementsarealready
oftypeint,sonoadditionalconversionisneeded.Inthesecondcall,each
stringliteralintheinitializerlistisconvertedtoastd::stringbycallingthe
string(charconst*)constructor.Thethirdcall(tog())performsauser-
definedconversionusingthe
std::vector(std::initializer_list<int>)constructor.Thenext
callinvokesthestd::complex(double,double)constructor,asifone
hadwrittenstd::complex<double>(1.5,2.5).Thefinalcallperforms
aggregateinitialization,whichinitializesthemembersofaninstanceofthe
Pointclassfromtheelementsintheinitializerlistwithoutcallingaconstructor
ofPoint.3
Thereareseveralinterestingoverloadingcasesforinitializerlists.When
convertinganinitializerlisttoaninitializer_list,asinthefirsttwo
callsoftheexampleabove,theoverallconversionisgiventhesamerankingas
theworstconversionfromanygivenelementintheinitializerlisttotheelement
typeoftheinitializer_list(i.e.,theTininitializer_list<T>).
Thiscanleadtosomesurprises,asinthefollowingexample:Clickheretoview
codeimage
overload/initlistovl.cpp
#include<initializer_list>
#include<iostream>
voidovl(std::initializer_list<char>){//#1
std::cout<<"#1\n";
}
voidovl(std::initializer_list<int>){//#2
std::cout<<"#2\n";
}
intmain()
{
ovl({’h’,’e’,’l’,’l’,’o’,’\0’});//prints#1
ovl({’h’,’e’,’l’,’l’,’o’,0});//prints#2
}
Inthefirstcalltoovl(),eachelementoftheinitializerlistisachar.Forthe
firstovl()function,theseelementsrequirenoconversionatall.Forthesecond
ovl()function,theseelementsrequireapromotiontoint.Becausethe
perfectmatchisbetterthanapromotion,thefirstcalltoovl()calls#1.
Inthesecondcalltoovl(),thefirstfiveelementsareoftypechar,while
thelastisoftypeint.Forthefirstovl()function,thecharelementsarea
perfectmatch,buttheintrequiresastandardconversion,sotheoverall
conversionisrankedasastandardconversion.Forthesecondovl()function,
thecharelementsrequireapromotiontoint,whiletheintelementatthe
endisaperfectmatch.Theoverallconversionforthesecondovl()functionis
rankedasapromotion,whichmakesitabettercandidatethanthefirstovl(),
eventhoughonlyasingleelement’sconversionwasbetter.
Wheninitializinganobjectofclasstypewithaninitializerlist,asinthecalls
tog()andh()inouroriginalexample,overloadresolutionproceedsintwo
phases:1.Thefirstphaseconsidersonlyinitializer-listconstructors,thatis,
constructorswhoseonlynonde-faultedparameterisoftype
std::initializer_list<T>forsometypeT(afterremovingthetop-
levelreferenceandconst/volatilequalifiers).
2.Ifnosuchviableconstructorisfound,thenthesecondphaseconsidersall
otherconstructors.Thereisoneexceptiontothisrule:Iftheinitializerlistis
emptyandtheclasshasadefaultconstructor,thefirstphaseisskippedsothat
thedefaultconstructorwillbecalled.
Theeffectofthisruleisthatanyinitializer-listconstructorisabettermatch
thananynon-initializer-listconstructor,asillustratedinthefollowingexample:
Clickheretoviewcodeimage
overload/initlistctor.cpp
#include<initializer_list>
#include<string>
#include<iostream>
template<typenameT>
structArray{
Array(std::initializer_list<T>){
std::cout<<"#1\n";
}
Array(unsignedn,Tconst&){
std::cout<<"#2\n";
}
};
voidarr1(Array<int>){
}
voidarr2(Array<std::string>){
}
intmain()
{
arr1({1,2,3,4,5});//prints#1
arr1({1,2});//prints#1
arr1({10u,5});//prints#1
arr2({"hello","initializer","list"});//prints#1
arr2({10,"hello"});//prints#2
}
Notethatthesecondconstructor,whichtakesanunsignedandaTconst&,
won’tbecalledwheninitializinganArray<int>objectfromaninitializerlist,
becauseitsinitializer-listconstructorisalwaysabettermatchthanitsnon-
initializer-listconstructors.WithArray<string>,however,thenon-
initializer-listconstructorwillbecalledwhentheinitializer-listconstructorisnot
viable,asinthesecondcalltoarr2().
C.3.5FunctorsandSurrogateFunctions
Wementionedearlierthatafterthenameofafunctionhasbeenlookedupto
createaninitialoverloadset,thesetistweakedinvariousways.Aninteresting
situationariseswhenacallexpressionreferstoaclasstypeobjectinsteadofa
function.Inthiscase,therearetwopotentialadditionstotheoverloadset.
Thefirstadditionisstraightforward:Anymemberoperator()(thefunction
calloperator)isaddedtotheset.Objectswithsuchoperatorsareusuallycalled
functorsorfunctionobjects(seeSection11.1onpage157).
Alessobviousadditionoccurswhenaclasstypeobjectcontainsanimplicit
conversionoperatortoapointertoafunctiontype(ortoareferencetoafunction
type).4Insuchsituations,adummy(orsurrogate)functionisaddedtothe
overloadset.Thissurrogatefunctioncandidateisconsideredtohaveanimplied
parameterofthetypedesignatedbytheconversionfunction,inadditionto
parameterswithtypescorrespondingtotheparametertypesinthedestination
typeofthatconversionfunction.Anexamplemakesthismuchclearer:Click
heretoviewcodeimage
usingFuncType=void(double,int);
classIndirectFunctor{
public:
…
voidoperator()(double,double)const;
operatorFuncType*()const;
};
voidactivate(IndirectFunctorconst&funcObj)
{
funcObj(3,5);//ERROR:ambiguous
}
ThecallfuncObj(3,5)istreatedasacallwiththreearguments:
funcObj,3,and5.Theviablefunctioncandidatesincludethemember
operator()(whichistreatedashavingparametertypes
IndirectFunctorconst&,double,anddouble)andasurrogate
functionwithparametersoftypeFuncType*,double,andint.The
surrogatefunctionhasaworsematchfortheimpliedparameter(becauseit
requiresauser-definedconversion),butithasabettermatchforthelast
parameter;hencethetwocandidatescannotbeordered.Thecallistherefore
ambiguous.
SurrogatefunctionsareinthemostobscurecornersofC++andrarelyoccurin
practice(fortunately).
C.3.6OtherOverloadingContexts
Sofarwehavediscussedoverloadinginthecontextofdeterminingwhich
functionshouldbecalledinacallexpression.However,thereareafewother
contextsinwhichasimilarselectionmustbemade.
Thefirstcontextoccurswhentheaddressofafunctionisneeded.Consider
thefollowingexample:Clickheretoviewcodeimage
intnumElems(Matrixconst&);//#1
intnumElems(Vectorconst&);//#2
…
int(*funcPtr)(Vectorconst&)=numElems;//selects#2
Here,thenamenumElemsreferstoanoverloadset,butonlytheaddressofone
functioninthatsetisdesirable.Overloadresolutionthenattemptstomatchthe
requiredfunctiontype(thetypeoffuncPtrinthisexample)totheavailable
candidates.
Theothercontextthatrequiresoverloadresolutionisinitialization.
Unfortunately,thisisatopicfraughtwithsubtletiesthatarebeyondwhatcanbe
coveredinanappendix.However,asimpleexampleatleastillustratesthis
additionalaspectofoverloadresolution:Clickheretoviewcodeimage
#include<string>
classBigNum{
public:
BigNum(longn);//#1
BigNum(doublen);//#2
BigNum(std::stringconst&);//#3
…
operatordouble();//#4
operatorlong();//#5
…
};
voidinitDemo()
{
BigNumbn1(100103);//selects#1
BigNumbn2("7057103224.095764");//selects#3
intin=bn1;//selects#5
}
Inthisexample,overloadresolutionisneededtoselecttheappropriate
constructororconversionoperator.Specifically,theinitializationofbn1calls
thefirstconstructor,thatofbn2callsthethirdconstructor,andthatofin()
callsoperatorlong().Inthevastmajorityofcases,theoverloadingrules
producetheintuitiveresult.However,thedetailsoftheserulesarequite
complex,andsomeapplicationsrelyonsomeofthemoreobscurecornersinthis
areaoftheC++language.
1ItcouldalsobeoftypeMyClassvolatile&orMyClassconst
volatile&ifthememberfunctionwasvolatile,butthisisextremely
rare.
2Notethattheambiguityexistsonlyonplatformsforwhichsize_tisa
synonymforunsignedint.Onplatformsforwhichitisasynonymfor
unsignedlong,thetypeptrdiff_tisatypealiasoflong,andno
ambiguityexistsbecausethebuilt-insubscriptoperatoralsorequiresa
conversionofthesubscriptexpression.
3AggregateinitializationisonlyavailableforaggregatetypesinC++,which
areeitherarraysorsimple,C-likeclassesthathavenouser-provided
constructors,noprivateorprotectednonstaticdatamembers,nobaseclasses,
andnovirtualfunctions.PriortoC++14,theymustalsonothaveadefault
memberinitializer.SinceC++17,publicbaseclassesareallowed.
4Theconversionoperatormustalsobeapplicableinthesensethat,for
example,anon-constoperatorisnotconsideredforconstobjects.
AppendixD
StandardTypeUtilities
TheC++standardlibrarylargelyconsistsoftemplates,manyofwhichrelyon
varioustechniquesintroducedanddiscussedinthisbook.Forthisreason,a
coupleoftechniqueswere“standardized”inthesensethatthestandardlibrary
definesseveraltemplatestoimplementlibrarieswithgenericcode.Thesetype
utilities(typetraitsandotherhelpers)arelistedandexplainedhereinthis
chapter.
Notethatsometypetraitsneedcompilersupport,whileotherscanjustbe
implementedinthelibraryusingexistingin-languagefeatures(wediscusssome
oftheminChapter19).
D.1UsingTypeTraits
Whenusingtypetraits,ingeneralyouhavetoincludetheheaderfile
<type_traits>:#include<type_traits>Thentheusagedependsonwhether
atraityieldsatypeoravalue:•Fortraitsyieldingatype,youcanaccessthe
typeasfollows:Clickheretoviewcodeimage
typenamestd::trait<…>::type
std::trait_t<…>//sinceC++14
•Fortraitsyieldingavalue,youcanaccessthevalueasfollows:Clickhereto
viewcodeimage
std::trait<…>::value
std::trait<…>()//implicitconversiontoitstype
std::trait_v<…>//sinceC++17
Forexample:
Clickheretoviewcodeimage
utils/traits1.cpp
#include<type_traits>
#include<iostream>
intmain()
{
inti=42;
std::add_const<int>::typec=i;//cisintconst
std::add_const_t<int>c14=i;//sinceC++14
static_assert(std::is_const<decltype(c)>::value,"cshouldbeconst");
std::cout<<std::boolalpha;
std::cout<<std::is_same<decltype(c),intconst>::value//true
<<’\n’;
std::cout<<td::is_same_v<decltype(c),intconst>//sinceC++17
<<’\n’;
if(std::is_same<decltype(c),intconst>{}){//implicitconversionto
bool
std::cout<<"same\n";
}
}
SeeSection2.8onpage40forthewaythe_tversionofthetraitsisdefined.
SeeSection5.6onpage83forthewaythe_vversionofthetraitsisdefined.
D.1.1std::integral_constantandstd::bool_constant
Allstandardtypetraitsyieldingavaluearederivedfromaninstanceofthe
helperclasstemplatestd::integral_constant:Clickheretoviewcode
image
namespacestd{
template<typenameT,Tval>
structintegral_constant{
staticconstexprTvalue=val;//valueofthetrait
usingvalue_type=T;//typeofthevalue
usingtype=integral_constant<T,val>;
constexproperatorvalue_type()constnoexcept{
returnvalue;
}
constexprvalue_typeoperator()()constnoexcept{//sinceC++14
returnvalue;
}
};
}
Thatis:
•Wecanusethevalue_typemembertoquerythetypeoftheresult.Since
manytraitsyieldingavaluearepredicates,value_typeisoftenjustbool.
•Objectsoftraitstypeshaveanimplicittypeconversiontothetypeofthevalue
producedbythetypetrait.
•InC++14(andlater),objectsoftypetraitsarealsofunctionobjects(functors),
wherea“functioncall”yieldstheirvalue.
•Thetypememberjustyieldstheunderlyingintegral_constant
instance.
IftraitsyieldBooleanvalues,theycanalsouse1
Clickheretoviewcodeimage
namespacestd{
template<boolB>
usingbool_constant=integral_constant<bool,B>;//sinceC++17
usingtrue_type=bool_constant<true>;
usingfalse_type=bool_constant<false>;
}
sothattheseBooleantraitsinheritfromstd::true_typeifaspecific
propertyappliesandfromstd::false_typeifnot.Thatalsomeansthat
theircorrespondingvaluemembersequaltrueorfalse.Havingdistinct
typesfortheresultingvaluestrueandfalseallowsustotag-dispatchbased
ontheresultoftypetraits(seeSection19.3.3onpage411andSection20.2on
page467).
Forexample:
Clickheretoviewcodeimage
utils/traits2.cpp
#include<type_traits>
#include<iostream>
intmain()
{
usingnamespacestd;
cout<<boolalpha;
usingMyType=int;
cout<<is_const<MyType>::value<<’\n’;//printsfalse
usingVT=is_const<MyType>::value_type;//bool
usingT=is_const<MyType>::type;//integral_constant<bool,false>
cout<<is_same<VT,bool>::value<<’\n’;//printstrue
cout<<is_same<T,integral_constant<bool,false>>::value
<<’\n’;//printstrue
cout<<is_same<T,bool_constant<false>>::value
<<’\n’;//printstrue(notvalid
//priortoC++17)
autoic=is_const<MyType>();//objectoftraittype
cout<<is_same<decltype(ic),is_const<int>>::value<<’\n’;//true
cout<<ic()<<’\n’;//functioncall(printsfalse)
staticconstexprautomytypeIsConst=is_const<MyType>{};
ifconstexpr(mytypeIsConst){//compile-timechecksinceC++17=>
false
…//discardedstatement
}
static_assert(!std::is_const<MyType>{},"MyTypeshouldnotbeconst");
}
Havingdistincttypesfornon-Booleanintegral_constantspecializations
isalsousefulinvariousmetaprogrammingcontexts.Seethediscussionofthe
similartypeCTValueinSection24.3onpage566anditsuseforelement
accessoftuplesinSection25.6onpage599.
D.1.2ThingsYouShouldKnowWhenUsingTraits
Thereareafewthingstonotewhenusingtraits:•Typetraitsapplydirectlyto
types,butdecltypeallowsustoalsotestthepropertiesofexpressions,
variables,andfunctions.Recall,however,thatdecltypeproducesthetypeof
avariableorfunctiononlyiftheentityisnamedwithnoextraneousparentheses;
foranyotherexpression,ityieldsatypethatalsoreflectsthetypecategoryof
theexpression.Forexample:Clickheretoviewcodeimage
voidfoo(std::string&&s)
{
//checkthetypeofs:
std::is_lvalue_reference<decltype(s)>::value//false
std::is_rvalue_reference<decltype(s)>::value//true,asdeclared
//checkthevaluecategoryofsusedasexpression:
std::is_lvalue_reference<decltype((s))>::value//true,susedas
lvalue
std::is_rvalue_reference<decltype((s))>::value//false
}
SeeSection15.10.2onpage298fordetails.
•Sometraitsmayhavenonintuitivebehaviorforthenoviceprogrammer.See
Section11.2.1onpage164forexamples.
•Sometraitshaverequirementsorpreconditions.Violatingthesepreconditions
resultsinundefinedbehavior.2SeeSection11.2.1onpage164forsome
examples.
•Manytraitsrequirecompletetypes(seeSection10.3.1onpage154).Tobe
abletousethemforincompletetypes,wecansometimesintroducetemplates
todefertheirevaluation(seeSection11.5onpage171fordetails).
•Sometimesthelogicaloperators&&,||,and!cannotbeusedtodefinenew
typetraitsbasedonothertypetraits.Inaddition,dealingwithtraitsthatmight
failcanbecomeaproblemoratleastcausesomedrawbacks.Forthisreason,
specialtraitsareprovidedthatallowustologicallycombineBooleantraits.
SeeSectionD.6onpage734fordetails.
•Althoughthestandardaliastemplates(endingwith_tor_v)areoften
convenient,theyalsohavedownsides,makingthemunusableinsome
metaprogrammingcontexts.SeeSection19.7.3onpage446fordetails.
D.2PrimaryandCompositeTypeCategories
Westartwiththestandardtraitsthattestprimaryandcompositetypecategories
(seeFigureD.1).3Ingeneral,eachtypebelongstoexactlyoneprimarytype
category(thewhiteelementsinFigureD.1).Compositetypecategoriesthen
mergeprimarytypecategoriesintohigher-levelconcepts.
FigureD.1.PrimaryandCompositeTypeCategories
D.2.1TestingforthePrimaryTypeCategory
Thissectiondescribestypeutilitiesthattesttheprimarytypecategoryofagiven
type.Foranygiventype,exactlyoneoftheprimarytypecategorieshasastatic
valuememberthatevaluatestotrue.4Theresultisindependentofwhether
thetypeisqualifiedwithconstand/orvolatile(cv-qualified).
Notethatfortypesstd::size_tandstd::ptrdiff_t,
is_integral<>yieldstrue.Fortypestd::max_align_t,whichone
oftheseprimarytypecategoriesyieldstrueisanimplementationdetail(thus,
itmightbeanintegralorfloating-pointorclasstype).Thelanguagespecifies
thatthetypeofalambdaexpressionisaclasstype(seeSection15.10.6onpage
310).Applyingis_classtothattypethereforeyieldstrue.
Trait Effect
is_void<T>Typevoid
is_integral<T>Integraltype(includingbool,
char,char16_t,char32_t,
wchar_t)
is_floating_point<T>Floating-pointtype(float,
double,longdouble)
is_array<T>Ordinaryarraytype(nottype
std::array)
is_pointer<T>Pointertype(includingfunction
pointerbutnotpointertonon-static
member)
is_null_pointer<T>Typeofnullptr(sinceC++14)
is_member_object_pointer<T>Pointertoanonstaticdatamember
is_member_function_pointer<T>Pointertoanonstaticmember
function
is_lvalue_reference<T>Lvaluereference
is_rvalue_reference<T>Rvaluereference
is_enum<T>Enumerationtype
is_class<T>Class/structorlambdatypebutnot
auniontype
is_union<T>Uniontype
is_function<T>Functiontype
TableD.1.TraitstoCheckthePrimaryTypeCategory
std::is_void<T>::value
•YieldstrueiftypeTis(cv-qualified)void.
•Forexample:
Clickheretoviewcodeimage
is_void_v<void>//yieldstrue
is_void_v<voidconst//yieldstrue
is_void_v<int>//yieldsfalse
voidf();
is_void_v<decltype(f)>//yieldsfalse(fhasfunctiontype)
is_void_v<decltype(f())>//yieldstrue(returntypeoff()isvoid)
std::is_integral<T>::value
•YieldstrueiftypeTisoneofthefollowing(cv-qualified)types:–bool
–acharactertype(char,signedchar,unsignedchar,char16_t,
char32_t,orwchar_t)–anintegertype(signedorunsignedvariantsof
short,int,long,orlonglong;thisincludesstd::size_tand
std::ptrdiff_t)std::is_floating_point<T>::value
•YieldstrueiftypeTis(cv-qualified)float,double,orlongdouble.
std::is_array<T>::value
•YieldstrueiftypeTisa(cv-qualified)arraytype.
•Recallthataparameterdeclaredasanarray(withorwithoutlength)by
languagerulesreallyhasapointertype.
•Notethatclassstd::array<>isnotanarraytype,butaclasstype.
•Forexample:
Clickheretoviewcodeimage
is_array_v<int[]>//yieldstrue
is_array_v<int[5]>//yieldstrue
is_array_v<int*>//yieldsfalse
voidfoo(inta[],intb[5],int*c)
{
is_array_v<decltype(a)>//yieldsfalse(ahastypeint*)
is_array_v<decltype(b)>//yieldsfalse(bhastypeint*)
is_array_v<decltype(c)>//yieldsfalse(chastypeint*)
}
•SeeSection19.8.2onpage453forimplementationdetails.
std::is_pointer<T>::value
•YieldstrueiftypeTisa(cv-qualified)pointer.
Thisincludes:
–pointerstostatic/global(member)functions–parametersdeclaredasarrays
(withorwithoutlength)orfunctiontypesThisdoesnotinclude:–pointer-to-
membertypes(e.g.,thetypeof&X::mwhereXisaclasstypeandmisa
nonstaticmemberfunctionoranonstaticdatamember)–thetypeof
nullptr,std::nullptr_t
•Forexample:
Clickheretoviewcodeimage
is_pointer_v<int>//yieldsfalse
is_pointer_v<int*>//yieldstrue
is_pointer_v<int*const>//yieldstrue
is_pointer_v<int*&>//yieldsfalse
is_pointer_v<decltype(nullptr)>//yieldsfalse
int*foo(inta[5],void(f)())
{
is_pointer_v<decltype(a)>//yieldstrue(ahastypeint*)
is_pointer_v<decltype(f)>//yieldstrue(fhastypevoid(*)())
is_pointer_v<decltype(foo)>//yieldsfalse
is_pointer_v<decltype(&foo)>//yieldstrue
is_pointer_v<decltype(foo(a,f))>//yieldstrue(forreturntype
int*)
}
•SeeSection19.8.2onpage451forimplementationdetails.
std::is_null_pointer<T>::value
•YieldstrueiftypeTisthe(cv-qualified)std::nullptr_t,whichisthe
typeofnullptr.
•Forexample:
Clickheretoviewcodeimage
is_null_pointer_v<decltype(nullptr)>//yieldstrue
void*p=nullptr;
is_null_pointer_v<decltype(p)>//yieldsfalse(phasnottype
std::nullptr_t)
•ProvidedsinceC++14.
std::is_member_object_pointer<T>::value
std::is_member_function_pointer<T>::value
•YieldstrueiftypeTisa(cv-qualified)pointer-to-membertype(e.g.,int
X::*orint(X::*)()forsomeclasstypeX).
std::is_lvalue_reference<T>::value
std::is_rvalue_reference<T>::value
•YieldstrueiftypeTisa(cv-qualified)lvalueorrvaluereferencetype,
respectively.
•Forexample:
Clickheretoviewcodeimage
is_lvalue_reference_v<int>//yieldsfalse
is_lvalue_reference_v<int&>//yieldstrue
is_lvalue_reference_v<int&&>//yieldsfalse
is_lvalue_reference_v<void>//yieldsfalse
is_rvalue_reference_v<int>//yieldsfalse
is_rvalue_reference_v<int&>//yieldsfalse
is_rvalue_reference_v<int&&>//yieldstrue
is_rvalue_reference_v<void>//yieldsfalse
•SeeSection19.8.2onpage452forimplementationdetails.
std::is_enum<T>::value
•YieldstrueiftypeTisa(cv-qualified)enumerationtype.Thisappliestoboth
scopedandun-scopedenumerationtypes.
•SeeSection19.8.5onpage457forimplementationdetails.
std::is_class<T>::value
•YieldstrueiftypeTisa(cv-qualified)classtypedeclaredwithclassor
struct,includingsuchatypegeneratedfrominstantiatingaclasstemplate.
Notethatthelanguageguaranteesthatthetypeofalambdaexpressionisa
classtype(seeSection15.10.6onpage310).
•Yieldsfalseforunions,scopedenumerationtype(despitebeingdeclared
withenumclass),std::nullptr_t,andanyothertype.
•Forexample:
Clickheretoviewcodeimage
is_class_v<int>//yieldsfalse
is_class_v<std::string>//yieldstrue
is_class_v<std::stringconst>//yieldstrue
is_class_v<std::string&>//yieldsfalse
autol1=[]{};
is_class_v<decltype(l1)>//yieldstrue(alambdaisaclassobject)
•SeeSection19.8.4onpage456forimplementationdetails.
std::is_union<T>::value
•YieldstrueiftypeTisa(cv-qualified)union,includingauniongenerated
fromaclasstemplatethatisauniontemplate.
std::is_function<T>::value
•YieldstrueiftypeTisa(cv-qualified)functiontype.Yieldsfalsefora
functionpointertype,thetypeofalambdaexpression,andanyothertype.
•Recallthataparameterdeclaredasanfunctiontypebylanguagerulesreally
hasapointertype.
•Forexample:
Clickheretoviewcodeimage
voidfoo(void(f)())
{
is_function_v<decltype(f)>//yieldsfalse(fhastypevoid(*)())
is_function_v<decltype(foo)>//yieldstrue
is_function_v<decltype(&foo)>//yieldsfalse
is_function_v<decltype(foo(f))>//yieldsfalse(forreturntype)
}
•SeeSection19.8.3onpage454forimplementationdetails.
D.2.2TestforCompositeTypeCategories
Thefollowingtypeutilitiesdeterminewhetheratypebelongstoamoregeneral
typecategorythatistheunionofsomeprimarytypecategories.Thecomposite
typecategoriesdonotformastrictpartition:Atypemaybelongtomultiple
compositetypecategories(e.g.,apointertypeisbothascalartypeanda
compoundtype).Again,cv-qualifiers(constandvolatile)donotmatterin
classifyingatype.
std::is_reference<T>::value
•YieldstrueiftypeTisareferencetype.
•Sameas:is_lvalue_reference_v<T>||
is_rvalue_reference_v<T>
•SeeSection19.8.2onpage452forimplementationdetails.
Trait Effect
is_reference<T>Lvalueorrvaluereference
is_member_pointer<T>Pointertononstaticmember
is_arithmetic<T>Integral(includingboolandcharacters)or
floating-pointtype
is_fundamental<T>void,integral(includingboolandcharacters),
floating-point,orstd::nullptr_t
is_scalar<T>Integral(includingboolandcharacters),
floating-point,enumeration,pointer,pointer-to-
member,andstd::nullptr_t
is_object<T>
Anytypeexceptvoid,function,orreference
is_compound<T>Theoppositeofis_fundamental<T>:array,
enumeration,union,class,function,reference,
pointer,orpointer-to-member
TableD.2.TraitstoCheckforCompositeTypeCategory
std::is_member_pointer<T>::value
•YieldstrueiftypeTisanypointer-to-membertype.
•Sameas:!(is_member_object_pointer_v<T>||
is_member_function_pointer_v<T>)std::is_arithmetic<
T>::value
•YieldstrueiftypeTisanarithmetictype(bool,charactertype,integertype,
orfloating-pointtype).
•Sameas:is_integral_v<T>||is_floating_point_v<T>
std::is_fundamental<T>::value
•YieldstrueiftypeTisafundamentaltype(arithmetictypeorvoidor
std::nullptr_t).
•Sameas:is_arithmetic_v<T>||is_void_v<T>||
is_null_pointer_v<T>
•Sameas:!is_compound_v<T>
•SeeIsFundaTinSection19.8.1onpage448forimplementationdetails.
std::is_scalar<T>::value
•YieldstrueiftypeTisa“scalar”type.
•Sameas:is_arithmetic_v<T>||is_enum_v<T>||
is_pointer_v<T>
||is_member_pointer_v<T>||is_null_pointer_v<T>>std::
is_object<T>::value
•YieldstrueiftypeTdescribesthetypeofanobject.
•Sameas:is_scalar_v<T>||is_array_v<T>||
is_class_v<T>||is_union_v<T>)
•Sameas:!(is_function_v<T>||is_reference_v<T>||
is_void_v<T>)std::is_compound<T>::value
•YieldstrueiftypeTisatypecompoundoutofothertypes.
•Sameas:!is_fundamental_v<T>
•Sameas:is_enum_v<T>||is_array_v<T>||is_class_v<T>
||is_union_v<T>
||is_reference_v<T>||is_pointer_v<T>||is_member_pointer_v<T>
||is_function_v<T>
D.3TypePropertiesandOperations
Thenextgroupoftraitstestsotherpropertiesofsingletypesaswellascertain
operations(e.g.,valueswapping)thatmayapplytothem.
D.3.1OtherTypeProperties
std::is_signed<T>::value
•YieldstrueifTisasignedarithmetictype(i.e.,anarithmetictypethat
includesnegativevaluerepresentations;thisincludestypeslike(signed)
int,float).
•Fortypebool,ityieldsfalse.
•Fortypechar,itisimplementationdefinedwhetherityieldstrueorfalse.
•Forallnonarithmetictypes(includingenumerationtypes)is_signedyields
false.
std::is_unsigned<T>::value
•YieldstrueifTisanunsignedarithmetictype(i.e.,anarithmetictypethat
doesnotincludenegativevaluerepresentations;thisincludestypeslike
unsignedintandbool).
•Fortypechar,itisimplementationdefinedwhetherityieldstrueorfalse.
•Forallnonarithmetictypes(includingenumerationtypes)is_unsigned
yieldsfalse.
std::is_const<T>::value
•Yieldstrueifthetypeisconst-qualified.
•Notethataconstpointerhasaconst-qualifiedtype,whereasanon-const
pointerorareferencetoaconsttypeisnotconst-qualified.Forexample:
Clickheretoviewcodeimage
is_const<int*const>::value//true
is_const<intconst*>::value//false
is_const<intconst&>::value//false
•Thelanguagedefinesarraystobeconst-qualifiediftheelementtypeis
const-qualified.5Forexample:Clickheretoviewcodeimage
is_const<int[3]>::value//false
is_const<intconst[3]>::value//true
is_const<int[]>::value//false
is_const<intconst[]>::value//true
Trait Effect
is_signed<T>Signedarithmetictype
is_unsigned<T>Unsignedarithmetictype
is_const<T>const-qualified
is_volatile<T>volatile-qualified
is_aggregate<T>Aggregatetype(sinceC++17)
is_trivial<T>Scalar,trivialclass,orarraysofthesetypes
is_trivially_copyable<T>Scalar,triviallycopyableclass,orarraysofthese
types
is_standard_layout<T>Scalar,standardlayoutclass,orarraysofthese
types
is_pod<T>Plainolddatatype(typewhere
workstocopyobjects)
is_literal_type<T>Scalar,reference,class,orarraysofthesetypes
(deprecatedsinceC++17)
is_empty<T>Classwithnomembers,virtualmember
functions,orvirtualbaseclasses
is_polymorphic<T>Classwitha(derived)virtualmemberfunction
is_abstract<T>Abstractclass(atleastonepurevirtualfunction)
is_final<T>Finalclass(aclassnotallowedtoderivefrom,
sinceC++14)
has_virtual_destructor<T>
Classwithvirtualdestructor
has_unique_object_representations<T>Anytwoobjectwithsamevaluehavesame
representationinmemory(sinceC++17)
alignment_of<T>
Equivalenttoalignof(T
rank<T>Numberofdimensionsofanarraytype(or
extent<T,I=0> ExtentofdimensionI(or0
underlying_type<T>Underlyingtypeofanenumerationtype
is_invocable<T,Args…>
Canbeusedascallablefor
is_nothrow_invocable<
beusedascallableforArgs…
(sinceC++17)
is_invocable_r<RT,T,Args…>
Canbeusedascallablefor
(sinceC++17)
is_nothrow_invocable_r<
Canbeusedascallablefor
withoutthrowing(sinceC++17)
invoke_result<T,Args…>
Resulttypeifusedascallablefor
C++17)
result_of<F,ArgTypes>
ResulttypeifcallingFwithargumenttypes
ArgTypes(deprecatedsinceC++17)
TableD.3.TraitstoTestSimpleTypeProperties
std::is_volatile<T>::value
•Yieldstrueifthetypeisvolatile-qualified.
•Notethatavolatilepointerhasavolatile-qualifiedtype,whereasa
non-volatilepointerorareferencetoavolatiletypeisnot
volatile-qualified.Forexample:Clickheretoviewcodeimage
is_volatile<int*volatile>::value//true
is_volatile<intvolatile*>::value//false
is_volatile<intvolatile&>::value//false
•Thelanguagedefinesarraystobevolatile-qualifiediftheelementtypeis
volatile-qualified.6
Forexample:
Clickheretoviewcodeimage
is_volatile<int[3]>::value//false
is_volatile<intvolatile[3]>::value//true
is_volatile<int[]>::value//false
is_volatile<intvolatile[]>::value//true
std::is_aggregate<T>::value
•YieldstrueifTisanaggregatetype(eitheranarrayoraclass/struct/union
thathasnouser-defined,explicit,orinheritedconstructors,noprivateor
protectednonstaticdatamembers,novirtualfunctions,andnovirtual,private,
orprotectedbaseclasses).7
•Helpstofindoutwhetherlistinitializationisrequired.Forexample:Clickhere
toviewcodeimage
template<typenameColl,typename…T>
voidinsert(Coll&coll,T&&…val)
{
ifconstexpr(!std::is_aggregate_v<typenameColl::value_type>){
coll.emplace_back(std::forward<T>(val)…);//invalidforaggregates
}
else{
coll.emplace_back(typenameColl::value_type{std::forward<T>(val)…});
}
}
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
or(cv-qualified)void.
•AvailablesinceC++17.
std::is_trivial<T>::value
•Yieldstrueifthetypeisa“trivial”type:–ascalartype(integral,float,enum,
pointer;seeis_scalar()onpage707)–atrivialclasstype(aclassthathas
novirtualfunctions,novirtualbaseclasses,no(indirectly)user-defineddefault
constructor,copy/moveconstructor,copy/moveassignmentoperator,or
destructor,noinitializerfornonstaticdatamembers,novolatilemembers,and
nonontrivalmembers)–anarrayofsuchtypes
–andcv-qualifiedversionsofthesetypes•Yieldstrueif
is_trivially_copyable_v<T>yieldstrueandatrivialdefault
constructorexists.
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
or(cv-qualified)void.
std::is_trivially_copyable<T>::value
•Yieldstrueifthetypeisa“triviallycopyable”type:–ascalartype(integral,
float,enum,pointer;seeis_scalar<>onpage707)–atrivialclasstype(a
classthathasnovirtualfunctions,novirtualbaseclasses,no(indirectly)user-
defineddefaultconstructor,copy/moveconstructor,copy/moveassignment
operator,ordestructor,noinitializerfornonstaticdatamembers,novolatile
members,andnonontrivalmembers)–anarrayofsuchtypes–andcv-
qualifiedversionsofthesetypes•Yieldsthesameasis_trivial_v<T>
exceptitcanproducetrueforaclasstypewithoutatrivialdefault
constructor.
•Incontrasttois_standard_layout<>,volatilemembersarenotallowed,
referencesareallowed,membersmighthavedifferentaccess,andmembers
mightbedistributedamongdifferent(base)classes.
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
or(cv-qualified)void.
std::is_standard_layout<T>::value
•Yieldstrueifthetypehasastandardlayout,which,forexample,makesit
easiertoexchangevaluesofthistypewithotherlanguages.
–ascalartype(integral,float,enum,pointer;seeis_scalar<>onpage707)
–astandard-layoutclasstype(novirtualfunctions,novirtualbaseclasses,no
nonstaticreferencemembers,allnonstaticmembersareinthesame(base)
classdefinedwiththesameaccess,allmembersarealsostandard-layout
types)–anarrayofsuchtypes
–andcv-qualifiedversionsofthesetypes•Incontrasttois_trivial<>,
volatilemembersareallowed,referencesarenotallowed,membersmightnot
havedifferentaccess,andmembersmightnotbedistributedamongdifferent
(base)classes.
•Requiresthatthegiventype(forarrays,thebasictype)iseithercomplete(see
Section10.3.1onpage154)or(cv-qualified)void.
std::is_pod<T>::value
•YieldstrueifTisaplainolddatatype(POD).
•Objectsofsuchtypescanbecopiedbycopyingtheunderlyingstorage(e.g.,
usingmemcpy()).
•Sameas:is_trivial_t<T>&&is_standard_layout_v<T>
•Yieldsfalsefor:–classesthatdon’thaveatrivialdefaultconstructor,
copy/moveconstructor,copy/moveassignment,ordestructor–classesthat
havevirtualmembersorvirtualbaseclasses–classesthathavevolatileor
referencemembers–classesthathavemembersindifferent(base)classesor
withdifferentaccess–thetypesoflambdaexpressions(calledclosuretypes)–
functions
–void
–typescomposedfromthesetypes•Requiresthatthegiventypeiseither
complete(seeSection10.3.1onpage154)or(cv-qualified)void.
std::is_literal_type<T>::value
•Yieldstrueifthegiventypeisavalidreturntypeforaconstexprfunction
(whichnotablyexcludesanytyperequiringnontrivialdestruction).
•YieldstrueifTisaliteraltype:–ascalartype(integral,float,enum,pointer;
seeis_scalar()onpage707)–areference
–aclasstypewithatleastoneconstexprconstructorthatisnota
copy/moveconstructorineach(base)class,nouser-definedorvirtual
destructorinany(base)classormember,andwhereeveryinitializationfor
nonstaticdatamembersisaconstantexpression–anarrayofsuchtypes
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
or(cv-qualified)void.
•NotethatthistraitisdeprecatedsinceC++17because“itistooweaktobeused
meaningfullyingenericcode.Whatisreallyneededistheabilitytoknowthat
aspecificconstructionwouldproduceconstantinitialization.”
std::is_empty<T>::value
•YieldstrueifTisaclasstypebutnotauniontype,whoseobjectsholdno
data.
•YieldstrueifTisdefinedasclassorstructwith–nononstaticdata
membersotherthanbit-fieldsoflength0
–novirtualmemberfunctions–novirtualbaseclasses
–nononemptybaseclasses
•Requiresthatthegiventypeiscomplete(seeSection10.3.1onpage154)ifit
isaclass/struct(anincompleteunionisfine).
std::is_polymorphic<T>::value
•YieldstrueifTispolymorphicclasstype(aclassthatdeclaresorinheritsa
virtualfunction).
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
orneitheraclassnorastruct.
std::is_abstract<T>::value
•YieldstrueifTisanabstractclasstype(aclassforwhichnoobjectscanbe
createdbecauseithasatleastonepurevirtualmemberfunction).
•Requiresthatthegiventypeiscomplete(seeSection10.3.1onpage154)ifit
isaclass/struct(anincompleteunionisfine).
std::is_final<T>::value
•YieldstrueifTisanfinalclasstype(aclassorunionthatcan’tserveasa
baseclassbecauseitisdeclaredasbeingfinal).
•Forallnon-class/uniontypessuchasint,itreturnsfalse(thus,thisisnot
thesameassomethinglikeisderivable).
•RequiresthatthegiventypeTiseithercomplete(seeSection10.3.1onpage
154)orneitherclass/structnorunion.
•AvailablesinceC++14.
std::has_virtual_destructor<T>::value
•YieldstrueiftypeThasavirtualdestructor.
•Requiresthatthegiventypeiscomplete(seeSection10.3.1onpage154)ifit
isaclass/struct(anincompleteunionisfine).
std::has_unique_object_representations<T>::value
•YieldstrueifanytwoobjectsoftypeThavethesameobjectrepresentation
inmemory.Thatis,twoidenticalvaluesarealwaysrepresentedusingthesame
sequenceofbytevalues.
•Objectswiththispropertycanproduceareliablehashvaluebyhashingthe
associatedbytesequence(thereisnoriskthatsomebitsnotparticipatinginthe
objectvaluemightdifferfromonecasetoanother).
•Requiresthatthegiventypeistriviallycopyable(seeSectionD.3.1onpage
712)andeithercomplete(seeSection10.3.1onpage154)or(cv-qualified)
voidoranarrayofunknownbounds.
•AvailablesinceC++17.
std::alignment_of<T>::value
•YieldsthealignmentvalueofanobjectoftypeTasstd::size_t(for
arrays,theelementtype;forreferences,thereferencedtype).
•Sameas:alignof(T)
•ThistraitwasintroducedinC++11beforethealignof(…)construct.Itis
stilluseful,however,becausethetraitcanbepassedaroundasaclasstype,
whichisusefulforcertainmetaprograms.
•Requiresthatalignof(T)isavalidexpression.
•Usealigned_union<>togetthecommonalignmentofmultipletypes(see
SectionD.5onpage733).
std::rank<T>::value
•YieldsthenumberofdimensionsofanarrayoftypeTasstd::size_t.
•Yields0forallothertypes.
•Pointersdonothaveanyassociateddimensions.Anunspecifiedboundinan
arraytypedoesspecifyadimension.(Asusual,afunctionparameterdeclared
withanarraytypedoesnothaveanactualarraytype,andstd::arrayisnot
anarraytypeeither.SeeSectionD.2.1onpage704.)Forexample:
Clickheretoviewcodeimage
inta2[5][7];
rank_v<decltype(a2)>;//yields2
rank_v<int*>;//yields0(noarray)
externintp1[];
rank_v<decltype(p1)>;//yields1
std::extent<T>::value
std::extent<T,IDX>::value
•YieldsthesizeofthefirstorIDX-thdimensionofanarrayoftypeTas
std::size_t.
•Yields0,ifTisnotanarray,thedimensiondoesn’texist,orthesizeofthe
dimensionisnotknown.
•SeeSection19.8.2onpage453forimplementationdetails.
Clickheretoviewcodeimage
inta2[5][7];
extent_v<decltype(a2)>;//yields5
extent_v<decltype(a2),0>;//yields5
extent_v<decltype(a2),1>;//yields7
extent_v<decltype(a2),2>;//yields0
extent_v<int*>;//yields0
externintp1[];
extent_v<decltype(p1)>;//yields0
std::underlying_type<T>::type
•YieldstheunderlyingtypeofanenumerationtypeT.
•Requiresthatthegiventypeisacomplete(seeSection10.3.1onpage154)
enumerationtype.Forallothertypes,ithasundefinedbehavior.
std::is_invocable<T,Args…>::value
std::is_nothrow_invocable<T,Args…>::value
•YieldstrueifTisusableasacallableforArgs…(withtheguaranteethatno
exceptionisthrown).
•Thatis,wecanusethesetraitstotestwhetherwecancallor
std::invoke()thegivencallableTforArgs….(SeeSection11.1onpage
157fordetailsaboutcallablesandstd::invoke().)•Requiresthatall
giventypesarecomplete(seeSection10.3.1onpage154)or(cv-qualified)
voidoranarrayofunknownbounds.
•Forexample:
Clickheretoviewcodeimage
structC{
booloperator()(int)const{
returntrue;
}
};
std::is_invocable<C>::value//false
std::is_invocable<C,int>::value//true
std::is_invocable<int*>::value//false
std::is_invocable<int(*)()>::value//true
•AvailablesinceC++17.8
std::is_invocable_r<RET_T,T,Args…>::value
std::is_nothrow_invocable_r<RET_T,T,Args…
>::value
•YieldstrueifwecanuseTasacallableforArgs…(withtheguaranteethat
noexceptionisthrown),returningavalueconvertibletotypeRET_T.
•Thatis,wecanusethesetraitstotestwhetherwecancallor
std::invoke()thepassedcallableTforArgs…andusethereturnvalue
asRET_T.(SeeSection11.1onpage157fordetailsaboutcallablesand
std::invoke().)•Requiresthatallpassedtypesarecomplete(seeSection
10.3.1onpage154)or(cv-qualified)voidoranarrayofunknownbounds.
•Forexample:
Clickheretoviewcodeimage
structC{
booloperator()(int)const{
returntrue;
}
};
std::is_invocable_r<bool,C,int>::value//true
std::is_invocable_r<int,C,long>::value//true
std::is_invocable_r<void,C,int>::value//true
std::is_invocable_r<char*,C,int>::value//false
std::is_invocable_r<long,int(*)(int)>::value//false
std::is_invocable_r<long,int(*)(int),int>::value//true
std::is_invocable_r<long,int(*)(int),double>::value//true
•AvailablesinceC++17.
std::invoke_result<T,Args…>::value
std::result_of<T,Args…>::value
•YieldsthereturntypeofthecallableTcalledforArgs….
•Notethatthesyntaxisslightlydifferent:–Toinvoke_result<>youhave
topassboththetypeofthecallableandthetypeoftheargumentsas
parameters.
–Toresult_of<>youhavetopassa“functiondeclaration”usingthe
correspondingtypes.
•Ifnocallispossible,thereisnotypememberdefined,sothatusingitisan
error(whichmightSFINAEoutafunctiontemplateusingitinitsdeclaration;
seeSection8.4onpage131).
•Thatis,wecanusethesetraitstogetthereturntypeobtainedwhenwecallor
std::invoke()thegivencallableTforArgs….(SeeSection11.1onpage
157fordetailsaboutcallablesandstd::invoke().)•Requiresthatall
giventypesareeithercomplete(seeSection10.3.1onpage154),(cv-
qualified)void,oranarraytypeofunknownbound.
•invoke_result<>isavailablesinceC++17andreplacesresult_of<>,
whichisdeprecatedsinceC++17,becauseinvoke_result<>provides
someimprovementssuchtheeasiersyntaxandpermittingabstracttypesforT.
•Forexample:
Clickheretoviewcodeimage
std::stringfoo(int);
usingR0=typenamestd::result_of<decltype(&foo)(int)>::type;//
C++11
usingR1=std::result_of_t<decltype(&foo)(int)>;//C++14
usingR2=std::invoke_result_t<decltype(foo),int>;//C++17
structABC{
virtual~ABC()=0;
voidoperator()(int)const{
}
};
usingT1=typenamestd::result_of<ABC(int)>::type;//ERROR:ABCis
abstract
usingT2=typenamestd::invoke_result<ABC,int>::type;//OKsince
C++17
SeeSection11.1.3onpage163forafullexample.
D.3.2TestforSpecificOperations
Trait Effect
is_constructible<T,Args…>CaninitializetypeT
withtypesArgs
is_trivially_constructible<T,Args…>Cantriviallyinitialize
typeTwithtypesArgs
is_nothrow_constructible<T,Args…>CaninitializetypeT
withtypesArgsandthat
operationcan’tthrow
is_default_constructible<T>CaninitializeTwithout
arguments
is_trivially_default_constructible<T>CantriviallyinitializeT
withoutarguments
is_nothrow_default_constructible<T>CaninitializeTwithout
argumentsandthat
operationcan’tthrow
is_copy_constructible<T>CancopyaT
is_trivially_copy_constructible<T>CantriviallycopyaT
is_nothrow_copy_constructible<T>CancopyaTandthat
operationcan’tthrow
is_move_constructible<T>CanmoveaT
is_trivially_move_constructible<T>CantriviallymoveaT
is_nothrow_move_constructible<T>CanmoveaTandthat
operationcan’tthrow
is_assignable<T,T2>CanassigntypeT2to
typeT
is_trivially_assignable<T,T2>Cantriviallyassign
typeT2totypeT
is_nothrow_assignable<T,T2>CanassigntypeT2to
typeTandthat
operationcan’tthrow
is_copy_assignable<T>CancopyassignaT
is_trivially_copy_assignable<T>Cantriviallycopy
assignaT
is_nothrow_copy_assignable<T>CancopyassignaT
andthatoperationcan’t
throw
is_move_assignable<T>CanmoveassignaT
is_trivially_move_assignable<T>Cantriviallymove
assignaT
is_nothrow_move_assignable<T>CanmoveassignaT
andthatoperationcan’t
throw
is_destructible<T>CandestroyaT
is_trivially_destructible<T>Cantriviallydestroya
T
is_nothrow_destructible<T>Cantriviallydestroya
Tandthatoperation
can’tthrow
is_swappable<T>Cancallswap()for
thistype(sinceC++17)
is_nothrow_swappable<T>Cancallswap()for
thistypeandthat
operationcan’tthrow
(sinceC++17)
is_swappable_with<T,T2>Cancallswap()for
thesetwotypeswith
specificvaluecategory
(sinceC++17)
is_nothrow_swappable_with<T,T2>Cancallswap()for
thesetwotypeswith
specificvaluecategory
andthatoperationcan’t
throw(sinceC++17)
TableD.4.TraitstoCheckforSpecificOperations
TableD.4liststhetypetraitsthatallowustocheckforsomespecificoperations.
Theformswithis_trivially_…additionallycheckwhetherall(sub-
)operationscalledfortheobject,members,orbaseclassesaretrivial(neither
user-definednorvirtual).Theformswithis_nothrow_…additionallycheck
whetherthecalledoperationguaranteesnottothrow.Notethatall
is_…_constructiblechecksimplythecorresponding
is_…_destructiblecheck.Forexample:Clickheretoviewcodeimage
utils/isconstructible.cpp
#include<iostream>
classC{
public:
C(){//defaultconstructorhasnonoexcept
}
virtual~C()=default;//makesCnontrivial
};
intmain()
{
usingnamespacestd;
cout<<is_default_constructible_v<C><<’\n’;//true
cout<<is_trivially_default_constructible_v<C><<’\n’;//false
cout<<is_nothrow_default_constructible_v<C><<’\n’;//false
cout<<is_copy_constructible_v<C><<’\n’;//true
cout<<is_trivially_copy_constructible_v<C><<’\n’;//true
cout<<is_nothrow_copy_constructible_v<C><<’\n’;//true
cout<<is_destructible_v<C><<’\n’;//true
cout<<is_trivially_destructible_v<C><<’\n’;//false
cout<<is_nothrow_destructible_v<C><<’\n’;//true
}
Duetothedefinitionofavirtualconstructor,alloperationsarenolongertrivial.
Andbecausewedefineadefaultconstructorwithoutnoexcept,itmight
throw.Allotheroperations,bydefault,guaranteenottothrow.
std::is_constructible<T,Args…>::value
std::is_trivially_constructible<T,Args…>::value
std::is_nothrow_constructible<T,Args…>::value
•YieldstrueifanobjectoftypeTcanbeinitializedwithargumentsofthe
typesgivenbyArgs…(withoutusinganontrivialoperationorwiththe
guaranteethatnoexceptionisthrown).Thatis,thefollowingmustbevalid:9
Tt(std::declval<Args>()…);
•Atruevalueimpliesthattheobjectcanbedestroyedaccordingly(i.e.,
is_destructible_v<T>,is_trivially_destructible_v<T>,
oris_nothrow_destructible_v<T>yieldstrue).
•Requiresthatallgiventypesareeithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,orarraysofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_constructible_v<int>//true
is_constructible_v<int,int>//true
is_constructible_v<long,int>//true
is_constructible_v<int,void*>//false
is_constructible_v<void*,int>//false
is_constructible_v<charconst*,std::string>//false
is_constructible_v<std::string,charconst*>//true
is_constructible_v<std::string,charconst*,int,int>//true
•Notethatis_convertiblehasadifferentorderforthesourceand
destinationtypes.
std::is_default_constructible<T>::value
std::is_trivially_default_constructible<T>::value
std::is_nothrow_default_constructible<T>::value
•YieldstrueifanobjectoftypeTcanbeinitializedwithoutanyargumentfor
initialization(withoutusinganontrivialoperationorwiththeguaranteethatno
exceptionisthrown).
•Sameasis_constructible_v<T>,
is_trivially_constructible_v<T>,or
is_nothrow_constructible_v<T>,respectively.
•Atruevalueimpliesthattheobjectcanbedestroyedaccordingly(i.e.,
is_destructible_v<T>,is_trivially_destructible_v<T>,
oris_nothrow_destructible_v<T>yieldstrue).
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,oranarrayofunknownbound.
std::is_copy_constructible<T>::value
std::is_trivially_copy_constructible<T>::value
std::is_nothrow_copy_constructible<T>::value
•YieldstrueifanobjectoftypeTcanbecreatedbycopyinganothervalueof
typeT(withoutusinganontrivialoperationorwiththeguaranteethatno
exceptionisthrown).
•YieldsfalseifTisnotareferenceabletype(either(cv-qualified)voidora
functiontypethatisqualifiedwithconst,volatile,&,and/or&&).
•ProvidedTisareferenceabletype,sameasis_constructible<T,T
const&>::value,is_trivially_constructible<T,T
const&>::value,oris_nothrow_constructible<T,T
const&>::valuerespectively.
•TofindoutwhetheranobjectofTwouldbecopyconstructiblefromanrvalue
oftypeT,useis_constructible<T,T&&>,andsoon.
•Atruevalueimpliesthattheobjectcanbedestroyedaccordingly(i.e.,
is_destructible_v<T>,is_trivially_destructible_v<T>,
oris_nothrow_destructible_v<T>yieldstrue).
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,oranarrayofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_copy_constructible_v<int>//yieldstrue
is_copy_constructible_v<void>//yieldsfalse
is_copy_constructible_v<std::unique_ptr<int>>//yieldsfalse
is_copy_constructible_v<std::string>//yieldstrue
is_copy_constructible_v<std::string&>//yieldstrue
is_copy_constructible_v<std::string&&>//yieldsfalse
//incontrastto:
is_constructible_v<std::string,std::string>//yieldstrue
is_constructible_v<std::string&,std::string&>//yieldstrue
is_constructible_v<std::string&&,std::string&&>//yieldstrue
std::is_move_constructible<T>::value
std::is_trivially_move_constructible<T>::value
std::is_nothrow_move_constructible<T>::value
•YieldstrueifanobjectoftypeTcanbecreatedfromanrvalueoftypeT
(withoutusinganontrivialoperationorwiththeguaranteethatnoexceptionis
thrown).
•YieldsfalseifTisnotareferenceabletype(either(cv-qualified)voidora
functiontypethatisqualifiedwithconst,volatile,&,and/or&&).
•ProvidedTisareferenceabletype,sameas
is_constructible<T,T&&>::value,
is_trivially_constructible<T,T&&>::value,or
is_nothrow_constructible<T,T&&>::valuerespectively.
•Atruevalueimpliesthattheobjectcanbedestroyedaccordingly(i.e.,
is_destructible_v<T>,is_trivially_destructible_v<T>,
oris_nothrow_destructible_v<T>yieldstrue).
•Notethatthereisnowaytocheckwhetheramoveconstructorthrowswithout
beingabletocallitdirectlyforanobjectoftypeT.Itisnotenoughforthe
constructortobepublicandnotdeleted;italsorequiresthatthecorresponding
typeisnotanabstractclass(referencesorpointerstoabstractclasseswork
fine).
•SeeSection19.7.2onpage443forimplementationdetails.
•Forexample:
Clickheretoviewcodeimage
is_move_constructible_v<int>//yieldstrue
is_move_constructible_v<void>//yieldsfalse
is_move_constructible_v<std::unique_ptr<int>>//yieldstrue
is_move_constructible_v<std::string>//yieldstrue
is_move_constructible_v<std::string&>//yieldstrue
is_move_constructible_v<std::string&&>//yieldstrue
//incontrastto:
is_constructible_v<std::string,std::string>//yieldstrue
is_constructible_v<std::string&,std::string&>//yieldstrue
is_constructible_v<std::string&&,std::string&&>//yieldstrue
std::is_assignable<TO,FROM>::value
std::is_trivially_assignable<TO,FROM>::value
std::is_nothrow_assignable<TO,FROM>::value
•YieldstrueifanobjectoftypeFROMcanbeassignedtoanobjectoftypeTO
(withoutusinganontrivialoperationorwiththeguaranteethatnoexceptionis
thrown).
•Requiresthatthegiventypesareeithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,orarraysofunknownbound.
•Notethatis_assignable_v<>foranonreference,nonclasstypeasfirst
typealwaysyieldsfalse,becausesuchtypesproduceprvalues.Thatis,the
statement42=77;isnotvalid.Forclasstypes,however,rvaluesmaybe
assignedto,givenanappropriateassignmentoperator(duetoanoldrulethat
non-constmemberfunctionscanbeinvokedonrvaluesofclasstypes).10
•Notethatis_convertiblehasadifferentorderforthesourceand
destinationtypes.
•Forexample:
Clickheretoviewcodeimage
is_assignable_v<int,int>//yieldsfalse
is_assignable_v<int&,int>//yieldstrue
is_assignable_v<int&&,int>//yieldsfalse
is_assignable_v<int&,int&>//yieldstrue
is_assignable_v<int&&,int&&>//yieldsfalse
is_assignable_v<int&,long&>//yieldstrue
is_assignable_v<int&,void*>//yieldsfalse
is_assignable_v<void*,int>//yieldsfalse
is_assignable_v<void*,int&>//yieldsfalse
is_assignable_v<std::string,std::string>//yieldstrue
is_assignable_v<std::string&,std::string&>//yieldstrue
is_assignable_v<std::string&&,std::string&&>//yieldstrue
std::is_copy_assignable<T>::value
std::is_trivially_copy_assignable<T>::value
std::is_nothrow_copy_assignable<T>::value
•YieldstrueifavalueoftypeTcanbe(copy-)assignedtoanobjectoftypeT
(withoutusinganontrivialoperationorwiththeguaranteethatnoexceptionis
thrown).
•YieldsfalseifTisnotareferenceabletype(either(cv-qualified)voidora
functiontypethatisqualifiedwithconst,volatile,&,and/or&&).
•ProvidedTisareferenceabletype,sameasis_assignable<T&,T
const&>::value,is_trivially_assignable<T&,T
const&>::value,oris_nothrow_assignable<T&,T
const&>::valuerespectively.
•TofindoutwhetheranrvalueoftypeTcanbecopyassignedtoanotherrvalue
oftypeT,useis_assignable<T&&,T&&>,andsoon.
•Notethatvoid,built-inarraytypes,andclasseswithdeletedcopy-assignment
operatorcannotbecopy-assigned.
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,oranarrayofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_copy_assignable_v<int>//yieldstrue
is_copy_assignable_v<int&>//yieldstrue
is_copy_assignable_v<int&&>//yieldstrue
is_copy_assignable_v<void>//yieldsfalse
is_copy_assignable_v<void*>//yieldstrue
is_copy_assignable_v<char[]>//yieldsfalse
is_copy_assignable_v<std::string>//yieldstrue
is_copy_assignable_v<std::unique_ptr<int>>//yieldsfalse
std::is_move_assignable<T>::value
std::is_trivially_move_assignable<T>::value
std::is_nothrow_move_assignable<T>::value
•YieldstrueifanrvalueoftypeTcanbemove-assignedtoanobjectoftypeT
(withoutusinganontrivialoperationorwiththeguaranteethatnoexceptionis
thrown).
•YieldsfalseifTisnotareferenceabletype(either(cv-qualified)voidora
functiontypethatisqualifiedwithconst,volatile,&,and/or&&).
•ProvidedTisareferenceabletype,sameas
is_assignable<T&,T&&>::value,
is_trivially_assignable<T&,T&&>::value,or
is_nothrow_assignable<T&,T&&>::valuerespectively.
•Notethatvoid,built-inarraytypes,andclasseswithdeletedmove-assignment
operatorcannotbemove-assigned.
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage154)
or(cv-qualified)voidoranarrayofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_move_assignable_v<int>//yieldstrue
is_move_assignable_v<int&>//yieldstrue
is_move_assignable_v<int&&>//yieldstrue
is_move_assignable_v<void>//yieldsfalse
is_move_assignable_v<void*>//yieldstrue
is_move_assignable_v<char[]>//yieldsfalse
is_move_assignable_v<std::string>//yieldstrue
is_move_assignable_v<std::unique_ptr<int>>//yieldstrue
std::is_destructible<T>::value
std::is_trivially_destructible<T>::value
std::is_nothrow_destructible<T>::value
•YieldstrueifanobjectoftypeTcanbedestroyed(withoutusinganontrivial
operationorwiththeguaranteethatnoexceptionisthrown).
•Alwaysyieldstrueforreferences.
•Alwaysyieldsfalseforvoid,arraytypeswithunknownbounds,and
functiontypes.
•is_trivially_destructibleyieldstrueifnodestructorofT,any
baseclass,oranynonstaticdatamemberisuser-definedorvirtual.
•Requiresthatthegiventypeiseithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,oranarrayofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_destructible_v<void>//yieldsfalse
is_destructible_v<int>//yieldstrue
is_destructible_v<std::string>//yieldstrue
is_destructible_v<std::pair<int,std::string>>//yieldstrue
is_trivially_destructible_v<void>//yieldsfalse
is_trivially_destructible_v<int>//yieldstrue
is_trivially_destructible_v<std::string>//yieldsfalse
is_trivially_destructible_v<std::pair<int,int>>//yieldstrue
is_trivially_destructible_v<std::pair<int,std::string>>//yields
false
std::is_swappable_with<T1,T2>::value
std::is_nothrow_swappable_with<T1,T2>::value
•YieldstrueifanexpressionoftypeT1canbeswap()’edwithan
expressionoftypeT2exceptthatreferencetypesonlydeterminethevalue
categoryoftheexpression(withtheguaranteethatnoexceptionisthrown).
•Requiresthatthegiventypesareeithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,orarraysofunknownbound.
•Notethatis_swappable_with_v<>foranonreference,nonclasstypeas
firstorsecondtypealwaysyieldsfalse,becausesuchtypesproduce
prvalues.Thatis,swap(42,77)isnotvalid.
•Forexample:
Clickheretoviewcodeimage
is_swappable_with_v<int,int>//yieldsfalse
is_swappable_with_v<int&,int>//yieldsfalse
is_swappable_with_v<int&&,int>//yieldsfalse
is_swappable_with_v<int&,int&>//yieldstrue
is_swappable_with_v<int&&,int&&>//yieldsfalse
is_swappable_with_v<int&,long&>//yieldsfalse
is_swappable_with_v<int&,void*>//yieldsfalse
is_swappable_with_v<void*,int>//yieldsfalse
is_swappable_with_v<void*,int&>//yieldsfalse
is_swappable_with_v<std::string,std::string>//yieldsfalse
is_swappable_with_v<std::string&,std::string&>//yieldstrue
is_swappable_with_v<std::string&&,std::string&&>//yieldsfalse
•AvailablesinceC++17.
std::is_swappable<T>::value
std::is_nothrow_swappable<T>::value
•YieldstrueiflvaluesoftypeTcanbeswapped(withtheguaranteethatno
exceptionisthrown).
•ProvidedTisareferenceabletype.sameas
is_swappable_with<T&,T&>::valueor
is_nothrow_swappable_with<T&,T&>::valuerespectively.
•YieldsfalseifTisnotareferenceabletype(either(cv-qualified)voidora
functiontypethatisqualifiedwithconst,volatile,&,and/or&&).
•TofindoutwhetheranrvalueofTwouldbeswappablewithanotherrvalueof
T,useis_swappable_with<T&&,T&&>.
•Requiresthatthegiventypeisacompletetype(Section10.3.1onpage154),
(cv-qualified)void,oranarrayofunknownbound.
•Forexample:
Clickheretoviewcodeimage
is_swappable_v<int>//yieldstrue
is_swappable_v<int&>//yieldstrue
is_swappable_v<int&&>//yieldstrue
is_swappable_v<std::string&&>//yieldstrue
is_swappable_v<void>//yieldsfalse
is_swappable_v<void*>//yieldstrue
is_swappable_v<char[]>//yieldsfalse
is_swappable_v<std::unique_ptr<int>>//yieldstrue
•AvailablesinceC++17.
D.3.3RelationshipsBetweenTypes
TableD.5liststhetypetraitsthatallowtestingcertainrelationshipsbetween
types.Thisincludescheckingwhichconstructorsandassignmentoperatorsare
providedforclasstypes.
Trait Effect
is_same<T1,T2>T1andT2arethesametypes(including
const/volatilequalifiers)
is_base_of<T,D>TypeTisbaseclassoftypeD
is_convertible<T,
T2>
TypeTisconvertibleintotypeT2
TableD.5.TraitstoTestTypeRelationships
std::is_same<T1,T2>::value
•YieldstrueifT1andT2namethesametypeincludingcv-qualifiers(const
andvolatile).
•Yieldstrueifatypeisatypealiasofanother.
•Yieldstrueiftwoobjectswereinitializedbyobjectsofthesametype.
•Yieldsfalseforthe(closure)typesassociatedwithtwodistinctlambda
expressionseveniftheydefinethesamebehavior.
•Forexample:
Clickheretoviewcodeimage
autoa=nullptr;
autob=nullptr;
is_same_v<decltype(a),decltype(b)>//yieldstrue
usingA=int;
is_same_v<A,int>//yieldstrue
autox=[](int){};
autoy=x;
autoz=[](int){};
is_same_v<decltype(x),decltype(y)>//yieldstrue
is_same_v<decltype(x),decltype(z)>//yieldsfalse
•SeeSection19.3.3onpage410forimplementationdetails.
std::is_base_of<B,D>::value
•YieldstrueifBisabaseclassofDorBisthesameclassasD.
•Itdoesn’tmatterwhetheratypeiscv-qualified,privateorprotectedinheritance
isused,DhasmultiplebaseclassesoftypeB,orDhasBasabaseclassvia
multipleinheritancepaths(viavirtualinheritance).
•Yieldsfalseifatleastoneofthetypesisaunion.
•RequiresthattypeDiseithercomplete(seeSection10.3.1onpage154),has
thesametypeasB(ignoringanyconst/volatilequalification),oris
neitherastructnoraclass.
•Forexample:
Clickheretoviewcodeimage
classB{
};
classD1:B{
};classD2:B{
};classDD:privateD1,privateD2{
};
is_base_of_v<B,D1>//yieldstrue
is_base_of_v<B,DD>//yieldstrue
is_base_of_v<Bconst,DD>//yieldstrue
is_base_of_v<B,DDconst>//yieldstrue
is_base_of_v<B,Bconst>//yieldstrue
is_base_of_v<B&,DD&>//yieldsfalse(noclasstype)
is_base_of_v<B[3],DD[3]>//yieldsfalse(noclasstype)
is_base_of_v<int,int>//yieldsfalse(noclasstype)
std::is_convertible<FROM,TO>::value
•YieldstrueifexpressionoftypeFROMisconvertibletotypeTO.Thus,the
followingmustbevalid:11
Clickheretoviewcodeimage
TOtest(){
returnstd::declval<FROM>();
}
•AreferenceontopoftypeFROMisonlyusedtodeterminethevaluecategory
oftheexpressionbeingconverted;theunderlyingtypeisthenthetypeofthe
sourceexpression.
•Notethatis_constructibledoesnotalwaysimplyis_convertible.
Forexample:Clickheretoviewcodeimage
classC{
public:
explicitC(Cconst&);//noimplicitcopyconstructor
…
};
is_constructible_v<C,C>//yieldstrue
is_convertible_v<C,C>//yieldsfalse
•Requiresthatthegiventypesareeithercomplete(seeSection10.3.1onpage
154),(cv-qualified)void,orarraysofunknownbound.
•Notethatis_constructible(seeSectionD.3.2onpage719)and
is_assignable(seeSectionD.3.2onpage721)haveadifferentorderfor
thesourceanddestinationtypes.
•SeeSection19.5onpage428forimplementationdetails.
D.4TypeConstruction
ThetraitslistedinTableD.6allowustoconstructtypesfromothertypes.
Trait Effect
remove_const<T>Correspondingtypewithoutconst
remove_volatile<T>Correspondingtypewithoutvolatile
remove_cv<T>Correspondingtypewithoutconstand
volatile
add_const<T>Correspondingconsttype
add_volatile<T>Correspondingvolatiletype
add_cv<T>Correspondingconstvolatiletype
make_signed<T>Correspondingsignednonreferencetype
make_unsigned<T>Correspondingunsignednonreferencetype
remove_reference<T>Correspondingnonreferencetype
add_lvalue_reference<T>Correspondinglvaluereferencetype(rvalues
becomelvalues)
add_rvalue_reference<T>Correspondingrvaluereferencetype(lvalues
remainlvalues)
remove_pointer<T>Referredtypeforpointers(sametype
otherwise)
add_pointer<T>Typeofpointertocorresponding
nonreferencetype
remove_extent<T>Elementtypesforarrays(sametype
otherwise)
remove_all_extents<T>Elementtypeformultidimensionalarrays
(sametypeotherwise)
decay<T>Transferstocorresponding“by-value”type
TableD.6.TraitsforTypeConstruction
std::remove_const<T>::type
std::remove_volatile<T>::type
std::remove_cv<T>::type
•YieldsthetypeTwithoutconstor/andvolatileatthetoplevel.
•Notethataconstpointerisaconst-qualifiedtype,whereasanon-const
pointerorreferencetoaconsttypeisnotconst-qualified.Forexample:Click
heretoviewcodeimage
remove_cv_t<int>//yieldsint
remove_const_t<intconst>//yieldsint
remove_cv_t<intconstvolatile>//yieldsint
remove_const_t<intconst&>//yieldsintconst&(onlyreferstoint
const)
Clearly,theorderinwhichtypeconstructiontraitsareappliedmatters:12
Clickheretoviewcodeimage
remove_const_t<remove_reference_t<intconst&>>//yieldsint
remove_reference_t<remove_const_t<intconst&>>//yieldsintconst
Instead,wemayprefertousestd::decay<>,which,however,alsoconverts
arrayandfunctiontypestocorrespondingpointertypes(seeSectionD.4onpage
731):Clickheretoviewcodeimage
decay_t<intconst&>//yieldsint
•SeeSection19.3.2onpage406forimplementationdetails.
std::add_const<T>::type
std::add_volatile<T>::type
std::add_cv<T>::type
•YieldsthetypeofTwithconstor/andvolatilequalifiersaddedatthetop
level.
•Applyingoneofthesetraitstoareferencetypeorafunctiontypehasnoeffect.
Forexample:Clickheretoviewcodeimage
add_cv_t<int>//yieldsintconstvolatile
add_cv_t<intconst>//yieldsintconstvolatile
add_cv_t<intconstvolatile>//yieldsintconstvolatile
add_const_t<int>//yieldsintconst
add_const_t<intconst>//yieldsintconst
add_const_t<int&>//yieldsint&
std::make_signed<T>::type
std::make_unsigned<T>::type
•Yieldsthecorrespondingsigned/unsignedtypeofT.
•RequiresthatTisanenumerationtypeora(cv-qualified)integraltypeother
thanbool.Allothertypesleadtoundefinedbehavior(seeSection19.7.1on
page442foradiscussionabouthowtoavoidthisundefinedbehavior).
•Applyingoneofthesetraitstoareferencetypeorafunctiontypehasnoeffect,
whereasanon-constpointerorreferencetoaconsttypeisnotconst-
qualified.Forexample:Clickheretoviewcodeimage
make_unsigned_t<char>//yieldsunsignedchar
make_unsigned_t<int>//yieldsunsignedint
make_unsigned_t<intconst&>//undefinedbehavior
std::remove_reference<T>::type
•YieldsthetypethereferencetypeTrefersto(orTitselfifitisnotareference
type).
•Forexample:
Clickheretoviewcodeimage
remove_reference_t<int>//yieldsint
remove_reference_t<intconst>//yieldsintconst
remove_reference_t<intconst&>//yieldsintconst
remove_reference_t<int&&>//yieldsint
•Notethatareferencetypeitselfisnotaconsttype.Forthisreason,theorder
ofapplyingtypeconstructiontraitsmatters:13
Clickheretoviewcodeimage
remove_const_t<remove_reference_t<intconst&>>//yieldsint
remove_reference_t<remove_const_t<intconst&>>//yieldsintconst
Instead,wemayprefertousestd::decay<>,which,however,alsoconverts
arrayandfunctiontypestocorrespondingpointertypes(seeSectionD.4onpage
731):Clickheretoviewcodeimage
decay_t<intconst&>//yieldsint
•SeeSection19.3.2onpage404forimplementationdetails.
std::add_lvalue_reference<T>::type
std::add_rvalue_reference<T>::type
•YieldsanlvalueorrvaluereferencetoTifTisareferenceabletype.
•YieldsTifTisnotreferenceable(either(cv-qualified)voidorafunctiontype
thatisqualifiedwithconst,volatile,&,and/or&&).
•NotethatifTalreadyisareferencetype,thetraitsusethereferencecollapsing
rules(seeSection15.6.1onpage277):Theresultisanrvaluereferenceonlyif
add_rvalue_referenceisusedandTisanrvaluereference.
•Forexample:
Clickheretoviewcodeimage
add_lvalue_reference_t<int>//yieldsint&
add_rvalue_reference_t<int>//yieldsint&&
add_rvalue_reference_t<intconst>//yieldsintconst&&
add_lvalue_reference_t<intconst&>//yieldsintconst&
add_rvalue_reference_t<intconst&>//yieldsintconst&(reference
collapsingrules)
add_rvalue_reference_t<remove_reference_t<intconst&>>//yields
int&&
add_lvalue_reference_t<void>//yieldsvoid
add_rvalue_reference_t<void>//yieldsvoid
•SeeSection19.3.2onpage405forimplementationdetails.
std::remove_pointer<T>::type
•YieldsthetypethepointertypeTpointsto(orTitselfifitisnotapointer
type).
•Forexample:
Clickheretoviewcodeimage
remove_pointer_t<int>//yieldsint
remove_pointer_t<intconst*>//yieldsintconst
remove_pointer_t<intconst*const*const>//yieldsintconst*const
std::add_pointer<T>::type
•YieldsthetypeofapointertoT,or,inthecaseofareferencetypeT,thetype
ofapointertounderlyingtypeofT.
•YieldsTifthereisnosuchtype(appliestocv-qualifiedfunctiontypes).
•Forexample:
Clickheretoviewcodeimage
add_pointer_t<void>//yieldsvoid*
add_pointer_t<intconst*const>//yieldsintconst*const*
add_pointer_t<int&>//yieldsint*
add_pointer_t<int[3]>//yieldsint(*)[3]
add_pointer_t<void(&)(int)>//yieldsvoid(*)(int)
add_pointer_t<void(int)>//yieldsvoid(*)(int)
add_pointer_t<void(int)const>//yieldsvoid(int)const(nochange)
std::remove_extent<T>::type
std::remove_all_extents<T>::type
•Givenanarraytype,remove_extentproducesitsimmediateelementtype
(whichcoulditselfbeanarraytype)andremove_all_extentsstripsall
“arraylayers”toproducetheunderlyingelementtype(whichisthereforeno
longeranarraytype).IfTisnotanarraytype,Titselfisproduced.
•Pointersdonothaveanyassociateddimensions.Anunspecifiedboundinan
arraytypedoesspecifyadimension.(Asusual,afunctionparameterdeclared
withanarraytypedoesnothaveanactualarraytype,andstd::arrayisnot
anarraytypeeither.SeeSectionD.2.1onpage704.)•Forexample:
Clickheretoviewcodeimage
remove_extent_t<int>//yieldsint
remove_extent_t<int[10]>//yieldsint
remove_extent_t<int[5][10]>//yieldsint[10]
remove_extent_t<int[][10]>//yieldsint[10]
remove_extent_t<int*>//yieldsint*
remove_all_extents_t<int>//yieldsint
remove_all_extents_t<int[10]>//yieldsint
remove_all_extents_t<int[5][10]>//yieldsint
remove_all_extents_t<int[][10]>//yieldsint
remove_all_extents_t<int(*)[5]>//yieldsint(*)[5]
•SeeSection23.1.2onpage531forimplementationdetails.
std::decay<T>::type
•YieldsthedecayedtypeofT.
•Indetail,fortypeTthefollowingtransformationsareperformed:–First,
remove_reference(seeSectionD.4onpage729)isapplied.
–Iftheresultisanarraytype,apointertotheimmediateelementtypeis
produced(seeSection7.1onpage107).
–Otherwise,iftheresultisafunctiontype,thetypeyieldedby
add_pointerforthatfunctiontypeisproduced(seeSection11.1.1onpage
159).
–Otherwise,thatresultisproducedwithoutanytop-levelconst/volatile
qualifiers.
•decay<>modelsby-valuepassingofargumentsorthetypeconversionswhen
initializinganobjectsoftypeauto.
•decay<>isparticularlyusefultohandletemplateparametersthatmaybe
substitutedbyreferencetypesbutusedtodetermineareturntypeora
parametertypeofanotherfunction.SeeSection1.3.2onpage12andSection
7.6onpage120forexamplesdiscussingandusingstd::decay<>()(the
latterwiththehistoryofimplementingstd::make_pair<>()).
•Forexample:
Clickheretoviewcodeimage
decay_t<intconst&>//yieldsint
decay_t<intconst[4]>//yieldsintconst*
voidfoo();
decay_t<decltype(foo)>//yieldsvoid(*)()
•SeeSection19.3.2onpage407forimplementationdetails.
D.5OtherTraits
TableD.7listsallremainingtypetraits.Theyqueryspecialpropertiesorprovide
morecomplicatedtypetransformations.
Trait Effect
enable_if<B,T=void
>
YieldstypeTonlyifboolBistrue
conditional<B,T,F>YieldstypeTifboolBistrueandtypeF
otherwise
common_type<T1,…> Commontypeofallpassedtypes
aligned_storage<Len>TypeofLenbyteswithdefaultalignment
aligned_storage<Len,
Align>
TypeofLenbytesalignedaccordingtoadivisor
ofsize_tAlign
aligned_union<Len,
Types…>
TypeofLenbytesalignedforaunionofTypes…
TableD.7.OtherTypeTraits
std::enable_if<cond>::type
std::enable_if<cond,T>::type
•YieldsvoidorTinitsmembertypeifcondistrue.Otherwise,itdoesnot
defineamembertype.
•Becausethetypememberisnotdefinedwhenthecondisfalse,thistrait
canandisusuallyusedtodisableorSFINAEoutafunctiontemplatebasedon
thegivencondition.
•SeeSection6.3onpage98fordetailsandafirstexample.SeeSectionD.6on
page735foranotherexampleusingparameterpacks.
•SeeSection20.3onpage469fordetailsabouthowstd::enable_ifis
implemented.
std::conditional<cond,T,F>::type
•YieldsTifcondistrue,andFotherwise.
•ThisisthestandardversionofthetraitIfThenElseTintroducedinSection
19.7.1onpage440.
•Notethat,unlikeanormalC++if-then-elsestatement,thetemplatearguments
forboththe“then”and“else”branchesareevaluatedbeforetheselectionis
made,soneitherbranchmaycontainill-formedcodeortheprogramislikely
tobeill-formed.Asaconsequence,youmighthavetoaddalevelof
indirectiontoavoidthatexpressionsinthe“then”and“else”branchesare
evaluatedifthebranchisnotused.Section19.7.1onpage440demonstrates
thisforthetraitIfThenElseT,whichhasthesamebehavior.
•SeeSection11.5onpage171foranexample.
•SeeSection19.7.1onpage440fordetailsabouthowstd::conditional
isimplemented.
std::common_type<T…>::type
•Yieldsthe“commontype”ofthegiventypesT1,T2,…,Tn.
•Thecomputationofacommontypeisalittlemorecomplexthanwewantto
coverinthisappendix.Roughlyspeaking,thecommontypeoftwotypesUand
Visthetypeproducedbytheconditionaloperator?:whenitssecondand
thirdoperandsareofthosetypesUandV(withreferencetypesusedonlyto
determinethevaluecategoryofthetwooperands);thereisnocommontypeif
thatisinvalid.decay_t(seepage731)isappliedtothisresult.Thisdefault
computationmaybeoverriddenbyuserspecializationof
std::common_type<U,V>(intheC++standardlibrary,partial
specializationexistsfordurationsandtimepoints).
•Ifnotypeisgivenornocommontypeexists,thereisnotypemember
defined,sothatusingitisanerror(whichmightSFINAEoutafunction
templateusingit).
•Ifasingletypeisgiven,theresultistheapplicationofdecay_ttothattype.
•Formorethantwotypes,common_typerecursivelyreplacesthefirsttwo
typesT1andT2bytheircommontype.Ifatanytimethatprocessfails,thereis
nocommontype.
•Whileprocessingthecommontype,thepassedtypesaredecays,sothatthe
traitalwaysyieldsadecayedtype(seeSectionD.4onpage731).
•SeeSection1.3.3onpage12foradiscussionandexampleoftheapplicationof
thistrait.
•Thecoreoftheprimarytemplateofthistraitisusuallyimplementedby
somethinglikethefollowing(hereusingonlytwoparameters):Clickhereto
viewcodeimage
template<typenameT1,typenameT2>
structcommon_type<T1,T2>{
usingtype=std::decay_t<decltype(true?std::declval<T1>()
:std::declval<T2>())>;
};
std::aligned_union<MIN_SZ,T…>::type
•Yieldsaplainolddatatype(POD)usableasuninitializedstoragethathasasize
ofatleastMIN_SZandissuitabletoholdanyofthegiventypesT1,T2,…,
Tn.
•Inaddition,ityieldsastaticmemberalignment_valuewhosevalueisthe
strictestalignmentofallthegiventypes,whichfortheresulttypeisequivalent
to–std::alignment_of_v<type>::value(seeSectionD.3.1onpage
715)–alignof(type)
•Requiresthatatleastonetypeisprovided.
•Forexample:
Clickheretoviewcodeimage
usingPOD_T=std::aligned_union_t<0,char,
std::pair<std::string,std::string>>;
std::cout<<sizeof(POD_T)<<’\n’;
std::cout<<std::aligned_union<0,char,
std::pair<std::string,std::string>
>::alignment_value;
<<’\n’;Notetheuseofaligned_unioninsteadofaligned_union_tto
getthevalueforthealignmentinsteadofthetype.
std::aligned_storage<MAX_TYPE_SZ>::type
std::aligned_storage<MAX_TYPE_SZ,DEF_ALIGN>::type
•Yieldsaplainolddatatype(POD)usableasuninitializedstoragethathasa
sizetoholdallpossibletypeswithasizeuptoMAX_TYPE_SZ,takingthe
defaultalignmentorthealignmentpassedasDEF_ALIGNintoaccount.
•RequiresthatMAX_TYPE_SZisgreaterthanzeroandtheplatformhasatleast
onetypewiththealignmentvalueDEF_ALIGN.
•Forexample:
Clickheretoviewcodeimage
usingPOD_T=std::aligned_storage_t<5>;
D.6CombiningTypeTraits
Inmostcontexts,multipletypetraitpredicatescanbecombinedbyusinglogical
operators.However,insomecontextsoftemplatemetaprogramming,thisisnot
enough:•Ifyouhavetodealwithtraitsthatmightfail(e.g.,duetoincomplete
types).
•Ifyouwanttocombinetypetraitdefinitions.
Thetypetraitsstd::conjunction<>,std::disjunction<>,and
std::negation<>areprovidedforthispurpose.
Oneexampleisthatthesehelpersshort-circuitBooleanevaluations(abortthe
evaluationafterthefirstfalsefor&&orfirsttruefor||,respectively).14For
example,ifincompletetypesareused:Clickheretoviewcodeimage
structX{
X(int);//convertsfromint
};
structY;//incompletetype
thefollowingcodemightnotcompilebecauseis_constructibleresultsin
undefinedbehaviorforincompletetypes(somecompilersacceptthiscode,
though):Clickheretoviewcodeimage
//undefinedbehavior:
static_assert(std::is_constructible<X,int>{}>
||std::is_constructible<Y,int>{},
"can’tinitXorYfromint");Instead,thefollowingisguaranteed
tocompile,sincetheevaluationofis_constructible<X,int>already
yieldstrue:Clickheretoviewcodeimage
//OK:
static_assert(std::disjunction<std::is_constructible<X,int>,
std::is_constructible<Y,int>>{},
"can’tinitXorYfromint");Theotherapplicationisaneasyway
todefinenewtypetraitsbylogicallycombiningexistingtype
traits.Forexample,youcaneasilydefineatraitthatchecks
whetheratypeis“notapointer”(neitherapointer,noramember
pointer,noranullpointer):Clickheretoviewcodeimage
template<typenameT>
structisNoPtrT:
std::negation<std::disjunction<std::is_null_pointer<T>,
std::is_member_pointer<T>,
std::is_pointer<T>>>
{
};
Herewecan’tusethelogicaloperators,becausewecombinethecorresponding
traitclasses.Withthisdefinition,thefollowingispossible:Clickheretoview
codeimage
std::cout<<isNoPtrT<void*>::value<<’\n’;//false
std::cout<<isNoPtrT<std::string>::value<<’\n’;//true
autonp=nullptr;
std::cout<<isNoPtrT<decltype(np)>::value<<’\n’;//false
Andwithacorrespondingvariabletemplate:Clickheretoviewcodeimage
template<typenameT>
constexprboolisNoPtr=isNoPtrT<T>::value;wecanwrite:
Clickheretoviewcodeimage
std::cout<<isNoPtr<void*><<’\n’;//false
std::cout<<isNoPtr<int><<’\n’;//true
Asalastexample,thefollowingfunctiontemplateisonlyenabledifallits
templateargumentsareneitherclassesnotunions:Clickheretoviewcode
image
template<typename…Ts>
std::enable_if_t<std::conjunction_v<std::negation<std::is_class<Ts>>…,
std::negation<std::is_union<Ts>>…
>>
print(Ts…)
{
…
}
Notethattheellipsisisplacedbehindeachnegationsothatitappliestoeach
elementoftheparameterpack.
TraitEffectconjunction<B…>LogicalandforBooleantraitsB…(since
C++17)disjunction<B…>LogicalorforBooleantraitsB…(sinceC++17)
negation<B>LogicalnotforBooleantraitB(sinceC++17)
Trait Effect
conjunction<B...> LogicalandforBooleantraitsB...(sinceC++17)
disjunction<B...> LogicalorforBooleantraitsB...(sinceC++17)
negation<B>LogicalnotforBooleantraitB(sinceC++17)
TableD.8.TypeTraitstoCombineOtherTypeTraits
std::conjunction<B…>::value
std::disjunction<B…>::value
•YieldswhetheralloroneofthepassedBooleantraitsB…yield(s)true.
•Logicallyappliesoperator&&or||,respectively,tothepassedtraits.
•Bothtraitsshort-circuit(aborttheevaluationafterthefirstfalseortrue).
Seethemotivatingexampleabove.
•AvailablesinceC++17.
std::negation<B>::value
•YieldswhetherthepassedBooleantraitByieldsfalse.
•Logicallyappliesoperator!tothepassedtrait.
•Seethemotivatingexampleabove.
•AvailablesinceC++17.
D.7OtherUtilities
TheC++standardlibraryprovidesafewotherutilitiesthatarebroadlyusefulto
writeportablegenericcode.
Trait Effect
declval<T>
()
yieldsan“object”(rvaluereference)ofatypewithout
constructingit
addressof(r)yieldstheaddressofanobjectorfunction
TableD.9.OtherUtilitiesforMetaprogramming
std::declval<T>()
•Definedinheader<utility>.
•Yieldsan“object”orfunctionofanytypewithoutcallinganyconstructoror
initialization.
•IfTisvoid,thereturntypeisvoid.
•Thiscanbeusedtodealwithobjectsorfunctionsofanytypeinunevaluated
expressions.
•Itissimplydefinedasfollows:Clickheretoviewcodeimage
template<typenameT>
add_rvalue_reference_t<T>declval()noexcept;Thus:
–IfTisaplaintypeoranrvaluereference,ityieldsaT&&.
–IfTisanlvaluereference,ityieldsaT&.
–IfTisvoid,ityieldsvoid.
•SeeSection19.3.4onpage415fordetailsandSection11.2.3onpage166and
thecommon_type<>typetraitinSectionD.5onpage732forexamples
usingit.
std::addressof(r)
•Definedinheader<memory>.
•Yieldstheaddressofobjectorfunctionrevenifoperator&isoverloaded
foritstype.
•SeeSection11.2.2onpage166fordetails.
1BeforeC++17,thestandarddidnotincludethealiastemplate
bool_constant<>.std::true_typeandstd::false_typedid
existinC++11andC++14,however,andwerespecifieddirectlyintermsof
integral_constant<bool,true>and
integral_constant<bool,false>,respectively.
2TheC++standardizationcommitteeconsideredaproposalforC++17to
requirethatviolationsofpreconditionsoftypetraitsalwaysresultina
compile-timeerror.However,becausesometypetraitscurrentlyhave
requirementsthatarestrongerthanstrictlynecessary(suchasalways
requiringcompletetypes)thischangewaspostponed.
3ThankstoHowardHinnantforprovidingthistypehierarchyin
http://howardhinnant.github.io/TypeHiearchy.pdf
4BeforeC++14,theonlyexceptionwasthetypeofnullptr,
std::nullptr_t,forwhichallprimarytypecategoryutilitiesyielded
false,becauseis_null_pointer<>wasnotpartofC++11.
5Thiswasclarifiedbytheresolutionofcoreissue1059afterC++11was
published.
6Thiswasclarifiedbytheresolutionofcoreissue1059afterC++11was
published.
7Notethatthebaseclassesand/ordatamembersofaggregatesdon’thavetobe
aggregates.PriortoC++14,aggregateclasstypescouldnothavedefault
memberinitializers.PriortoC++17,aggregatescouldnothavepublicbase
classes.
8LateinthestandardizationprocessofC++17,is_invocablewasrenamed
fromis_callable.
9SeeSection11.2.3onpage166fortheeffectofstd::declval.
10ThankstoDanielKrüglerforpointingthisout.
AppendixE
Concepts
Formanyyearsnow,C++languagedesignershaveexploredhowtoconstrain
theparametersoftemplates.Forexample,inourprototypicalmax()template,
we’dliketostateupfrontthatitshouldn’tbecalledfortypesthataren’t
comparableusingtheless-thanoperator.Othertemplatesmaywanttorequire
thattheybeinstantiatedwithtypesthatarevalid“iterator”types(forsome
formaldefinitionofthatterm)orvalid“arithmetic”type(whichcouldbea
broadernotionthanthesetofbuilt-inarithmetictypes).
Aconceptisanamedsetofconstraintsononeormoretemplateparameters.
WhileC++11wasbeingadeveloped,averyrichconceptsystemwasdesigned
forit,butintegratingthefeatureintothelanguagespecificationendedup
requiringtoomanycommitteeresources,andthatversionofconceptswas
eventuallydroppedfromC++11.Sometimelater,adifferentdesignofthe
featurewasproposed,anditappearsthatitwilleventuallymakeitintothe
languageinsomeform.Infact,justbeforethisbookwenttopress,the
standardizationcommitteevotedtointegratethenewdesignintothedraftfor
C++20.Inthisappendix,wedescribethemainelementsofthatnewerdesign.
Wealreadymotivatedandshowedsomeapplicationsofconceptsinthemain
chapterofthisbook:•Section6.5onpage103illustrateshowtouse
requirementsandconceptstoenableaconstructoronlyifthetemplateparameter
isconvertibletoastring(toavoidaccidentallyusingaconstructorasacopy
constructor).
•Section18.4onpage377showshowtouseconceptstospecifyandrequire
constraintsontypesusedtorepresentgeometricobjects..
E.1UsingConcepts
Let’sfirstexaminehowtouseconceptsinclientcode(i.e.,thecodethatdefines
templateswithoutnecessarilydefiningtheconceptsthatapplytothetemplate
parameters).
DealingwithRequirements
Hereisourhabitualtwo-parametermax()templateequippedwithaconstraint:
Clickheretoviewcodeimage
template<typenameT>requiresLessThanComparable<T>
Tmax(Ta,Tb){
returnb<a?a:b;
}
Theonlyadditionistherequiresclause
Clickheretoviewcodeimage
requiresLessThanComparable<T>whichassumesthatwehavepreviously
declared—mostlikelythroughaheaderinclusion—theconcept
LessThanComparable.
SuchaconceptisaBooleanpredicate(i.e.,anexpressionproducingavalueof
typebool)thatevaluatestoaconstant-expression.Thatisimportantbecause
theconstraintsareevaluatedatcompiletimeandthereforeproducenooverhead
intermsofthegeneratedcode:Thisconstrainedtemplatewillproducecodethat
isjustasfastastheunconstrainedversionswehavediscussedelsewhere.
Whenweattempttousethistemplate,itwillnotbeinstantiateduntilthe
requiresclausehasbeenevaluatedandfoundtoproduceatruevalue.Ifit
producesafalsevalue,anerrormightbeemittedexplainingwhichpartofthe
requirementfailed(or,amatchingoverloadedtemplatethatdoesn’tfailthe
requirementsmightbeselected).
Requiresclausesdonothavetobeexpressedintermsofconcepts(although
doingsoisgoodpracticeandtendstoproducebetterdiagnostics):AnyBoolean
constant-expressioncanbeused.Forexample,asdiscussedinSection6.5on
page103,thefollowingcodeensuresthatatemplateconstructorcan’tbeusedas
copyconstructor:Clickheretoviewcodeimage
classPerson
{
private:
std::stringname;
public:
template<typenameSTR>
requiresstd::is_convertible_v<STR,std::string>
explicitPerson(STR&&n)
:name(std::forward<STR>(n)){
std::cout<<"TMPL-CONSTRfor’"<<name<<"’\n";
}
…
};
Here,notusinganamedconcept(seeSectionE.2onpage742)canbe
appropriate,becausetheadhocBooleanexpression(usingatypetraitinthis
case)std::is_convertible_v<STR,std::string>
isusedtofixtheproblemthatatemplateconstructormightbeusedinsteadof
acopyconstructor.Detailsofhowtoorganizeconceptsandconstraintsarestill
anactivefieldofexplorationbytheC++community,andwilllikelyevolveover
time,butthereseemstobegeneralagreementthatconceptsshouldreflectwhat
codemeansandnotwhetheritcompiles.
DealingwithMultipleRequirements
Intheexampleabove,thereisonlyonerequirement,butit’snotuncommonto
havemultiplerequirements.Forexample,onemightimagineaSequence
conceptthatdescribesasequenceofelementvalues(matchingthesamenotion
inthestandard)andatemplatefind()that,givenasequenceandavalue,
returnsaniteratorreferringtothefirstoccurrenceofthevalueinthatsequence
(ifany).Thattemplatemightbedefinedasfollows:Clickheretoviewcode
image
template<typenameSeq>
requiresSequence<Seq>&&
EqualityComparable<typenameSeq::value_type>
typenameSeq::iteratorfind(Seqconst&seq,
typenameSeq::value_typeconst&val)
{
returnstd::find(seq.begin(),seq.end(),val);
}
Here,anycalltothistemplatewillfirstcheckeachrequirementinturnandonly
ifallrequirementsproducetruevaluescanthetemplatebeselectedforthecall
andbeinstantiated(provided,ofcourse,overloadresolutiondoesnotdiscardthe
templateforotherreasons,suchasanothertemplatebeingabettermatch).
Itisalsopossibletoexpress“alternative”requirementsusing||.Thisis
rarelyneededandshouldnotbedonetoocasuallybecauseexcessiveuseofthe
||operatorinrequiresclausesmaypotentiallytaxcompilationresources(i.e.,
makecompilationnoticeablyslower).However,itcanbequiteconvenientin
somesituations.Forexample:Clickheretoviewcodeimage
template<typenameT>
requiresIntegral<T>||
FloatingPoint<T>
Tpower(Tb,Tp);
Asinglerequirementcanalsoinvolvemultipletemplateparameters,andasingle
conceptcanexpressapredicateovermultipletemplateparameters.Forexample:
Clickheretoviewcodeimage
template<typenameT,typenameU>
requiresSomeConcept<T,U>
autof(Tx,Uy)->decltype(x+y)Thus,conceptscanimposea
relationshipbetweentypeparameters.
ShorthandNotationforSingleRequirements
Tolightenthenotationaloverheadofrequiresclauses,asyntacticshortcutis
availablewhenaconstraintonlyinvolvesoneparameteratatime.Thisis
perhapsmosteasilyillustratedbyusingtheshorthandforthedeclarationofour
constrainedmax()templateabove:Clickheretoviewcodeimage
template<LessThanComparableT>
Tmax(Ta,Tb){
returnb<a?a:b;
}
Thisisfunctionallyequivalenttothepriordefinitionofmax().When
redeclaringaconstrainedtemplate,however,thesameformmustbeusedasthe
originaldeclaration(inthatsenseitisfunctionallyequivalent,butnot
equivalent).
Wecanusethesameshorthandforoneofthetworequirementsinthe
find()template:Clickheretoviewcodeimage
template<SequenceSeq>
requiresEqualityComparable<typenameSeq::value_type>
typenameSeq::iteratorfind(Seqconst&seq,
typenameSeq::value_typeconst&val)
{
returnstd::find(seq.begin(),seq.end(),val);
}
Again,thisisequivalenttothepriordefinitionofthefind()templatefor
sequencetypes.
E.2DefiningConcepts
Conceptsaremuchlikeconstexprvariabletemplatesoftypebool,butthe
typeisnotexplicitlyspecified:Clickheretoviewcodeimage
template<typenameT>conceptLessThanComparable=…;Herethe“…”
couldperhapsbereplacedbyanexpressionthatusesvarioustraits
toestablishwhethertypeTisindeedcomparableusingthe<
operator,buttheconceptsproposalprovidesatooltosimplifythis
task:therequiresexpression(whichisdistinctfromtherequires
clausedescribedabove).Hereishowtheconcept’scomplete
definitionmaylooklike:Clickheretoviewcodeimage
template<typenameT>
conceptLessThanComparable=requires(Tx,Ty){
{x<y}->bool;
};
Notehowtherequiresexpressioncanincludeanoptionalparameterlist:These
parametersareneverreplacedbyargumentsbutcaninsteadbethoughtofasa
setof“dummyvariables”usabletoexpressrequirementsinthebodyofthe
requiresexpression.Inthiscase,thereisjustonesuchrequirementexpressedby
thephraseClickheretoviewcodeimage
{x<y}->bool;Thissyntaxmeansthat(a)theexpressionx<y
mustbevalidinaSFINAEsenseand(b)theresultofthatexpression
mustbeconvertibletobool.Inaphraseofthisform,thekeyword
noexceptcanbeinsertedbeforethe->tokentoexpressthatthe
expressioninthebracesshouldbeknownnottothrowanexception
(i.e.,noexcept(…)appliedtothatexpressionshouldbetrue.The
implicitconversionpartofthephrase(i.e.,->type)canbeleft
outaltogetherifnosuchconstraintisneeded,andifonlythe
validityoftheexpressionmustbechecked,thebracescanbe
dropped,sothatthephrasereducestojustanexpression.For
example,Clickheretoviewcodeimage
template<typenameT>
conceptSwappable=requires(Tx,Ty){
swap(x,y);
};
Requiresexpressionscanalsoexpresstheneedforassociatedtypes.Consider
theSequenceconceptwehypothesizedearlier:Inadditiontorequiringthe
validityofexpressionslikeseq.begin(),italsorequirescorresponding
sequenceiteratortypes.Thatcanbeexpressedasfollows:Clickheretoview
codeimage
template<typenameSeq>
conceptSequence=requires(Seqseq){
typenameSeq::iterator;
{seq.begin()}->Seq::iterator;
…
};
Sothephrasetypenametype;expressestherequirementthattypeexists(this
iscalledatyperequirement).Inthisexample,thetypethathastoexistisa
memberoftheconcepttemplateparameter,butthatneednotalwaysbethecase.
Wecould,forexample,requirethatthereexistatypeIteratorFor<Seq>
instead,andthatwouldbeachievedwiththerequirement-phraseClickhereto
viewcodeimage
…
typenameIteratorFor<Seq>;
…
TheSequenceconceptdefinitionaboveshowshowphrasescanbecombined
byjustlistingthemoneafteranother.Thereisathirdclassofrequirement-
phrase,whichconsistsinjustinvokinganotherconcept.Forexample,let’s
assumethatwehaveaconceptforthenotionofaniterator.We’dwantour
SequenceconcepttorequirenotonlythatSeq::iteratorbeatype,but
alsothatthattypesatisfiestheconstraintsoftheIteratorconcept.Thatis
expressedasfollows:Clickheretoviewcodeimage
template<typenameSeq>
conceptSequence=requires(Seqseq){
typenameSeq::iterator;
requiresIterator<typenameSeq::iterator>;
{seq.begin()}->Seq::iterator;
…
};
Thatis,wecanjustaddrequiresclausesinrequiresexpressions(andthissortof
phraseiscalledanestedrequirement).
E.3OverloadingonConstraints
Let’sassumeforamomentthatwehavedefinedconceptsIntegerLike<T>
andStringLike<T>,andwedecidetowritetemplatestoprintoutvaluesof
typesofeitherconcept.Wecoulddothatasfollows:Clickheretoviewcode
image
template<IntegerLikeT>voidprint(Tval);//#1
template<StringLikeT>voidprint(Tval);//#2
Ifitweren’tforthedifferingconstraints,thesetwodeclarationswoulddeclare
thesametemplate.However,theconstraintsarepartofthetemplatesignature
andallowthetemplatestobedistinguishableduringoverloadresolution.In
particular,ifbothtemplatesarefoundtobeviablecandidates,butonlytemplate
#1hasitsconstraintssatisfied,thenoverloadselectsthesatisfiedtemplate.For
example,assumingintsatisfiesIntegerLikeandstd::stringsatisfies
StringLike,butnotviceversa:Clickheretoviewcodeimage
intmain()
{
printf(1);//selectstemplate#1
printf("1"s);//selectstemplate#2
}
Wecouldimagineastring-liketypethatsupportsinteger-likecomputations.For
example,if"6"_NSand"7"_NSaretwoliteralsforthattype,multiplying
thoseliteralswouldproducethesamevalueas"42"_NS.Suchatypemight
satisfybothIntegerLikeandStringLikeand,therefore,acalllike
print("42"_NS)wouldbeambiguous.
E.3.1ConstraintSubsumption
Ourfirstdiscussionofoverloadingfunctiontemplatesdistinguishedby
constraintsinvolvedconstraintsthatareexpectedtogenerallybemutually
exclusive.Forexample,inourexamplewithIntegerLikeand
StringLike,wecouldenvisiontypesthatsatisfybothconcepts,butwe
expectthattoberareenoughthatouroverloadedprinttemplatesremain
useful.
Thereare,however,setsofconceptsthatarenevermutuallyexclusivebut
whereone“subsumes”theother.Theclassicalexamplesofthisarethestandard
libraryiteratorcategories:inputiterator,forwarditerator,bidirectionaliterator,
randomaccessiterator,and,inC++17,contiguousiterator.1Supposewehavea
definitionforForwardIterator:Clickheretoviewcodeimage
template<typenameT>
conceptForwardIterator=…;Thenthe“morerefined”concept
BidirectionalIteratormightbedefinedasfollows:Clickheretoview
codeimage
template<typenameT>
conceptBidirectionIterator=
ForwardIterator<T>&&
requires(Tit){
{--it}->T&
};
Thatis,weaddtheabilitytoapplyprefixoperator--ontopofthe
capabilitiesalreadyprovidedbyforwarditerators.
Considernowthestd::advance()algorithm(whichwe’llcall
advanceIter()),overloadedforforwardandbidirectionaliteratorsusing
constrainedtemplates:Clickheretoviewcodeimage
template<ForwardIteratorT,typenameD>
voidadvanceIter(T&it,Dn)
{
assert(n>=0);
for(;n!=0;--n){++it;}
}
template<BidirectionalIteratorT,typenameD>
voidadvanceIter(T&it,Dn)
{
if(n>0){
for(;n!=0;--n){++it;}
}elseif(n<0){
for(;n!=0;++n){--it;}
}
}
WhencallingadvanceIter()withaplainforwarditerator(i.e.,onethatis
notabidirectionaliterator),onlytheconstraintsofthefirsttemplatewillbe
satisfied,andoverloadresolutionisstraightforward:Thefirsttemplateis
selected.However,abidirectionaliteratorwillsatisfytheconstraintsofboth
templates.Incaseslikethat,whenoverloadresolutiondoesnototherwiseprefer
onecandidateoveranother,itwillpreferthecandidatewhoseconstraints
subsumetheconstraintsoftheothercandidate,whilethereverseisnottrue.The
exactdefinitionofsubsumptionisalittlebeyondthisintroductoryappendix,but
sufficeittoknowthatifaconstraintC2<Ts…>isdefinedbyrequiringa
constraintC1<Ts…>andadditionalconstraints(i.e.,&&),thentheformer
subsumesthelatter.2Clearly,inourexample,
BidirectionalIterator<T>subsumesForwardIterator<T>,and
hencethesecondadvanceIter()templateispreferredwhencalledwitha
bidirectionaliterator.
E.3.2ConstraintsandTagDispatching
RecallthatinSection20.2onpage467,weaddressedtheissueofoverloading
theadvanceIter()algorithmusingtagdispatching.Thatmethodcanbe
integratedinconstrainedtemplatesinafairlyelegantway.Forexample,input
iteratorsandforwarditeratorsarenotdistinguishablethroughtheirsyntactic
interfaces.Soinstead,wecanreachtotagstodefineoneintermsoftheother:
Clickheretoviewcodeimage
template<typenameT>
conceptForwardIterator=
InputIterator<T>&&
requires{
typenamestd::iterator_traits<T>::iterator_category;
is_convertible_v<std::iterator_traits<T>::iterator_category,
std::forward_iterator_tag>;
};
Withthat,ForwardIterator<T>subsumesInputIterator<T>,andwe
cannowoverloadtemplatesconstrainedforbothiteratorcategories.
E.4ConceptTips
AlthoughC++conceptshavebeenworkedonformanyyearsnow,and
experimentalimplementationshavebeenavailableinsomeformforovera
decade,broadexperiencewiththemisjuststartingtoemerge.Wehopethata
futureeditionofthisbookwillbeabletoprovidemuchmorepracticalguidance
abouthowtodesignconstrainedtemplatelibraries.Meanwhile,however,we
offerthreeobservations.
E.4.1TestingConcepts
ConceptsareBooleanpredicatesthatarevalidconstant-expressions.Therefore,
givenaconceptCandsometypesT1,T2,…thatmodeltheconcept,wecan
staticallyassertthatobservation:Clickheretoviewcodeimage
static_assert(C<T1,T2,…>,"Modelfailure");Whendesigning
concepts,itisthereforerecommendedtoalsodesignsimpletypes
thattesttheminthisway.Thatincludestypesthatpushthe
boundariesofwhattheconceptentails,answeringquestionssuchas
thefollowing:•Dointerfacesand/oralgorithmsneedtocopyand/or
moveobjectsofthetypesbeingmodeled?
•Whatconversionsareacceptable?Whichonesareneeded?
•Isthebasicsetofoperationsassumedbythetemplateunique?Forexample,
canitoperateusingeither*=or*and=?
Here,too,thenotionofarchetypes(seeSection28.3onpage655)canbeuseful.
E.4.2ConceptGranularity
WithconceptsbecomingpartoftheC++language,itisnaturaltowanttobuild
“librariesofconcepts,”justaswebuiltclasslibrariesandtemplatelibrariesas
soonasthosefeatureswereavailable.Aswithotherlibraries,itisalsonaturalto
wanttolayerourconceptsinvariousways.Webrieflydiscussedtheexampleof
iteratorcategories,andit’snotagreatleaptoenvisionthatwecouldbuild
“rangecategories”alongsidethoseandperhaps“sequenceconcepts”ontopof
those,andsoon.
Ontheotherhand,wemightbetemptedtobuildallofthoseconceptsontop
of“elementarysyntax”concepts.Forexample,wecouldimagine:Clickhereto
viewcodeimage
template<typenameT,typenameU>
conceptAddable=
requires(Tx,Uy){
x+y;
}
Thatisnotrecommended,however,becauseitisaconceptthathasnoclear
semanticconnotation,anddisparatetypeswillsatisfyit.Forexample,itis
satisfiedwhenTandUarebothstd::stringorwhenonetypeisapointer
andtheotheranintegraltype,andofcoursewitharithmetictypes.Yetinthose
threecases,thenotionofAddablemeanssomethingfundamentallydifferent
(respectively,concatenation,iteratordisplacement,andvariantsofarithmetic
addition).Introducingsuchaconceptisthereforearecipeforlibrarieswith
fuzzyinterfacesandlikelytotriggeroddambiguities.
Instead,itappearsthatconceptsarebestdesignedtomodelrealsemantic
notionsthatariseintheproblemdomain.Doingsoinadisciplinedfashionis
boundtoimprovetheoveralldesignofourlibraries,asitwillbringconsistency
andclaritytotheinterfacespresentedtoourclients.Thatwasverymuchthe
casewhentheStandardTemplateLibrary(STL)wasaddedtotheC++standard
library.Althoughithadnolanguage-based“concepts”toworkwith,itwasvery
muchdesignedwithanotionofconceptsinmind(suchasiteratorsandthe
iteratorhierarchy),andtherestishistory.
E.4.3BinaryCompatibility
ExperiencedC++programmersareawarethatwhencertainentities(functions
andmemberfunctionsinparticular)arecompileddowntolow-levelmachine
code,anameisassociatedwiththemthatcombinesthedeclarednamewiththe
typeandscopeoftheentity.Thisname,commonlyreferredtoasthemangled
nameoftheentity,iswhatisusedbytheobjectcodelinkertoresolvereferences
totheentity(e.g.,fromotherobjectfiles).Forexample,themanglednameofa
functiondefinedasClickheretoviewcodeimage
namespaceX{
voidf(){}
}
is_ZN1X1fEvintheItaniumC++ABI[ItaniumABI](thelettersXandfinthis
encodingcomefromthenamespacenameandfunctionname,respectively).
Manglednamescannot“collide”withinaprogram.Therefore,iftwo
functionscanpotentiallycoexistinaprogram,theymusthavedistinctmangled
names.That,inturn,meansthatconstraintsmustbeencodedinthefunction
name(becausetemplatespecializationsthatareidenticalineverywayexcept
theirconstraintsandtheirfunctionbodycanappearindifferenttranslation
units.)Considerthefollowingtwotranslationunits:Clickheretoviewcode
image
#include<iostream>
template<typenameT>
conceptHasPlus=requires(Tx,Ty){
x+y;
};
template<typenameT>intf(Tp)requiresHasPlus<T>{
std::cout<<"TU1\n";
}
voidg();
intmain(){
f(1);
g();
}
and
Clickheretoviewcodeimage
#include<iostream>
template<typenameT>
conceptHasMult=requires(Tx,Ty){
x*y;
};
template<typenameT>intf(Tp)requiresHasMult<T>{
std::cout<<"TU2\n";
}
templateintf(int);
voidg(){
f(2);
}
Thisprogrammustoutput
TU1
TU2
whichmeansthatthetwodefinitionoff()mustbemangleddifferently.3
1Contiguousiteratorsarearefinementofrandomaccessiteratorsintroduced
inC++17.Nostd::contiguous_iterator_tagwasaddedforthem
becauseexistingalgorithmsthatrelyon
std::random_access_iterator_tagwouldnolongerbeselectedif
thetagwerechanged.
2Thespecificationbeingproposedforstandardizationisalittlemorepowerful
thanthis.Itbreaksdownconstraintsintosetsof“atomiccomponents”
(includingthepartsofrequiresexpressions)andanalyzesthosesetstoseeif
oneisclearlyastrictsubsetoftheother.
3TheexperimentalimplementationofconceptsinGCC7.1isknowntobe
deficientinthisrespect.
CodeSnippets
Bibliography
Thisbibliographyliststheresourcesthatwerementioned,adopted,orcitedin
thisbook.Thesedays,manyoftheadvancementsinprogramminghappenin
electronicforums.Itisthereforenotsurprisingtofind,inadditiontothemore
traditionalbooksandarticles,quiteafewWebsites.Wedonotclaimthatourlist
isclosetobeingcomprehensive.However,wedofindthattheresourcesare
relevantcontributionstothetopicofC++templates.
Websitesaretypicallyconsiderablymorevolatilethanbooksandarticles.The
Internetlinkslistedheremaynotbevalidinthefuture.Therefore,weprovide
theactuallistoflinksforthisbookatthefollowingsite(andweexpectthissite
tobestable):http://www.tmplbook.com
Beforelistingthebooks,articles,andWebsites,weintroducethemore
interactivekindofresourcesthatareprovidedbynewsgroups.
Forums
Inthefirsteditionofthisbook,wereferredtoUsenetgroups(alargecollection
ofpre-world-wide-webon-lineforums)asasourcefordiscussionsaboutthe
C++programminglanguage.Thosegroupshavemostlyfadedsincethenbut
manyotheronlineprogrammingcommunitieshavearisen,andseveralofthese
catertoC++programmers.Welistsomeofthemostpopularhere:•
Cppreference“wiki”(i.e.,collectivelyeditable)ofreferenceinformationaboutC
andC++(invariouslanguages).
http://www.cppreference.com
•StackoverflowisabroaddevelopercommunitythatalsocoversC++andC++
templatesinparticular.
https://stackoverflow.com/questions/tagged/c%2b%2b
https://stackoverflow.com/questions/tagged/c%2b%2b%20templates
•QuoraissimilartoStackoverflow,butnotlimitedtotechnicaldiscussions.
https://www.quora.com/topic/C++-programming-language
•TheStandardC++Foundationisanonprofitorganizationrunbysome
prominentmembersoftheC++standardizationcommittee(thoughthetwo
groupsareseparate)tosupporttheC++programmingcommunity.Ithelps
fundsomeaspectsofthestandardizationcommittee’smeetings,aswellas
CppCon,amajoryearlyconferenceaboutC++(highlyrecommendedifyou
enjoyedthematerialinthisbook).Italsoincludesadirectoryofon-line
forums(hostedas“Googlegroups”)thatcovervariousC++topics.
https://isocpp.org/forums
https://cppcon.org
•TheAssociationofCandC++Users(ACCU)isanorganizationbasedinthe
UnitedKingdomfor“anyoneinterestedindevelopingandimproving
programmingskills.”Ithostsayearlyprogrammingconferencewitha
particularlystrongtrackaboutC++.
https://www.accu.org
BooksandWebSites
[AbrahamsGurtovoyMeta]
DavidAbrahamsandAlekseyGurtovoyC++TemplateMetaprogramming–
Concepts,Tools,andTechniquesfromBoostandBeyond
Addison-Wesley,Boston,MA,2005
[AlexandrescuDesign]
AndreiAlexandrescuModernC++Design–GenericProgrammingandDesign
PatternsApplied
Addison-Wesley,Boston,MA,2001
[AlexandrescuDiscriminatedUnions]
AndreiAlexandrescuDiscriminatedUnions(partsI,II,III)
C/C++UsersJournal,April/June/August,2002
[AlexandrescuAdHocVisitor]
AndreiAlexandrescuGenericProgramming:TypelistsandApplications
Dr.Dobb’sJournal,February,2002
[AusternSTL]
MatthewH.AusternGenericProgrammingandtheSTL–UsingandExtending
theC++StandardTemplateLibrary
Addison-Wesley,Boston,MA,1999
[BartonNackman]
JohnJ.BartonandLeeR.NackmanScientificandEngineeringC++–An
IntroductionwithAdvancedTechniquesandExamples
Addison-Wesley,Boston,MA,1994
[BCCL]
JeremySiekTheBoostConceptCheckLibrary
http://www.boost.org/libs/concept_check/concept_check.htm
[Blitz++]
ToddVeldhuizenBlitz++:Object-OrientedScientificComputing
http://blitz.sourceforge.net/
[Boost]
TheBoostRepositoryforFree,Peer-ReviewedC++Libraries
http://www.boost.org
[BoostAny]
KevlinHenneyBoostAnyLibrary
http://www.boost.org/libs/any
[BoostFusion]
JoeldeGuzman,DanMarsden,andTobiasSchwingerTheBoostFusionLibrary
http://boost.org/libs/fusion
[BoostHana]
LouisDionneTheBoostHanaLibraryforMetaprogramming
http://boostorg.github.io/hana
[BoostIterator]
DavidAbrahams,JeremySiek,ThomasWittBoostIterator
http://www.boost.org/libs/iterator
[BoostMPL]
AlekseyGurtovoyandDavidAbrahamsBoostMPL
http://www.boost.org/libs/mpl
[BoostOperators]
DavidAbrahamsBoostOperators
http://www.boost.org/libs/utility/operators.htm
[BoostTuple]
JaakkoJ¨arviTheBoostTupleLibrary
http://boost.org/libs/tuple
[BoostOptional]
FernandoLuisCacciolaCarballalBoostOptionalLibrary
http://www.boost.org/libs/optional
[BoostSmartPtr]
SmartPointerLibrary
http://www.boost.org/libs/smart_ptr
[BoostTypeTraits]
TypeTraitsLibrary
http://www.boost.org/libs/type_traits
[BoostVariant]
EricFriedmanandItayMamanBoostVariantLibrary
http://www.boost.org/libs/variant
[BrownSIunits]
WalterE.BrownIntroductiontotheSILibraryofUnit-BasedComputation
http://lss.fnal.gov/archive/1998/conf/Conf-98-328.pdf
[C++98]
ISO
InformationTechnology—ProgrammingLanguages—C++
ISO/IEC,DocumentNumber14882-1998,1998
[C++03]
ISO
InformationTechnology—ProgrammingLanguages—C++
ISO/IEC,DocumentNumber14882-2003,2003
[C++11]
ISO
InformationTechnology—ProgrammingLanguages—C++
ISO/IEC,DocumentNumber14882-2011,2011
[C++14]
ISO
InformationTechnology—ProgrammingLanguages—C++
ISO/IEC,DocumentNumber14882-2014,2014
[C++17]
ISO
InformationTechnology—ProgrammingLanguages—C++
ISO/IEC,DocumentNumber14882-2017,2017
[CacciolaKrzemienski2013]
FernandoLuisCacciolaCarballalandAndrzejKrzemieńskiAProposaltoAdda
UtilityClasstoRepresentOptionalObjects
http://wg21.link/n3527
[CargillExceptionSafety]
TomCargillExceptionHandling:AFalseSenseofSecurity
C++Report,November-December1994
[CoplienCRTP]
JamesO.CoplienCuriouslyRecurringTemplatePatterns
C++Report,February1995
[CoreIssue1395]
C++StandardCoreIssue1395
http://wg21.link/cwg1395
[CzarneckiEiseneckerGenProg]
KrzysztofCzarneckiandUlrichW.EiseneckerGenerativeProgramming–
Methods,Tools,andApplications
Addison-Wesley,Boston,MA,2000
[DesignPatternsGoF]
ErichGamma,RichardHelm,RalphJohnson,andJohnVlissidesDesign
Patterns–ElementsofReusableObject-OrientedSoftware
Addison-Wesley,Boston,MA,1995
[DosReisMarcusAliasTemplates]
GabrialDosReisandMatMarcusProposaltoAddTemplateAliasestoC++
http://wg21.link/n1449
[EDG]
EdisonDesignGroupCompilerFrontEndsfortheOEMMarket
http://www.edg.com
[EiseneckerBlinnCzarnecki]
UlrichW.Eisenecker,FrankBlinn,andKrzysztofCzarneckiMixin-Based
ProgramminginC++
Dr.DobbsJournal,January,2001
[EllisStroustrupARM]
MargaretA.EllisandBjarneStroustrupTheAnnotatedC++ReferenceManual
(ARM)
Addison-Wesley,Boston,MA,1990
[GregorJarviPowellVariadicTemplates]
DouglasGregor,JaakkoJ¨arvi,andGaryPowellVariadicTemplates
http://wg21.link/n2080
[HenneyValuedConversions]
KevlinHenneyValuedConversions
C++Report12(7),July-August2000
[OverloadingProperties]
JaakkoJ¨arvi,JeremiahWillcock,HowardHinnant,andAndrewLumsdaine
FunctionOverloadingBasedonArbitraryPropertiesofTypes
C/C++UsersJournal12(6),June,2003
[ItaniumABI]
ItaniumC++ABI
http://itanium-cxx-abi.github.io/cxx-abi/
[JosuttisLaunder]
NicolaiJosuttisOnlaunder()
https://wg21.link/p0532r0
[JosuttisStdLib]
NicolaiM.JosuttisTheC++StandardLibrary–ATutorialandReference(2nd
edition)
Addison-Wesley,Boston,MA,2012
[KarlssonSafeBool]
BjornKarlssonTheSafeBoolIdiom
C++Source,July,2004
[KoenigMooAcc]
AndrewKoenigandBarbaraE.MooAcceleratedC++–Practical
ProgrammingbyExample
Addison-Wesley,Boston,MA,2000
[LambdaLib]
JaakkoJ¨arviandGaryPowellLL,TheLambdaLibrary
http://www.boost.org/libs/lambda
[LibIssue181]
C++LibraryIssue181
http://wg21.link/lwg181
[LippmanObjMod]
StanleyB.LippmanInsidetheC++ObjectModel
Addison-Wesley,Boston,MA,1996
[MeyersCounting]
ScottMeyersCountingObjectsInC++
C/C++UsersJournal,April1998
[MeyersEffective]
ScottMeyersEffectiveC++–50SpecificWaystoImproveYourProgramsand
Design(2ndedition)
Addison-Wesley,Boston,MA,1998
[MeyersMoreEffective]
ScottMeyersMoreEffectiveC++–35NewWaystoImproveYourPrograms
andDesigns
Addison-Wesley,Boston,MA,1996
[MoonFlavors]
DavidA.MoonObject-orientedprogrammingwithFlavors
ConferenceproceedingsonObject-orientedprogrammingsystems,languages
andapplications,1986
[MTL]
AndrewLumsdaineandJeremySiekMTL,TheMatrixTemplateLibrary
http://www.osl.iu.edu/research/mtl
[MusserWangDynaVeri]
D.R.MusserandC.WangDynamicVerificationofC++GenericAlgorithms
IEEETransactionsonSoftwareEngineering,Vol.23,No.5,May1997
[MyersTraits]
NathanC.MyersTraits:ANewandUsefulTemplateTechnique
http://www.cantrip.org/traits.html
[NewMat]
RobertDaviesNewMat10,AMatrixLibraryinC++
http://www.robertnz.net/nm_intro.htm
[NewShorterOED]
LeslieBrown(Ed.)TheNewShorterOxfordEnglishDictionary(4thedition)
OxfordUniversityPress,Oxford,1993
[POOMA]
POOMA:AHigh-PerformanceC++ToolkitforParallelScientificComputation
http://www.nongnu.org/freepooma/
[SmaragdakisBatoryMixins]
YannisSmaragdakisandDonS.BatoryMixin-BasedProgramminginC++
ProceedingsoftheSecondInternationalSymposiumonGenerativeand
Component-BasedSoftwareEngineering,October,2000
[SpicerSFINAE]
JohnSpicerSolvingtheSFINAEProblemforExpressions
http://wg21.link/n2634
[StepanovLeeSTL]
AlexanderStepanovandMengLeeTheStandardTemplateLibrary–HP
LaboratoriesTechnicalReport95-11(R.1)
November14,1995
[StepanovNotes]
AlexanderStepanovNotesonProgramming
http://stepanovpapers.com/notes.pdf
[StroustrupC++PL]
BjarneStroustrupTheC++ProgrammingLanguage(SpecialEdition)Addison-
Wesley,Boston,MA,2000
[StroustrupDnE]
BjarneStroustrupTheDesignandEvolutionofC++
Addison-Wesley,Boston,MA,1994
[StroustrupGlossary]
BjarneStroustrupBjarneStroustrup’sC++Glossary
http://www.stroustrup.com/glossary.html
[SutterExceptional]
HerbSutterExceptionalC++–47EngineeringPuzzles,Programming
Problems,andSolutions
Addison-Wesley,Boston,MA,2000
[SutterMoreExceptional]
HerbSutterMoreExceptionalC++–40NewEngineeringPuzzles,
ProgrammingProblems,andSolutions
Addison-Wesley,Boston,MA,2001
[UnruhPrimeOrig]
ErwinUnruhOriginalMetaprogramforPrimeNumberComputation
http://www.erwin-unruh.de/primorig.html
[VandevoordeJosuttisTemplates1st]
DavidVandevoordeandNicolaiM.JosuttisC++Templates:TheComplete
Guide
Addison-Wesley,Boston,MA,2003
[VandevoordeSolutions]
DavidVandevoordeC++Solutions
Addison-Wesley,Boston,MA,1998
[VeldhuizenMeta95]
ToddVeldhuizenUsingC++TemplateMetaprograms
C++Report,May1995
Glossary
Thisglossaryisacompilationofthemostimportanttechnicaltermsthatare
usedinthisbook.See[StroustrupGlossary]foracomprehensive,general
glossaryoftermsusedbyC++programmers.
abstractclass
Aclassforwhichthecreationofconcreteobjects(instances)isimpossible.
Abstractclassescanbeusedtocollectcommonpropertiesofdifferentclassesin
asingletypeortodefineapolymorphicinterface.Becauseabstractclassesare
usedasbaseclasses,theacronymABCissometimesusedforabstractbase
class.
ADL
Anacronymforargument-dependentlookup.ADLisaprocessthatlooksfora
nameofafunction(oroperator)innamespacesandclassesthatareinsomeway
associatedwiththeargumentsofthefunctioncallinwhichthatfunction(or
operatorname)appears.Forhistoricalreasons,itissometimescalledextended
KoeniglookuporjustKoeniglookup(thelatterisalsousedforADLappliedto
operatorsonly).
aliastemplate
Aconstructthatrepresentsafamilyoftypealiases.Itspecifiesapatternfrom
whichactualtypealiasescanbegeneratedbysubstitutingthetemplate
parametersbyspecificentities.Analiastemplatecanbeaclassmember.
anglebrackethack
AC++featurethatrequiresacompilertoaccepttwoconsecutive>charactersas
twoclosinganglebrackets.Forexample,theanglebrackethackcauses
vector<list<int>>tobetreatedidenticallytovector<list<int>>.
It’scalleda(lexical)hackbecauseitdoesn’tfitwellintheC++formal
specification(thegrammar,inparticular)norinthegeneralarchitectureof
typicalcompilers.Anothersimilarhackdealswithaccidentaldigraphformation
(seedigraph).
anglebrackets
Thecharacters<and>whentheyareusedasdelimitersratherthanasless-than
andgreater-thanoperators.
ANSI
AnacronymforAmericanNationalStandardInstitute,aprivate,nonprofit
organizationthatcoordinateseffortstoproducestandardspecificationsofall
kinds.SeeINCITS.
argument
Avalue(inabroadsense)thatsubstitutesaparameterofaprogrammaticentity.
Forexample,inafunctioncallabs(-3),theargumentis-3.Insome
programmingcommunities,argumentsarecalledactualparameters(whereas
parametersarecalledformalparameters).Seealsotemplateargument.
argument-dependentlookup
SeeADL.
class
Thedescriptionofacategoryofobjects.Theclassdefinesasetofcharacteristics
foranyobjectofthattype.Theseincludeitsdata(attributes,datamembers)as
wellasitsoperations(methods,memberfunctions).InC++,classesare
structureswithmembersthatcanalsobefunctionsandaresubjecttoaccess
limitations.Theyaredeclaredusingthekeywordsclassorstruct.
classtemplate
Aconstructthatrepresentsafamilyofclasses.Itspecifiesapatternfromwhich
actualclassescanbegeneratedbysubstitutingthetemplateparametersby
specificentities.Classtemplatesaresometimescalledparameterizedclasses.
classtype
AC++typedeclaredwithclass,struct,orunion.
collectionclass
Aclassthatisusedtomanageagroupofobjects.InC++,collectionclassesare
alsocalledcontainers.
compiler
Aprogramorlibrarycomponentthattranslatesthesourcecodeinatranslation
unitintoobjectcode(machinecodewithsymbolicannotationsthatallowa
linkertoresolvereferencesacrosstranslationunits).
completetype
Anytypethatisnotincomplete:adefinedclass,anarrayofcompleteelements
andknownsize,anenumerationtypewithdefinedunderlyingtype,andany
fundamentaldatatypeexceptvoid(option-allywithconstand/or
volatile).
concept
Anamedsetofconstraintsthatcanbeappliedtooneormoretemplate
parameters.SeeAppendixE.
constant-expression
Anexpressionwhosevaluecanbecomputedatcompiletimebythecompiler.
Wesometimescallthisatrueconstanttoavoidconfusionwithconstant
expression(withouthyphen).Thelatterincludesexpressionsthatareconstant
butcannotalwaysbecomputedatcompiletimebythecompiler.
constmemberfunctionAmemberfunctionthatcanbecalledforconstantand
temporaryobjectsbecauseitdoesnotnormallymodifymembersofthe*this
object.
container
Seecollectionclass.
conversionfunction
Aspecialmemberfunctionthatdefineshowanobjectcanimplicitly(or
explicitly)beconvertedtoanobjectofanothertype.Itisdeclaredusingtheform
operatortype().
conversionoperator
Asynonymforconversionfunction.Thelatteristhestandardterm,butthe
formerisalsocommonlyused.
CPPfile
Afileinwhichdefinitionsofvariablesandnoninlinefunctionsarelocated.Most
oftheexecutable(asopposedtodeclarative)codeofaprogramisnormally
placedinCPPfiles.TheyarenamedCPPfilesbecausetheyareusuallynamed
withthesuffix.cpp.Butforhistoricalreasonsthesuffixalsomightbe.C,.c,
.cc,or.cxx.Seealsoheaderfileandtranslationunit.
CRTP
Anacronymforcuriouslyrecurringtemplatepattern.Thisreferstoacode
patternwhereaclassXderivesfromabaseclassthathasXasatemplate
argument.
curiouslyrecurringtemplatepattern
SeeCRTP.
decay
Theimplicitconversionofanarrayorafunctiontoapointer.Forexample,the
stringliteral"Hello"hastypecharconst[6],butinmanyC++contexts,
itisimplicitlyconvertedtoapointeroftypecharconst*(whichpointsto
thefirstcharacterofthestring).
declaration
AC++constructthatintroducesorreintroducesanameintoaC++scope.See
alsodefinition.
deduction
Theprocessthatimplicitlydeterminestemplateargumentsfromthecontextin
whichtemplatesareused.Thecompletetermistemplateargumentdeduction.
definition
Adeclarationthatmakesthedetailsofthedeclaredentityknownor,inthecase
ofvariables,thatforcesstoragespacetobereservedforthedeclaredentity.For
classtypesandfunctiondefinitions,thisamountstodeclarationsthatincludea
brace-enclosedbody.Forexternalvariabledeclarations,thismeanseithera
declarationwithnoexternkeywordoradeclarationwithaninitializer.
dependentbaseclass
Abaseclassthatdependsonatemplateparameter.Specialcaremustbetakento
accessmembersofdependentbaseclasses.Seealsotwo-phaselookup.
dependentname
Anamethemeaningofwhichdependsonatemplateparameter.Forexample,
A<T>::xisadependentnamewhenAorTisatemplateparameter.Thename
ofafunctioninafunctioncallisalsodependentifanyoftheargumentsinthe
callhasatypethatdependsonatemplateparameter.Forexample,fin
f((T*)0)isdependentifTisatemplateparameter.Thenameofatemplate
parameterisnotconsidereddependent,however.Seealsotwo-phaselookup.
digraph
Acombinationoftwoconsecutivecharactersthatareequivalenttoanother
singlecharacterinC++code.Thepurposeofdigraphsistoallowtheinputof
C++sourcecodewithkeyboardsthatlackcertaincharacters.Althoughitisused
relativelyrarely,thedigraph<:issometimesaccidentallyformedwhenaleft
anglebracketisfollowedbyascoperesolutionoperator(::)withoutthe
requiredinterveningwhitespace.C++11introducedalexicalhacktodisable
digraphinterpretationunderthosecircumstances.
EBCO
Anacronymforemptybaseclassoptimization.Anoptimizationperformedby
mostmoderncompilerswherebyan“empty”baseclasssubobjectdoesnot
occupyanystorage.
emptybaseclassoptimization
SeeEBCO.
explicitinstantiationdirective
AC++constructwhosesolepurposeistocreateapointofinstantiation(POI).
explicitspecialization
Aconstructthatdeclaresordefinesanalternativedefinitionforasubstituted
template.Theoriginal(generic)templateiscalledtheprimarytemplate.Ifthe
alternativedefinitionstilldependsononeormoretemplateparameters,itis
calledapartialspecialization.Otherwise,itisafullspecialization.
expressiontemplate
Aclasstemplateusedtorepresentapartofanexpression.Thetemplateitself
representsaparticularkindofoperation.Thetemplateparametersstandforthe
kindsofoperandstowhichtheoperationapplies.
forwardingreference
OneoftwotermsforrvaluereferencesoftheformT&&whereTisadeducible
templateparameter.Specialrulesthatdifferfromordinaryrvaluereferences
apply(seeSection6.1onpage91).ThetermwasintroducedbyC++17as
replacementforuniversalreference,becausetheprimaryuseofsuchareference
istoforwardobjects.However,notethatitdoesnotautomaticallyforward.That
is,thetermdoesnotdescribewhatitisbutforwhatitistypicallyusedfor.
friendnameinjection
Theprocessthatmakesafunctionnamevisiblewhenitsonlydeclarationisa
frienddeclaration.
fullspecialization
Seeexplicitspecialization.
functionobject
Anobjectthatcanbecalledusingfunctioncallsyntax.InC++,thesearepointers
tofunctions,classeswithanoverloadedoperator()(seefunctor),andclasses
withaconversionfunctionyieldingapointertofunctionorreferenceto
function.
functiontemplate
Aconstructthatrepresentsafamilyoffunctions.Itspecifiesapatternfrom
whichactualfunctionscanbegeneratedbysubstitutingthetemplateparameters
byspecificentities.Notethatafunctiontemplateisatemplateandnota
function.Functiontemplatesaresometimescalledparameterizedfunctions.
functor
Anobjectofaclasstypewithanoverloadedoperator(),whichcanbecalled
usingfunctioncallsyntax.Thisincludestheclosuretypeofalambdaexpression.
glvalue
Acategoryofexpressionsthatproducesalocationforastoredvalue
(generalizedlocalizablevalue).Aglvaluecanbeanlvalueoranxvalue.See
valuecategoryandSectionB.2onpage674.
headerfile
Afilemeanttobecomepartofatranslationunitthrougha#includedirective.
Suchfilesoftencontaindeclarationsofvariablesandfunctionsthatarereferred
tofrommorethanonetranslationunit,aswellasdefinitionsoftypes,inline
functions,templates,constants,andmacros.Theyareusuallynamedwitha
suffixlike.hpp,.h,.H,.hh,or.hxx.Theyarealsocalledincludefiles.See
alsoCPPfileandtranslationunit.
INCITS
AnacronymforInterNationalCommitteeforInformationTechnologyStandards,
whichisaU.S.standardsdevelopmentorganization(formerlyknownaX3)
accreditedbyANSI.AsubcommitteecalledJ16isadrivingforcebehindthe
standardizationofC++.ItcooperatescloselywiththeInternationalOrganization
forStandardization(ISO).
includefile
Seeheaderfile.
incompletetype
Aclassthatisdeclaredbutnotdefined,anarrayofincompleteelementtypeor
ofunknownsize,anenumerationtypewithouttheunderlyingtypedefined,or
void(optionallywithconstand/orvolatile).
indirectcall
Afunctioncallforwhichthecalledfunctionisnotknownuntilthecallactually
occurs(atruntime).
initializer
Aconstructthatspecifieshowtoinitializeanobject.Forexample,in
std::complex<float>z1=1.0,z2(0.0,1.0);theinitializers
are=1.0and(0.0,1.0).
initializerlist
Acomma-separatedlistofexpressionsenclosedinbracesusedtoinitialize
objectsandreferences.Initializerlistsarecommonlyusedtoinitializevariables
butalso,forexample,toinitializemembersandbaseclassesinconstructor
definitions.Thisinitializationmayhappendirectlyorviaanintermediate
std::initializer_listobject.
injectedclassname
Thenameofaclassasvisibleinitsowndefinitionscope.Forclasstemplates,
thenameofthetemplateistreatedwithinthescopeofthetemplateasaclass
nameifthenameisnotfollowedbyatemplateargumentlist.
instance
TheterminstancehastwomeaningsinC++programming:Themeaningthatis
takenfromtheobject-orientedterminologyisaninstanceofaclass:anobject
thatistherealizationofaclass.Forexample,inC++,std::coutisan
instanceoftheclassstd::ostream.Theothermeaning(andtheonethatis
almostalwaysintendedinthisbook)isatemplateinstance:aclass,afunction,
oramemberfunctionobtainedbysubstitutingallthetemplateparametersby
specificvalues.Inthissense,aninstanceisalsocalledaspecialization,although
thelattertermisoftenmistakenforexplicitspecialization.
instantiation
Thereplacementofthetemplateparametersinatemplatedefinitiontocreatea
concreteentity(function,class,variable,oralias).Ifonlythedeclarationofa
templatebutnotitsdefinitionissubstituted,thetermpartialtemplate
instantiationissometimesused.Seealsosubstitution.Thealternativesenseof
creatinganinstance(object)ofaclassisnotusedinthisbook(seeinstance).
ISO
WorldwideacronymforInternationalOrganizationforStandardization.AnISO
workgroupcalledWG21isadrivingforcebehindtheeffortstostandardizeand
developC++.
iterator
Anobjectthatknowshowtotraverseasequenceofelements.Often,these
elementsbelongtoacollection(seecollectionclass).
linkableentity
Anyofthefollowing:afunctionormemberfunction,aglobalvariableorastatic
datamember,includinganysuchthingsgeneratedfromatemplate,asvisibleto
thelinker.
linker
Aprogramoroperatingsystemservicethatlinkstogethercompiledtranslation
unitsandresolvesreferencestolinkableentitiesacrossthosetranslationunits.
lvalue
Acategoryofexpressionsthatproducesalocationforastoredvaluethatisnot
assumedtobemovable(i.e.,glvaluesthatarenoxvalues).Typicalexamplesare
expressionsdenotingnamedobjects(variablesormembers)andstringliterals.
SeevaluecategoryandSectionB.1onpage673.
memberclasstemplate
Aconstructthatrepresentsafamilyofmemberclasses.Itisaclasstemplate
declaredinsideanotherclassorclasstemplatedefinition.Ithasitsownsetof
templateparameters(unlikeamemberclassofaclasstemplate).
memberfunctiontemplate
Aconstructthatrepresentsafamilyofmemberfunctions.Ithasitsownsetof
templateparameters(unlikeamemberfunctionofaclasstemplate).Itisvery
similartoafunctiontemplate,butwhenallthetemplateparametersare
substituted,theresultisamemberfunction(insteadofanordinaryfunction).
Memberfunctiontemplatescannotbevirtual.
membertemplate
Amemberclasstemplate,memberfunctiontemplate,orstaticdatamember
template.
ModernC++
ThephraseusedinthisbooktorefertothelanguageasstandardizedinC++11or
later(i.e.,C++11,C++14,orC++17).
nondependentname
Anamethatisnotdependentonatemplateparameter.Seedependentnameand
two-phaselookup.
ODR
Anacronymforone-definitionrule.Thisruleplacessomerestrictionsonthe
definitionsthatappearinaC++program.SeeSection10.4onpage154and
AppendixAfordetails.
one-definitionrule
SeeODR.
overloadresolution
Theprocessthatselectswhichfunctiontocallwhenseveralcandidates(usually
allhavingthesamename)exist.SeealsoAppendixC.
parameter
Aplaceholderentitythatismeanttobesubstitutedwithanactual“value”(an
argument)atsomepoint.Formacroparametersandtemplateparameters,the
substitutionoccursatcompiletime.Forfunctioncallparameters,ithappensat
runtime.Insomeprogrammingcommunities,parametersarecalledformal
parameters(whereasargumentsarecalledactualparameters).Seealso
argumentandtemplateargument.
parameterizedclass
Aclasstemplateoraclassnestedinaclasstemplate.Bothareparameterized
becausetheydonotcorrespondtoauniqueclassuntilthetemplatearguments
havebeenspecified.
parameterizedfunction
Afunctionormemberfunctiontemplateoramemberfunctionofaclass
template.Allareparameterizedbecausetheydonotcorrespondtoaunique
function(ormemberfunction)untilthetemplateargumentshavebeenspecified.
partialspecialization
Aconstructthatdeclaresordefinesanalternativedefinitionforcertain
substitutionsofatemplate.Theoriginal(generic)templateiscalledtheprimary
template.Thealternativedefinitionstilldependsontemplateparameters.
Currently,thisconstructexistsonlyforclasstemplates.Seealsoexplicit
specialization.
POD
Anacronymfor“plainolddata(type).”PODtypesaretypesthatcanbedefined
withoutcertainC++features(likevirtualmemberfunctions,accesskeywords,
andsoforth).Forexample,everyordinaryCstructisaPOD.
POI
Anacronymforpointofinstantiation.APOIisalocationinthesourcecode
whereatemplate(oramemberofatemplate)isconceptuallyexpandedby
substitutingtemplateparameterswithtemplatearguments.Inpractice,this
expansiondoesnotneedtooccurateveryPOI.Seealsoexplicitinstantiation
directive.
pointofinstantiation
SeePOI.
policyclass
Aclassorclasstemplatethemembersofwhichdescribeconfigurablebehavior
foragenericcomponent.Policiesarenormallypassedastemplatearguments.
Forexample,asortingtemplatemayhaveanorderingpolicy.Policyclassesare
alsocalledpolicytemplatesorjustpolicies.Seealsotraitstemplate.
polymorphism
Theabilityofanoperation(whichisidentifiedbyitsname)toapplytoobjects
ofdifferentkinds.InC++,thetraditionalobject-orientedconceptof
polymorphism(alsocalledrun-timeordynamicpolymorphism)isachieved
throughvirtualfunctionsthatareoverriddeninderivedclasses.Inaddition,C++
templatesenablestaticpolymorphism.
precompiledheader
Aprocessedformofsourcecodethatcanquicklybeloadedbythecompiler.The
sourcecodeunderlyingaprecompiledheadermustbethefirstpartofa
translationunit(i.e.,itcannotstartsomewhereinthemiddleofatranslation
unit).Often,aprecompiledheadercorrespondstoanumberofheaderfiles.
Usingprecompiledheaderscansubstantiallyreducethetimeneededtobuilda
largeapplicationwritteninC++.
primarytemplate
Atemplatethatisnotapartialspecialization.
prvalue
Acategoryofexpressionsthatperforminitializations.Prvaluescanbeassumed
todesignatepuremathematicalvaluesuchas1ortrueandtemporaries
(especiallyvaluesreturnedbyvalue).AnyrvaluebeforeC++11isaprvaluein
C++11.SeevaluecategoryandSectionB.2onpage674.
qualifiedname
Anamecontainingascopequalifier(::).
referencecounting
Aresourcemanagementstrategythatkeepscountofhowmanyentitiesare
referringtoaparticularresource.Whenthecountdropstozero,theresourcecan
bedisposedof.
rvalue
Acategoryofexpressionsthatarenotlvalues.Anrvaluecanbeaprvalue(such
asatemporary)oranxvalue(e.g.,anlvaluemarkedwithstd::move()).
WhatwascalledarvaluebeforeC++11iscalledaprvalueinC++11.Seevalue
categoryandSectionB.2onpage674.
SFINAE
Anacronymforsubstitutionfailureisnotanerror.Amechanismthatsilently
discardstemplatesinsteadoftriggeringcompilationerrorswhenattemptingto
substitutetemplateargumentsininvalidways.Othertemplatesinanoverloadset
thengetachancetobeselectediftheirsubstitutionissuccessful.
sourcefile
AheaderfileoraCPPfile.
specialization
Theresultofsubstitutingtemplateparameterswithactualvalues.A
specializationmaybecreatedbyaninstantiationorbyanexplicitspecialization.
Thistermissometimesmistakenlyequatedwithexplicitspecialization.Seealso
instance.
staticdatamembertemplate
Avariabletemplatethatisamemberofaclassorclasstemplate.
substitution
Theprocessofreplacingtemplateparametersintemplatedentitiesbyactual
types,values,ortemplates.Theextentofthesubstitutiondependsonthecontext.
Duringoverloadresolution,forexample,onlytheminimumamountof
substitutiontoestablishthetypeofacandidatefunctionisperformed,andifthat
substitutionleadstoinvalidconstructs,theSFINAErulesapply.Seealso
instantiation.
template
Aconstructthatrepresentsafamilyoftypes,functions,memberfunctions,or
variables.Itspecifiesapatternfromwhichactualtypes,functions,member
functions,orvariablescanbegeneratedbysubstitutingthetemplateparameters
byspecificentities.Inthisbook,thetermdoesnotincludefunctions,classes,
staticdatamembers,andtypealiasesthatareparameterizedonlybyvirtueof
beingmembersofaclasstemplate.Seealiastemplate,variabletemplate,class
template,parameterizedclass,functiontemplate,andparameterizedfunction.
templateargument
The“value”substitutedforatemplateparameter.Thisvalueisusuallyatype,
althoughcertainconstantvaluesandtemplatescanbevalidtemplatearguments
too.Seealsoargument.
templateargumentdeduction
Seededuction.
template-id
Thecombinationofatemplatenamefollowedbytemplateargumentsspecified
betweenanglebrackets(e.g.,std::list<int>).
templateparameter
Agenericplaceholderinatemplate.Themostcommonkindoftemplate
parameteraretypeparameters,whichrepresenttypes.Nontypeparameters
representconstantvaluesofacertaintype,andtemplatetemplateparameters
representtypetemplates.Seealsoparameter.
templatedentity
Atemplateoranentitydefinedorcreatedinatemplate.Thelatterincludes
thingslikeanordinarymemberfunctionofaclasstemplateortheclosuretype
ofalambdaexpressionappearinginatemplate.
traitstemplate
Aclasstemplatewithmembersthatdescribecharacteristics(traits)ofthe
templatearguments.Usuallythepurposeoftraitstemplatesistoavoidan
excessivenumberoftemplateparameters.Seealsopolicyclass.
translationunit
ACPPfilewithalltheheaderfilesandstandardlibraryheadersitincludesusing
#includedirectives,minustheprogramtextthatisexcludedbyconditional
compilationdirectivessuchas#if.Forsimplicity,itcanalsobethoughtofas
theresultofpreprocessingaCPPfile.SeeCPPfileandheaderfile.
trueconstant
Anexpressionwhosevaluecanbecomputedatcompiletimebythecompiler.
Seeconstant-expression.
tuple
AgeneralizationoftheCstructconceptsuchthatmemberscanbeaccessed
bynumber.
two-phaselookup
Thenamelookupmechanismusedfornamesintemplates.Thetwophasesare
(1)theprocessingofthetemplatedefinition,and(2)theinstantiationofthe
templateforspecifictemplatearguments.Nondependentnamesarelookedup
onlyinthefirstphase,nondependentbaseclassesarenotconsideredduringthat
phase.Dependentnameswithascopequalifier(::)arelookeduponlyinthe
secondphase.Dependentnameswithoutascopequalifiermaybelookedupin
bothphases,butinthesecondphase,onlyargument-dependentlookupis
performed.
typealias
Analternativenameforatype,introducedusingatypedefdeclaration,an
aliasdeclaration,ortheinstantiationofanaliastemplate.
typetemplate
Aclasstemplate,memberclasstemplate,oraliastemplate.
universalreference
OneoftwotermsforrvaluereferencesoftheformT&&whereTisadeducible
templateparameter.Specialrulesthatdifferfromordinaryrvaluereferences
apply(seeSection6.1onpage91).ThetermwascoinedbyScottMeyersasa
commontermforbothlvaluereferenceandrvaluereference.Because
“universal”was,well,toouniversal,theC++17standardintroducedtheterm
forwardingreferenceinstead.
user-definedconversion
Atypeconversiondefinedbytheprogrammer.Itcanbeaconstructorthatcanbe
calledwithoneargumentoraconversionfunction.Unlesstheconstructoror
conversionfunctionisdeclaredwiththekeywordexplicit,thetype
conversioncanoccurimplicitly.
valuecategory
Aclassificationofexpressions.Thetraditionalvaluecategorieslvaluesand
rvalueswereinheritedfromC.C++11introducedalternativecategories:glvalues
(generalizedlvalues),whoseevaluationidentifiesstoredobjects,andprvalues
(purervalues),whoseevaluationinitializeobjects.Additionalcategories
subdivideglvaluesintolvalues(localizablevalues)andxvalues(eXpiring
values).Inaddition,inC++11rvaluesserveasageneralcategoryforboth
xvaluesandprvalues(beforeC++11,rvalueswerewhatprvaluesareinC++11).
SeeAppendixBfordetails.
variabletemplate
Aconstructthatrepresentsafamilyofvariablesorstaticdatamembers.It
specifiesapatternfromwhichactualvariablesandstaticdatamemberscanbe
generatedbysubstitutingthetemplateparametersbyspecificentities.
whitespace
InC++,thisisthespacethatdelimitsthetokens(identifiers,literals,symbols,
andsoon)insourcecode.Besidesthetraditionalblankspace,newline,and
horizontaltabulationcharacters,thisalsoincludescomments.Otherwhitespace
characters(e.g.,thepagefeedcontrolcharacter)aresometimesalsovalid
whitespace.
xvalue
Acategoryofexpressionsthatproducealocationforastoredobjectthatcanbe
assumedtobenolongerneeded.Atypicalexampleisanlvaluemarkedwith
std::move().SeevaluecategoryandSectionB.2onpage674.
Index
->249
<
parsing225
>
intemplateargumentlist50
parsing225
>>
versus>>28,226
[]685
A
ABC759,seeabstractbaseclassaboutthebook249
Abrahams,David515,547,573
AbrahamsGurtovoyMeta750
abstractbaseclass369
asconcept377
abstractclass759
ACCU750
actualparameter155
Adamczyk,Steve321,352
adapteriterator505
add_const729
add_cv729
add_lvalue_reference730
add_pointer730
addressof166,737
add_rvalue_reference730
add_volatile729
ADL217,218,219,759
aggregate692
template43
trait711
Alexandrescu,Andrei266,397,463,547,573,601,628
AlexandrescuAdHocVisitor750
AlexandrescuDesign750
AlexandrescuDiscriminatedUnions750
algorithmspecialization465,557
aliasdeclaration38
aliastemplate39,312,446
asmember178
drawbacks446
specialization338
aligned_storage733,734
aligned_union733
alignment_of715
allocator85,462
anglebrackethack28,226,759
anglebrackets4,760
parsing225
anonymousunion246
ANSI760
apply592
archetype655
argument155,192,760
byvalueorbyreference20
conversions287
deduction269,seeargumentdeductionderivedclass495
forfunctiontemplates192
fortemplatetemplateparameters85,197
match682
named512
nontypearguments194
typearguments194
versusparameter155
argumentdeduction7
forclasstemplates40
forfunctiontemplates10
argument-dependentlookup217,218,219,760
argumentlistoperator>50
argumentpack200
array43
arrayasparameterintemplates71
astemplateparameter186
conversiontopointer107,270
array
deductionguide64
arraypassing115
qualification453
Array<>635
assignmentoperatorastemplate79
withtypeconversion74
associatedclass219
associatednamespace219
AusternSTL750
auto12
auto&&167
auto294
andinitializerlists303
astemplateparameter50,296
deduction303
returntype11,296
automaticinstantiation243
avoidingdeduction497
B
back()
forvectors26
back()forvectors23
baggage462
Barton,JohnJ.497,515,547
BartonNackman751
Barton-Nackmantrick497
versusCRTP515
baseclassconversionto689
dependent70,237,238
duplicate513
empty489
nondependent236
parameterizedbyderivedclass495
variadic65
Batory,DonS.516
BCCL751
bibliography749
binarycompatibilitywithconcepts747
binaryrightfold208
bitset79
Blinn,Frank516
Blitz++751
books749
bool
contextuallyconvertibleto485
conversionto689
BoolConstant411
bool_constant699
BoostAny751
Boost751
BoostFusion751
BoostHana751
BoostIterator751
BoostMPL751
BoostOperators752
BoostOptional752
BoostSmartPtr752
BoostTuple752
BoostTypeTraits752
BoostVariant752
Borland256,649
boundedpolymorphism375
bridgepattern379
Bright,Walter266
Brown,WalterE.547
BrownSIunits752
byvaluevs.byreference105,638
C
C++03752
C++11753
C++14753
C++17753
C++98752
CacciolaKrzemienski2013753
calldefaultarguments289
parameter9
callable157
callback157
CargillExceptionSafety753
categorycompositetype702
forvalues673
primarytype702
char*
astemplateargument49,354
char_traits462
Chochlík,Matúvs.547
chronolibrary534
class4,185,760
associated219
astemplateargument49
definition668
dependentbase237
nameinjection221
nondependentbase236
policyclass394
qualification456
templateseeclasstemplateversusstruct151
classtemplate23,151,760
argumentdeduction40
asmember74,178
declaration24,177
defaultargument36
enable_if477
friend75,209
fullspecialization338
overloading359
parameters288
partialspecialization347
tagdispatching479
classtype151,760
qualification456
Clinkage183
closure310,422
codebloat348
codelayoutprinciples490
collapsingreferences277
collectionclass760,seecontainercommon_type12,622,732
compiler760
compile-timeif134,263,474
compiling255,651
models243
completetype154,245,760
complex6
compositetype(category)702
computationmetaprogramming538
concept29,103,651,654,739,760
abstractbaseclass377
binarycompatibility747
definition742
disablefunctiontemplates475
withstatic_assert29
conditional171,443,732
evaluationofunusedbranches442
implementation440
conjunction736
conscells571
const
astemplateparameter186
rvaluereference110
constmemberfunction761
constanttrueconstant769
constant-expression156,543,761
constexpr21,125
astemplateparameters356
formetaprogramming530
forvariabletemplates473
versusenumeration543
constexprif134,263,474
container761
elementtype401
contextdeduced271
context-sensitive215
contextuallyconvertibletobool485
conversionarraytopointer107,270
base-to-derived689
derived-to-base689
forpointers689
ofarguments287
sequence689
standard683
tobool689
toellipsis417,683
tovoid*689
user-defined195,683
withtemplates74
conversionfunction761
conversion-function-id216
conversionoperator761
Coplien,James515
CoplienCRTP753
copyconstructorastemplate79
disable102
copy-elisionrules346
copy-on-write107
copyoptimizationsforstrings107
CoreIssue1395753
CPPfile137,761
Cppreference749
cref()112
CRTP495,761
forVariant606
versusBarton-Nackmantrick515
curiouslyrecurringtemplatepattern495,761
forVariant606
currentinstantiation223
memberof240
cv-qualified702
Czarnecki,Krysztof573
Czarnecki,Krzysztof516
CzarneckiEiseneckerGenProg753
D
debugging651
decay12,41,270,524,731,761
forarrays107
forfunctions159
implementation407
oftemplateparameters186
returntype166
declaration153,177,664,761
forward244
ofclasstemplate24
versusdefinition153,664
decltype11,282,298,414
forexpressions678
decltype(auto)162,301
andvoid162
asreturntype301
astemplateparameter302
declval166,415,737
decompositiondeclarations306
deducedcontext271
parameter11
deduction269,761
auto303
avoiding497
classtemplatearguments40
forrvaluereferences277
fromdefaultarguments8,289
functiontemplatearguments7
offorwardingreferences278
deductionguide42,314
explicit319
foraggregates43
guidedtype42,314
variadic64
defaultargumentseedefaultargumentcallargumentdeduction8,289
fortemplatetemplateparameter197
nontypeparameter48
defaultargumentdependingonfollowingarguments621
forcallparameters180
forclasstemplates36
forfunctiontemplates13
fortemplates190
fortemplatetemplateparameters85
deferevaluation171
definition3,153,664,762
ofclasstypes668
ofconcepts742
versusdeclaration153,664
definitiontime6
dependentbaseclass237,762
dependentexpression233
dependentname215,217,762
inusingdeclarations231
oftemplates230
oftypes228
dependenttype223,228
deque85
derivation236,489
derivedclassasbaseclassargument495
design367
designpattern379
DesignPatternsGoF753
determiningtypes448,460
digraph227,762
Dimov,Peter488
Dionne,Louis463,547
directiveforexplicitinstantiation260
discriminatedunion603
disjunction736
dispatchingtag467,487
dominationrule515
DosReis,Gabriel214,321
DosReisMarcusAliasTemplates754
double
astemplateargument49,356
astraitsvalue391
zeroinitialization68
duplicatebaseclass513
dynamicpolymorphism369,375
E
EBCDIC387
EBCO489,593,762
andtuples593
EDG266
EDG754
EdisonDesignGroup266,352
Eisenecker,Ulrich516,573
EiseneckerBlinnCzarnecki754
ellipsis417,683
EllisStroustrupARM754
emailtotheauthors249
empty()forvectors23
emptybaseclassoptimization489,593,762
EnableIfplacement472
enable_if98,732
andparameterpacks735
classtemplates477
disablecopyconstructor102
implementation469
placement472
entitytemplated181
enumerationqualification457
versusstaticconstant543
erasureoftypes523
errorhandling651
errormessage143
evaluationdeferred171
exceptionsafety26
specifications290
expansionrestricted497
explicitindeductionguide319
explicitgenericinitialization69
explicitinstantiationdeclaration262
definition260
directive260,762
explicitspecialization152,224,228,238,338,762
explicittemplateargument233
exportedtemplates266
expressiondependent233
instantiation-dependent234
type-dependent217,233
value-dependent234
expressiontemplate629,762
limitations646
performance646
extendedKoeniglookup242
extent110,715
F
facade501
FalseType411
false_type413,699
implementation411
fileorganization137
final493
fixedtraits386
float
astemplateargument49,356
astraitsvalue391
zeroinitialization68
foldexpression58,207
deductionguide64
formalparameter155
forums749
forwarddeclaration244
forwardingmetafunction407,447,452,552
perfect91,280
forwardingreference93,111,763
auto&&167
deduction278
friend185,209
class209
classtemplates75
function211
functionversusfunctiontemplate499
nameinjection221,241,498,763
template213
fullspecialization338,763
ofclasstemplates338
offunctiontemplates342
ofmemberfunctiontemplate78
ofmembertemplates344
function517,519
functionastemplateparameter186
dispatchtable257
fortypes401
qualification454
signature328
spilledinline257
surrogate694
templateseefunctiontemplatefunctioncallwrapper162
functionobject157,763
andoverloading694
functionobjecttype157
functionparameterpack204
functionpointer517
FunctionPtr519
functiontemplate3,151,763
argumentdeduction10
arguments192
asmember74,178
declaration177
defaultcallargument180
defaulttemplateargument13
friend211
fullspecialization342
inline20,140
nontypeparameters48
overloading15,326
partialspecialization356
versusfriendfunction499
functor763
andoverloading694
fundamentaltypequalification448
futuretemplatefeatures353
G
generatedspecialization152
generationmetaprogramming538
genericlambda80,309
forSFINAE421
genericprogramming380
get()fortuples598
Gibbons,Bill241,515
glossary759
glvalue674,763
greedyinstantiation256
Gregor,Doug214
GregorJarviPowellVariadicTemplates754
guardforheaderfiles667
guidedtype42,314
Gurtovoy,Aleksey547,573
H
Hartinger,Roland358
HasDereference654
has_unique_object_representations714
has_virtual_destructor714
headerfile137,763
guard667
order141
precompiled141
std.hpp142
Henney,Kevlin528
HenneyValuedConversions754
heterogeneouscollection376
Hewlett-Packard241,352
higher-ordergenericity214
Hinnant,Howard488,547
hybridmetaprogramming532
I
identifier216
if
compile-time263
constexpr134,263,474
IfThenElseT<>440,541
immediatecontext285
implicitinstantiation243
INCITS763
includefile764,seeheaderfile#includeorder141
inclusionmodel139,254
incompletetype154,171,764
usingtraits734
indexlist570,586
indexsequence570,586
indirectcall764
inheritance236,489
dominationrule515
duplicatebaseclass513
initializationexplicit69
offundamentaltypes68
initializer764
initializerlist69,764
andauto303
andoverloading691
initializer_list
andoverloading691
deduction274
injectedclassname221,764
friendname221,241,498
inline20,140
andfullspecialization78
forvariables178
inlinevariable392
instance6,764
instantiatedspecialization152
instantiation5,6,152,243,764
automatic243
costs539
current223
explicitdefinition260
explicitdirective260
greedy256
implicit243
iterated259
lazy245
levels542
manual260
mechanisms243
model249
on-demand243
point250
queried257
recursive542
shallow652
virtual246
instantiation-dependentexpression234
instantiation-safetemplate482
instantiationtime6
int
parsing277
parsingliterals599
zeroinitialization68
integer_sequence586
integral_constant566
integral_constant698
Internetresources749
intrusive375
invasive375
invoke()160
trait716
invoke_result163,717
is_abstract714
is_aggregate711
is_arithmetic707
is_array110,704
IsArrayT<>453
is_assignable722
is_base_of726
is_callable716
is_class705
IsClassT<>456
is_compound707
is_const709
is_constructible719
is_convertible727
implementation428
IsConvertibleT428
is_copy_assignable722
is_copy_constructible720
is_default_constructible720
is_destructible724
is_empty714
is_enum705
IsEnumT<>457
is_final714
is_floating_point703
is_function706
is_fundamental707
IsFundaT<>448
is_integral703
is_invocable716
is_invocable_r716
is_literal_type713
is_lvalue_reference705
IsLValueReferenceT<>452
is_member_function_pointer705
is_member_object_pointer705
is_member_pointer706
is_move_assignable723
is_move_constructible721
is_nothrow_assignable722
is_nothrow_constructible719
is_nothrow_copy_assignable722
is_nothrow_copy_constructible720
is_nothrow_default_constructible720
is_nothrow_destructible724
is_nothrow_invocable716
is_nothrow_invocable_r716
is_nothrow_move_assignable723
is_nothrow_move_constructible721
is_nothrow_swappable725
is_nothrow_swappable_with724
is_null_pointer704
ISO764
is_object707
isocpp.org750
is_pod713
is_pointer704
IsPointerT<>451
is_polymorphic714
IsPtrMemT<>454
is_reference706
IsReferenceT<>452
is_rvalue_reference705
IsRValueReferenceT<>452
is_same726
implementation410
IsSameT410
is_scalar707
is_signed709
is_standard_layout712
is_swappable725
is_swappable_with724
is_trivial712
is_trivially_assignable722
is_trivially_constructible719
is_trivially_copyable712
is_trivially_copy_assignable722
is_trivially_copy_constructible720
is_trivially_default_constructible720
is_trivially_destructible724
is_trivially_move_assignable723
is_trivially_move_constructible721
is_union706
is_unsigned709
is_void703
is_volatile711
ItaniumABI754
iteratedinstantiation259
iterator380,765
iteratoradapter505
iterator_traits399,462
J
J¨arvi,Jaakko214,321,488,649
JosuttisLaunder754
JosuttisStdLib755
K
Karlsson,Bjorn485
KarlssonSafeBool755
Klarer,Rober662
Koenig,Andrew217,218,242
Koeniglookup218,242
KoenigMooAcc755
L
lambdaascallable160
asfunctor160
closure310
generic80,309,618
primarytypecategory702
LambdaLib755
launder()617
layoutprinciples490
lazyinstantiation245
leftfold208
levelsofinstantiation542
lexing224
LibIssue181755
limitlevelsofinstantiation542
linkableentity154,256,765
linkage182,183
linker765
linking255
LippmanObjMod755
LISPconscells571
list380
literaloperator277
parsing277,599
literal-operator-id216
literaltype391
trait713
lookupargument-dependent217,218
fornames217
Koeniglookup218,242
ordinary218,249
qualified216
two-phase249
unqualified216
loopsplit634
Lumsdaine,Andrew488
lvalue674,765
beforeC++11673
lvaluereference105
M
Maddock,John662
make_pair()120
make_signed729
avoidundefinedbehavior442
make_unsigned729
avoidundefinedbehavior442
manualinstantiation260
Marcus,Mat214
match682
best681
perfect682
materialization676
Maurer,Jens214,267,321,322
max_align_tandtypetraits702
max_element()380
maximumlevelsofinstantiation542
munch226
memberaliastemplate178
asbaseclass492
classtemplate74,178,765
functionseememberfunctionfunctiontemplate74,178
initialization69
ofcurrentinstantiation240
ofunknownspecialization229,240
templateseemembertemplatetypecheck431
memberfunction181
astemplate74,178
implementation26
template151,765
virtual182
memberfunctiontemplatespecialization78
membertemplate74,178,765
fullspecialization344
genericlambda80
versustemplatetemplateparameter398
virtual182
Merrill,Jason214,321
metafunctionforwarding407,447,452,552
metaprogramming123,529,549
chronolibrary534
constexpr529
dimensions537
forunittypes534
hybrid532
ontypes531
onvalues529
unrollingloops533
Metaware241,545
Meyers,Scott516
MeyersCounting755
MeyersEffective755
MeyersMoreEffective755
mixin203,508
curious510
modules366
MoonFlavors756
motivationoftemplates1
moveconstructorastemplate79
detectnoexcept443
movesemanticsperfectforwarding91
MTL756
MusserWangDynaVeri756
Myers,Nathan397,462,515
MyersTraits756
N
Nackman,LeeR.497,515,547
name155,215,216
classnameinjection221
dependent215,217
dependentoftemplates230
dependentoftypes228
friendnameinjection221,241,498
lookup215,217
nondependent217
qualified215,216
two-phaselookup249
unqualified216
name()
ofstd::type_info138
namedtemplateargument358,512
namespaceassociated219
scope177
template231
unnamed666
narrowingnontypeargumentfortemplates194
Naumann,Axel547
negation736
nestedclass181
astemplate74,178
NewMat756
NewShorterOED756
Niebler,Eric649
NIHCL383
noexcept290,415
indeclval415
traits443
nondeducedparameter10
nondependentbaseclass236
name217,765
nonintrusive376
noninvasive376
nonreferenceversusreference115,270,638,687
nontemplateoverloadingwithtemplate332
nontypeargumentfortemplates194
nontypeparameter45,186
restrictions49
nullptrtypecategory704
numericparsing277
parsingliterals599
trait707
numeric_limits462
O
ODR154,663,765
on-demandinstantiation243
one-definitionrule154,663,765
operator[]
atcompiletime599
operator>
intemplateargumentlist50
operator""
parsing277,599
operator-function-id216
optimizationforcopyingstrings107
foremptybaseclass489
oracle662
orderingpartial330
rules331
orderofheaderfiles141
ordinarylookup218,249
overloading15,323,681
classtemplates359
forstringliterals71
initializerlists691
nonreferenceversusreference687
offunctiontemplates326
partialordering330
referenceversusnonreference687
templatesandnontemplates332
OverloadingProperties754
overloadresolution681,766
forvariadictemplates335
shallnotparticipate131
P
packexpansion201
nested205
pattern202
parameter155,185,766
actual155
array186
auto50,296
byvalueorbyreference20
const186
constexpr356
ellipsis417,683
forbaseclass70,238
forcall9
formal155
function186
match682
nontype45,186
ofclasstemplates288
reference167,187
referenceversusnonreference115,270,638
string54
templatetemplateparameter83,187
type185
versusargument155
void6
void*186
parameterizationclause177
parameterizedclass766
parameterizedfunction669,766
parameterizedtraits394
parameterpack56,204,454,549
andenable_if735
deduction275
expansion202
foldexpression207
function204
slicing365
template188,200
versusC-stylevarargs409
withdeducedtype298,569
parsing224
maximummunch226
ofanglebrackets225
partialorderingofoverloading330
partialspecialization33,152,638,766
additionalparameters453
forcodeselection127
forfunctiontemplates356
ofclasstemplates347
participateinoverloadresolution131
pass-by-reference20,108
pass-by-value20,106
pattern379
CRTP495,606
packexpansion202
PCH141
Pennello,Tom241
perfectforwarding91,280
ofreturnvalues300
temporary167
perfectmatch682,686
perfectreturning162
placeholderclasstype314
placeholdertype422
astemplateparameter50
auto294
decltype(auto)301
placementnewandlaunder()617
POD151,766
trait713
POI250,668,766
pointerconversions689
conversiontovoid*689
iteratortraits400
qualification451
zeroinitialization68
pointer-to-memberqualification454
pointofinstantiation250,668,766
policyclass394,395,766
versustraits397
policytraits458
polymorphicobject667
polymorphism369,767
bounded375
dynamic369,375
static372,376
unbounded376
POOMA756
pop_back()
forvectors26
pop_back()forvectors23
Powell,Gary214
practice137
precompiledheader141,767
predicatetraits410
prelinker259
preprocessorguard667
primarytemplate152,184,348,767
primarytype(category)702
primenumbers545
promotion683
propertytraits458
prvalue674,767
ptrdiff_t
versussize_t685
ptrdiff_tandtypetraits702
push_back()forvectors23
Q
qualificationofarraytypes453
ofclasstypes456
ofenumerationtypes457
offunctiontypes454
offundamentaltypes448
ofpointer-to-membertypes454
ofpointertypes451
ofreferencetypes452
qualified-id216
qualifiedlookup216
qualifiedname215,216,767
queriedinstantiation257
Quora749
R
rank715
ratiolibraryclass534
read-onlyparametertypes458
recursiveinstantiation542
ref()112
referenceandSFINAE432
astemplateargument270
astemplateparameter167,187
binding679
collapsing277
forwarding111
lvalue105
qualification452
rvalue105
versusnonreference115,270,638,687
referencecounting767
reference_wrapper112
reflectioncheckfordatamembers434
checkformembersfunctions435
checkfortypemembers431
future363
metaprogramming538
remove_all_extents731
remove_const728
remove_cv728
remove_extent731
remove_pointer730
remove_reference118,729
remove_volatile728
requires103,740
clause476,740
expression742
restrictedtemplateexpansion497
result_of163,717
resulttypetraits413
returnperfectly162
returntypeauto11,296
decay166
decltype(auto)301
deduction296
trailing282
returnvaluebyvaluevs.byreference117
perfectforwarding300
rightfold208
run-timeanalysisoracles662
rvalue674,767
beforeC++11673
rvaluereference105
const110
deduction277
perfectmatch687
valuecategory92
S
Sankel,David547
scanning224
semantictransparency325
separationmodel266
sequenceofconversions689
SFINAE129,284,767
examples416
friendly424
functionoverloading416
genericlambdas421
partialspecialization420
referencetypes432
SFINAEout131
shallnotparticipateinoverloadresolution131
shallowinstantiation652
Siek,Jeremy515,662
signature328
SiliconGraphics462
sizeof…57
sizeof401
size_t
versusptrdiff_t685
size_ttypeandtypetraits702
smallstringoptimization107
Smalltalk376,383
Smaragdakis,Yannis516
SmaragdakisBatoryMixins756
Smith,Richard214,321
sourcefile767
spaces770
specialization31,152,243,323,768
algorithm557
explicit152,224,228,238,338
full338
generated152
inline78
instantiated152
ofalgorithms465
ofmemberfunctiontemplate78
partial152,347,638
partialforfunctiontemplates356
unknown223
specialmemberfunction79
disable102
templify102
Spertus,Mike321
Spicer,John321,352
SpicerSFINAE756
spilledinlinedfunction257
splitloop634
SSO107
Stack<>23
Stackoverflow749
StandardC++Foundation750
standard-layouttype712
standardlibraryutilities697
StandardTemplateLibrary241,380,seeSTL
static_assert654
asconcept29
intemplates6
staticconstantversusenumeration543
staticdatamembertemplate768
staticif266
staticmember28,181
staticpolymorphism372,376
std.hpp142
StepanovLeeSTL757
StepanovNotes757
STL241,380,649
string[]685
andreferencetemplateparameters271
asparameter54
astemplateargument49,354
literalastemplateparameters271
stringliteralasparameterintemplates71
parsing277
passing115
valuecategory674
Stroustrup,Bjarne214,321
StroustrupC++PL757
StroustrupDnE757
StroustrupGlossary757
struct
definition668
qualification456
versusclass151
structuredbindings306
substitution152,768
SunMicrosystems257
surrogate694
Sutter,Herb266,321,547
SutterExceptional757
SutterMoreExceptional757
Sutton,Andrew214,547
syntaxchecking6
T
tagdispatching467,487
classtemplates479
Taligent240
taxonomyofnames215
template768
alias39,312,446
argument155,seetemplateargumentdefaultargument190
fornamespaces231
friend213
idseetemplate-idinline20,140
instantiation152,243,seeinstantiationinstantiation-safe482
membertemplate74,178
metaprogramming529,549
motivation1
name155,215
nontypearguments194
oftemplate28
overloadingwithnontemplate332
parameter3,9,155,185
partialspecialization33
primary152,184,348
specialization31,152
substitution152
typearguments194
typedef38
union180
variable80,447
variadic55,190,200
.template79,231
->template80,231
::template231
templateargument155,192,768
array453
char*49,354
class49
conversions287
deduction269,768
derivedclass495
double49,356
explicit233
float49,356
named358,512
string49,271,354
templateargumentlistoperator>50
templateclass151,seeclasstemplatetemplatedentity181,768
templatefunction151,seefunctiontemplatetemplate-id151,155,192,216,
231,768
templatememberfunction151,seememberfunctiontemplatetemplate
parameter768
array186,453
auto50,296
const186
constexpr356
decay186
function186
reference167,187
string54
stringliteral271
void6
void*186
templateparameterpack188,200
withdeducedtype298,569
templatetemplateargument85,197
templatetemplateparameter83,187,398
argumentmatching85,197
versusmembertemplate398
temploid181
temporaries630
temporaryperfectforwarding167
temporarymaterialization676
terminology151,759
this->70,238
*this
valuecategory686
tokenization224
maximummunch226
to_string()forbitsets79
Touton,James321
tracer657
trailingreturntype282
traits385,638,769,seetypetraitsaspredicates410
detectingnoexcept443
factory422
fixed386
forincompletetypes734
forvalueandreference638
iterator_traits399
parameterized394
policytraits458
standardlibrary697
template769
valuetraits389
variadictemplates,multipletypetraits734
versuspolicyclass397
translationunit154,663,769
transparency324
trivialtype712
trueconstant769
TrueType411
true_type413,699
implementation411
tuple575,769
EBCO593
get()598
operator[]599
tuple_element308
tuple_size308
two-phaselookup6,238,249,769
two-phasetranslation6
two-stagelookup249
typealias38
arguments194
categoryseetypecategoryclosuretype310
complete154,245
compositetype(category)702
conversion74
definitionxxxii,38,seetypealiasdependent223,228
dependentname228
erasure523
forbool_constant699
forintegral_constant698
function401
incomplete154
metaprogramming531
of*this686
ofcontainerelement401
parameter185
POD151
PODtrait713
predicates410
primarytype(category)702
qualification448,460
read-onlyparameter458
requirement743
safety376
standard-layout712
trivial712
utilitiesinstandardlibrary697
typealias769
typecategorycomposite702
primary702
typedef38
type-dependentexpression217,233
typeid138
type_info138
typelist455,549
typename4,67,185,229
future354
typeparameter4
TypeT<>460
typetemplate769
typetraits164,seetraitsaspredicates410
factory422
forincompletetypes734
standardlibrary697
_tversion40,83
unexpectedbehavior164
variadictemplates,multipletypetraits734
__type_traits462
U
UCN216
unaryfold208
unboundedpolymorphism376
underlying_type716
unevaluatedoperand133,667
unionanonymous246
definition668
discriminated603
qualification456
template180
unittypesmetaprogramming534
universalcharactername216
universalreference93,111,769,seeforwardingreferenceunknown
specialization223,228
memberof229,240
unnamednamespace666
unqualified-id216
unqualifiedlookup216
unqualifiedname216
unrollingloops533
Unruh,Erwin352,545
UnruhPrimeOrig757
user-definedconversion195,769
using4,231
usingdeclaration239
dependentname231
variadicexpressions65
utilitiesinthestandardlibrary697
V
valarray648
Vali,Faisal321
valueasparameter45
forbool_constant699
forintegral_constant698
functions401
valuecategory282,673,770
*this686
beforeC++11673
decltype678
ofstringliterals674
oftemplateparameters187
sinceC++11674
value-dependentexpression234
valueinitialization68
valuemetaprogramming529
valuetraits389
value_type
forbool_constant699
forintegral_constant698
Vandevoorde,David242,321,516,547,647
VandevoordeJosuttisTemplates1st757
VandevoordeSolutions758
varargsinterface55
variabletemplate80,447,770
constexpr473
variadicbaseclasses65
using65
variadictemplate55,190,200
andenable_if735
deductionguide64
foldexpression58
multipletypetraits734
overloadresolution335
perfectforwarding281
variant603
vector23
vector380
Veldhuizen,Todd546,647
VeldhuizenMeta95758
virtual
functiondispatchtable257
instantiation246
membertemplates182
parameterized510
visitorforVariant617
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets
CodeSnippets