New features of C++11/14 -- template enhancement and variable templates

Posted by slick101 on Wed, 19 Jan 2022 06:59:05 +0100


Template enhancement

1. External template

1) Problems of traditional C + +

In traditional C + +, templates are instantiated by the compiler only when used.
In other words, as long as the fully defined template is encountered in the code compiled in each compilation unit (file), it will be instantiated.
This results in an increase in compilation time due to repeated instantiation. Also, there is no way to tell the compiler not to trigger template instantiation.


2) C + + Solution

C++11 introduces an external template and extends the original syntax of forcing the compiler to instantiate the template at a specific location, so that it can explicitly tell the compiler when to instantiate the template. Keyword: extern

template class std::vector<bool>;            // Forced instantiation
extern template class std::vector<double>;   // The template is not instantiated in the compiled file 


2. Angle brackets ">"

In traditional C + + compilers, > > is treated as a shift right operator.
But in fact, we can easily write the code of nested templates:

std::vector<  std::vector<int> > wow; 	//C++98 writing method, add a space in the middle of > >
std::vector<std::vector<int>> wow; 		//C++98 will be considered a right shift operator by the compiler

Starting from C++11, the (2) continuous angle brackets will become legal and can be compiled successfully.


3. Type alias template

Before understanding the type alias template, you need to understand the difference between template and type. Understand this sentence carefully: templates are used to generate types.
In traditional C + +, typedef can define a new name for the type, but there is no way to define a new name for the template. Because the template is not a type.

For example:

template< typename T, typename U, int value>
class SuckType
{
public:
    T a;
    U b;
    SuckType():a(value),b(value){}
};
template< typename U>
typedef SuckType<std::vector<int>, U, 1> NewType; // wrongful

Using using, C++11 introduces the following writing method, and supports the same effect as the traditional typedef:

Generally, the syntax of using typedef to define an alias is: typedef original name new name;
However, the definition syntax of aliases such as function pointers is different, which usually makes it difficult to read directly.

typedef int (*process)(void *);  		// Defines a function pointer type with a return type of int and a parameter of void *, called process
using process = int(*)(void *); 		// Ditto, more intuitive

template <typename T>
using NewType = SuckType<int, T, 1>;    // legitimate


4. Default template parameters

Define an addition function

template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y)
{
    return x+y
}

However, it is found that to use the add function, you must specify the type of its template parameter every time.
In C++11, it is convenient to specify the default parameters of the template:

template<typename T = int, typename U = int>
auto add(T x, U y) -> decltype(x+y)
{
    return x+y;
}


5. Variable length parameter template

Template has always been one of the black magic unique to C + +. Before C++11, both class templates and function templates can only accept a fixed number of template parameters according to their specified appearance; C++11 adds a new representation method, which allows any number and any category of template parameters. At the same time, it is not necessary to fix the number of parameters during definition.

template<typename... Ts> class Magic;

The object of template class Magic can accept an unlimited number of typename s as formal parameters of the template, such as the following definitions:

class Magic<int,  std::vector<int>,  std::map<std::string, std::vector<int>>> darkMagic;

Since it is in any form, the number of template parameters with 0 is also OK: class magic < > nothing;

If you do not want to generate 0 template parameters, you can manually define at least one template parameter:
template<typename Require, typename... Args> class Magic;

Variable length parameter templates can also be directly adjusted to template functions. Although the printf function in traditional C can also call an indefinite number of formal parameters, it is not class safe. C++11 can not only define class safe variable length parameter functions, but also enable functions like printf to naturally deal with objects without their own classes. In addition to using... In template parameters to represent variable length template parameters, function parameters also use the same representation to represent variable length parameters, which provides a convenient means for us to simply write variable length parameter functions, such as:
template<typename... Args> void printf(const std::string &str, Args... args);


Then, we have defined variable length template parameters. How to unpack the parameters?
1) First, we can use sizeof... To calculate the number of parameters:

template<typename... Args>
void magic(Args... args)
{
    std::cout << sizeof...(args) << std::endl;
}
//We can pass any parameter to magic function:
magic();        // Output 0
magic(1);       // Output 1
magic(1, "");   // Output 2


2) Secondly, unpack the parameters. Up to now, there is no simple method to deal with the parameter package, but there are two classical methods.

Technique 1: recursive template function
Recursion is a very easy way to think of, and it is also the most classic processing method. This method continuously recursively passes template parameters to the function, so as to recursively traverse all template parameters.

#include <iostream>
template<typename T>
void printf(T value)
{
    std::cout << value << std::endl;
}
 
template<typename T, typename... Args>
void printf(T value, Args... args)
{
    std::cout << value << std::endl;
    printf(args...);
}
 
int main()
{
    printf(1, 2, "123", 1.1);
    return 0;
}


Technique 2: initialize list expansion
Recursive template function is a standard practice, but the obvious disadvantage is that a function that terminates recursion must be defined.

Here is a black magic expansion using initialization list:

template<typename T, typename... Args>
auto print(T value, Args... args)
{
    std::cout << value << std::endl;
    return std::initializer_list<T>
    {
        ([&] {std::cout << args << std::endl;}(), value)...
    };
}
 
int main()
{
    print(1, 2.1, "123");
    return 0;
}

By initializing the list, (lambda expression, value)... Will be expanded. Due to the appearance of comma expression, the previous lambda expression will be executed first to complete the output of parameters. The only ugly thing is that if you don't use return, the compiler will give unused variables as warnings.


2, variadic templates

Here, with the help of Mr. Hou Jie's courseware:







Topics: C++ C++11 templates