Using Ellipsis (…) and initializer_list


Using Ellipsis or variable argument list and initializer_list in C and C++

Ellipsis 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 each of them in detail and also try to draw a conclusion of which should be preferred over which and when.

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 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 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 no name exists through which we can access the value.There is however a way to access the value not by using the parameter’s name but by knowing 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.This parameter(the first one) 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.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 ; ///a parameter of type from <cstdarg>
int list ;

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

while( (list=va_arg(start , int ) ) != 0 )   /* va_arg( ) will return the value present in the argument list consecutively*/
{
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 ;
}

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.While accessing the value present in the ellipsis we will compare each value with the limiting value,if the condition is true 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.

Link : decltype()

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

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 ;
}

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 using 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 with the parameter type explicitly declared will be called.Confused? 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.So 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.





Initializer_lists (included in C++11)

Ellipsis was inherited from C so it represents the C way of allowing function to accept multiple arguments without it being checked by the compiler.The C++ standard has introduced a new feature like the ellipses to allow passing of unknown number of arguments to the function.But in this case the type of all the arguments must be same.This feature can be implemented using the initializer_list parameter and this parameter is included in the library header <initializer_list> .The program below implements the feature.

void func( initializer_list<int> li )
{
for ( auto l = li.begin() ; l != li.end() ; l++ )
{
cout << *l << endl ;
}
}

int main( )
{
func( { 23 , 2, 90, 0, 100, 2345 } ) ;

cin.get( ) ;
return 0 ;
}

As shown in the program all the arguments must be written inside the { }(braces) and they must be of the same type.



Comparison of initializer_list with ellipsis

Initializer_list allow passing of single type but varying number of arguments to a function with a single initializer_list parameter.This means if the second argument type differ from the first then we have to declare another initializer_list parameter for the second type.

typedef initializer_list<string> ilstring;
typedef initializer_list<char> ilchar;

string append( ilstring st , ilchar c )
{
string str=”” ;

for (auto ls = st.begin( ) ; ls != st.end( ) ; ls++ )
{
str += *ls ;
}

for(auto cc = c.begin(); cc!=c.end() ; cc++ )
{
char ch = *cc ;
str += ch;
}

return str ;
}

int main()
{
cout << append({ “hello”} , { ‘d’ }) << endl;

cin.get( );
return 0 ;
}

Looking at this scenario we can obviously say that initializer_list behave like the normal parameter which require a distinct declaration of parameter type for each different argument type passed.But if we look at ellipsis declaring a parameter for each argument type is superfluous.This make declaration of parameters with ellipsis (actually not declared) clear cut and simpler.

string Append(int i , … )
{
va_list start ;

va_start(start, i);

string s = va_arg(start, char*) ;
char c = va_arg(start, char);

s += c;

va_end(start);
return s ;
}

int main()
{
cout<< Append(0 , “Doom ” , ‘d’ ) ;

return 0;

By looking at the program we can say using ellipsis in a function is simpler.But hold on! let’s not come to conclusion yet that ellipsis is better.If we want to access the values passed to the ellipsis then we must use some macros (va_start(),va_arg ,…).However ,to access the value with va_arg,each type of the argument must be known and this is quite problematic because the type cannot be known in any way, except when the client or user pass only one type of argument.But if a client pass only one type of argument things get simpler with initializer_list parameter.With it(initializer_list parameter) the definite number of arguments can be known,whereas with ellipsis it is impossible to know the number of arguments and so while accessing the value we might read a memory whose address is beyond the actual argument storage and this is dangerous.One solution to this problem was to pass the number of arguments as the first argument whenever ellipsis base function is called.However,if initializer_list is used passing the the first argument becomes redundant. Moreover,accessing the value in initializer_list is a whole lot simpler and easier.The question is,if initializer_list can process indeterminate number of arguments with ease compare to ellipsis why not used it instead of ellipsis? off course we must!.

The bottom line is if only one type but indeterminate number of arguments is required initializer_list is the better choice but if random type is required ellipsis can be used but you have to figure out the type at each position of the argument list.