C++ Using Ellipsis (…) or variable argument list-everything you need to know

Ellipsis(also known as variable argument list) and initializer_list are the two ways in C++ which allow the function to accept an indeterminate number of arguments.Although they exhibit similar implementation their syntax and their internal workings are different.In this post we will discuss ellipsis and in another post we will discuss ‘initializer_list’ in detail.

Link :C++11 initializer_list

Functions are allowed to accept any number of parameters as long as their type are mentioned in the parameter lists.So if a function want to accept five int type parameters the type and their names must be mentioned specifically in the parameter list.

This is the standard norm that we follow in C and C++.However,C(also inherited by C++) provide a way to accept any number and any type of arguments indiscriminately without having to mention the type and it’s name in the parameter lists.Such function can be written by including the ‘…‘(ellipse) also known as variable argument list in the parameter list .The ellipse simply tells the function to accept any type and any number of arguments.Here is a code example.

void func( … )
{
}

int main( )
{
func( 90 , “string” , 879.987 , ‘ ‘ , 675 ) ; //works fine

func( 78 , 23.3 , 23e5 ) //works fine

cin.get( ) ;
return 0 ;
}

Even if you had passed 20 or more arguments to func( ),it would have accepted it without any questions asked.Such is the power of ellipsis when used as a parameter.


Why ellipsis work?

Why does ellipsis(…) make the function behave the way it is?. The reason is simple. Whenever you use ellipsis you tell the compiler to not check the type and number of the parameters the function should accept.

Simply put by using ellipsis you are telling the compiler that you know what you are doing.To this the compiler will respond “Ok boss!”, and so it does not check the type and number of arguments.

Another reason is when a function is called the type and number of arguments are the only two entities the compiler checks for.And in using the ellipsis the compiler will stop checking the type and number of arguments.When this happens the function caller has the right to pass any type and any number of arguments.


How to make use of the values passed to variable argument list(ellipsis)

Ellipsis allow the function to accept any type and any number of arguments,but we cannot make use of the arguments passed to the function,not when we use ellipsis in the way shown above or shown below.

void func( … ) ///cannot access the arguments
{
} 

In the function func( ) we cannot make use of the argument passed to it because we do not know the name of the parameters through which we can access the value.

To access the parameters in ellipsis we can access it’s value not by name but by using the location of the parameter in the parameter list.To know the location of the parameter in the parameter list what we will do is the first parameter will be given a name and the second parameter can be the ellipsis.

The first parameter will serve as the threshold location of the variable lists.Using this threshold value and some macros provided by <cstdarg> header we can access the value present in the variable argument list.This is a pretty simple method if you know which macros are required and how to use them.

Unfortunately,I won’t be showing here which macros are required,instead a brief explanation of the macro function will be given as a comment whenever it is used in the program.The simple program below will output all the values passed to the ellipsis until a value 0 is found in the ellipsis.

void func(int i , … ) //'i' is the threshold value
{
va_list start ; //va_list found in <cstdarg> and start is it's type
int list ;

/*** initializes 'start' with the information to access the value present after 'i' ***/
va_start( start , i ) ;

/*** va_arg( ) will return the value present in the argument list consecutively ***/
while( (list=va_arg(start , int ) ) != 0 )
{
cout<< list << endl ;
}

va_end(start) ;//ends the use of va_list
}

int main( )
{
func( 2 , 23 , 345 , 748 , 9000 , 0 ) ;

cin.get( ) ;
return 0 ;
} 

The line ‘(list=va_arg(start , int ) ) != 0 ‘ means va_arg() will return any value of type ‘int’ starting from ‘start’ variable until 0 is found.

In the macro function va_arg(),the second argument type must match the type of the value in the argument lists.

In the above example all the values passed are of int type,so we can access the value easily using the while loop.If the argument’s type vary then we must know the type of each argument specifically to access the value.Consider the program below.

void func( int i , … )
{
va_list start ;
va_start(start,i) ;

string st=va_arg(start , char* ) ; //accessing the second argument
double d=va_arg(start , double ) ; //accessing the third argument
int ii=va_arg( start , int ) ; //Accessing the fourth argument
char c=va_arg( start , int ) ; //the fifth argument type is accepted as int type

cout<< st << d << ii << c << endl ;

va_end(start) ;
}

