đ Advanced C++ Templates: The Magic Toolbox
Imagine you have a magic toolbox. Instead of having separate tools for each job, you have ONE tool that transforms into whatever you need. Thatâs what Advanced Templates do in C++!
đŻ What Youâll Master
- Variadic Templates â Accept any number of ingredients
- Fold Expressions â Combine ingredients automatically
- SFINAE â Smart tool selection
- Concepts â Clear instruction labels
- Requires Clauses â Safety checks before use
đł The Universal Analogy: The Magic Kitchen
Think of C++ templates as a magic kitchen:
- Your recipe (template) works with ANY ingredients
- You can add 1, 5, or 100 ingredients (variadic)
- The kitchen auto-combines them (fold expressions)
- It refuses wrong ingredients quietly (SFINAE)
- It has clear labels on what goes where (concepts)
- It checks ingredients before cooking (requires)
1ïžâŁ Variadic Templates
The Story
Imagine youâre making a smoothie. Sometimes you want 2 fruits. Sometimes 10. You donât want 10 different blenders!
Variadic templates = ONE blender that accepts ANY number of fruits.
The Magic Words
template<typename... Fruits>
void blend(Fruits... fruits);
The ... is the magic! It means âany number of things.â
Simple Example
// Print anything, any amount!
template<typename... Args>
void printAll(Args... args) {
// We'll combine these next!
}
// Use it:
printAll(1, "hello", 3.14);
printAll("just one");
printAll(1, 2, 3, 4, 5);
Whatâs Happening?
graph TD A["printAll called"] --> B{How many args?} B --> C["1 arg? OK!"] B --> D["3 args? OK!"] B --> E["100 args? OK!"] C --> F["Template expands"] D --> F E --> F
Key Terms
| Term | Meaning |
|---|---|
typename... |
âZero or more typesâ |
Args... |
A parameter pack |
args... |
Expanding the pack |
2ïžâŁ Fold Expressions
The Story
You have 5 numbers. You want to add them ALL. Before C++17, this was hard. Now? One line.
Fold expressions = The kitchen automatically mixes all ingredients.
The Four Fold Types
// LEFT fold: ((1 + 2) + 3) + 4
(... + args)
// RIGHT fold: 1 + (2 + (3 + 4))
(args + ...)
// LEFT fold with start: ((0 + 1) + 2) + 3
(init + ... + args)
// RIGHT fold with start: 1 + (2 + (3 + 0))
(args + ... + init)
Simple Example
template<typename... Numbers>
auto addAll(Numbers... nums) {
return (... + nums); // Magic!
}
// Use it:
int sum = addAll(1, 2, 3, 4, 5);
// Result: 15
Visual: How Folding Works
graph TD A["#40;... + nums#41;"] --> B["nums = 1, 2, 3"] B --> C["Step 1: 1 + 2 = 3"] C --> D["Step 2: 3 + 3 = 6"] D --> E["Result: 6"]
More Fold Magic
// Print all with commas
template<typename... Args>
void printComma(Args... args) {
((std::cout << args << ", "), ...);
}
// Check if ALL are true
template<typename... Bools>
bool allTrue(Bools... b) {
return (... && b);
}
3ïžâŁ SFINAE
The Story
SFINAE = âSubstitution Failure Is Not An Errorâ
Imagine a restaurant with two doors:
- Door A: âCustomers with reservationsâ
- Door B: âWalk-ins welcomeâ
If you donât have a reservation, Door A doesnât yell at you. It just⊠doesnât open. You try Door B instead!
How It Works
// This only works for numbers
template<typename T>
typename std::enable_if<
std::is_arithmetic<T>::value, T
>::type
double_it(T x) {
return x * 2;
}
// This works for strings
template<typename T>
typename std::enable_if<
!std::is_arithmetic<T>::value, T
>::type
double_it(T x) {
return x + x; // Concatenate!
}
The Magic Flow
graph TD A["Call double_it"] --> B{Is it a number?} B -->|Yes| C["First version"] B -->|No| D{Is it a string?} D -->|Yes| E["Second version"] D -->|No| F["Compile error"] C --> G["x * 2"] E --> H["x + x"]
Simpler SFINAE with void_t
// Check if T has .size()
template<typename T, typename = void>
struct has_size : std::false_type {};
template<typename T>
struct has_size<T,
std::void_t<decltype(
std::declval<T>().size()
)>
> : std::true_type {};
Why âNot An Errorâ?
| Situation | Without SFINAE | With SFINAE |
|---|---|---|
| Wrong type tried | đ„ Error! | đ€· Try next |
| No match found | đ„ Error! | đ„ Error! |
| Match found | â Works | â Works |
4ïžâŁ Concepts (C++20)
The Story
SFINAE works but itâs ugly. Like writing âNO DOGS ALLOWEDâ in tiny print on page 47.
Concepts = Big, clear signs at the entrance! đȘ§
Defining a Concept
// "Addable" means: can use + operator
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
Using Concepts
// Method 1: After template
template<typename T>
requires Addable<T>
T add(T a, T b) {
return a + b;
}
// Method 2: In template
template<Addable T>
T add(T a, T b) {
return a + b;
}
// Method 3: Shorthand
Addable auto add(Addable auto a,
Addable auto b) {
return a + b;
}
Built-in Concepts
#include <concepts>
std::integral<T> // int, long, etc.
std::floating_point<T> // float, double
std::same_as<T, U> // T is exactly U
std::convertible_to<From, To>
std::derived_from<Child, Parent>
SFINAE vs Concepts
graph LR A["Old Way: SFINAE"] --> B["enable_if<is_integral<T>>"] C["New Way: Concepts"] --> D["requires std::integral<T>"] B --> E["Hard to read đ”"] D --> F["Easy to read đ"]
5ïžâŁ Requires Clauses
The Story
Concepts say WHAT something must be. Requires clauses say exactly WHAT IT MUST DO.
Like a checklist:
- â Can it be copied?
- â Can it be printed?
- â
Does it have
.size()?
Basic Syntax
template<typename T>
requires std::is_integral_v<T>
void onlyIntegers(T x) { }
Compound Requirements
template<typename T>
requires std::integral<T> &&
(sizeof(T) >= 4)
void bigIntegers(T x) { }
The requires Expression
template<typename T>
concept Printable = requires(T x) {
// Simple: this must compile
std::cout << x;
// Compound: must return bool
{ x.empty() } -> std::same_as<bool>;
// Nested: must satisfy concept
requires std::movable<T>;
};
Requires-Requires (Yes, Twice!)
template<typename T>
requires requires(T x) {
x.push_back(1);
}
void needsPushBack(T& container) {
container.push_back(42);
}
First requires = âI have a requirementâ
Second requires = âHereâs what Iâm checkingâ
đź Putting It All Together
Complete Example
#include <concepts>
#include <iostream>
// Concept: must be printable
template<typename T>
concept Printable = requires(T x) {
std::cout << x;
};
// Variadic + Concepts + Fold
template<Printable... Args>
void printAll(Args... args) {
((std::cout << args << " "), ...);
std::cout << "\n";
}
int main() {
printAll(1, 2.5, "hello");
// Output: 1 2.5 hello
}
The Complete Flow
graph TD A["Write Template"] --> B["Add Concept"] B --> C["Use Variadic Pack"] C --> D["Fold to Combine"] D --> E["Clean, Safe Code!"]
đ§ Quick Reference
| Feature | Purpose | Example |
|---|---|---|
typename... |
Any number of types | template<typename... T> |
(... + x) |
Combine with operator | return (... + nums); |
enable_if |
Old-style constraints | SFINAE |
concept |
Named constraint | concept Addable = ... |
requires |
Inline constraint | requires std::integral<T> |
đĄ Remember
- Variadic = Accept anything, any amount
- Fold = Combine automatically
- SFINAE = Silent failure, try next
- Concepts = Clear, readable rules
- Requires = Precise checks
đ You Did It!
You now understand the most powerful features of C++ templates!
These arenât just âadvancedâ â theyâre the tools that make modern C++ libraries possible. Every time you use std::vector, std::optional, or any modern library, these features are working behind the scenes.
Go forth and template everything! đ
