In-depth exploration of C++ templates: from basics to advanced applications

Table of contents

1. Generic programming

1.1 Why do we need generic programming?

2. Template

2.1 Concept

2.2 Function Templates

2.2.1 Concept

2.2.2 Grammar

2.2.3 Examples

 2.2.4 Template instantiation

implicit instantiation

display instantiation

2.2.5 Matching principle of template parameters

2.3 Class Templates

2.3.1 Concept

2.3.2 Syntax

2.3.3 Examples

2.3.4 Precautions

2.3.5 Notes on Interpretation

1. Member function definition:

2. Template parameter derivation:

3. Template specialization:

3. Template file writing (special)

1. Generic programming

When we speak of generic programming, we generally refer to a method of writing generic code in a programming language to work efficiently on different data types. This makes the code more flexible, generic and reusable. In C++, generic programming is mainly implemented through templates, allowing us to create generic code that can be applied to multiple data types.

1.1 Why do we need generic programming?

When writing code, we often need to deal with various types of data. Writing specialized code for each data type each time will result in code redundancy and reduce efficiency. The goal of generic programming is to solve this problem by creating generic code that can be applied to different data types without having to re-write it.

for example:

We want to implement a general function for exchanging data. In C language, we can only implement functions with different names, which is very troublesome. In C++, we can implement function overloading, but there are also disadvantages: the overloaded functions are only of different types , the code reuse rate is relatively low, as long as a new type appears, you need to manually add the corresponding function implementation, and the maintainability of the code will also decrease!

void Swap(int &left, int &right) {
    int temp = left;
    left = right;
    right = temp;
}

void Swap(double &left, double &right) {
    double temp = left;
    left = right;
    right = temp;
}

void Swap(char &left, char &right) {
    char temp = left;
    left = right;
    right = temp;
}

//其他类型
.........

So is there any other way? I just need to provide the type I need, and the compiler will automatically implement the corresponding version of the function!

C++ templates are a powerful tool to solve this problem.

2. Template

2.1 Concept

C++ templates are an important feature for implementing generic programming, allowing you to write generic code that can be applied to multiple data types. Templates are used extensively in C++, especially in the standard library, to create common containers, algorithms, and data structures.

There are two main types of templates in C++: function templates and class templates .

2.2 Function Templates

2.2.1 Concept

Function templates are a mechanism in C++ for creating generic functions that allow you to write a generic function that can be used with multiple data types without having to write separate functions for each data type. Function template is one of the important tools to realize generic programming in C++.

2.2.2 Grammar

The syntax of a function template is simple, it consists of a template header and a function body :

 Among them, template <typename T>a template is declared, which is a placeholderT for type parameters . Used in function parameters and return types , the compiler will automatically determine the actual data type according to the parameter type. (typename is used to define template parameter keywords, and class can also be used ( remember: struct cannot be used instead of class)).T

2.2.3 Examples

It is possible to create generic functions that can be used with different data types, such as the maximum function:

 2.2.4 Template instantiation

Function template instantiation refers to the process in which the compiler generates a specific type of function implementation code according to the actual parameter type passed when using the function template. This process takes place during the compile phase to ensure that the correct code is generated for parameters of a particular type.

Template parameter instantiation is divided into: implicit instantiation and explicit instantiation

implicit instantiation

It means that when a function template is called in the code, the compiler will implicitly generate a function implementation of a specific data type according to the passed parameter type. This is a common way of instantiating function templates.

template <typename T>
T Max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    int result = Max(5, 10); // 隐式实例化为 int Max(int a, int b)
    return 0;
}

Assuming we call Max(5, 10), the compiler will perform the following steps to instantiate the function template:

  1. The compiler sees the call Max(5, 10)and needs to instantiate the function template Max.
  2. The compiler analyzes the parameters 5and 10, to determine what their types are int.
  3. The compiler Treplaces the template parameter with int, generating a specific implementation like this:

 So how do I call it like this?

int a = 2;
double b = 3.0;

Max(a, b);

nature of the problem

This statement cannot be compiled . When the compiler encounters multiple actual parameters of different types, it needs to determine a suitable template parameter type. However, in some cases it may arise that the type of the template parameter cannot be determined unambiguously because there is only one template parameter in the template parameter list, but the actual argument may be of a different type.

