Variadic Functions in C++
Tags: cpp
Published: 2024-05-07 15:29:22

---
title: Variadic Functions in C++
published: true
description: How to declare and implement functions with a variable number of parameters in C++.
tags: #cpp
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-05-01 16:42 +0000
---

## Introduction

In [Variadic Functions in C](https://dev.to/pauljlucas/variadic-functions-in-c-53ml), I wrote:

> C++ inherited variadic functions from C, warts and all. However, in C++ there are the much better alternatives of function overloading, initializer lists, and variadic templates that can be used to implement functions that accept varying numbers of arguments in a type-safe way — but that’s a story for another time.

That time is now.

## Function Overloading & Default Arguments

If you want a function that can take a varying (but finite) number of arguments, possibly of different types, you can (and should) simply use either function overloading or default arguments.  Most wouldn’t even consider such functions to be variadic.  Nevertheless, function overloading and default arguments can eliminate some of the use-cases for variadic functions in a simple and type-safe way.

## Initializer Lists

However, if you want a function that can take a varying (and unbounded) number of arguments of the _same_ type, C++ offers [initializer lists](https://en.cppreference.com/w/cpp/utility/initializer_list).  For example, here’s a C++ implementation of the `sum_n()` function given in the C article:

```cpp
int sum_n( std::initializer_list<int> list ) {
  int sum = 0;
  for ( int i : list )
    sum += i;
  return sum;
}
```

Then you can call it like:

```cpp
int r = sum_n( { 1, 2, 5 } );  // r = 8; {} are required
```

Note that the `{}` are required to specify an initializer list literal.  Without the `{}`, you’d be attempting to call `sum_n()` with 3 separate `int` arguments rather than 1 initializer list of 3 `int` elements.

If you don’t like the required use of `{}`, you can hide them with a [macro](https://dev.to/pauljlucas/cc-preprocessor-macros-fh5):

```cpp
#define sum_n(...)  sum_n( { __VA_ARGS__ } )

int r = sum_n( 1, 2, 5 );      // no {} now
```

> Reminder: the C preprocessor will _not_ expand a macro that references itself.

An `initializer_list<T>` is implemented as if it were a `const T[N]` where _N_ is the number of elements in the list.  While efficient, this has a few caveats:

1. The `const` prevents moving.
2. The storage for the elements is contiguous.
3. Either of those caveats can cause elements to be copied.

For trivial types like `int`, none of these caveats matter; but for types that have non-trivial constructors or lack copy assignment operators, these can matter a lot. Consider:

```cpp
void f( std::initializer_list<std::string> list );

void g() {
  std::string s1{ "hello" }, s2{ "world" };
  f( { s1, s2 } );
}
```

Under the hood, it’s as if this happens:

```cpp
  std::string const _list[2] = { s1, s2 };
  f( _list );
```

That is, the strings are copied into a temporary array to make the storage contiguous.

If you want a function that can take a varying (and unbounded) number of arguments without the caveats of `initializer_list` or of possibly different types, C++ also offers _variadic templates_.

## Variadic Templates

Here’s a C++ implementation of the `sum_n()` function given in the C article using variadic templates:

```cpp
constexpr int sum_n( std::convertible_to<int> auto... ns ) {
  return (0 + ... + ns);
}
```

Then you can call it just as before:

```cpp
int r = sum_n( 1, 2, 5 );  // r = 8
```

This has big differences from either the C or previous C++ version:

+ The implementation is _much_ shorter.
+ It’s type-safe.
+ The iteration over the arguments is done at compile-time!

The use of `auto` makes this an _abbreviated function template_. That is, it’s a shorthand for the more verbose:

```cpp
template<std::convertible_to<int>... Ts>
constexpr int sum( Ts... ns ) {
  return (0 + ... + ns);
}
```

> Unlike the `initializer_list` implementation, this template implementation _must_ be in a header file.

The `std::convertible_to<int>` constrains the type that `auto` (in the original code) or `Ts` (in the verbose code) can be to one that either is or convertible to `int`.

The `...` in the parameter declaration in the original code denotes a _function template parameter pack_, that is zero or more parameters of a type that are convertible to `int`.

> It’s no accident that the `...` token that originally was used to specify variadic parameters was reused to specify variadic templates also.

The `...` in `(0 + ... + ns)` denotes a [fold expression](https://en.cppreference.com/w/cpp/language/fold) (here, specifically a _binary left fold_) that’s a concise way of performing the operation (here, `+`) on the sequence of expanded function arguments comprising `ns`.  Note that the `()` are _required_.

> Personally, I think the `()` syntax for fold expressions is too subtle since, historically, the addition of `()` to any expression didn’t change its meaning, e.g., `a + b` and `(a + b)` mean the same thing.

The `constexpr` makes the compiler evaluate the function at compile-time if all arguments are constant expressions.

## Another Example: No Sentinel Needed

Here’s a C++ implementation of the `str_is_any()` function given in the C article:

```cpp
bool str_is_any( std::string const &needle,
                 std::convertible_to<char const*> auto... haystack ) {
  return ( (needle == haystack) || ... );
}
```

And you can call it like:

```cpp
if ( str_is_any( s, "struct", "union", "class" ) )
```

This also has big differences from the C version:

+ The implementation is _much_ shorter.
+ It’s type-safe.
+ The iteration over the arguments is done at compile-time!
+ The caller doesn’t pass a sentinel of `nullptr`.

The fold expression creates a disjunction (`||`) of `needle` `==` each value of `haystack`.

## Iterating Variadic Arguments with Recursion

Fold expressions are both powerful and concise, but they can be used only with an operator.  If you need to do something more involved with the arguments, you need to expand them yourself.

Suppose you want a function that prints all of its arguments separated by commas, e.g.:

```cpp
print_list( std::cout, "hello", "world" );
```

One trick is to split the arguments into the first argument and the rest of the arguments:

```cpp
void print_list( std::ostream &o, auto const &first,
                 auto const&... rest ) {
  o << first;
  if constexpr ( sizeof...( rest ) > 0 ) {
    o << ", ";
    print_list( o, rest... );
  }
}
```

This works as follows:

+ The `first` argument is printed.

+ Unlike its namesake `sizeof` operator that returns the number of bytes of its argument, the `sizeof...` operator returns the number of arguments in a parameter pack.

+ If the number of remaining arguments in `rest` > 0, print a comma and recursively call `print_list()`.

+ In the recursive call, the first argument of `rest...`  becomes the _new_ `first` and the remaining arguments (if any) become the _new_ `rest`.  Each recursive call “peels” an argument off the front.

+ Eventually, `sizeof...(rest)` will be 0 and the recursion will stop.

Hence, “iterating” over variadic arguments can be done via recursion.  Note that the recursion is done at compile-time.

## Iterating Variadic Arguments with the Comma Operator

I recently wrote:

> Fold expressions are both powerful and concise, but they can be used only with an operator.  If you need to do something more involved with the arguments, you need to expand them yourself.

However, there is an operator that can be used to do most anything for a sequence of expressions: the comma operator.  As a reminder, in C++:

```cpp
expr1, expr2;
```

evaluates `expr1` (discards the result, if any) followed by `expr2` (which is the result).  It’s not commonly used except in the iteration expression of a `for` loop, e.g.:

```cpp
for ( int i = 0, j = 0; i < m && j < n; ++i, ++j ) {
```

Above, the `++i, ++j` is a typical use of the comma operator.  With template parameter packs, the comma operator gains more use.

Suppose you want a function where you can call `push_back()` with multiple arguments to push them all back:

```cpp
template<typename Container>
using value_type = typename Container::value_type;

template<typename Container>
void push_all_back( Container *c,
                    std::convertible_to<value_type<Container>> auto&&... args ) {
  c->reserve( sizeof...(args) );
  ( c->push_back( std::forward<value_type<Container>>( args ) ), ... );
}
```

And you can call it like:

```cpp
std::vector<int> v;
push_all_back( &v, 1, 2, 5 );
```

This works as follows:

+ The `value_type` declaration is just a convenience type to lessen typing.

+ The `&&` is a [forwarding reference](https://dev.to/pauljlucas/c-references-3b1b) so arguments will work effectively with temporary objects.

+ The `reserve( sizeof...(args) )` will ensure the container is resized at most once for sufficient space to push all of the arguments.

+ The `std::forward<value_type<Container>>(args)` will ensure _perfect forwarding_ of the arguments.  (See [C++ References](https://dev.to/pauljlucas/c-references-3b1b).)

+ The `push_back()` with the `, ...` is a fold expression using the comma operator that will push all of the arguments one at a time.

Hence, “iterating” over variadic arguments can be done via the comma operator.  As in other examples, the iteration is done at compile-time.

## Conclusion

Any of function overloading, default arguments, initializer lists, or variadic templates offer a mechanism to implement variadic functions in a type-safe way.  In C++, there is no reason ever to use C-style variadic functions.