In-depth explanation of C++ elementary templates

insert image description here

non-type template

insert image description here

Non-type template (Non-Type Template) is a form of template in C++, which allows you to pass other values ​​besides types in the template, such as integers, enumerations, pointers, etc. These parameters can be parsed at compile time and used to generate instantiated versions of templates.

A non-type template parameter (Non-Type Template Parameter) is in the template declaration, as part of the parameter, not part of the type. They can be constant expressions such as integer constants, enumerations, pointers, references, etc. The values ​​of non-type template parameters must be known at compile time, since they are used to generate a specific instance of the template.

Template parameters classify type parameters to non-type parameters.

Type parameter : Appears in a template parameter list, followed by a parameter type name such as or class. Non-type parameter : It is to use a constant as a parameter of a class (function) template, and the parameter can be used as a constant in the class (function) template. A typical example of using non-type template parameters is to implement a fixed-size array container, where the size of the array is specified by the template parameter at compile time.typename

For example, here's an example using a non-type template parameter:

template <typename T, int Size>
class FixedSizeArray {
    
    
private:
    T data[Size];
public:
    // ...
};

// 实例化一个大小为 10 的 FixedSizeArray,存储 int 类型
FixedSizeArray<int, 10> intArray;

In this case, int Sizeit is a non-type template parameter that determines FixedSizeArraythe size of the array of . At compile time, intArraythe size of will be determined as the parameter passed to 10at instantiation time .10Size

Notice:

  1. Floats, class objects, and strings are not allowed as non-type template parameters
  2. Non-type template parameters must have a compile-time confirmed result.

template specialization

Template Specialization ( Template Specialization ) is C++a mechanism that allows you to provide custom implementations for specific types or specific conditions. Template specialization allows you to provide a specialized template implementation that overrides the behavior of a generic template for certain types or conditions.

In general templates, you might provide a common template implementation for different types of data. However, in some cases, specific types may require special handling or customized behavior. At this point, you can provide special implementations for these specific types through template specialization.

There are two types of template specializations:

Class Template Specialization: A special implementation for a particular type.
Function Template Specialization: A special implementation for a particular type.

In some cases, failure to use a specialization of a template can cause the compiler to choose the wrong implementation, resulting in ambiguous errors or unexpected behavior. A common situation is that parameter matching of function templates is ambiguous, and the compiler cannot determine which template implementation should be used. In this case, specialization can provide explicit information to help the compiler choose the correct implementation.

Here's an example of what can go wrong without template specialization:

#include <iostream>

template <typename T>
void printType(T value) {
    
    
    std::cout << "Generic template" << std::endl;
}

// 模板特化版本,针对 int 类型
template <>
void printType<int>(int value) {
    
    
    std::cout << "Specialized template for int" << std::endl;
}

int main() {
    
    
    int num = 5;
    printType(num);  // 编译错误,不明确的调用

    return 0;
}

In the example above, without template specialization, the compiler cannot determine which template implementation should be called, because the printTypefunction can accept arguments of any type. This results in compilation errors because the compiler cannot choose the correct implementation based on the context.

By printType<int>providing a specialization for , we explicitly tell the compiler intwhich implementation it should use when dealing with type parameters. In this case, specialization can resolve the problem of ambiguous calls and ensure that the compiler chooses the correct implementation.

1. Function template specialization

The specialization steps of a function template:

  1. There must be a basic function template first
  2. A keyword templateis followed by a pair of empty angle brackets<>
  3. The function name is followed by a pair of angle brackets, which specify the type to be specialized
  4. Function parameter table: It must be exactly the same as the basic parameter type of the template function. If it is different, the compiler may report some strange errors.

Example of function template specialization:

#include <iostream>

// 通用的函数模板
template <typename T>
T add(T a, T b) {
    
    
    return a + b;
}

// 函数模板的特化版本,针对 int 类型
template <>
int add(int a, int b) {
    
    
    std::cout << "Specialized version for int" << std::endl;
    return a + b + 10;
}

int main() {
    
    
    int result1 = add(5, 3);    // 调用通用版本
    std::cout << "Result 1: " << result1 << std::endl;  // 输出: 8

    int result2 = add<int>(5, 3);  // 调用特化版本
    std::cout << "Result 2: " << result2 << std::endl;  // 输出: Specialized version for int
                                                        // Result 2: 18

    return 0;
}

