How to use non-type template parameters & considerations when using non-type template parameters & how to control the instantiation of templates to save memory space

Non-type template parameters

Precautions when overloading functions with non-type template parameters

Form 1:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 }, Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (int(*)(int const&))AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const int& obj) {cout << obj << " "; });  
} 

 

Form two:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 }, Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const int& obj) {cout << obj << " "; });  
}  

 

We see the above two forms, the biggest difference is:

①transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (int(*)(int const&))AddValue<int, 5>);

② transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), AddValue<int, 5>);

We sometimes have questions: the output results of these two codes are the same, but why is the type of function pointer coerced in form one?

There is a passage in "C++ templates" that is thought-provoking:

 

The meaning of this passage is to say:

AddValue<int,5> is equivalent to a function pointer. For many C++ overloaded functions with the same name but different parameters, simply look at the independent function pointer AddValue and not the parameter type. I don’t know the function pointer AddValue at all. Points to the overloaded version of the function. Although the compiler can observe and judge the parameters passed to the AddValue function pointer in Transform to determine which function overloaded version is called, but we don't know it! Therefore, we have to perform a forced type conversion. Although it is useless to force it or not, it can at least make our thinking clear, until this function calls the overloaded version of the function to manipulate the pile of data.

for example:

#include <iostream>  
using namespace std;  
#include <vector>  
#include <algorithm>  
  
template <typename T, int val>  
T AddValue(T const& obj)  
{  
    return obj + val;  
}  
template <typename T, int val>  
double AddValue(double const& obj)  
{  
    return (obj + (double)val) / 2;  
}  
  
int main()  
{  
    vector<int> Vector_Obj1{ 1,2,3,4,5,6 };  
    vector<double> Vector_Obj2;  
    Vector_Obj2.resize(Vector_Obj1.size());  
    transform(Vector_Obj1.begin(), Vector_Obj1.end(), Vector_Obj2.begin(), (double(*)(double const&))AddValue<int, 5>);  
    for_each(Vector_Obj2.begin(), Vector_Obj2.end(), [](const double& obj) {cout << obj << " "; });  
}  

 

In this example, forced type conversion is very necessary. Without forced type conversion, the compiler simply cannot identify which function overloaded version is to be executed based on a bare function pointer. When the compiler appears to be uncertain When the phenomenon occurs, false warnings follow one after another:

 

Parameter restrictions for non-type templates

①It cannot be a quantity of floating point type (variable/constant):

When matching template specializations, the compiler will match template parameters, including non-type parameters.

By their very nature, floating-point values ​​are not precise, and the C++ standard does not specify their implementation. Therefore, it is difficult to determine when two floating-point non-type parameters actually match:

template <float f> void foo () ;void bar () {

    foo< (1.0/3.0) > ();

foo< (7.0/21.0) > ();

}

These expressions do not necessarily produce the same "bit patterns", so it is impossible to guarantee that they use the same specialization-there is no special wording to cover this.

②Non -type template parameters cannot use internal link objects (such as string) as actual parameters

When we use the same string string to instantiate non-type parameter templates in multiple source files of a project, it stands to reason that the non-type parameter templates instantiated by the same string string should be the same, but don’t forget, We instantiate non-type parameter templates in different source files. The string constants in different source files are stored in different areas. The non-type parameter templates instantiated from data in different areas are not for the compiler. The same, this is a bit scary. In our opinion, the same string string should be instantiated with the same template, but due to the different definition files of the same string string, the final instantiated template is different. In this case, the compiler gave "formal parameters of non-type template parameters cannot use internal link objects".

How to use string type as a non-type template parameter? (Change link properties)

C++ stipulates that the variables modified by const, not only cannot be modified, but also have internal link properties, which is only visible in this file. (This is the function of the static modifier of the original C language, and now const also has this function.) It is also added that when extern const is combined with modification, extern will suppress the internal link attribute of const. Therefore, the extern const string will still have external link properties, but it is still unmodifiable.

showing.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const string& obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

main.cpp

#include <iostream>  
#include "ShowInf.hpp"  
using namespace std;  
  
extern const string str = "hello world!";  
  
int main()  
{  
    ShowInf<str>();  
}

​​​​​​​  

Output result:

 

Note: extern can only exist outside the function body in main.cpp. The following situations are wrong:

#include <iostream>  
#include "ShowInf.hpp"  
using namespace std;  
    
int main()  
{  
    extern const string str = "hello world!";      
    ShowInf<str>();  
}  

 

