Variadic template
In computer programming, variadic templates or variation generics are templates that take a variable number of type parameters. Variadic templates are supported by C++ (since the C++11 standard), and the D programming language. While other languages, such as Java or C# offer support for type-safe variadic functions, they do not offer the variadic generics support like that of C++ and D.
In computer programming, variadic templates or variation generics are templates that take a variable number of type parameters.
Variadic templates are supported by C++ (since the C++11 standard), and the D programming language. While other languages, such as Java or C# offer support for type-safe variadic functions, they do not offer the variadic generics support like that of C++ and D.
C++
[edit]Prior to the introduction of variadic templates to C++, variadic functions could only be implemented using va_list or variadic macros from C, which lack type safety. The variadic template feature of C++ was designed by Douglas Gregor and Jaakko Järvi [1][2] and was later standardized in C++11. Prior to C++11, templates (classes and functions) could only take a fixed number of arguments, which had to be specified when a template was first declared. C++11 allows template definitions to take an arbitrary number of arguments of any type. Thus, the only way to get type-safe variadic functions is thus through variadic templates over functions, as C++ lacks non-templated variadic functions like those of Java and C#.
// takes zero or more arguments
template <typename... Ts>
class Tuple;
The above template class Tuple (representing a product type or tuple) will take any number of typenames as its template parameters. Here, an instance of the above template class is instantiated with three type arguments:
using String = std::string;
template <typename T>
using Vector = std::vector<T>;
template <typename K, typename V>
using TreeMap = std::map<K, V>;
Tuple<int, Vector<int>, TreeMap<String, Vector<int>>> someInstanceName;
The number of arguments can be zero, so Tuple<> someInstanceName; will also work.
If the variadic template should only allow a positive number of arguments, then this definition can be used:
// takes one or more arguments
template <typename First, typename... Rest>
class Tuple;
A common implementation of a tuple uses recursive decomposition like so:
// Forward declaration
template <typename... Ts>
class Tuple;
// Empty case
template <>
class Tuple<> {};
// Recursive case
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> {
private:
Head head;
Tuple<Tail...> tail;
public:
Tuple() = default;
explicit Tuple(const Head& head, const Tail&... tail):
head{head}, tail{tail...} {}
// ...
};
Variadic templates may also apply to functions, thus not only providing a type-safe add-on to variadic functions (such as printf()), but also allowing a function called with printf-like syntax to process non-trivial objects.
using std::string_view;
template <typename... Args>
void myPrintf(string_view fmt, Args... parameters);
The ellipsis (...) operator has two roles. When it occurs to the left of the name of a parameter, it declares a parameter pack. Using the parameter pack, the user can bind zero or more arguments to the variadic template parameters. Parameter packs can also be used for non-type parameters. By contrast, when the ellipsis operator occurs to the right of a template or function call argument, it unpacks the parameter packs into separate arguments, like the args... in the body of printf below. In practice, the use of an ellipsis operator in the code causes the whole expression that precedes the ellipsis to be repeated for every subsequent argument unpacked from the argument pack, with the expressions separated by commas.
The fixed-point combinator is often implemented using variadic template arguments (note that [](auto&&... args) is equivalent to []<template... Args>(Args&&... args)):
auto fix = [](auto f) {
return [f](auto&&... args) -> decltype(auto) {
return f(f, std::forward<decltype(args)>(args)...);
};
};
auto factorial = fix([](auto self, long n) -> long {
return n == 0 ? 1 : n * self(self, n - 1);
});
std::println("5! = {}", factorial(5)); // prints 120
The use of variadic templates is often recursive. The variadic parameters themselves are not readily available to the implementation of a function or class. Therefore, the typical mechanism for defining something like a C++11 variadic printf() replacement would be as follows:
using std::runtime_error;
// base case
void myPrintf(const char s[]) {
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
} else {
throw runtime_error("Invalid format string: missing arguments");
}
}
std::println("{}", *s++);
}
}
// recursive
template <typename T, typename... Args>
void myPrintf(const char s[], T value, Args... args) {
while (*s) {
if (*s == '%') {
if (*(s + 1) != '%') {
// pretend to parse the format: only works on 2-character format strings ( %d, %f, etc ); fails with %5.4f
s += 2;
// print the value
std::println("{}", value);
// called even when *s is 0 but does nothing in that case (and ignores extra arguments)
myPrintf(s, args...);
return;
}
++s;
}
std::println("{}", *s++);
}
}
This is a recursive template. Notice that the variadic template version of myPrintf() calls itself, or (in the event that args... is empty) calls the base case.
There is no simple mechanism to iterate over the values of the variadic template. However, there are several ways to translate the argument pack into a single argument that can be evaluated separately for each parameter. Usually this will rely on function overloading, or — if the function can simply pick one argument at a time — using a dumb expansion marker:
template <typename... Args>
inline void pass(Args&&... args) {
// ...
}
which can be used as follows:
template <typename... Args>
inline void expand(Args&&... args) {
pass(someFunction(args)...);
}
expand(42, "answer", true);
which will expand to something like:
pass(someFunction(arg1), someFunction(arg2), someFunction(arg3) /* etc... */ );
The use of this "pass" function is necessary, since the expansion of the argument pack proceeds by separating the function call arguments by commas, which are not equivalent to the comma operator. Therefore, someFunction(args)...; will never work. Moreover, the solution above will only work when the return type of someFunction is not void. Furthermore, the someFunction calls will be executed in an unspecified order, because the order of evaluation of function arguments is undefined. To avoid the unspecified order, brace-enclosed initializer lists can be used, which guarantee strict left-to-right order of evaluation. An initializer list requires a non-void return type, but the comma operator can be used to yield 1 for each expansion element.
struct Pass {
template <typename... T>
explicit Pass(T... args) {
// ...
}
};
Pass{(someFunction(args), 1)...};
Instead of executing a function, a lambda expression may be specified and executed in place, which allows executing arbitrary sequences of statements in-place.
Pass{([&]() -> void { std::println("{}", args); }(), 1)...};
However, in this particular example, a lambda function is not necessary. A more ordinary expression can be used instead:
Pass{(std::println("{}", args), 1)...};
In C++17, these can be rewritten using fold expressions on the comma operator:
([&]() -> void {
std::println("{}", args);
}(), ...);
((std::println("{}", args)), ...);
Another way is to use overloading with "termination versions" of functions. This is more universal, but requires a bit more code and more effort to create. One function receives one argument of some type and the argument pack, whereas the other receives neither. (If both had the same list of initial parameters, the call would be ambiguous, as a variadic parameter pack alone cannot disambiguate a call.) For example:
void func() {
// termination version
}
template <typename First, typename... Args>
void func(const First& first, const Args&&... args) {
process(first);
func(args...); // note: first does not appear here!
}
If args... contains at least one argument, it will redirect to the second version — a parameter pack can be empty, in which case it will simply redirect to the termination version, which will do nothing.
Variadic templates can also be used in an exception specification, a base class list, or the initialization list of a constructor. For example, a class can specify the following:
template <typename... BaseClasses>
class ClassName : public BaseClasses... {
public:
explicit ClassName(BaseClasses&&... bases):
BaseClasses(bases)... {}
};
The unpack operator will replicate the types for the base classes of ClassName, such that this class will be derived from each of the types passed in. Also, the constructor must take a reference to each base class, so as to initialize the base classes of ClassName.
With regard to function templates, the variadic parameters can be forwarded. When combined with universal references (see above), this allows for perfect forwarding:
template <typename T>
using SharedPtr = std::shared_ptr<T>;
template <typename T>
struct SharedPtrAllocator {
template <typename... Args>
SharedPtr<T> construct(Args&&... params) {
return SharedPtr<T>(new T(std::forward<Args>(params)...));
}
};
This unpacks the argument list into the constructor of TypeToConstruct. The std::forward<Args>(params) syntax perfectly forwards arguments as their proper types, even with regard to rvalue-ness, to the constructor. The unpack operator will propagate the forwarding syntax to each parameter. This particular factory function automatically wraps the allocated memory in a std::shared_ptr for a degree of safety with regard to memory leaks.
Additionally, the number of arguments in a template parameter pack can be determined as follows:
template <typename... Args>
struct SomeStruct {
static const int size = sizeof...(Args);
};
The expression SomeStruct<Type1, Type2>::size will yield 2, while SomeStruct<>::size will give 0.
Variadic friends
[edit]In C++26, variadic friends were added to the language, which rely on variadic templates.
import std;
template <typename... Friends>
class ClassWithFriends {
private:
int secretValue;
friend Friends...;
public:
explicit ClassWithFriends(int secret):
secretValue{secret} {}
};
class A {
public:
void readSecret(const class ClassWithFriends<A, B>& instance) const {
std::println("Inspecting secret value from A: {}", instance.secretValue);
}
};
class B {
public:
void readSecret(const class ClassWithFriends<A, B>& instance) const {
std::println("Inspecting secret value from B: {}", instance.secretValue);
}
};
int main(int argc, char* argv[]) {
ClassWithFriends<A, B> secretHolder(135);
A a;
B b;
a.readSecret(secretHolder);
b.readSecret(secretHolder);
}
Pack indexing
[edit]Pack indexing was introduced to C++ in C++26, which retrieves a type or value at a specified index of a pack with a less[further explanation needed] recursive syntax, while the index is required to be a constant expression.[3] The syntax of pack indexing is id-expression...[expression].
import std;
using std::index_sequence;
using std::index_sequence_for;
using std::tuple;
// This function, reversed(), takes any number of arguments and returns a
// tuple<Ts...> containing the same arguments in reversed order with each
// T in Ts... decayed as decay_t<T>.
template <typename... Ts>
requires (sizeof...(Ts) > 0)
constexpr auto reversed(Ts&&... args) noexcept {
constexpr size_t MAX_INDEX_SIZE = sizeof...(args) - 1;
auto reverser = [&args...]<size_t... Is>(index_sequence<Is...>) {
auto reverseForOneIndex = [&args...]<size_t I> {
return args...[MAX_INDEX_SIZE - I];
};
return std::make_tuple(reverseForOneIndex.template operator()<Is>()...);
};
return reverser(index_sequence_for<Ts...>{});
}
int main() {
// type of result:
// tuple<char, long double, double, float, const char*, int>
constexpr tuple result = reversed(13, "hello", 2.718f, 3.14, 9.9375L, 'X');
static_assert(std::get<0>(result) == 'X');
}
Iterating over type and parameter packs
[edit]C++26 adds expansion statements, declared with template for (init_stmtopt; for-range-declaration : expansion-initializer) compound-stmt to iterate over std::tuple<Ts...>, collection types like std::array<T, N> and std::vector<T>, and type/parameter packs (Ts... args).[4]
import std;
void printAllObjects(auto&&... args) {
template for (auto&& arg : {args...}) {
std::print("{}", arg);
}
}
int main() {
printAllObjects("13 + 31 = ", 44, "\nHello, world!\n", 3.14, '\n', 2.718f, '\n', 735.9572L, false);
return 0;
}
Expansion statements allow for break and continue control flow statements, which behave the same as in any other loop.[4]
import std;
using std::index_sequence;
using std::index_sequence_for;
template <typename... Ts>
constexpr bool areEvenIndicesEven(Ts&&... args) noexcept {
auto checkEvenIndices = [&args...]<size_t... Is>([[maybe_unused]] index_sequence<Is...> s) -> bool {
template for (constexpr size_t I : {Is...}) {
if constexpr (I % 2 != 0) {
continue;
}
if (args...[I] % 2 != 0) {
return false;
}
}
return true;
};
return checkEvenIndices(index_sequence_for<Ts...>{});
}
int main() {
static_assert(areEvenIndicesEven(0, 1, 2, 3));
}
Carbon
[edit]Carbon, a language designed with interoperability with C++ in mind, offers variadics, in particular pack expansions.[5]
// Takes an arbitrary number of vectors with arbitrary element types, and
// returns a vector of tuples where the i-th element of the vector is
// a tuple of the i-th elements of the input vectors.
fn Zip[... each ElementType:! type](... each vector: Vector(each ElementType)) -> Vector((... each ElementType)) {
... var each iter: auto = each vector.Begin();
var result: Vector((... each ElementType));
while (...and each iter != each vector.End()) {
result.push_back((... each iter));
... each iter++;
}
return result;
}
D
[edit]Definition
[edit]The definition of variadic templates in D is similar to their C++ counterpart:
template VariadicTemplate(Args...) {
// body here...
}
Likewise, any argument can precede the argument list:
template VariadicTemplate(T, string value, alias symbol, Args...) {
// body here...
}
Basic usage
[edit]Variadic arguments are very similar to constant array in their usage. They can be iterated upon, accessed by an index, have a length property, and can be sliced. Operations are interpreted at compile time, which means operands can't be runtime value (such as function parameters).
Anything which is known at compile time can be passed as a variadic arguments. It makes variadic arguments similar to template alias arguments, but more powerful, as they also accept basic types (char, short, int...).
Here is an example that prints the string representation of the variadic parameters. StringOf and StringOf2 produce equal results.
static int staticInt;
struct Dummy {}
void main() {
pragma(msg, StringOf!("Hello world", uint, Dummy, 42, staticInt));
pragma(msg, StringOf2!("Hello world", uint, Dummy, 42, staticInt));
}
template StringOf(Args...) {
enum StringOf = Args[0].stringof ~ StringOf!(Args[1..$]);
}
template StringOf() {
enum StringOf = "";
}
template StringOf2(Args...) {
static if (Args.length == 0) {
enum StringOf2 = "";
} else {
enum StringOf2 = Args[0].stringof ~ StringOf2!(Args[1..$]);
}
}
Outputs:
"Hello world"uintDummy42staticInt
"Hello world"uintDummy42staticInt
AliasSeq
[edit]Variadic templates are often used to create a sequence of aliases, named AliasSeq. The definition of an AliasSeq is actually very straightforward:
alias AliasSeq(Args...) = Args;
This structure allows one to manipulate a list of variadic arguments that will auto-expand. The arguments must either be symbols or values known at compile time. This includes values, types, functions or even non-specialized templates. This allows any operation you would expect:
import std.meta;
void main() {
// Note: AliasSeq can't be modified, and an alias can't be rebound, so we'll need to define new names for our modifications.
alias numbers = AliasSeq!(1, 2, 3, 4, 5, 6);
// Slicing
alias lastHalf = numbers[$ / 2 .. $];
static assert(lastHalf == AliasSeq!(4, 5, 6));
// AliasSeq auto expansion
alias digits = AliasSeq!(0, numbers, 7, 8, 9);
static assert(digits == AliasSeq!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
// std.meta provides templates to work with AliasSeq, such as anySatisfy, allSatisfy, staticMap, and Filter.
alias evenNumbers = Filter!(isEven, digits);
static assert(evenNumbers == AliasSeq!(0, 2, 4, 6, 8));
}
template isEven(int number) {
enum isEven = (0 == (number % 2));
}
Related implementations
[edit]In C#/.NET, the class System.Tuple has specific instantiations for 1 to 8 elements. In order to create a tuple with nine or more components, the final parameter TRest of Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> is supplied as another tuple, as C# lacks variadic template parameters.[6]
In Java, one can emulate variadic parameters using the top type java.lang.Object (similarly in C#/.NET, with System.Object) however this lacks the type-safety of C++-style variadic parameters.
import java.util.List;
void printAllObjects(Object... args) {
for (Object arg : args) {
System.out.println(arg);
}
}
void main() {
printAllObjects("Hello, world!", 42, 3.14159f, true, List.of(1, 2, 3));
}
See also
[edit]For articles on variadic constructs other than templates
References
[edit]- ^ Douglas Gregor & Jaakko Järvi (February 2008). "Variadic Templates for C++0x". Journal of Object Technology. 7 (2, Special Issue OOPS Track at SAC 2007): 31–51. doi:10.5381/jot.2008.7.2.a2.
- ^ Douglas Gregor; Jaakko Järvi & Gary Powell. (February 2004). "Variadic templates. Number N1603=04-0043 in ISO C++ Standard Committee Pre-Sydney mailing".
- ^ "Pack indexing (since C++26)". cppreference.com. 16 April 2025. Retrieved 29 March 2026.
- ^ a b Dan Katz (20 June 2025). "Expansion statements". open-std.org. WG21.
- ^ Carbon Language Project (1 April 2026). "Variadics - Carbon Language documentation". docs.carbon-lang.dev. Carbon Language Project.
- ^ "Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> Class". learn.microsoft.com. Microsoft Learn. Retrieved 14 October 2025.