[C++11]C++ Variable Parameter Template

Keywords: PHP

Variable parameter template

Links to the original text: http://blog.csdn.net/xiaohu2022/article/details/69076281
Ordinary templates can only take a fixed number of template parameters. However, sometimes we want templates to accept any number of template parameters, and then we can use variable parameter templates. For variable parameter templates, it will contain at least one template parameter package, which can receive 0 or more parameters of template parameters. Correspondingly, there is a function parameter package, which means that the function parameter can receive any number of parameters.

Rules of Use

A variable parameter class template is defined as follows:

template<typename ... Types>
class Tuple
{};

 

Tuple can be instantiated with any number of types:

Tuple<> t0;
Tuple<int> t1;
Tuple<int, string> t2;
// Tuple<0> error;  0 is not a type

 

If you want to avoid instantiating variable parameter templates with 0 template parameters, you can define templates as follows:

template<typename T, typename ... Types>
class Tuple
{};

 

When instantiating, at least one template parameter must be passed in, otherwise it cannot be compiled.
Similarly, a variable parameter function template that receives arbitrary parameters can be defined:

template<typename ... Types>
void f(Types ... args);

// Some legitimate calls
f();
f(1);
f(3.4, "hello");

 

For class templates, the variable template parameter package must be the last parameter in the template parameter list. However, for function templates, there is no such limitation. Consider the following situations:

template<typename ... Ts, typename U>
class Invalid
{};   // This is an illegal definition, because it can never be inferred. U Types of

template<typename ... Ts, typename U>
void valid(U u, Ts ... args);  // This is legal, because it can be inferred. U Types of
// void invalid(Ts ... args, U u); // Illegal, never inferable U

valid(1.0, 1, 2, 3); // At this time, U The type is double,Ts yes{int, int, int}

 

Examples of Templates for Variable Parameter Functions

It is not possible to traverse the different parameters passed to the variable parameter template directly, but the variable parameter template can be used recursively. Variable parameter templates allow the creation of type-safe variable-length parameter lists. Next, we define a variable parameter function template processValues(), which allows you to accept different types of variable number of parameters in a type-safe manner. The function processValues() processes each value in the variable parameter list and executes the corresponding version of handleValue() for each parameter.

// Processing each type of actual function
void handleValue(int value) { cout << "Integer: " << value << endl; }
void handleValue(double value) { cout << "Double: " << value << endl; }
void handleValue(string value) { cout << "String: " << value << endl; }

// Fundamental functions for terminating iterations
template<typename T>
void processValues(T arg)
{
    handleValue(arg);
}

// Variable parameter function template
template<typename T, typename ... Ts>
void processValues(T arg, Ts ... args)
{
    handleValue(arg);
    processValues(args ...); // Unpack, then recurse
}

 

You can see that this example uses the... Operator three times, but it has two different meanings. Used in parameter template lists and function parameter lists, it represents parameter packages. As mentioned earlier, parameter packages can accept any number of parameters. The.... operator used in the actual call of a function, which represents the expansion of the parameter package. At this time, args is unpacked, the parameters are expanded, and separated by commas. Templates always need at least one parameter, and processValues() can be called recursively by args... Unpacking, so that at least one template parameter is used for each call. For recursion, a termination condition is needed. When there is only one parameter after unpacking, the processValues() function that receives a parameter template is called to terminate the whole recursion.

Suppose processValues() is called as follows:

processsValues(1, 2.5, "test");

 

The resulting recursive calls are as follows:

processsValues(1, 2.5, "test");
    handleValue(1);
    processsValues(2.5, "test");
        handleValue(2.5);
        processsValues("test");
            handleValue("test");

 

Because the processValues() function automatically calls the correct version of the handleValue() function based on the actual type, this variable parameter list is completely type-safe. If the processValues() function is called with a parameter and there is no corresponding version of the handleValue() function, the compiler generates an error.

There is a fatal flaw in the previous implementation, that is, the parameters are copied and passed in recursive invocation, and for some types of parameters, the cost may be high. An efficient and reasonable way is to pass values by reference, but there is a problem with calling processValues() for literal quantities, because literal quantities are only allowed to pass const reference parameters. Fortunately, we can consider right-value references. Use std::forward() A function can do this by passing a right-value reference to the processValues() function, which is passed as a right-value reference, but if a left-value reference is passed to the processValues() function, it is passed as a left-value reference. The following is the concrete realization:

// Fundamental functions for terminating iterations
template<typename T>
void processValues(T &&arg)
{
    handleValue(std::forward<T>(arg));
}

// Variable parameter function template
template<typename T, typename ... Ts>
void processValues(T&& arg, Ts&& ... args)
{
    handleValue(std::forward<T>(arg));
    processValues(std::forward<Ts>(args) ...); // First use forward After the function is processed, it is unpacked, and then recursively
}

 

Implementing simplified printf function

Here we implement a simplified version of the printf function with variable parameter templates:

// Basis function
void tprintf(const char* format)
{
    cout << format;
}

template<typename T, typename ... Ts>
void tprintf(const char* format, T&& value, Ts&& ... args)
{
    for (; *format != '\0'; ++format)
    {
        if (*format == '%')
        {
            cout << value;
            tprintf(format + 1, std::forward<Ts>(args) ...); // recursion
            return;
        }
        cout << *format;
    }
}
int main()
{

    tprintf("% world% %\n", "Hello", '!', 2017);
    // output: Hello, world! 2017
    cin.ignore(10);
    return 0;
}

 

Its method is basically the same as processValues(), but because the first parameter of tprintf is fixed to the const char * type.

References

[1] Marc Gregoire. Professional C++, Third Edition, 2016.
[2] cppreference parameter pack



Author: General Xiao Bai
Link: https://www.jianshu.com/p/4bf4d1860588

Posted by raul_7 on Sun, 28 Jul 2019 02:57:03 -0700