Variadic Templates
Effective C++
Sheffield Hallam University
Jack Carey
May 2022
Contents
1 Introduction 1
2 Problem Statement 1
2.1 Case Study: Policy-based ’Lifeform’ Library Class . . . . . . . . . . 2
2.2 Compiling the Code . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3 Solution 4
3.1 What are Variadic Templates? . . . . . . . . . . . . . . . . . . . . . 4
3.2 Parameter Packs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.3 Fold Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.4 Constraints and concepts . . . . . . . . . . . . . . . . . . . . . . . . 5
4 Conclusion 6
5 References 8
6 Appendices 9
6.1 Case Study Templates . . . . . . . . . . . . . . . . . . . . . . . . . 9
6.1.1 Policy Examples . . . . . . . . . . . . . . . . . . . . . . . . 9
6.1.2 Template Source Code . . . . . . . . . . . . . . . . . . . . . 11
1
6.1.3 Template Compiled Code . . . . . . . . . . . . . . . . . . . 12
6.2 Variadic Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
6.3 Post C++11 Examples . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.3.1 Fold Expression . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.3.2 Constraints & Concepts . . . . . . . . . . . . . . . . . . . . 14
6.4 Expansion Statement Syntax Proposal . . . . . . . . . . . . . . . . 14
6.4.1 Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
6.4.2 Expanded Equivalent . . . . . . . . . . . . . . . . . . . . . . 15
2
1 Introduction
This report covers the use of variadic templates in C++. It describes what variadic
templates are, how they can be used, and their implementation details. The
report works through two implementation approaches of a case study in order to
demonstrate how variadic templates can be beneficial to programmers, as well as
how these benefits have improved over time. The report ends with an evaluation
of the approaches and the author’s conclusion on their current & future use.
While it’s necessary to explain fixed count parameter templates in this report,
as well as some of the later language additions that affect variadic templates, this
report will not explicitly detail or focus on other compile-time features like macros,
constant expressions
1
, or compile-time computations (metaprogramming).
Variadic templates allow programmers to write the definition for a class or
function only once, irrespective of the number of type parameters. This definition
is then expanded during compilation to create instances of each class or function
that use the specific combination of types declared elsewhere in the programmers
code. This is called monomorphization and it allows programmers to create generic
data types while maintaining the strongly typed nature of C++. It has been
referred to as ”compile-time polymorphism” for this reason.
Templates were introduced to C++ before the language’s standard was first
formalised by the ISO
2
in 1998 [ISO, 1998]. Variadic templates were added to the
ISO standard in 2011 [ISO, 2011]. Before C++11, the number of parameters for
each template was fixed, so programmers had to manually specify a new template
for each additional parameter, even if the same work was done within each. By
allowing a variable number of parameters in templates, variadic templates make
code easier to maintain by reducing the number of overall templates the program-
mer has to write manually and review.
2 Problem Statement
In order to demonstrate why variadic templates exist in C++ and the problem
they are designed to solve, a case study on a policy-based class is presented below.
The case study demonstrates a non-variadic approach to creating templates with
1
https://en.cppreference.com/w/cpp/language/constexpr
2
International Organization for Standardization - https://iso.org/
1
multiple arguments. The variadic alternative for the case study is presented in the
subsequent ’Solution’ section. The case study intentionally avoids looking at the
std::tuple implementation as many examples already exist for this elsewhere.
The code for this section can be found in appendix ’6.1 Case Study Templates’.
2.1 Case Study: Policy-based ’Lifeform’ Library Class
Imagine a programmer is creating animal management systems for clients such as
zoos, veterinary practices, labs, or shelters. The programmer knows that these
clients care for many forms of life, but they do not know specific details about
the inhabitants. It is known however, that each client has a fixed specialism such
as a lab that only tracks rats & flies, or a shelter looking after only dogs & cats.
It’s decided therefore that the programmer will create a common template library
that can be re-used to match each client’s specifications.
The use of templates for this purpose will allow unused library code to be ig-
nored by compilers, resulting in no run-time overheads and smaller source code
files, as overloading won’t need to be done manually. It will allow the programmer
to streamline their own implementations in the future or to share their library
with clients for them to customize themselves. In both cases, they can easily ask
each client to specify what they want from their system. The following types of
policy were specified for the Lifeform class:
TaxonomyPolicy - specifying the kingdom, phylum, genus, class, order, fam-
ily, and species.
MovementPolicy - specifying methods and descriptions for movement such
as bipedal, quadrupedal, flight, slithering, swimming, gliding, ciliary, et. al.
AppearancePolicy - specifying physical attributes such as width, height,
length, circumference, and counts for items like arms, legs, eyes, ears, and
the like.
FeedingPolicy - specifying what the animal consumes or eats. Herbivorous,
carnivourous, omnivourous, photosynthesizing, etc.
ReproductionPolicy - specifying how the lifeform reproduces (through eggs,
babies, pollination, mitosis, or otherwise) as well as the strategy (intermit-
2
tent, cyclically, or only once).
RestPolicy - how the lifeform rests and sleeps, including duration, REM
cycles, time of day, and hibernation patterns. This might be diurnal, noc-
turnal, not at all (in single cells) and include unihemispheric sleep (where
only half of an animals brain sleeps at once).
Examples of these policies can be found in the appendix subsection ’7.1.1 Policy
Examples’.
As can be seen in the source code (appendix ’7.1.2 Template Source Code’), the
LifeformNV template only inherits one of each type of policy. When defining a
species as generically as possible (such as cat1) this is acceptable, however if the
need arises to be more specific, such as with each type of dog, the programmer
must manually implement policies that combine the specific attributes they require
in order to fit them into the type identifier for the template. This can be seen
in the ShortHairedBrownDog appearance policy. The flexibility that the policy
based design was supposed to provide is restricted.
Prior to C++11, a solution for this was to implement new template classes
with a greater number of policies that allows the library user more flexibility,
though this still has the same limitations as LifeformNV. In LifeformNV2 an
extra appearance policy can be used and in LifeformUnspecified, the user need
not worry about the order or inclusion of specific policy classes, specifying only
the ones they need. They are still limited to a fixed number of classes though, so
the underlying problem has not been fixed as the library user must still manually
create templates with the correct number of type arguments.
2.2 Compiling the Code
Templates are instantiated towards the end of the compilation process, during
phase 8[ISO, 2011]
3
. At this point, preprocessing means that the compiler is
already aware of every data type and member function that is needed by the
application so the generic template definition can be ”duplicated” (expanded),
such that each copy is specialized in a lazy manner (to use only the types and
methods it needs). This lazy expansion reduces the size of the final compiled
application. The template signatures produced by the compiler for this case study
3
see: https://en.cppreference.com/w/cpp/language/translation
p
hases
3
can be seen in appendix ‘7.1.3 Template Compiled Code‘
4
.
3 Solution
The code for this section can be found in appendix ’6.2 Variadic Templates’.
3.1 What are Variadic Templates?
Where ordinary templates give programmers the flexibility to share common code
in classes and functions between instances with different data types, such as a ‘sum‘
function that can accept ‘double‘, ‘float‘, and ‘int‘ types or a class that can store an
attribute as a ‘string‘ or a ‘char‘ array, variadic templates are designed to reduce
technical debt even further by allowing programmers to implement templates that
can take in any number of arguments. As this takes place during compilation, at
a point when the type of each parameter is already known, the code remains type-
safe and errors can be caught early, which may not be the case when using pointers
and type casting to implement similar functionality with run-time polymorphism.
In the animal management system, this is beneficial because an unlimited num-
ber of properties and methods can be combined to represent a new life form for
each client, with as much nuance as needed. Unnecessary policies that would oth-
erwise be specified only to fill the required type parameters can now be omitted.
3.2 Parameter Packs
”Parameter pack” is the name given to the series of template arguments used in
a variadic template. The syntax is similar to ordinary templates, but uses ellipses
(...) to denote the use of expansion within the template. When used to the left of
a parameter (typically with template parameters), this represents the parameter
pack. The inverse of this, when used on the right of a parameter, indicates that
the pack should be expanded.
In order to allow variadic templates to be used as generically as possible, pa-
rameter packs allow the use of zero or more template arguments. At first, this
may seem like a strange idea as without any types the compiler will not know how
to represent the data inside a class or function. However, C++11 also introduced
template aliases that allow programmers to partially or even fully pre-fill the pa-
4
from https://cppinsights.io/
4
rameter pack arguments for easy re-use. This can be seen in the Fly example.
The empty parameter pack is still required so that the compiler knows that the
type it has encounter is still a template and that it should use the alias, rather
than a non-templated type from elsewhere.
Expansion is when an ellipsis follows on from a named parameter pack. The
compiler changes this code such that each element of the pack is inserted in or-
der with commas separating each argument. While not important in function
arguments, where the order of evaluation is undefined, in the animal management
system it means that each policy in the Lifeform class is inherited in this order
too. Library users and policy class designers must take care to avoid ”dominance”
conflicts, whereby an attribute or method in the last inherited policy will be the
one that the compiler refers to.
3.3 Fold Expressions
The pack expansion syntax in C++11 is limited to signatures where a type pa-
rameter is expected, such as when specifying inheritance or function arguments.
It cannot be used inside the body of methods. The C++17 standard solves this
5
with the introduction of a third use for ellipses in variadic templates [ISO, 2017].
A fold expression uses ellipses inside parentheses to introduce associative operator
expansion. This makes it possible to apply joins, reductions, or logical operations
to all of the arguments.
An example that returns a string of policy names for a lifeform is given in
appendix 6.3.1.
3.4 Constraints and concepts
Now that the programmer may use a variable number of policies in their derived
Lifeform classes and operate on them inside methods with fold expressions, it’s
possible that a library user might forget to include a type of policy that is required
by their system. In the animal management system, one such example is the
Taxonomy policies that categorize each animal by kingdom, species, etc. While the
first argument of the parameter pack may be used to indicate through intelligent
code completion that a taxonomy should be included, code that omits this policy
may still compile. To ensure that circumstances like this cannot occur, the latest
5
partially - see Conclusion
5
C++ standard [ISO, 2020] details constraints and concepts.
Concept - A named set of requirements that must evaluate as true (a pred-
icate). These can replace the keywords typename and ‘class‘ in a parameter
pack.
Constraint - The use of concepts with templates.
Constraints are checked at the beginning of the template instantiation process
during compilation (phase 8) and compilers will produce a useful error code to
tell the user when a concept has not been satisfied. Appendix 6.3.2 is an example
of a taxonomy constraint applied to the Lifeform class using parameter pack
expansion.
Figure 1: A constraint error in Visual Studio 2019.
4 Conclusion
From their implementation in C++11, to the addition of folds in C++17 and
constraints & template autos in C++20, variadic templates have matured to a
point where they provide a comprehensive and useful set of constructs for library
authors and users. This report has shown how variadic templates, and accompa-
nying language features, can be used to simplify the work done by programmers
in order to produce flexible, robust libraries that remain type-safe.
The case study in this report started with template variations that had to be
manually created and managed by the programmer implementing the animal man-
agement system. It ended with one templated class in the source code, that can
constrain users to specific policy types and automatically expand repetitive code.
Variadic templates have automated the work previously done by the programmer.
Library authors must take care to constrain their templates whenever necessary
and, in the case of policy-based designs, library users must ensure that their po-
lices do not conflict. Due to their expanding nature, careful design considerations
should be made when using variadic templates so that compiled code doesn’t be-
come bloated; Library authors & users can use constraints and template aliases
when designing their systems to ensure each compiled class is used as efficiently as
possible. For example, this could be done by adding a constraint to the maximum
6
number of arguments in a parameter pack or by defining easily reusable aliases
that standardize parameter packs to reduce the number of unique combinations.
While variadic templates have already proven their value, proposal P1306R1
’Expansion Statements’
6
describes a useful syntax that would implement for...’
expansion inside variadic templates. Similar to the way fold expressions expand
on binary operators, the ellipsis in this case would statically expand the code
inside the loop to unroll it automatically. An example from the proposal is given
in appendix 7.4. The benefit of this is that it effectively allows templates to
make use of run-time code, however as this process would happen unconditionally
during compilation (without the use of preprocessor if directives), normal for
loop instructions like break and continue cannot be applied. For this reason,
I believe confusion about the purpose of the syntax could be avoided by using
expand... instead.
Unfortunately, the P1306R1 proposal was not ratified into the C++20 standard
and doesn’t currently appear in the latest C++23 draft
7
. Including this standard
in C++ after 2023 would result in source code that is cleaner and therefore easier
to read & maintain.
6
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1306r1.pdf
7
https://open-std.org/jtc1/sc22/wg21/docs/papers/2022/n4910.pdf
7
5 References
References
[ISO, 1998] ISO (1998). ISO/IEC 14882:1998 Information Technology Pro-
gramming languages C++.
[ISO, 2011] ISO (2011). ISO/IEC 14882:2011 Information Technology Pro-
gramming languages C++.
[ISO, 2017] ISO (2017). ISO/IEC 14882:2017 Information Technology Pro-
gramming languages C++.
[ISO, 2020] ISO (2020). ISO/IEC 14882:2020 Information Technology Pro-
gramming languages C++.
8
6 Appendices
6.1 Case Study Templates
6.1.1 Policy Examples
More examples can be found in the source files.
1 //Dog Taxonomy
2 struct DogTaxonomy : public MammaliaTaxonomy {
3 std::string Order = "Carnivora";
4 std::string Family = "Canidae";
5 std::string Genus = "Canis";
6 std::string LatinName = "Canis familiaris";
7 };
1 //Cat Taxonomy
2 struct CatTaxonomy : public MammaliaTaxonomy {
3 std::string Order = "Carnivora";
4 std::string Family = "Felidae";
5 std::string Genus = "Felis";
6 std::string LatinName = "Felis catus";
7 };
1 //Quadrupedal, four-legged, Movement
2 struct QuadrupedalMovement : public MovementBase {
3 bool canLope = true; //move with thesame side hand and foot
together,
4 bool canCatWalk = true;//move with opposite hand and foot
together,
5 bool canBound = true; //move with feet first then
6 private:
7 std::string policy_name = "Quadrupedal Movement";
8 };
1 /*
2 * Non-variadic Appearance Polices
3 */
4 struct DogAppearance : AppearanceBase {
5 int tailLength = 10;
9
6 int legs = 4;
7 private:
8 std::string policy_name = "Dog Appearance";
9 };
10
11 struct ShortHaired: DogAppearance {
12 std::string furLength = "Short";
13 };
14
15 struct BrownFur : DogAppearance {
16 std::string furColor = "Brown";
17 };
18
19 struct ShortHairedBrownDog : ShortHaired, BrownFur {
20 };
21
22 struct ShortHairedBlackDog : ShortHaired {
23 std::string furColor = "Black";
24 }
25
26 struct LongHairedDog : DogAppearance {
27 std::string furDescription = "Long, black";
28 };
1 /*
2 * Non - variadic Feeding Policies
3 */
4 struct HerbivoreFeeding :FeedingBase {
5 bool consumesPlants = true;
6 private:
7 std::string policy_name = "Herbivore";
8 };
9
10 struct CarnivoreFeeding :FeedingBase {
11 bool consumesMeat = true;
12 private:
13 std::string policy_name = "Carnivore";
10
14 };
15
16 struct OmnivoreFeeding :FeedingBase {
17 bool consumesPlants, consumesMeat = true;
18 private:
19 std::string policy_name = "Omnivore";
20 };
1 /*
2 * Reproductive Policies
3 */
4
5 struct BabyReproduction :ReproductiveBase {
6 std::string reproductive_type = "Babies";
7 private:
8 std::string policy_name = "Baby Reproduction";
9 };
10
11 struct MammalReproduction :BabyReproduction {
12 std::string reproductive_strategy = "Intermittent";
13 private:
14 std::string policy_name = "Mammal Reproduction";
15 };
6.1.2 Template Source Code
1 //non-variadic lifeforms
2 #define CatNV LifeformNV<CatTaxonomy,
QuadrupedalMovement,CatAppearance,CarnivoreFeeding,
MammalReproduction, DiurnalRest>
,
,
3 #define LongBlackDogNV LifeformNV<DogTaxonomy,
QuadrupedalMovement,LongHairedDog, CarnivoreFeeding,
MammalReproduction, DiurnalRest>
,
,
4 #define ShortBlackDogNV LifeformNV<DogTaxonomy,
QuadrupedalMovement,ShortHairedBlackDog,CarnivoreFeeding,MammalReproduction,
DiurnalRest>
,
,
11
5 #define ShortBrownDogNV LifeformNV<DogTaxonomy,
QuadrupedalMovement,ShortHairedBrownDog,CarnivoreFeeding,MammalReproduction,
DiurnalRest>
,
,
6 #define ShortBrownDogNV2 LifeformNV2<DogTaxonomy,
QuadrupedalMovement,ShortHaired,BrownFur,CarnivoreFeeding,MammalReproduction,
DiurnalRest>
,
,
7
8 CatNV cat1("Cat");
9 LongBlackDogNV dog1("Long Haired, Black Dog");
10 ShortBlackDogNV dog2("Short Haired, Black Dog");
11 ShortBrownDogNV dog3("Short Haired, Brown Dog");
12 ShortBrownDogNV2 dog4("Short Haired, Brown Dog");
6.1.3 Template Compiled Code
1 // the template class
2 template<class TaxonomyPolicy, class MovementPolicy,class
AppearancePolicy, class FeedingPolicy,class
ReproductionPolicy,class RestPolicy>class LifeformNV : public
TaxonomyPolicy, public MovementPolicy,public
AppearancePolicy, public FeedingPolicy,public
ReproductionPolicy, public RestPolicy {
,
,
,
,
,
3 //...
4 }
5
6 //the first specialization made by the compiler
7 template<>
8 class LifeformNV<CatTaxonomy, QuadrupedalMovement,
CatAppearance,CarnivoreFeeding,MammalReproduction, DiurnalRest>
: public CatTaxonomy,public QuadrupedalMovement, public
CatAppearance,public CarnivoreFeeding, public
MammalReproduction, public DiurnalRest
,
,
,
,
9 {
10 //...
11 }
12
13 //the second specialization made by the compiler
12
14 template<>
15 class LifeformNV<DogTaxonomy, QuadrupedalMovement,
LongHairedDog,CarnivoreFeeding, MammalReproduction,
DiurnalRest> : public DogTaxonomy,public QuadrupedalMovement,
public LongHairedDog, public CarnivoreFeeding,public
MammalReproduction, public DiurnalRest
,
,
,
,
16 {
17 //...
18 }
19
20 //the third specialization made by the compiler
21 template<>
22 class LifeformNV<DogTaxonomy, QuadrupedalMovement,
ShortHairedBlackDog,CarnivoreFeeding, MammalReproduction,
DiurnalRest> : public DogTaxonomy,public QuadrupedalMovement,
public ShortHairedBlackDog, public CarnivoreFeeding,public
MammalReproduction, public DiurnalRest
,
,
,
,
23 {
24 //...
25 }
6.2 Variadic Templates
1 //variadic lifeforms - using template aliases
2 template<class ...P>
3 using Fly =
Lifeform<FlyTaxonomy,FlyingMovement,FlyReproduction,P...>,
4
5 //...
6
7 //variadic lifeforms
8 Lifeform<RatTaxonomy, BrownFur, RodentTail> rat("Common Brown
Rat");,
9 // no args necessary here, handled by template alias
10 Fly<> fly1 = Fly<>("Black Fly");
11 Fly<GreenShell, ShinyShell> fly2 = Fly<GreenShell,
ShinyShell>("Green Bottle Fly");,
13
12 }
6.3 Post C++11 Examples
6.3.1 Fold Expression
1 template<class ...Policies>
2 class Lifeform : public Policies... {
3 std::string policy_names() {
4 return ((Policies::policy_name + ",") + ...);
5 }
6 }
6.3.2 Constraints & Concepts
1 //Variadic Lifeform.h
2 template<class ...Policies>
3 //This constraint concept requires that all lifeforms have at least
one taxonomy class,
4 requires(sizeof ... (Policies) == 0 || (std::derived_from<Policies,
TaxonomyBase> || ...)),
5 class Lifeform : public Policies... {
6 //...
7 }
8
9 //inside main()
10 //this won't compile because the bat has no taxonomy
11 Lifeform<NocturnalRest> bat = Lifeform<NocturnalRest>("Bat");
6.4 Expansion Statement Syntax Proposal
6.4.1 Source Code
1 // Accept a variable number of types & arguments
2 // and print each on a new line using their default
3 // string representation
4 auto tup = std::make_tuple(0, 'a', 3.14);
5 for... (auto elem : tup)
6 std::cout << elem << std::endl;
14
6.4.2 Expanded Equivalent
1 auto tup = std::make_tuple(0, 'a', 3.14);
2 {
3 auto elem = std::get<0>(tup);
4 std::cout << elem << std::endl;
5 }
6 {
7 auto elem = std::get<1>(tup);
8 std::cout << elem << std::endl;
9 }
10 {
11 auto elem = std::get<2>(tup);
12 std::cout << elem << std::endl;
13 }
15