The difference in template parameter passing (automatic type inference for function templates)

The difference in template parameter passing (automatic type inference for function templates)

Pass by value

The consequence of passing by value is: leading to decay of the automatic type inference of the function template, what is degeneration, let me talk about it carefully below:

① Passing by value will cause the incoming parameters to lose const and volatile attributes:

#include <iostream>  
using namespace std;  
  
template <typename T>  
void ShowInf(T obj)  
{  
    cout << typeid(T).name() << endl;  
}  
  
int main()  
{  
    const int a = 10;  
    ShowInf(a);  
}  

 

 

We see that the type of the function parameter is inconsistent with the type of the actual parameter we passed, and the const constant attribute is removed.

Why use the volatile keyword?

The volatile keyword is a type modifier, and the type variable declared with it can be changed by some factors unknown to the compiler. For example: operating system, hardware, or other threads. When encountering the variable declared by this keyword, the compiler will no longer optimize the code that accesses the variable, so as to provide stable access to the special address.

Syntax when declaring: volatile int vInt;

When the value of a variable declared by volatile is required, the system always reads data from the memory where it is located, even if the previous instruction has just read data from it. E.g:

volatile int i=10;  
int a = i;  
int b = i;   

 

Volatile points out that i may change at any time. It must be read from the address of i every time it is used. Therefore, the assembly code generated by the compiler will read data from the address of i and put it in b. The optimization method is that since the compiler finds that the code between the two codes that read data from i has not operated on i, it will automatically put the data read last time in b. Instead of reading from i again. In this way, if i is a register variable or represents a port data, it is prone to errors, so volatile can guarantee stable access to special addresses.

The following is to insert assembly code to test whether there is a volatile keyword and its impact on the final code of the program:

#include <stdio.h>  
void main()  
{  
    int i = 10;  
    int a = i;  
  
    printf("i = %d", a);  
  
    // 下面汇编语句的作用就是改变内存中 i 的值  
    // 但是又不让编译器知道  
    __asm {  
        mov dword ptr [ebp-4], 20h  
    }  
  
    int b = i;  
    printf("i = %d", b);  
}  

 

Then, run the program in Debug version mode, and the output results are as follows:

i = 10  
i = 32  

 

Then, run the program in Release version mode, and the output results are as follows:

i = 10  
i = 10  

 

The output result clearly shows that in Release mode, the compiler optimized the code and did not output the correct i value the second time. Next, we add the volatile keyword to the declaration of i to see what changes:

#include <stdio.h>  
void main()  
{  
    volatile int i = 10;  
    int a = i;  
  
    printf("i = %d", a);  
  
    // 下面汇编语句的作用就是改变内存中 i 的值  
    // 但是又不让编译器知道  
    __asm {  
        mov dword ptr [ebp-4], 20h  
    }  
  
    int b = i;  
    printf("i = %d", b);  
}  

 

Run the program in Debug and Release respectively, the output is:

i = 10  
i = 32  

 

This shows that the volatile keyword has played its role. In fact, it is not just "inline assembly manipulating the stack" that is a variable change that is not recognized by the compiler. In addition, it is more likely that when multiple threads concurrently access shared variables, a thread changes the value of the variable. How to make the changed value pair Other threads are visible. Generally speaking, volatile is used in the following places:

1) Variables modified in the interrupt service program for detection by other programs need to be volatile;

2) The flags shared between tasks in a multitasking environment should be volatile;

3) The memory-mapped hardware register usually also needs to add volatile description, because each read and write to it may have a different meaning.

② Passing by value will cause the incoming parameters to lose their reference attributes:

#include <iostream>  
using namespace std;  
  
template <typename T>  
void ShowInf(T obj)  
{  
    cout << typeid(T).name() << endl;  
}  
  
int main()  
{  
    int b = 10;  
    int& a = b;  
    ShowInf(a);  
}  

 

 

③ Passing by value will cause the passed array to degenerate into a pointer:

#include <iostream>  
using namespace std;  
#include <string>  
#include <type_traits>  
  
template <typename T>  
void ShowInf(T obj)  
{  
    cout << typeid(T).name() << endl;  
    cout << "is array:" << is_array<T>::value << endl;  
}  
  
int main()  
{  
    char ch[] = "111";  
    ShowInf<>(ch);  
    ShowInf<char[]>(ch);  
}  

 

 

We see that when using an explicit type, the incoming ch data is the first address of the array. We can determine within the function that the incoming parameter is the first address of the function, but when using implicit type conversion, it is automatically When the type is deduced, this pointer is an ordinary pointer inside the function.

Pass by reference

#include <iostream>  
using namespace std;  
#include <type_traits>  
#include <any>  
  
template <typename T>  
void ShowInf(T& obj)  
{  
    if (is_const<T>::value)  
    {  
        cout << "const int &" << endl;  
    }  
}  
  
int main()  
{  
    const int obj = 10;  
    ShowInf(obj);  
}  

 

Here, we pass a variable of const int& by reference, so the template T& at this time is const int&, that is, T is of const int type. What we should pay attention to is that T is const int and not const int&. We have seen that when using T& for implicit type inference, the type of data can be deduced correctly.

#include <iostream>  
using namespace std;  
#include <type_traits>  
#include <any>  
  
template <typename T>  
void ShowInf(T& obj)  
{  
    if (is_array<T>::value)  
    {  
        cout << "char[]" << endl;  
    }  
    else  
    {  
        cout << "char*" << endl;  
    }  
}  
  
int main()  
{  
    char ch[] = "111";  
    ShowInf(ch);  
}  

 

When using & for data type derivation, you can retain the characteristics of array pointers, and will not degenerate into ordinary pointers.

note:

Typeid<T>.name() outputs constants of const char* data type, such as int, const int*, etc., but unfortunately, const int& cannot be output in this way:

#include <iostream>  
using namespace std;  
#include <type_traits>  
#include <any>  
  
template <typename T>  
void ShowInf(T& obj)  
{  
    cout << is_const<T>::value << endl; // 注意:这里的is_const只能判断纯const变量,const int&不是纯const变量  
    cout << typeid(obj).name() << endl;  
}  
  
int main()  
{  
    const int obj = 10;  
    ShowInf(obj);  
}  

 

 

We can see from the result that the type of this variable is indeed const int&, but it is not shown here.

 

Guess you like

Origin blog.csdn.net/weixin_45590473/article/details/111938962