How can I write a function that accepts a variable number of arguments? Is this possible, how?
17 Answers
You probably shouldn't, and you can probably do what you want to do in a safer and simpler way. Technically to use variable number of arguments in C you include stdarg.h. From that you'll get the va_list
type as well as three functions that operate on it called va_start()
, va_arg()
and va_end()
.
#include<stdarg.h>
int maxof(int n_args, ...)
{
va_list ap;
va_start(ap, n_args);
int max = va_arg(ap, int);
for(int i = 2; i <= n_args; i++) {
int a = va_arg(ap, int);
if(a > max) max = a;
}
va_end(ap);
return max;
}
If you ask me, this is a mess. It looks bad, it's unsafe, and it's full of technical details that have nothing to do with what you're conceptually trying to achieve. Instead, consider using overloading or inheritance/polymorphism, builder pattern (as in operator<<()
in streams) or default arguments etc. These are all safer: the compiler gets to know more about what you're trying to do so there are more occasions it can stop you before you blow your leg off.
In C++11 you have two new options, as the Variadic functions reference page in the Alternatives section states:
- Variadic templates can also be used to create functions that take variable number of arguments. They are often the better choice because they do not impose restrictions on the types of the arguments, do not perform integral and floating-point promotions, and are type safe. (since C++11)
- If all variable arguments share a common type, a std::initializer_list provides a convenient mechanism (albeit with a different syntax) for accessing variable arguments.
Below is an example showing both alternatives (see it live):
#include <iostream>
#include <string>
#include <initializer_list>
template <typename T>
void func(T t)
{
std::cout << t << std::endl ;
}
template<typename T, typename... Args>
void func(T t, Args... args) // recursive variadic function
{
std::cout << t <<std::endl ;
func(args...) ;
}
template <class T>
void func2( std::initializer_list<T> list )
{
for( auto elem : list )
{
std::cout << elem << std::endl ;
}
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
func(1,2.5,'a',str1);
func2( {10, 20, 30, 40 }) ;
func2( {str1, str2 } ) ;
}
If you are using gcc
or clang
we can use the PRETTY_FUNCTION magic variable to display the type signature of the function which can be helpful in understanding what is going on. For example using:
std::cout << __PRETTY_FUNCTION__ << ": " << t <<std::endl ;
would results int following for variadic functions in the example (see it live):
void func(T, Args...) [T = int, Args = <double, char, std::basic_string<char>>]: 1
void func(T, Args...) [T = double, Args = <char, std::basic_string<char>>]: 2.5
void func(T, Args...) [T = char, Args = <std::basic_string<char>>]: a
void func(T) [T = std::basic_string<char>]: Hello
In Visual Studio you can use FUNCSIG.
Update Pre C++11
Pre C++11 the alternative for std::initializer_list would be std::vector or one of the other standard containers:
#include <iostream>
#include <string>
#include <vector>
template <class T>
void func1( std::vector<T> vec )
{
for( typename std::vector<T>::iterator iter = vec.begin(); iter != vec.end(); ++iter )
{
std::cout << *iter << std::endl ;
}
}
int main()
{
int arr1[] = {10, 20, 30, 40} ;
std::string arr2[] = { "hello", "world" } ;
std::vector<int> v1( arr1, arr1+4 ) ;
std::vector<std::string> v2( arr2, arr2+2 ) ;
func1( v1 ) ;
func1( v2 ) ;
}
and the alternative for variadic templates would be variadic functions although they are not type-safe and in general error prone and can be unsafe to use but the only other potential alternative would be to use default arguments, although that has limited use. The example below is a modified version of the sample code in the linked reference:
#include <iostream>
#include <string>
#include <cstdarg>
void simple_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
while (*fmt != '\0') {
if (*fmt == 'd') {
int i = va_arg(args, int);
std::cout << i << '\n';
} else if (*fmt == 's') {
char * s = va_arg(args, char*);
std::cout << s << '\n';
}
++fmt;
}
va_end(args);
}
int main()
{
std::string
str1( "Hello" ),
str2( "world" );
simple_printf("dddd", 10, 20, 30, 40 );
simple_printf("ss", str1.c_str(), str2.c_str() );
return 0 ;
}
Using variadic functions also comes with restrictions in the arguments you can pass which is detailed in the draft C++ standard in section 5.2.2
Function call paragraph 7:
When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (18.7). The lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the argument expression. After these conversions, if the argument does not have arithmetic, enumeration, pointer, pointer to member, or class type, the program is ill-formed. If the argument has a non-POD class type (clause 9), the behavior is undefined. [...]
A C++17 solution: full type safety + nice calling syntax
Since the introduction of variadic templates in C++11 and fold expressions in C++17, it is possible to define a template-function which, at the caller site, is callable as if it was a varidic function but with the advantages to:
- be strongly type safe;
- work without the run-time information of the number of arguments, or without the usage of a "stop" argument.
Here is an example for mixed argument types
template<class... Args>
void print(Args... args)
{
(std::cout << ... << args) << "\n";
}
print(1, ':', " Hello", ',', " ", "World!");
And another with enforced type match for all arguments:
#include <type_traits> // enable_if, conjuction
template<class Head, class... Tail>
using are_same = std::conjunction<std::is_same<Head, Tail>...>;
template<class Head, class... Tail, class = std::enable_if_t<are_same<Head, Tail...>::value, void>>
void print_same_type(Head head, Tail... tail)
{
std::cout << head;
(std::cout << ... << tail) << "\n";
}
print_same_type("2: ", "Hello, ", "World!"); // OK
print_same_type(3, ": ", "Hello, ", "World!"); // no matching function for call to 'print_same_type(int, const char [3], const char [8], const char [7])'
// print_same_type(3, ": ", "Hello, ", "World!");
^
More information:
- Variadic templates, also known as parameter pack Parameter pack(since C++11) - cppreference.com.
- Fold expressions fold expression(since C++17) - cppreference.com.
- See a full program demonstration on coliru.
In C++11 there is a way to do variable argument templates which lead to a really elegant and type safe way to have variable argument functions. Bjarne himself gives a nice example of printf using variable argument templates in the C++11FAQ.
Personally, I consider this so elegant that I wouldn't even bother with a variable argument function in C++ until that compiler has support for C++11 variable argument templates.
Apart from varargs or overloading, you could consider to aggregate your arguments in a std::vector or other containers (std::map for example). Something like this:
template <typename T> void f(std::vector<T> const&);
std::vector<int> my_args;
my_args.push_back(1);
my_args.push_back(2);
f(my_args);
In this way you would gain type safety and the logical meaning of these variadic arguments would be apparent.
Surely this approach can have performance issues but you should not worry about them unless you are sure that you cannot pay the price. It is a sort of a a "Pythonic" approach to c++ ...
The only way is through the use of C style variable arguments, as described here. Note that this is not a recommended practice, as it's not typesafe and error-prone.
There is no standard C++ way to do this without resorting to C-style varargs (...
).
There are of course default arguments that sort of "look" like variable number of arguments depending on the context:
void myfunc( int i = 0, int j = 1, int k = 2 );
// other code...
myfunc();
myfunc( 2 );
myfunc( 2, 1 );
myfunc( 2, 1, 0 );
All four function calls call myfunc
with varying number of arguments. If none are given, the default arguments are used. Note however, that you can only omit trailing arguments. There is no way, for example to omit i
and give only j
.
It's possible you want overloading or default parameters - define the same function with defaulted parameters:
void doStuff( int a, double termstator = 1.0, bool useFlag = true )
{
// stuff
}
void doStuff( double std_termstator )
{
// assume the user always wants '1' for the a param
return doStuff( 1, std_termstator );
}
This will allow you to call the method with one of four different calls:
doStuff( 1 );
doStuff( 2, 2.5 );
doStuff( 1, 1.0, false );
doStuff( 6.72 );
... or you could be looking for the v_args calling conventions from C.
Using variadic templates, example to reproduce console.log
as seen in JavaScript:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
Filename e.g. js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};
// spawn: allocate and initialize (a simple function)
template<typename T>
T * spawn(size_t n, ...){
T * arr = new T[n];
va_list ap;
va_start(ap, n);
for (size_t i = 0; i < n; i++)
T[i] = va_arg(ap,T);
return arr;
}
User writes:
auto arr = spawn<float> (3, 0.1,0.2,0.3);
Semantically, this looks and feels exactly like an n-argument function. Under the hood, you might unpack it one way or the other.
It is possible now...using boost any and templates In this case, arguments type can be mixed
#include <boost/any.hpp>
#include <iostream>
#include <vector>
using boost::any_cast;
template <typename T, typename... Types>
void Alert(T var1,Types... var2)
{
std::vector<boost::any> a( {var1,var2...});
for (int i = 0; i < a.size();i++)
{
if (a[i].type() == typeid(int))
{
std::cout << "int " << boost::any_cast<int> (a[i]) << std::endl;
}
if (a[i].type() == typeid(double))
{
std::cout << "double " << boost::any_cast<double> (a[i]) << std::endl;
}
if (a[i].type() == typeid(const char*))
{
std::cout << "char* " << boost::any_cast<const char*> (a[i]) <<std::endl;
}
// etc
}
}
void main()
{
Alert("something",0,0,0.3);
}