Variadic_templates
Variadic Templates
Section titled “Variadic Templates”Variadic templates allow functions and classes to accept any number of template arguments. This powerful feature enables type-safe variadic functions and metaprogramming.
Introduction
Section titled “Introduction”Variadic templates use typename... Args or class... Args to represent a pack of template parameters that can expand to zero or more arguments.
// Args is a template parameter packtemplate<typename... Args>void function(Args... args);
// Can be used with zero or more argumentsfunction(); // OKfunction(1); // OKfunction(1, 2.0); // OKfunction(1, 2.0, "three"); // OKBasic Variadic Function
Section titled “Basic Variadic Function”Print Function
Section titled “Print Function”#include <iostream>
// Base case: no more argumentsvoid print() { std::cout << "\n";}
// Recursive case: print first, then resttemplate<typename T, typename... Args>void print(T first, Args... args) { std::cout << first; if (sizeof...(args) > 0) { std::cout << ", "; } print(args...); // Recurse with remaining args}
int main() { print(1); print(1, 2); print(1, 2.5, "hello"); print(1, 2.5, "hello", 'c');
return 0;}Sum Function
Section titled “Sum Function”#include <iostream>
// Base case: single elementtemplate<typename T>T sum(T value) { return value;}
// Recursive: first + sum of resttemplate<typename T, typename... Args>T sum(T first, Args... args) { return first + sum(args...);}
int main() { std::cout << sum(1, 2, 3, 4, 5) << "\n"; // 15 std::cout << sum(1.5, 2.5, 3.0) << "\n"; // 7.0 std::cout << sum(1, 2.5) << "\n"; // 3.5
return 0;}Parameter Pack Operations
Section titled “Parameter Pack Operations”sizeof… Operator
Section titled “sizeof… Operator”#include <iostream>
template<typename... Args>void showCount(Args... args) { std::cout << "Argument count: " << sizeof...(Args) << "\n"; std::cout << "Pack size: " << sizeof...(args) << "\n";}
int main() { showCount(); // 0 showCount(1); // 1 showCount(1, 2, 3); // 3 showCount(1, 2.0, "a", 'c'); // 4
return 0;}Index Sequence (C++14)
Section titled “Index Sequence (C++14)”#include <iostream>#include <utility>#include <array>
// C++14 index_sequencetemplate<size_t... Is>struct index_sequence {};
template<size_t N, size_t... Is>struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> {};
template<size_t... Is>struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
// Using index_sequencetemplate<typename T, size_t N>void printArray(const std::array<T, N>& arr, std::index_sequence<Is...>) { ((std::cout << arr[Is] << " "), ...);}
template<typename T, size_t N>void printArray(const std::array<T, N>& arr) { printArray(arr, std::make_index_sequence<N>{});}
int main() { std::array<int, 5> arr = {1, 2, 3, 4, 5}; printArray(arr); // 1 2 3 4 5
return 0;}C++17 Fold Expressions
Section titled “C++17 Fold Expressions”#include <iostream>
// Binary foldtemplate<typename... Args>auto sum(Args... args) { return (... + args); // ((1 + 2) + 3) + 4}
// Unary foldtemplate<typename... Args>void print(Args... args) { (std::cout << ... << args); // (((cout << 1) << 2) << 3)}
// With separatortemplate<typename... Args>void printWithSep(Args... args) { ((std::cout << args << " "), ...);}
// Binary fold with custom operatortemplate<typename... Args>bool allTrue(Args... args) { return (... && args); // ((true && true) && false) = false}
int main() { std::cout << sum(1, 2, 3, 4, 5) << "\n"; // 15
print(1, 2, 3); // 123
printWithSep(1, 2, 3); // 1 2 3
std::cout << allTrue(true, true, false) << "\n"; // 0
return 0;}Variadic Class Templates
Section titled “Variadic Class Templates”Simple Tuple
Section titled “Simple Tuple”#include <iostream>#include <tuple>
// Access element by indextemplate<size_t I, typename T>struct TupleElement { T value;};
template<typename... Args>class Tuple;
// Base case: empty tupletemplate<>class Tuple<> {};
// Recursive casetemplate<typename T, typename... Args>class Tuple<T, Args...> : public TupleElement<sizeof...(Args), T>, public Tuple<Args...> {public: T& get() { return TupleElement<sizeof...(Args), T>::value; } const T& get() const { return TupleElement<sizeof...(Args), T>::value; }
template<size_t I> auto& get() { return Tuple<Args...>::template get<I>(); }};
// Using std::tuple insteadvoid useStdTuple() { auto t = std::make_tuple(1, 2.5, "hello");
std::get<0>(t); // 1 std::get<1>(t); // 2.5 std::get<2>(t); // "hello"}
int main() { useStdTuple(); return 0;}Type List
Section titled “Type List”#include <iostream>
// Type listtemplate<typename... Types>struct TypeList {};
using MyTypes = TypeList<int, double, char>;
// Get type by indextemplate<size_t I, typename... Types>struct GetType;
template<typename T, typename... Types>struct GetType<0, TypeList<T, Types...>> { using type = T;};
template<size_t I, typename T, typename... Types>struct GetType<I, TypeList<T, Types...>> { using type = typename GetType<I - 1, TypeList<Types...>>::type;};
int main() { using T = GetType<1, TypeList<int, double, char>>::type; // T is double
static_assert(std::is_same_v<T, double>);
return 0;}Variadic Inheritance
Section titled “Variadic Inheritance”Visitor Pattern
Section titled “Visitor Pattern”#include <iostream>#include <variant>
// Base visitortemplate<typename... Types>struct BaseVisitor {};
// Visitable basetemplate<typename Derived, typename... Types>struct Visitable { void accept(BaseVisitor<Types...>&) {}};
// Concrete visitorstemplate<typename T>struct PrintVisitor { void operator()(const T& value) { std::cout << value << "\n"; }};
// Using with std::variantusing MyVariant = std::variant<int, double, std::string>;
struct VariantPrinter { void operator()(int i) const { std::cout << "int: " << i << "\n"; } void operator()(double d) const { std::cout << "double: " << d << "\n"; } void operator()(const std::string& s) const { std::cout << "string: " << s << "\n"; }};
int main() { MyVariant v1 = 42; MyVariant v2 = 3.14; MyVariant v3 = std::string("hello");
std::visit(VariantPrinter{}, v1); std::visit(VariantPrinter{}, v2); std::visit(VariantPrinter{}, v3);
return 0;}Perfect Forwarding
Section titled “Perfect Forwarding”Variadic Forwarding
Section titled “Variadic Forwarding”#include <iostream>#include <memory>
class Widget {public: Widget(int a, double b) { std::cout << "Widget(" << a << ", " << b << ")\n"; }};
template<typename T, typename... Args>std::unique_ptr<T> makeUnique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}
int main() { auto w = makeUnique<Widget>(1, 2.5);
// Equivalent to: auto w2 = std::make_unique<Widget>(1, 2.5);
return 0;}Wrapper Function
Section titled “Wrapper Function”#include <iostream>#include <functional>
void process(int a, int b, int c) { std::cout << a << " " << b << " " << c << "\n";}
template<typename F, typename... Args>void wrapper(F f, Args... args) { // Perfect forward through wrapper f(std::forward<Args>(args)...);}
int main() { wrapper(process, 1, 2, 3); // 1 2 3
return 0;}Type Traits with Variadic Templates
Section titled “Type Traits with Variadic Templates”Are All Types the Same?
Section titled “Are All Types the Same?”#include <type_traits>
template<typename... Args>struct all_same : std::true_type {};
template<typename T, typename U, typename... Args>struct all_same<T, U, Args...> : std::integral_constant<bool, std::is_same_v<T, U> && all_same<U, Args...>::value> {};
int main() { static_assert(all_same<int, int, int>::value); // true static_assert(!all_same<int, int, double>::value); // false
return 0;}First Type
Section titled “First Type”#include <type_traits>
template<typename... Args>struct first_type;
template<typename T, typename... Args>struct first_type<T, Args...> { using type = T;};
int main() { static_assert( std::is_same_v<first_type<int, double, char>::type, int> );
return 0;}Practical Examples
Section titled “Practical Examples”Logger
Section titled “Logger”#include <iostream>#include <sstream>#include <chrono>
class Logger {public: enum Level { DEBUG, INFO, WARN, ERROR };
static void log(Level level, const char* message) { std::cout << "[" << level << "] " << message << "\n"; }
template<typename... Args> static void log(Level level, const char* fmt, Args... args) { // Simple implementation - just print std::cout << "[" << level << "] "; ((std::cout << args), ...); std::cout << "\n"; }};
int main() { Logger::log(Logger::INFO, "User", " logged in at ", 42); // Output: [1] User logged in at 42
return 0;}Signal/Slot System
Section titled “Signal/Slot System”#include <iostream>#include <vector>#include <functional>
template<typename... Args>class Signal { std::vector<std::function<void(Args...)>> slots_;
public: void connect(std::function<void(Args...)> slot) { slots_.push_back(slot); }
void emit(Args... args) { for (auto& slot : slots_) { slot(args...); } }};
void handler(int a, int b) { std::cout << "Handler: " << a << ", " << b << "\n";}
int main() { Signal<int, int> signal; signal.connect(handler); signal.connect([](int a, int b) { std::cout << "Lambda: " << a * b << "\n"; });
signal.emit(3, 4); // Both handlers called
return 0;}Key Takeaways
Section titled “Key Takeaways”- Variadic templates use
typename... Argsfor parameter packs - Use recursion or fold expressions (C++17) to process packs
sizeof...(Args)gives pack size- Enable perfect forwarding with
std::forward - Essential for type-safe variadic functions and tuples
- Fold expressions simplify common patterns in C++17