int main( )
{
func( 1 , “Meow” , 34.567 , 23 , ‘B’ ) ;

cin.get( ) ;
return 0 ;
} 

This program works because we know the second argument type is string,and the third is double,and int and a char.But in real time application there is no way of knowing which argument type our client will pass and there is no method to decode the type by using any of the standard library function.So using ellipsis in a function which require different arguments type is a bit senseless.

Another problem when using the ellipsis is we cannot determined the number of arguments present in the variable argument list.To solve this problem a method used in the second example of this post was providing a limiting value zero.While accessing the value present in the ellipsis we will compare each value with the limiting value,if the condition is true or 0 is found we will know the end of arguments has reached and we will stop accessing the arguments list.Pretty simple isn’t?

Another way to know the number of argument is to pass it as the threshold value.This is rather a direct approach.The program below uses this approach to find the largest value in the variable argument list.

/*** i is the threshold value and the number of arguments passed ***/
void largest(int i , … )
{
va_list start ;
decltype(i) = value , next; //same as 'int value,next ;'

va_start(start , i ) ;

value = va_arg(start , decltype(i) ) ;]

for( int ii=1 ; ii< i ; ii++ )
{
next=va_arg(start , decltype(i) );
if( next > value )
{
value=next ;
}
}
cout<< “Largest value is ” << value << endl ;

va_end(start) ;
}

int main( )
{
largest( 5 , 9000 , 12 , 0 , 12000 , 34000 ) ;

cin.get( ) ;
return 0 ;
} 

Since ‘i’ is the number of arguments passed to ellipsis all we have to do is extract ‘i’ number of values from the ellipsis using the for() statement.Passing the number of arguments as the threshold value can make our program easier to write.

The threshold argument type can be int type even if the argument present in the ellipsis may be of different type.This ensure us that we can always use this method when the argument is not of int type.Consider another program which find the position of the character ‘e‘ in each of the string passed as argument.



void find_e(size_t sz , … )
{
va_list start;
char * str , test ;

va_start(start , sz ) ;

for(auto i=0; i<sz; i++)
{
  str=va_arg(start , char*) ;

  cout&l;t< str << endl ;
  int pos=0 ;
  while( (test=str[pos]) != ‘\0’ )
  {
  if( test== ‘e’ )
  {
  cout<< ” \’e\’ occurs in \”” << str << “\” at ” << pos+1 << ” position ” << endl ;

  pos++ ;
  }
  else { pos++ ; }
  }
}
va_end(start);
}

int main( )
{
find_e( 3 , “New” , “Dope” , “Dome” ) ;

cin.get( ) ;
return 0 ;
}

Since the number of arguments passed to the ellipsis is known there is no threat of accessing any memory beyond the valid point.If we hadn’t known the limiting point and access some invalid memory who knows! what might be the output.


Overloading with ellipsis

C++ allow overloading of function using an ellipsis but not using only ellipsis as a parameter,meaning there should be other parameter besides ellipsis.This make sense because ellipsis allows passing any type of arguments and two functions with the same name with only ellipsis as parameter can render an ambiguous call when one of the functions is called.So to overload a function with an ellipsis you must at least provide one different parameter before the ellipsis is written.

void func( int i , … ) //works fine
{
}

void func( string st , … ) //function overloaded, works fine
{
}

void func( … ) //works fine
{
} 

The third function is also valid because the first two functions have their first parameter type explicitly declared.So the third function with only ellipsis as the parameter will be treated as different function.

when two or more parameters type is provided the function that matches the argument type passed will be called. Look at the program below.

void type_check(char c, … )
{ }

void type_check(char c, int i, …)
{ }

void type_check(…) { }

int main( )
{
type_check( ‘V’ , 78 , “string” , 78 ) ; //the second function is called

type_check( ‘M’ , “Hola” , 456.56 ) ; //the first function is called

type_check( “Sorority” , 23 , ‘M’ ) ; //the third function is called

cin.get( ) ;
return 0 ;
}

The first call passed a char and an int type as first and second argument,the function with the parameter type matching the arguments in order is called.Which means the second function is called.

For the second call the char argument match the first function parameter so it is called.

As for the third call no function with the parameter type explicitly declared match the argument passed but still the third function can be called,hence it is called.



Leave a Reply

Your email address will not be published. Required fields are marked *