Type Inference and Type Conversion

During template instantiation, the compiler usually does not perform implicit type conversions, as this could lead to ambiguous results. For example, in your case, the compiler doesn't know whether it should Tinfer the template parameter as intor double, and thus cannot make a correct instantiation.

Note: In templates, the compiler generally does not perform type conversion operations

There are two common ways to handle this:

1. User forced conversion

2. Explicit instantiation

forced conversion

Max(a, (int)b);

//或者

Max((double)a, b);
display instantiation

Specifying the actual type of the template parameter in <> after the function name can explicitly tell the compiler the type of instantiation to avoid the problem of template parameter inference

Max<int>(a, b);

//或者

Max<double>(a, b);

If the types do not match, the compiler will try to perform an implicit type conversion, and if the conversion fails, the compiler will report an error .

2.2.5 Matching principle of template parameters

When there are non-template functions and function templates with the same name, the compiler will select the appropriate function according to the matching rules when calling the function. Details are as follows:

  1. Prefer non-template functions:

    If there is a non-template function that exactly matches the type of the actual parameter, the compiler will prefer to call this non-template function because it is more specialized in type matching.
  2. Template parameter matching:

    If there is no non-template function that exactly matches the actual parameter type, the compiler will try to perform template parameter matching to find the best matching function template.
  3. A better match:

    If the template function can generate a better matching instance, the compiler will choose to call this template function. For example, if the template parameters can better match the actual parameters through implicit type conversion, then the template will be chosen.

Let's take a look at an example

#include <iostream>

// 通用输出函数模板
template<class T>
void Print(T value) {
    std::cout << value << std::endl;
}

// 重载的输出函数,专门处理 const char* 类型
void Print(const char* value) {
    std::cout << "String: " << value << std::endl;
}

int main() {
    Print(42);          // 调用通用函数模板 Print<T>
    Print("Hello");     // 调用重载函数 Print(const char*)
    Print(3.14);        // 调用通用函数模板 Print<T>
    Print<const char*>("Hello");    //显示调用通用函数模板 Print<T>

    return 0;
}
  1. template<class T> void Print(T value)is a generic output function template for outputting different types of data.

  2. void Print(const char* value)is an const char*overload of the output function that deals specifically with the type.

  3. In maina function, calling Print(42)will match the generic function template because the types match.

  4. Calling Print("Hello")will match the overloaded function because const char*is more specialized.

  5. Calling Print(3.14)will still match the generic function template.

  6. Showing the call template, Print<const char*>("Hello") will obviously call the function template.

another example

#include <iostream>

// 专门处理 int 的加法函数
int Add(int left, int right) {
    return left + right;
}

// 通用加法函数模板
template<class T1, class T2>
T1 Add(T1 left, T2 right) {
    return left + right;
}

void Test() {
    Add(1, 2);       // 调用非函数模板,与 int Add(int left, int right) 匹配
    Add(1, 2.0);     // 调用函数模板,生成更匹配的版本
}
  1. int Add(int left, int right)is an intaddition function that deals specifically with the type.

  2. template<class T1, class T2> T1 Add(T1 left, T2 right)is a generic addition function template that can be applied to different types of data.

  3. When calling Add(1, 2), the compiler chooses to call the non-function template because it exactly matches the actual argument types.

  4. When calling Add(1, 2.0), the compiler chooses to call the function template. Although non-function templates can also match, function templates can generate a more matching version, and the compiler will generate a more matching Addfunction based on the actual parameters.

2.3 Class Templates

2.3.1 Concept

When it comes to class templates, you are creating a template that can generate different classes based on different data types. Class templates allow you to write generic class definitions that apply to multiple data types without having to write separate code for each type.

2.3.2 Syntax

The syntax of a class template is similar to that of a function template, but applied to the definition of a class. Here is an example of a simple class template:

template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

2.3.3 Examples

int main() {
    MyContainer<int> intContainer(42);
    MyContainer<double> doubleContainer(3.14);

    std::cout << intContainer.GetValue() << std::endl;    // 输出: 42
    std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14

    return 0;
}