Why can't Const char* variables work?

You have to pass the const char array in the following way:

Main.cpp

#include <iostream>  
#include <string>  
#include "Person.hpp"  
#include "ShowInf.hpp"  
using namespace std;  
  
const char s[] = "hello";  // 加不加extern都OK
  
int main()  
{  
    ShowInf<s>();  
}  

 

ShowInf.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const char* obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

Note: The variable s must be declared as: const char s[] = "hello", not as const char* s = "hello"!

Is it possible to use the class type as the argument of a non-type template in the above way?

I tried the following and the answer was: no.

Because class types cannot be used as compile-time constants. In fact, you can remember this cleverly: a class type is an organic combination of multiple basic data types. Float and double floating-point types can not be used as parameters of non-type templates. How can the class type take on this big task!

Person.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
class Person  
{  
private:  
    int age;  
    string name;  
public:  
    Person(int age, string name)  
    {  
        this->age = age;  
        this->name = name;  
    }  
    void Define(int age, string name)  
    {  
        this->age = age;  
        this->name = name;  
    }  
    friend ostream& operator << (ostream& ostr, Person& obj);  
};  
  
ostream& operator << (ostream& ostr, Person& obj)  
{  
    ostr << obj.name << "的姓名为" << obj.age;  
    return ostr;  
}  

 

Showing.hpp

#include <iostream>  
#include <string>  
using namespace std;  
  
template <const Person& obj>  
void ShowInf()  
{  
    cout << obj << endl;  
}  

 

Main.cpp

#include <iostream>  
#include <string>  
#include "Person.hpp"  
#include "ShowInf.hpp"  
using namespace std;  
  
extern  Person& obj;  
  
int main()  
{  
    obj.Define(19, "张三");  
    ShowInf<obj>();  
}

​​​​​​​  

Output error

 

What are internal links and external links?

Internal linkage means that the symbol is only valid in the compilation unit and cannot be seen by other compilation units. Therefore, multiple compilation units can have the same symbol. The const variable can appear in multiple .cpp files without conflict because it is an internal link.

External linking means that other compilation units can see the symbols of the current compilation unit. If there are the same external link symbols, a symbol redefinition error will be reported during linking.

For templates whose parameters are types, the templates instantiated by the same type will be stored in different storage areas due to the different files they belong to. This is a very arrogant waste of our precious memory. How should we avoid this extravagance What about wasteful situations?

Template instantiation:

It will only be instantiated when the template is used. This feature means that the same instance may appear in multiple object files. For example, when two or more independently compiled source files use the same template and provide the same template parameters, each file will have an instance where the template applies the parameters.

The above problem is not a big deal in a small program, but in a large program, the additional overhead of instantiating the same template in multiple files can be very serious. In the new standard, we can avoid this overhead by controlling explicit instantiation.

Control instantiation

The same as declaring a variable in multiple files, the keyword extern can be used to promise that there will be a non-extern declaration of the instantiation in other locations of the program. You only need to link the .o file containing the instantiation when linking into an executable program. That's it. Therefore, the use of the keyword extern to declare a template will not generate instantiation code in this file.

Demonstrate the specific operation as follows:

The first file is mainly used to define templates (of course the first file here refers to many template files defined in the project):

// Stack.hpp
#include <iostream>  
using namespace std;  
#include <vector>  
  
// 模板的缺省值  
template <class T, class CONT = vector<T> >  
class Stack  
{  
private:  
    CONT element;  
    int Size;  
public:  
    Stack();  
    Stack(T obj);  
    Stack(vector<T> obj);  
  
    void Push(T obj);  
    void Pop();  
    T& Top();  
    bool Empty();  
    int PrintSize();  
    T& At(int order);  
};  
  
template <class T, class CONT /*= vector<T> */>  
T& Stack<T, CONT>::At(int order)  
{  
    if (order >= this->Size)  
    {  
        throw out_of_range("over array range!");  
    }  
    return this->element.at(order);  
}  
  
template <class T, class CONT /*= vector<T> */>  
int Stack<T, CONT>::PrintSize()  
{  
    return this->Size;  
}  
  
template <class T, class CONT /*= vector<T> */>  
bool Stack<T, CONT>::Empty()  
{  
    return this->element.empty();  
}  
  
template <class T, class CONT /*= vector<T> */>  
T& Stack<T, CONT>::Top()  
{  
    if (this->Size == 0)  
    {  
        throw out_of_range("Stack is empty!");  
    }  
    return *(this->element.end());  
}  
  
