Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A variadic parameter is a language mechanism that allows a function, template, or macro to accept an arbitrary number of arguments. C++ supports two distinct implementations: compile-time variadic templates (introduced in C++11) and runtime C-style variadic functions.

Variadic Templates (Parameter Packs)

Variadic templates utilize an ellipsis (...) to declare a parameter pack. The compiler resolves these at compile time, ensuring strict type safety and generating specific function signatures for each unique invocation. There are two distinct types of parameter packs:
  1. Template parameter pack: Represents zero or more template parameters (types, non-types, or templates).
  2. Function parameter pack: Represents zero or more function parameters.
// 'Args' is a template parameter pack
// 'args' is a function parameter pack
template <typename... Args>
void evaluate(Args... args) {
    // Implementation
}

Pack Expansion

Parameter packs cannot be indexed or accessed directly; they must be expanded. The compiler expands the pack by replacing it with a comma-separated list of its instantiated elements. Historically, this required recursive template instantiation, peeling off one argument at a time until a base case was reached.
// Base case for recursive expansion
void expand() {}

// Recursive expansion
template <typename T, typename... Rest>
void expand(T first, Rest... rest) {
    // 'first' is isolated; 'rest...' is expanded in the recursive call
    expand(rest...); 
}

Perfect Forwarding

When a function parameter pack is declared as a forwarding reference (Args&&...), it can be combined with std::forward to perfectly forward an arbitrary number of arguments. The expansion std::forward<Args>(args)... preserves the exact value category (lvalue or rvalue) and const/volatile qualifiers of every argument in the pack.
#include <utility>

template <typename... Args>
void forward_wrapper(Args&&... args) {
    // Expands to: target(std::forward<T1>(arg1), std::forward<T2>(arg2), ...)
    target_function(std::forward<Args>(args)...);
}

Fold Expressions (C++17)

C++17 introduced fold expressions, which allow a parameter pack to be reduced over a binary operator without recursive instantiation. The compiler expands the expression directly into a sequence of operations. A critical language rule applies to empty parameter packs: unary folds over most operators (including +, -, *, /) are ill-formed and will trigger a compilation error if the pack is empty. Only &&, ||, and , have default values for empty unary folds. To safely handle potentially empty packs with other operators, a binary fold must be used to provide an explicit initial value.
template <typename... Args>
auto compute_sum(Args... args) {
    // Binary left fold: handles empty packs by providing an initial value (0)
    // Expands to: (((0 + arg1) + arg2) + ...)
    return (0 + ... + args); 
}

The sizeof... Operator

The sizeof... operator queries the number of elements contained within a parameter pack. It evaluates at compile time and returns a std::size_t constant expression.
template <typename... Args>
constexpr std::size_t get_pack_size() {
    return sizeof...(Args);
}

C-Style Variadic Functions

Inherited from C, this legacy mechanism uses the ellipsis (...) at the end of a function parameter list. It bypasses C++ compile-time type checking and relies on runtime stack manipulation via macros defined in the <cstdarg> header.
#include <cstdarg>

// Requires at least one named parameter to anchor the stack frame
void legacy_variadic(int count, ...) {
    va_list args;
    va_start(args, count); // Initialize the argument pointer

    for (int i = 0; i < count; ++i) {
        // The type must be explicitly known and passed to va_arg
        int value = va_arg(args, int); 
    }

    va_end(args); // Clean up the argument pointer
}

Mechanics and Limitations

  • Type Erasure: The compiler does not enforce type safety for arguments passed to the ellipsis. The receiving function must deduce types implicitly (e.g., via a format string) or assume a specific type.
  • Default Argument Promotions: Arguments passed through C-style variadics undergo implicit promotion. For example, float is promoted to double, and narrow integer types (char, short) are promoted to int or unsigned int.
  • Object Restrictions: Passing non-trivial class types (e.g., std::string, std::vector) to an ellipsis is conditionally-supported with implementation-defined semantics (since C++11), or results in undefined behavior (pre-C++11). The C++ standard does not guarantee that constructors or destructors will be invoked correctly through this mechanism.
Master C++ with Deep Grasping Methodology!Learn More