2.3.4 Precautions

  1. Member function definition: Member functions of class templates usually also need to be defined within the template class, otherwise keywords need to be used outside the definition templatefor template declaration and implementation.

  2. Template parameter deduction: When instantiating a class template, the compiler can automatically deduce the type of the template parameter, or use an explicitly specified method for instantiation.

  3. Template specialization: Class templates can also be specialized to provide custom implementations for specific types, similar to the template specialization of function templates.

  4. Code generation at instantiation: When a class template is instantiated, the compiler will generate a corresponding class definition based on the actual type parameters, thereby creating a class of a specific type.

2.3.5 Notes on Interpretation

1. Member function definition:

In a class template, member functions can be defined inside the template class, and can also be templatedeclared and implemented outside the class using keywords.

template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

// 在类外部定义成员函数模板
template <class T>
T MyContainer<T>::GetValue() {
    return value * 2;
}

2. Template parameter derivation:

When the compiler instantiates a class template, it can automatically deduce the type of the template parameter, or it can use an explicitly specified method for instantiation.

int main() {
    MyContainer intContainer(42);
    MyContainer<double> doubleContainer(3.14);

    std::cout << intContainer.GetValue() << std::endl;    // 输出: 42
    std::cout << doubleContainer.GetValue() << std::endl; // 输出: 3.14

    return 0;
}

3. Template specialization:

Class templates can also be specialized to provide custom implementations for specific types, similar to template specialization of function templates.

// 类模板定义
template <class T>
class MyContainer {
private:
    T value;

public:
    MyContainer(T val) : value(val) {}

    T GetValue() {
        return value;
    }
};

// 类模板的特化版本
template <>
class MyContainer<int> {
private:
    int value;

public:
    MyContainer(int val) : value(val) {}

    int GetValue() {
        return value * 2; // 自定义的实现
    }
};

3. Template file writing (special)

Different from normal code file writing, template file file writing involves some special precautions and methods to ensure correct instantiation and linking of templates. I'll give you a simple example of how to split templates into header and source files.

Example:

Suppose we have a class template MyTemplate, which contains member functions, and we want to separate it into header and source files.

MyTemplate.h (header file):

#ifndef MYTEMPLATE_H
#define MYTEMPLATE_H

template <typename T>
class MyTemplate {
public:
    MyTemplate(T value);
    void PrintValue();
    
private:
    T data;
};

#endif

MyTemplate.cpp (source file):

#include <iostream>
#include "MyTemplate.h"

template <typename T>
MyTemplate<T>::MyTemplate(T value) : data(value) {}

template <typename T>
void MyTemplate<T>::PrintValue() {
    std::cout << "Value: " << data << std::endl;
}

// 显式实例化模板
template class MyTemplate<int>;
template class MyTemplate<double>;

main.cpp (main file):

#include "MyTemplate.h"

int main() {
    MyTemplate<int> intObj(42);
    MyTemplate<double> doubleObj(3.14);
    
    intObj.PrintValue();
    doubleObj.PrintValue();
    
    return 0;
}

In the source file MyTemplate.cpp, we provide the implementation of the class template , including the definition of the constructor and member functions. We also use explicit instantiation ( template class MyTemplate<int>;and template class MyTemplate<double>;) in the source file to ensure that a specific type of template instance is generated during compilation.

Finally, in the main file main.cpp, we just need to include the header file MyTemplate.hand use the class template. When compiling, the compiler will extract the implementation part of the class template MyTemplate.cppfrom and instantiate it.

While this way of writing templates in separate files requires some extra steps, it does make the code more organized and maintainable.

Further explanation

MyTemplate.cppExplicit instantiation is used in source files to ensure that the compiler generates template instantiation code for a specific type during compilation. Although the definition of a function template is usually placed in a header file, due to C++'s separate compilation model, the implementation of the template must be in the same compilation unit so that the compiler can instantiate it when needed.

In the implementation of class templates, since the template parameters may be of many different types, the compiler will not automatically generate instantiation code for each type unless the template is explicitly used where required. This is why explicit instantiation is used: you tell the compiler to generate instantiation code for a particular type, to ensure that it can find the correct template implementation at link time.

The same principle applies to writing functions in separate files! ! !

This is the end of the basics of function templates! The author will continue to update the advanced template tutorial! !

Guess you like

Origin blog.csdn.net/weixin_57082854/article/details/132178260