In the above example, addit is a function template to calculate the sum of two numbers. By add<int>providing a specialization for , we override the default behavior and customize it for a particular type. In mainthe function, we show how to call the generic and specialized versions, and their output.

Note :In general, if a function template encounters a type that cannot be handled or is handled incorrectly, the function is usually given directly for the sake of simplicity.

2. Class template specialization

In C++, a class template specialization refers to providing a customized implementation of a class template for a particular type or condition. There are two types of class template specialization: Full Specialization and Partial Specialization.

Full Specialization : A full specialization is a complete specialization for a particular type. When you provide a full specialization for a particular type, it overrides the generic class template definition.The syntax for full specialization is to add after the template name <>and specify the type of specialization.

Example:

// 通用的类模板
template <typename T>
class MyTemplate {
    
    
public:
    void print() {
    
    
        std::cout << "Generic Template" << std::endl;
    }
};

// 类模板的全特化版本,针对 int 类型
template <>
class MyTemplate<int> {
    
    
public:
    void print() {
    
    
        std::cout << "Specialized Template for int" << std::endl;
    }
};

Partial Specialization : Partial specialization is the specialization of template parameters under certain conditions, usually for finer-grained customization. Partial specialization allows you to provide specializations for certain cases, rather than full specializations for every type.The syntax for partial specialization is to add after the template name <>, and specify the parameters to be specialized in angle brackets.

Example:

// 通用的类模板
template <typename T, typename U>
class Pair {
    
    
public:
    Pair(T first, U second) : first_(first), second_(second) {
    
    }
    void print() {
    
    
        std::cout << "Generic Pair: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    U second_;
};

// 类模板的偏特化版本,针对两个相同类型的参数
template <typename T>
class Pair<T, T> {
    
    
public:
    Pair(T first, T second) : first_(first), second_(second) {
    
    }
    void print() {
    
    
        std::cout << "Specialized Pair for same type: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    T second_;
};

In the above example, a full specialization provides a specialization for an integral type, while a partial specialization provides a specialization for two parameters of the same type.

Summarize:

Full specialization : Provides a full specialization for a particular type.
Partial specialization : Provide specialized versions for specific cases or conditions.

By using template specialization, you can provide a precise implementation for a class template in cases where custom behavior is required, increasing the flexibility and applicability of the template.

Class templates allow us to provide the same code structure for different data types to accommodate multiple types of needs. However, in some cases the compiler may not be able to deduce how to compare objects of different types. That's why, in the example below, the compiler may not sort the Date class correctly without using a specialization of the class template.

By default, std::sortthe function uses element- <wise operators to compare element sizes. This is no problem for primitive types such as integers, or < types that support operators. butFor custom types (such as the Date class), the compiler has no way of knowing how to perform the comparison.

By using specializations of class templates, we provide explicit comparison logic for different types of date classes, namely operator<operator overloading. This tells the compiler how to compare Date objects under certain circumstances, so that std::sortthe function will work correctly.

Specialized class templates allow us to provide custom implementations for specific types where needed, thereby solving problems that the compiler cannot infer and ensuring that the program can run correctly.

Here is an example on the date class, using the class template specialization to compare the size and std::sortsort the dates via the function .
In the example, we define a Dateclass template and then Dateprovide specializations for the class to implement date comparisons:

#include <iostream>
#include <vector>
#include <algorithm>

// 通用的类模板
template <typename T>
class Date {
    
    
public:
    Date(T year, T month, T day) : year_(year), month_(month), day_(day) {
    
    }

    // 比较运算符
    bool operator<(const Date& other) const {
    
    
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }

    void print() {
    
    
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }

private:
    T year_;
    T month_;
    T day_;
};

// 类模板的特化版本,用于 int 类型
template <>
class Date<int> {
    
    
public:
    Date(int year, int month, int day) : year_(year), month_(month), day_(day) {
    
    }

    bool operator<(const Date<int>& other) const {
    
    
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }

    void print() {
    
    
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }

private:
    int year_;
    int month_;
    int day_;
};

int main() {
    
    
    std::vector<Date<int>> dates = {
    
    
        {
    
    2022, 8, 15},
        {
    
    2021, 12, 25},
        {
    
    2022, 1, 1},
        {
    
    2022, 3, 20}
    };

    std::cout << "Before sorting:" << std::endl;
    for (const auto& date : dates) {
    
    
        date.print();
    }

    std::sort(dates.begin(), dates.end());

    std::cout << "After sorting:" << std::endl;
    for (const auto& date : dates) {
    
    
        date.print();
    }

    return 0;
}

In the above example, we first define a generic Dateclass template for storing year, month, and day information. We then Date<int>provide specializations for that implement intdate comparison operations based on the type.

In mainthe function, we create an array to store the dates std::vector, and then use std::sortthe function to sort the dates. Since we Date<int>provide a specialization for , it compares dates and sorts them correctly. Finally, we output the date order before and after sorting respectively.

Template separate compilation

1. What is separate compilation

Separate Compilation ( Separate Compilation ) is a software development technique that divides a large program into multiple small source code files, and each file contains the definition and implementation of one or more related functions, classes, or variables. These source code files can be compiled in different compilation units and then combined into an executable program during the linking stage.

The main goal of separate compilation is to improve code maintainability, compilation speed, and resource utilization. Here are some advantages of separate compilation:

Modular development : Divide the program into multiple modules, each module is responsible for a specific function. In this way, different developers can work on different modules independently, thereby improving development efficiency.
Code reuse : In different projects, modules that have already been written and tested can be reused, thereby reducing development time and resources.
Compilation speed : Only modified modules need to be recompiled, other unmodified modules can remain unchanged. This can significantly speed up compile times.
Resource utilization : Only required modules will be compiled, reducing unnecessary compilation and memory usage.

The basic process of separate compilation is as follows:

Writing modules : Divide the program into modules and write the definition and implementation of each module.
Compile Modules : Compile the source code of each module separately, producing object files (eg .obj, .o files).
Link module : Link all object files together, resolve reference relationships, and generate the final executable file.

In separate compilation, header files (.h 文件)are usually used to store function and class declarations, while source files (.cpp 文件)contain function and class implementations. This division can help the compiler understand the interface and implementation of each module, so that it can establish the correct link between different modules.

Separate compilation is an important practice in modern software development, which helps organize complex projects, improve development efficiency, and reduce maintenance costs.

2. Separate compilation of templates

In C++, separate compilation of templates refers to placing the declaration and implementation of templates in different files . The declaration of the template is usually placed in the header file ( .hor .hppfile), and the implementation of the template is placed in the source file ( .cppfile).

The separate compilation of templates is to solve the problem of template instantiation at link time . The C++ compiler needs to instantiate the template where the template is used, but the compiler can only see the content of the current source file when compiling a source file, and cannot know the implementation details of the template in other source files. therefore,If the declaration and implementation of the template are placed in the header file and referenced by multiple source files, the template will be instantiated multiple times, and eventually there will be multiple identical instantiations in the linking phase, causing a redefinition error.

The general practice of separating compilation definitions of templates is:

Put the declaration of the template in a header file (eg. .hfile).
Put the implementation of the template in a source file (eg. .cppfile) and include the implementation of the template at the end of the source file.

The advantage of this is that each source file will only instantiate the template once, avoiding redefinition problems.
However, separate definitions of templates can also cause problems, such as:

Compilation errors are difficult to locate : If there is an error in the implementation of the template, the compiler may not be able to give detailed error information where the template is used, making debugging difficult.
Difficulty in code maintenance : The implementation of templates is scattered in multiple source files, which may make code maintenance more complicated. It is necessary to ensure that the template implementations of each source file are consistent.
Reduced readability : The implementation of the template is separated into the source file, which may reduce the readability and understandability of the code.

In order to avoid the problems caused by the template separation definition, some programming practices recommend that both the template declaration and implementation be placed in the header file, so that the complete implementation details can be seen where the template is used. If the implementation of the template is more complicated, the problem of separate definition can be solved by specializing the template.

Give an example of separate definitions of templates in C++

This example demonstrates that redefinition errors can result if the declaration and implementation of a template are separated into different files.

Suppose we have the following two files:

Stack.h (header file, contains template declarations):

#ifndef STACK_H
#define STACK_H

template <typename T>
class Stack {
    
    
public:
    Stack();
    void push(const T& value);
    T pop();

private:
    T elements[10];
    int top;
};

#include "Stack.cpp"

#endif

Stack.cpp (source file, contains the implementation of the template):

#ifndef STACK_CPP
#define STACK_CPP

template <typename T>
Stack<T>::Stack() : top(-1) {
    
    }

template <typename T>
void Stack<T>::push(const T& value) {
    
    
    elements[++top] = value;
}

template <typename T>
T Stack<T>::pop() {
    
    
    return elements[top--];
}

#endif

In this example, we try to include the source file in the header file Stack.cpp. This can cause the following problems:

Redefinition error : When multiple source files include the same header file, each source file will include Stack.cppthe template implementation in , causing a redefinition error at link time.
The solution is,Put both template declaration and implementation in a header file, or use explicit instantiation of templates to avoid redefinition errors.
Explicit instantiation is a way of telling the compiler to instantiate a template on a specific type, and you can avoid problems by using the following syntax in your source files:

template class Stack<int>;
template class Stack<double>;
// 等等

This ensures that the template will only be instantiated once on a particular type, avoiding redefinition errors.

While explicit instantiation solves the problem of separate definition of templates, it has some potential drawbacks:

