Variadic Templates
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 67 of 104.
What if you need a function that accepts any number of arguments of any types? Traditional templates require you to specify exactly how many type parameters you need. Variadic templates solve this by allowing templates to accept an arbitrary number of template arguments.
The syntax uses ... (ellipsis) to create a parameter pack:
template <typename... Args>
void printAll(Args... args) {
// Args is a template parameter pack
// args is a function parameter pack
}To process the arguments, you typically use recursion with a base case. The parameter pack expands one argument at a time until none remain:
// Base case: no arguments left
void print() {
std::cout << std::endl;
}
// Recursive case: process first argument, recurse with rest
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Expand remaining arguments
}
print(1, 3.14, "hello", 'x'); // Output: 1 3.14 hello xYou can also use fold expressions (C++17) for simpler operations without explicit recursion:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold expression: adds all arguments
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Output: 10Variadic templates power many standard library features like std::make_unique, std::tuple, and std::function. They enable type-safe functions that work with any combination of argument types and counts.
Challenge
EasyLet's build a flexible logging system that uses variadic templates to handle messages with any number of arguments. You'll create utilities that can concatenate values, count arguments, and print formatted output — all using parameter packs.
You'll organize your code across two files:
Variadic.h: Define your variadic template functions here.Create a
printAllfunction that prints all its arguments separated by spaces, followed by a newline. Use the recursive approach with a base case that prints just a newline, and a recursive case that prints the first argument, a space, then recurses with the remaining arguments.Create a
countArgsfunction that returns the number of arguments passed to it. Usesizeof...to get the size of the parameter pack.Create a
sumfunction that adds all numeric arguments together and returns the result. Use a fold expression with the+operator.Create a
productfunction that multiplies all numeric arguments together and returns the result. Use a fold expression with the*operator.main.cpp: Read four inputs (each on a separate line):- An integer
- A double
- A string
- A character
Demonstrate your variadic templates by:
- Calling
printAllwith all four inputs and printing the result - Printing the count of arguments when calling
countArgswith the integer, double, and character:Argument count: <count> - Calculating the sum of the integer, the double, and the values 10 and 5, then printing:
Sum: <result> - Calculating the product of the integer and the values 2 and 3, then printing:
Product: <result> - Calling
printAllwith just the string (single argument) - Calling
printAllwith no arguments (should print just a newline)
For example, with inputs 5, 2.5, Hello, and X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
Notice how printAll handles any number of arguments of mixed types, from four arguments down to zero. The sum and product functions use fold expressions to elegantly combine all values without explicit recursion. Your countArgs function demonstrates how sizeof... gives you the pack size at compile time.
Cheat sheet
Variadic templates allow templates to accept an arbitrary number of template arguments using the ... (ellipsis) syntax to create a parameter pack:
template <typename... Args>
void printAll(Args... args) {
// Args is a template parameter pack
// args is a function parameter pack
}Process arguments using recursion with a base case:
// Base case: no arguments left
void print() {
std::cout << std::endl;
}
// Recursive case: process first argument, recurse with rest
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Expand remaining arguments
}
print(1, 3.14, "hello", 'x'); // Output: 1 3.14 hello xUse sizeof... to get the number of arguments in a parameter pack:
template <typename... Args>
size_t countArgs(Args... args) {
return sizeof...(args);
}Use fold expressions (C++17) for simpler operations without explicit recursion:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold expression: adds all arguments
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Output: 10Fold expressions work with other operators like multiplication:
template <typename... Args>
auto product(Args... args) {
return (args * ...); // Multiplies all arguments
}Try it yourself
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// Read inputs
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: Call printAll with all four inputs
// TODO: Print argument count using countArgs with intVal, doubleVal, and charVal
// Format: "Argument count: <count>"
// TODO: Calculate and print sum of intVal, doubleVal, 10, and 5
// Format: "Sum: <result>"
// TODO: Calculate and print product of intVal, 2, and 3
// Format: "Product: <result>"
// TODO: Call printAll with just the string
// TODO: Call printAll with no arguments
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container