template <class T, class CONT /*= vector<T> */>  
void Stack<T, CONT>::Pop()  
{  
    if (this->Size == 0)  
    {  
        throw out_of_range("Stack is empty!");  
    }  
    this->element.pop_back();  
    this->Size--;  
}  
  
template <class T, class CONT /*= vector<T> */>  
void Stack<T, CONT>::Push(T obj)  
{  
    this->element.push_back(obj);  
    this->Size++;  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack(vector<T> obj)  
{  
    this->Size = 0;  
    this->element.clear();  
  
    this->element = obj;  
    this->Size = obj.size();  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack(T obj)  
{  
    this->Size = 0;  
    this->element.clear();  
  
    this->element.push_back(obj);  
    this->Size++;  
}  
  
template <class T, class CONT /*= vector<T> */>  
Stack<T, CONT>::Stack()  
{  
    this->Size = 0; // 赋值前一定要初始化  
    this->element.clear();  
}  

 

In the second file, we mainly define all the instantiation templates we want to use in the project:

// TemplateBuild.cpp
#include "Stack.hpp"  
#include <iostream>  
#include <string>  
using namespace std;  
  
template Stack<string>; // 加不加template关键字都OK  
template Stack<int>;  

 

In the third file, we can use the previously defined instantiation template through external links:

// TemplateApplication.cpp
#include "Stack.hpp"  
#include <iostream>  
#include <string>  
using namespace std;  
  
extern template Stack<string>;  
  
int main()  
{  
    Stack<string> Stack_Obj1("张三");  
    cout << Stack_Obj1.At(0) << endl;  
}

​​​​​​​  

When compiling, each .cpp file (TemplateApplication.cpp & TemplateBuild.cpp) in a project will be compiled into a corresponding .o file. The next step after the compiler is compiled is to follow the "extern link keyword extern "Link each .o file (TemplateApplication.o & TemplateBuild.o) obtained in the project together to generate an executable file.

Note: Because we use external links, link external templates that have already been instantiated and use them, the compiler needs to instantiate all members of the template, which is different from our normal template instantiation (in different template instantiations, what members are used Just instantiate what member), this method of instantiating a template will instantiate all members of the template.

Formal parameter requirements for non-type parameter templates

Non-type template parameters are restricted, usually constant integers (including enumeration values) or pointers/references to external linked objects.

Template parameters: constant integer (int, short, long... integer data type that can be determined by the compiler)

#include <iostream>  
using namespace std;  
  
template <unsigned int N>  
void ShowInf()  
{  
    cout << N << endl;  
}  
  
int main()  
{  
    const int a = 10;  
    ShowInf<a>();  
}  

 

Template parameters: pointers/references to external link objects

#include <iostream>  
using namespace std;  
  
template <typename T, T(*Func)(const T& val1,const T& val2) >  
T Operator(T para1, T para2)  
{  
    return Func(para1, para2);  
}  
  
int Add(const int& para1, const int& para2)  
{  
    return para1 + para2;  
}  
  
int main()  
{  
    int para1 = 9, para2 = 10;  
    cout << Operator<int, Add>(para1, para2) << endl;  
} 

​​​​​​​ 

The above code shows the code writing format when the template parameter is a function pointer.

Template parameters: template

#include <iostream>  
using namespace std;  
  
template<typename T>  
class Print  
{  
public:  
    void operator()(const T& val)  
    {  
        cout << val << " ";  
    }  
};  
  
template<typename T>  
class Inc  
{  
public:  
    void operator()(T& val)  
    {  
        val++;  
    }  
};  
  
template<typename T>  
class Dec  
{  
public:  
    void operator()(T& val)  
    {  
        val--;  
    }  
};  
  
template< typename T, template<typename T> class Func>  
void Foreach(T Array[], unsigned size)  
{  
    Func<T> FuncA;  
    for (int i = 0; i < size; i++)  
    {  
        FuncA(Array[i]); // []为数组索引符号  
    }  
    cout << endl;  
}  
  
int main()  
{  
    int Array[] = { 1,2,3,4,5,6,7,8,9,10 };  
    Foreach<int, Inc>(Array, 10);  
    Foreach<int, Print>(Array, 10);  
    Foreach<int, Dec>(Array, 10);  
    Foreach<int, Print>(Array, 10);  
}  

 

operation result:

 

Guess you like

Origin blog.csdn.net/weixin_45590473/article/details/112586725
Recommended