  1. Difficult to maintain : If multiple different types are used for instantiation in the code, each type needs to be explicitly instantiated once in the source file. this mayLead to code redundancy and increase the difficulty of maintenance, especially in large projects where templates are used extensively.
  2. Reduced readability :The syntax for explicit instantiation is relatively cumbersome and may reduce code readability. Programmers need to understand this special syntax and make appropriate explicit instantiations in source files.
  3. Affects compile time : Explicit instantiation causes the compiler to generate concrete instantiation code for the template at compile time, which increases compile time. Especially when templates are heavily used,Compilation time may increase significantly
  4. Limitations :Explicit instantiation only applies to those templates that are known to be instantiated on a particular type. For some generic templates that may be used on different types, it may be impractical to explicitly instantiate them for every possible type.

To sum up, while explicit instantiation is one way to solve the problem of template separation definition, it may introduce some inconveniences and potential problems. Therefore, some projects prefer to put the template declaration and implementation in the header file to avoid these problems.

Template Summary

advantage:

  1. Versatility and reusability :Templates allow writing generic code that works with many data types and data structures. This generality promotes code reuse and reduces the need to write repetitive code.
  2. Type Safety : Templates can be type-checked at compile time to ensure that the correct data type is used when the template is instantiated. thisHelps avoid type errors at runtime
  3. Performance advantages :Template-generated code is generated at compile time from actual types, so there is no function call overhead, can improve the performance to a certain extent.
  4. Generic algorithms : Both algorithms and containers in the C++ standard library use templates, allowing developersIt is convenient to use common sorting, searching, traversal and other algorithms
  5. Abstraction and encapsulation :Templates can implement abstract data types, encapsulating data structures and operations, providing a higher level of abstraction.
  6. Compile-time error checking :Template errors are usually detected at compile time, enabling developers to detect and fix problems early.

shortcoming:

  1. Difficult to understand compile-time error messages : Compiler error messages for template errors can be very complex and difficult to understand for beginners. This mayIncreased difficulty of debugging
  2. Compile time increases :Use of templates can lead to increased compile times, especially in large projects. Instantiation of templates generates multiple versions of the code at compile time, possibly causing the compiler to spend more time.
  3. Code bloat :Instantiation of templates can result in multiple copies of similar code being generated, potentially increasing the size of the executable.
  4. Decreased readability :Some complex template code can be difficult to read and understand, especially where metaprogramming tricks are involved.
  5. Maintenance Difficulty : When template implementations are separated into different files,maintenance can become difficult, especially when it comes to things like explicit instantiation.

All things considered, templates are a powerful tool that can provide huge advantages in many situations. However, when using templates, developers need to weigh their advantages and disadvantages and make an appropriate choice based on the specific situation.

Guess you like

Origin blog.csdn.net/kingxzq/article/details/132260644