C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
58: Variadic Templates
Imagine you're putting together a custom gift basket for a friend. You don't know exactly what's going to go in there—maybe just a bottle of wine, or maybe a candle, some chocolates, a gift card, and a handwritten note. Regardless of how many items you toss in, the "process" is the same: you take an item, place it in the basket, and repeat until you've run out of things to add.
Variadic templates are essentially C++'s way of letting you define a "gift basket" for your functions and classes. Instead of writing ten different versions of a function to handle one, two, three, or ten arguments, you write one template that says, "I'll take whatever number of arguments you give me, of whatever types they happen to be."
Here is how that maps to the code:
- The Basket: This is the
Args...(called a template parameter pack). It represents the collection of all types passed in. - Tossing an item in: This is the
args...(a function parameter pack). It's the actual values being passed. - Processing one by one: This is where we use recursion or "fold expressions" to peel off the first item and handle the rest.
The Parameter Pack Magic
To make this work, we use an ellipsis (...). When it's to the left of a name, it's "packing" the arguments. When it's to the right, it's "unpacking" them.
template <typename... Args>
void logEverything(Args... args) {
// 'Args' is the pack of types
// 'args' is the pack of values
}
If you call logEverything(10, "Error", 3.14), the compiler effectively generates a version of the function where Args is {int, const char*, double}. It's an incredibly powerful way to avoid writing repetitive overloads.
Stopping the Recursive Loop
In the older days of C++11, we had to handle these packs using recursion. You'd write one function to handle the "first" item and then call the function again for the "rest" of the pack. But since you can't recurse forever, you had to write a "base case" function—basically a version of the function that takes zero arguments—to tell the compiler when to stop.
// Base case: stop when there are no more arguments
void printAll() {
std::cout << std::endl;
}
template <typename T, typename... Args>
void printAll(T first, Args... rest) {
std::cout << first << " ";
printAll(rest...); // Unpack the rest and recurse
}
I'll be honest: writing base cases is a chore and it bloats your code. It feels like a workaround rather than a feature.
Cutting Out the Middleman with Fold Expressions
If you're using C++17 or later (which you should be), you can throw the recursion and the base case in the trash. C++ introduced "Fold Expressions," which allow you to apply a binary operator to every element in a pack in a single line.
Look at how much cleaner this is. Instead of two functions and a recursive loop, we can do this:
template <typename... Args>
void printAll(Args... args) {
(std::cout << ... << args) << std::endl;
}
That (... << args) syntax tells the compiler: "Take the << operator and apply it across every single element in the args pack." It's concise, it's faster to compile, and it's much easier to read. I always recommend fold expressions over recursion unless you're stuck on a legacy compiler from a decade ago.
📋 Practical Task
The Multi-Type Arithmetic Aggregator
Your task is to create a template function called sumAll that can take any number of numeric arguments (ints, doubles, floats, etc.) and return their total sum.
Requirements:
- Use a variadic template with a fold expression (C++17 style) to calculate the sum.
- The function should return the resulting sum.
- Test it by calling
sumAllwith a mix of types, for example:sumAll(10, 2.5, 5, 1.1f). - Ensure your code handles the edge case of being called with no arguments (it should return 0).
There are no comments for now.