Skip to content

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.

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 pack
template<typename... Args>
void function(Args... args);
// Can be used with zero or more arguments
function(); // OK
function(1); // OK
function(1, 2.0); // OK
function(1, 2.0, "three"); // OK
#include <iostream>
// Base case: no more arguments
void print() {
std::cout << "\n";
}
// Recursive case: print first, then rest
template<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;
}
#include <iostream>
// Base case: single element
template<typename T>
T sum(T value) {
return value;
}
// Recursive: first + sum of rest
template<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;
}
#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;
}
#include <iostream>
#include <utility>
#include <array>
// C++14 index_sequence
template<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_sequence
template<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;
}
#include <iostream>
// Binary fold
template<typename... Args>
auto sum(Args... args) {
return (... + args); // ((1 + 2) + 3) + 4
}
// Unary fold
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args); // (((cout << 1) << 2) << 3)
}
// With separator
template<typename... Args>
void printWithSep(Args... args) {
((std::cout << args << " "), ...);
}
// Binary fold with custom operator
template<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;
}
#include <iostream>
#include <tuple>
// Access element by index
template<size_t I, typename T>
struct TupleElement {
T value;
};
template<typename... Args>
class Tuple;
// Base case: empty tuple
template<>
class Tuple<> {};
// Recursive case
template<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 instead
void 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;
}
#include <iostream>
// Type list
template<typename... Types>
struct TypeList {};
using MyTypes = TypeList<int, double, char>;
// Get type by index
template<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;
}
#include <iostream>
#include <variant>
// Base visitor
template<typename... Types>
struct BaseVisitor {};
// Visitable base
template<typename Derived, typename... Types>
struct Visitable {
void accept(BaseVisitor<Types...>&) {}
};
// Concrete visitors
template<typename T>
struct PrintVisitor {
void operator()(const T& value) {
std::cout << value << "\n";
}
};
// Using with std::variant
using 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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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;
}
#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;
}
  • Variadic templates use typename... Args for 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