C++ variable parameter function and template example analysis

  • 2020-04-02 02:37:10
  • OfStack

In this paper, the example shows the C++ variable parameter function and template implementation method, to help you better understand the variable parameter function and template application, the specific content is as follows:

First of all, the so-called variable parameter refers to the function of variable number of parameters, parameter type. To write functions that can handle different Numbers of arguments, C++ provides two main methods: if all arguments are of the same type, you can pass a standard library type named initializer_list; If the arguments are of different types, we can write variable-parameter templates. In addition, C++ has a special ellipsis parameter that you can use to pass a variable number of arguments, but this is generally only used for interfaces that interact with C functions.

I. variable parameter function

1. Initializer_list parameter

If the number of arguments for a function is unknown but all arguments are of the same type, we can use the parameters of the initializer_list type (the new C++11 standard). Like vector, initializer_list is a template type. Here's a look at some of the actions provided by initializer_list:


#include<initializer_list> //The header file
initializer_list<T> lst;  //Initializes, by default, an empty list of elements of type T
initializer_list<T> lst{a,b,c...}; //Initialize as a copy of the list of initializers
lst2(lst)   //Copy or assign does not copy elements in a list; After the copy,
lst2 = lst  //The original list and copy share elements
lst.size()  //The number of elements in the list
lst.begin()  //Returns a pointer to the first element in the LST
lst.end()   //Returns a pointer to the next position of the trailing element in the LST

As an example, it is important to note that a function with initializer_list parameters can also have other parameters. In addition, if you want to pass a sequence of arguments to the initializer_list parameter, you must place the sequence in a pair of curly braces:


string func(initializer_list<string> li) 
{ 
  string str(""); 
  for(auto beg=li.begin(); beg!=li.end(); ++beg) 
    str += *beg; 
  return str; 
} 
 
int main() 
{ 
  cout << func({"This"," ","is"," ","C++"}) << endl; 
  return 0; 
} 

2. Ellipsis parameter

The function can use the ellipsis parameter "... "For the indefinite parameter part, the ellipsis parameter can only appear at the last position in the parameter list. It takes the form as follows:


void foo(parm_list, ...); 
//A typical example
int printf(const char* format, ...) 

Ellipsis parameters should be used only for C and C++ generic types, because most objects of class types are not copied correctly when passed to ellipsis parameters. The following is < cstdarg > Several macro definitions in the header file:


#include<cstdarg> //C is <Stdarg. H>
 
//Va_list is a data type and args is used to hold variable parameters.
//Define a typedef char* va_list;
va_list args; 
 
//Call va_start and pass in two parameters: the first parameter is a variable of type va_list
//The second parameter is "..." Last parameter name before
//Initialize args to point to the first parameter (variable-parameter list)
va_start(args, paramN); 
 
//The first parameter of va_arg is the va_list variable, and the second parameter specifies the type of return value
//Each call to va_arg takes the current parameter and automatically updates it to point to the next mutable parameter.
va_arg(args,type); 
 
//Release the va_list variable
va_end(args);

Here's an example:


int add_nums(int count,...) 
{ 
  int result = 0; 
   
  va_list args; 
  va_start(args, count); 
  for(int i=0; i<count; ++i) 
    result += va_arg(args, int); 
  va_end(args); 
  return result; 
} 
 
int main() 
{ 
  cout << add_nums(4, 25, 25, 50, 50) << endl; 
  return 0; 
} 

The compiler passes arguments on a stack. When arguments are passed, the compiler pushes the arguments from the arguments list in right-to-left order. For add_nums(4, 25, 25, 50), the order is 50, 50, 25, 25, 4 (note that there is no difference between mutable and immutable arguments). Since the address of the stack is from high to low, you can get the address of each parameter after you know the address of the first parameter and the type of the parameter.

Ii. Variable parameter template

A variadic template is a template function or template class that accepts a variable number of arguments. A variable number of parameters is called parameter packet. There are two kinds of parameter packages: template parameter packages (representing zero or more template parameters) and function parameter packages (representing zero or more function parameters).

As mentioned above, we can use an initializer_list to define a function that accepts a variable number of arguments, but all arguments must have the same type. When we don't know either the number of arguments to process or their type, we need to use a function template with variable arguments. We use an ellipsis to indicate that a template parameter or function parameter represents a package: in a template parameter list, class... Or typename... Indicates that the next parameter represents a list of zero or more types; A type name followed by an ellipsis represents a list of zero or more non-type parameters for a given type. In the function parameter list, if the type of an argument is a template parameter package, the argument is also a function parameter package.


//Args is a template parameter package; Rest is a package of function parameters
template <typename T, typename...Args> 
void foo(const T &t, const Args&...rest); 

Variable-parameter function templates are usually recursive. The first step calls the first argument in the processing package, and then calls itself with the remaining arguments. To terminate the recursion, we also need to define a function template with non-variable parameters:


//Used to terminate recursion and process the last element in the package
template <typename T> 
void print(const T &t) 
{ 
  cout << t; 
} 
 
//All but the last element in the package calls this version of print
template <typename T, typename...Args> 
void print(const T &t, const Args&...rest) 
{ 
  cout << t << " ";   //Print the first argument
  print(rest...);    //Recursive call to print other arguments
} 
 
//test
int main() 
{ 
  print("string1", 2, 3.14f, "string2", 42); 
  cout << endl; 
  return 0; 
} 

The non-variable-parameter version of print is responsible for terminating the recursion and printing the last argument in the initial call. For the last recursive call to print(42), both print versions are available. However, non-variable-parameter templates are more specific than variable-parameter templates, so the compiler selects the non-variable-parameter version.


Related articles: