C++基础与深度解析 | 模板 | 函数模板 | 类模板与成员函数模板 | concepts | 完美转发 | 模板的其他内容

文章目录

  • 一、函数模板
  • 二、类模板与成员函数模板
  • 三、Concepts(C++20)
  • 四、模板相关内容
    • 1.数值模板参数与模板模板参数
    • 2.别名模板与变长模板
    • 3.包展开与折叠表达式
    • 4.完美转发与lambda表达式模板
    • 5.消除歧义与变量模板

一、函数模板

  在C++中,函数模板是一种允许你编写可以处理多种数据类型的函数的方式。函数模板通过使用模板参数来实现泛型编程,这样同一个函数就可以用不同的数据类型来调用

函数模板不是函数。

使用 template 关键字引入模板

  使用 template<typename T> 或者 template<class T> 来定义一个函数模板。typenameclass 在这里可以互换使用,但通常 typename 更常用于模板参数。

template<typename T> 
void fun(T) 
{
    //...
}

函数模板的声明与定义

  在一个翻译单元中,函数声明可以包含多次,但函数定义只能包含一次。函数模板的声明和定义通常写在一起。例如:

template<typename T>
void functionTemplate(T param) {
    // 函数实现
}

这个函数模板 functionTemplate 可以接受任何类型的参数 param

函数模板参数

  函数模板中包含了两对参数:函数形参 / 实参;模板形参 / 实参。

  • 函数形参:函数定义中的参数,如上面例子中的 param
  • 函数实参:调用函数时传递给函数的具体参数值。
  • 模板形参:在模板定义中使用的类型或值的占位符,如 T
  • 模板实参:在调用模板时,提供给模板形参的具体类型或值

函数模板的显式实例化

  在C++中,函数模板的显式实例化是一种告诉编译器创建一个特定函数模板实例的操作。

  • 显式实例化的语法

    显式实例化使用模板函数名后跟尖括号内指定的类型参数来完成。例如,fun<int>(3) 告诉编译器实例化模板函数 fun 并使用 int 作为模板参数,然后调用这个实例化函数并传递整数 3 作为参数。

  • 实例化会使得编译器产生相应的函数(函数模板并非函数,不能调用)

    使用C++ Insights可知:

    image-20240606113537606

  • 编译期的两阶段处理(函数模板的实例化发生在编译期)

    • 模板语法检查

      编译器首先检查模板代码的语法是否正确

    • 模板实例化

      编译器根据提供的模板参数来生成具体的函数代码。

  • 模板必须在实例化时可见–翻译单元的一处定义原则

    模板的定义只在一个翻译单元中出现一次,以避免链接错误。

  • 与内联函数的异同

    虽然函数模板与内联函数都满足翻译单元级别的一次定义原则而非程序级别的一次定义原则,但原因是不同的。

    • 共同点:两者都可以在编译时进行优化,并且都可以在头文件中定义。
    • 不同点:内联函数不涉及类型参数,而函数模板是类型安全的泛型函数。

函数模板的重载

  在C++中,函数模板的重载指的是可以定义多个具有相同名称但模板参数不同的函数模板。当编译器尝试确定哪个函数模板实例与给定的调用匹配时,它会根据传递给函数的实参类型来解析重载。如果存在多个匹配的模板实例,编译器将选择最匹配的一个。

示例:

#include <iostream>

template<typename T>
void fun(T input)
{
	std::cout << input << std::endl;
}

template<typename T>
void fun(T* input)
{
	std::cout << *input << std::endl;
}

template<typename T, typename T2>
void fun(T input, T2 input2)
{
	std::cout << input << std::endl;
  	std::cout << input2 << std::endl;
}

int main()
{
  	double x = 3.14;
	fun<int>(3);
  	fun<double>(&x);
}

编译后的结果为

#include <iostream>

template<typename T>
void fun(T input)
{
  (std::cout << input) << std::endl;
}

/* First instantiated from: insights.cpp:25 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void fun<int>(int input)
{
  std::cout.operator<<(input).operator<<(std::endl);
}
#endif


template<typename T>
void fun(T * input)
{
  (std::cout << *input) << std::endl;
}

/* First instantiated from: insights.cpp:26 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void fun<double>(double * input)
{
  std::cout.operator<<(*input).operator<<(std::endl);
}
#endif


template<typename T, typename T2>
void fun(T input, T2 input2)
{
  (std::cout << input) << std::endl;
  (std::cout << input2) << std::endl;
}

int main()
{
  double x = 3.1400000000000001;
  fun<int>(3);
  fun<double>(&x);
  return 0;
}

模板实参的类型推导:(隐式实例化)

  在C++中,模板实参的类型推导是一个自动确定模板参数类型的过程,推导是基于函数实参(表达式)确定模板实参的过程。如果函数模板在实例化时没有显式指定模板实参,那么系统会尝试进行推导。

模板实参类型推导的基本原则:(与auto类型推导相似)

  • 当函数形参是左值引用或指针

    忽略实参表达式的引用部分,并尝试匹配表达式的类型与形参类型来确定模板实参。

    示例:

    template<typename T>
    void func(T& param) {
        // ...
    }
    int main() 
    {
        const int a = 5;
    	func(a); // T& 推导为 const int
    }
    

    image-20240606133737821

  • 当函数形参是万能引用(使用 T&& 声明)时

    模板实参的类型推导将根据实参表达式的值类别(左值或右值)来确定:

    • 如果实参是一个右值,模板实参将被推导为被推导为去掉引用的基本类型
    • 如果实参是一个左值,模板实参将被推导为左值引用类型,这将触发引用折叠规则。

    示例:

    template<typename T>
    void func(T&& param) {
        // ...
    }
    int main() 
    {
        const int a = 5;
    	func(a); // T 推导为 const int&,引用折叠指的是const int& && ==> const int&
      	func(10.1); //T 推导为double
    }
    

    image-20240606134324822

  • 当函数形参不包含引用

    模板实参的类型推导将忽略实参表达式的引用部分和顶层 const,并且:

    • 数组和函数类型将转换成相应的指针类型。
    • 其他类型将直接推导为该类型。

    示例:

    template<typename T>
    void func(T param) {
        // ...
    }
    
    void someFunction()
    {
    }
    
    int main()
    {
      	int x = 2;
      	const int& y = x;
      	func(x);	//T推导为 int,忽略引用与顶层const
      	const int* const ptr = &x;
      	func(ptr);	//T推导为const int*,忽略顶层const
        int arr[] = {1, 2, 3};
        func(arr); // T 推导为 int*,因为 arr 是数组类型
    
        void (*funcPtr)() = someFunction;
        func(funcPtr); // T 推导为 void (*)(),因为 funcPtr 是函数指针
    }
    
    

    image-20240606140314544

模板实参并非总是能够推导得到

  • 如果模板形参与函数形参的类型无关,则编译器可能无法从函数实参推断出模板实参的类型。

    例如:

    template<typename T, typename U>
    U func(T param) {
        // ...
    }
    
  • 即使相关,也不一定能进行推导,

  • 推导成功也可能存在因歧义而无法使用

    例如:

    template<typename T>
    void func(T param1, T param2) {
        // ...
    }
    
    int main()
    {
        func(3, 5.0);
    }
    

在无法推导时,编译器会选择使用缺省模板实参,可以为任意位置的模板形参指定缺省模板实参。

注意与函数缺省实参的区别,不需要保证缺省实参右边全部为缺省实参

例如:

template<typename T, typename U = int>
U func(T param) {
    // ...
}

int main()
{
    func(3);
}

显式指定部分模板实参

  即在调用模板函数时明确指定其中一些模板参数,而让编译器自动推导剩余的参数。

  • 显式指定的模板实参必须从最左边开始,依次指定

    一旦你开始显式指定模板实参,编译器将自动推导剩余未指定的模板参数。如果推导失败,将导致编译错误。

  • 模板形参的声明顺序会影响调用的灵活性

    例如:

    //这种模板形参的声明顺序会编译错误
    template<typename T, typename U>
    U func(T param) {
        // ...
    }
    
    template<typename U, typename T>
    U func(T param) {
        // ...
    }
    
    int main()
    {
        func<int>(3);
    }
    

    image-20240606143454338

函数模板自动推导时会遇到几种情况

  • 函数形参无法匹配—— SFINAE (替换失败并非错误)

    当模板参数推导失败时,如果是因为类型不匹配导致的,编译器会认为这不是一个错误,而是简单地排除这个模板实例化选项。

  • 模板与非模板同时匹配,匹配等级相同,此时选择非模板的版本

    如果模板函数和非模板函数都可以匹配同一个调用,并且它们的匹配等级相同,那么编译器将选择非模板函数。这是因为在C++中,非模板函数的优先级高于模板函数:

    例如:

    void func(int i) {
        // ...
    }
    
    template<typename T>
    void func(T t) {
        // ...
    }
    
    int main()
    {
        func(5); // 调用非模板 func(int i),因为匹配等级相同
    }
    
  • 多个模板同时匹配,此时采用偏序关系确定选择”最特殊“的版本

    如果有多个模板实例都可以匹配同一个调用,C++的重载解析规则将根据偏序关系来确定“最特殊”的版本。这通常涉及到模板参数的特化程度,更具体的模板实例会被优先选择:

    例如:

    template<typename T>
    void func(T t) {
        // ...
    }
    
    template<>
    void func<int>(int i) {
        // ...
    }
    
    int main()
    {
       func(5); // 调用模板特化 func<int>(int i),因为它更具体 
    }
    

函数模板的实例化控制:显式实例化但不调用

  • 显式实例化定义

    显式实例化定义是告诉编译器为特定的模板参数生成一个实例。这通常在模板定义的实现文件中完成。

    template 
    void fun<int>(int);
    或者
    template 
    void fun(int);
    

    image-20240606151909033

  • 显式实例化声明

    显式实例化声明用于告诉编译器存在一个显式实例化的定义。

    extern template 
    void fun<int>(int);
    或者
    extern template 
    void fun(int);
    

    如果引入了显式实例化声明,就不会产生模板实例,减轻了编译器的负担,在链接过程中也不需要将相同的实例删除掉,提升编译与链接速度。

  • 注意一处定义原则(程序级别)

    C++要求每个模板显式实例化在程序中只能有一个定义。这意味着显式实例化定义只能出现在一个编译单元(通常是一个.cpp文件)中。违反这一原则会导致链接错误。

  • 注意实例化过程(显式实例化定义)中的模板形参推导

函数模板的(完全)特化

  C++中的函数模板特化是为特定类型提供特定实现的一种方式。与函数重载不同,特化是为已经存在的模板函数提供针对特定类型的特定实现。本质上函数模板特化就是函数模板实例化。

完全特化语法:

// 模板定义
template<typename T>
void f(T t) {
    // 通用实现
}

// 函数模板的完全特化
template<>
void f<int>(int t) {
    // int 类型的具体实现
}
  • 并不引入新的(同名)名称,只是为某个模板针对特定模板实参提供优化算法

    特化不会创建新的函数名称。它只是为模板函数提供了一个针对特定参数的定制版本。这意味着特化和非特化版本在函数名上是相同的,只是参数类型不同。

  • 注意与重载的区别

    • 重载:涉及创建多个具有相同名称但参数类型或数量不同的函数。
    • 特化:为模板函数提供针对特定类型的定制实现,不增加新的函数名称。
  • 注意特化过程中的模板形参推导

    特化可以影响模板形参的推导过程。

避免使用函数模板的特化

  • 不参与重载解析,会产生反直觉的效果(重载解析是在函数模板特化之前完成的)

    函数模板特化不参与普通的重载解析过程。这意味着即使存在一个更匹配的非特化版本,编译器也可能选择特化版本,因为特化版本在重载候选中具有更高的优先级。这可能导致一些反直觉的结果。

  • 通常可以用重载代替(函数重载会参与重载解析)

    优先使用函数重载而不是模板特化。函数重载遵循标准的重载解析规则,这使得代码的行为更加可预测和直观。

  • 一些不便于重载的情况:无法建立模板形参与函数形参的关联,可以考虑一下替代方案

    • 使用if constexpr解决

      if constexpr 是C++17引入的一个特性,它允许在编译时根据模板参数的值选择执行不同的代码路径。这可以用来模拟重载的效果

      #include <type_traits>
      
      template<typename T>
      void func(T t) {
          if constexpr (std::is_same_v<T, int>) {
              // 针对 int 类型的代码
          } else {
              // 通用代码
          }
      }
      
    • 引入“假”函数形参

    • 通过类模板特化解决

      使用类模板特化,然后在类中定义需要重载的函数。这种方法可以将重载的复杂性封装在类内部:

      template<typename T>
      struct MyClass {
          void func(T t) {
              // 通用实现
          }
      };
      
      template<>
      struct MyClass<int> {
          void func(int t) {
              // 针对 int 类型的实现
          }
      };
      

函数模板的简化形式(C++20):使用auto定义模板参数类型

  在C++20中,引入了使用auto来定义函数模板参数类型的简化形式。

  • 优势:书写简捷

  • 劣势:在函数内部需要间接获取参数类型信息

image-20240606161910859

二、类模板与成员函数模板

  在C++中,类模板是一种泛型编程工具,允许你创建可以处理多种数据类型的类。

类模板不是类

  • 使用template关键字引入类模板

      使用 template<typename T>template<class T> 来声明一个类模板。typenameclass 在这里可以互换使用,但 typename 更常用于模板参数。

    template<typename T>
    class B {
        // 包括成员变量、成员函数的实现
    };
    
  • 类模板的声明与定义:翻译单元级别的一处定义原则

      类模板的声明和定义通常写在一起。类模板的定义包括成员变量、成员函数的实现等。

  • 成员函数只有在调用时才会被实例化

      类模板的成员函数只有在被调用时才会被实例化。这意味着编译器会根据成员函数调用时提供的实参来生成具体的函数实现。

    #include <iostream>
    
    template<typename T>
    class B {
    public:
        void fun(T input)
        {
            std::cout << input << std::endl;
        }
    };
    
    int main()
    {
        B<int> x;
        x.fun(3);
    }
    

    经编译器实例化后如下:

    #include <iostream>
    
    template<typename T>
    class B
    {
      
      public: 
      inline void fun(T input)
      {
        (std::cout << input) << std::endl;
      }
      
    };
    
    /* First instantiated from: insights.cpp:14 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class B<int>
    {
      
      public: 
      inline void fun(int input)
      {
        std::cout.operator<<(input).operator<<(std::endl);
      }
      
      // inline constexpr B() noexcept = default;
    };
    
    #endif
    
    int main()
    {
      B<int> x;
      x.fun(3);
      return 0;
    }
    

    类模板中的成员函数本质上是内联函数

  • 类内类模板名称的简写

    在类模板的成员函数中,可将类模板名称进行简写

    image-20240606171534460

  • 类模板成员函数的定义(类内、类外)

    类模板的成员函数可以在类内定义(内联定义)或类外定义。

    • 类内定义

      成员函数在类模板内部定义时,不需要再次使用 template 关键字,编译器能够从上下文中推断出模板参数。

      template<typename T>
      class B {
      public:
          void memberFunc() {
              // 实现
          }
      };
      
    • 类外定义

      当成员函数在类外部定义时,需要使用模板关键字,并显式指定模板参数。

      template<typename T>
      class B {
      public:
          void memberFunc();
      };
      
      template<typename T>
      void B<T>::memberFunc() {
          // 实现
      }
      

成员函数模板

  • 类的成员函数模板

    位于类内部的函数模板,同上也可分为类内定义与类外定义

    class B {
    public:
        template<typename T>
        void func(T input);
    };
    
    template<typename T>
    void B::func(T input)
    {
        
    }
    
    int main()
    {
        B x;
        x.func<int>(3);
    }
    
  • 类模板的成员函数模板

    类模板可以包含成员模板函数,这些函数在类内或类外定义

    template<typename T>
    class B {
    public:
        template<typename T2>
        void func(T2 input);
    };
    
    template<typename T>
    template<typename T2>
    void B<T>::func(T2 input)
    {
        
    }
    
    int main()
    {
        B<int> x;
        x.func<int>(3);
    }
    

友元函数模板:(很少使用)

  • 可以声明一个函数模板为某个类(模板)的友元
  • C++11 支持声明模板参数为友元
template<typename T>
class B {
public:
    template<typename T2>
    friend void func(T2 input);
  
private:
  	int x;
};

template<typename T2>
void func(T2 input)
{
    B<int> tmp1;
  	tmp1.x;
  	
  	B<char> tmp2;
  	tmp2.x;
}

int main()
{
    func<float>(3);
}

类模板的实例化

C++中的类模板实例化与函数模板实例化在概念上是相似的,都涉及到根据提供的模板实参生成具体的类型或函数。

详细内容可参考:https://en.cppreference.com/w/cpp/language/class_template

  • 实例化过程

    类模板的实例化是通过替换模板参数来创建一个具体类的版本。这个过程可以是隐式的,也可以是显式的。

  • 隐式实例化

    当你创建一个类模板的对象或调用其成员函数时,如果模板参数没有明确指定,编译器会自动推导这些参数,从而实例化类或成员函数。

    template<typename T>
    class Box {
        T item;
    public:
        Box(T t) : item(t) {}
        T getItem() const { return item; }
    };
    
    int main() {
        Box<int> myBox(10); // 隐式实例化 Box<int>
        return 0;
    }
    
  • 显式实例化

    显式地要求编译器实例化类模板的特定版本。这通常在类模板的定义完成后进行

    template class Box<int>; // 显式实例化 Box<int>
    
  • 可以实例化整个类模板或者类模板中的某个成员函数

    • 实例化整个类模板,即为该类创建一个具体的类型版本

      Box<double> anotherBox(5.5); // 实例化 Box<double> 的对象
      
    • 实例化类模板中的某个成员函数,特别是当成员函数是模板时

      template<typename T>
      class MyClass {
      public:
          template<typename U>
          U templatedMethod(U u) {
              return u;
          }
      };
      int main()
      {
       	// 实例化 MyClass<int> 的 templatedMethod<double>
          MyClass<int> myObject;
          double result = myObject.templatedMethod<double>(3.14);   
      }
      

类模板的(完全)特化 / 部分特化

  • 完全特化

    完全特化是指为类模板的特定类型参数提供完全定制的类定义。特化版本与基础版本可以完全不同,不继承或包含基础模板的任何成员。

    // 基础模板类定义
    template<typename T>
    class MyClass {
    public:
        void func() { /* ... */ }
    };
    
    // 完全特化版本
    template<>
    class MyClass<int> {
    public:
        void func() { /* 完全不同的实现 */ }
    };
    

    MyClass<int>MyClass 的一个完全特化版本,它具有与基础模板完全不同的实现。

  • 部分特化

    部分特化或偏特化是指当类模板接受多个类型参数时,可以为其中一些参数提供特化,而其他参数保持通用。

    // 基础模板类定义,接受两个类型参数
    template<typename T1, typename T2>
    class MyClass {
    public:
        void func() { /* ... */ }
    };
    
    // 部分特化版本,T2 特化为 int
    template<typename T1>
    class MyClass<T1, int> {
    public:
        void func() { /* 针对 T1 的任意类型和 T2 为 int 的特化实现 */ }
    };
    

    MyClass<T1, int> 是一个部分特化版本,它只针对 T2int 的情况提供了定制的实现,而 T1 可以是任何类型。

类模板的实参推导:(从C++17开始)

  • 基于构造函数的实参推导

    C++17允许编译器根据构造函数的参数来推导类模板的模板参数。如果构造函数的参数能够明确地推导出模板参数的类型,编译器将自动实例化类模板。、

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {}
    };
    
    int main() {
        Wrapper w(42); // C++17 允许从 42 推导 T 为 int
    }
    

    经编译器翻译后如下:

    template<typename T>
    class Wrapper
    {
      public: 
      T value;
      inline Wrapper(T v)
      : value(v)
      {
      }
    };
    
    /* First instantiated from: insights.cpp:9 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class Wrapper<int>
    {
      public: 
      int value;
      inline Wrapper(int v)
      : value{v}
      {
      }
    };
    
    #endif
    
    int main()
    {
      Wrapper<int> w = Wrapper<int>(42);
      return 0;
    }
    
    template<typename T>
    Wrapper(T v) -> Wrapper<T>;
    
    /* First instantiated from: insights.cpp:9 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    Wrapper(int v) -> Wrapper<int>;
    #endif
    
  • 用户自定义的推导指引

    C++17还引入了自定义的推导指引(Deduction Guide),允许开发者提供构造函数的重载来帮助编译器进行类型推导。推导指引需要在类模板的作用域外部定义,以便编译器能够在整个程序中找到并使用它们。

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {} // 普通构造函数
    };
    
    // 自定义推导指引
    template<typename T>
    Wrapper(T) -> Wrapper<T>;
    
  • 注意:引入实参推导并不意味着降低了类型限制

    即使类模板的参数可以从构造函数参数中推导出来,这并不意味着模板参数可以是任何类型。模板参数仍然需要满足类模板中定义的任何类型约束或要求。

  • C++ 17 之前的解决方案:引入辅助模板函数

    template<typename T>
    class Wrapper {
    public:
        T value;
        Wrapper(T v) : value(v) {} // 普通构造函数
    };
    
    // 辅助模板函数
    template<typename T>
    Wrapper<T> make_Wrapper(T v) {
        return Wrapper<T>(v);
    }
    
    int main() {
        auto w = make_Wrapper(42); // 使用辅助函数创建 Wrapper 实例
    }
    

    C++中有很多类似的函数,如:make_pair

  • 类模板实参推导的限制

    • 类模板实参推导只适用于构造函数。
    • 如果构造函数重载,编译器需要能够从上下文推导出应该使用哪一个构造函数。
    • 如果存在多个可能的模板实例化,编译器将尝试找到最佳匹配。

三、Concepts(C++20)

详细内容可参考:https://en.cppreference.com/w/cpp/language/constraints

  C++模板的问题:没有对模板参数引入相应的限制,会造成如下两个问题:

  • 参数是否可以正常工作,通常需要阅读代码进行理解

  • 编译报错友好性较差(vector<int&>)

    C++编译器在模板实例化失败时生成的错误信息通常很长且难以理解。这是因为模板在编译时展开,如果模板参数不符合要求,编译器需要报告所有相关的错误。例如, vector<int&> 是一个错误用法,因为 vector 需要其元素类型是可复制的,而引用类型 int& 不满足这一要求。这将导致编译错误,但错误信息可能不会直接指出问题所在。

  C++20 引入了 Concepts(概念),这是一种新的类型系统特性,用于在编译期对模板参数进行更严格的约束。Concepts 允许开发者定义模板参数必须满足的条件,这些条件被称为编译期谓词,它们返回 truefalse 来表明模板参数是否符合预期

基本概念

  • concept:编译期谓词,它定义了一组类型必须满足的要求

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
    
    int main()
    {
    	return IsAvail<int>;
    }
    
  • constraints(约束):concept与 constraints ( require 从句)一起使用限制模板参数。通常置于表示模板形参的尖括号后面进行限制。

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
    
    template <typename T>
      requires IsAvail<T>
    void fun(T input)
    {
    
    }
    
    int main()
    {
    	fun(3);
    }
    

concept 的定义与使用

  • 包含一个模板参数的 concept

    • 使用 requires 从句

      #include <iostream>
      #include <type_traits>
      
      template <typename T>
      concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
      
      template <typename T>
        requires IsAvail<T>
      void fun(T input)
      {
      
      }
      
      int main()
      {
      	fun(3);
      }
      
    • 直接替换 typename

      #include <iostream>
      #include <type_traits>
      
      template <typename T>
      concept IsAvail = std::is_same_v<T, int> || std::is_same_v<T, float>;
      
      template <IsAvail T>
      void fun(T input)
      {
      
      }
      
      int main()
      {
      	fun(3);
      }
      
  • 包含多个模板参数的 concept

    #include <iostream>
    #include <type_traits>
    
    template <typename T, typename T2>
    concept IsAvail = !std::is_same_v<T, T2>;
    
    template <typename T, typename T2>
    	requires IsAvail<T, T2>
    void fun(T input, T2 input2)
    {
    
    }
    
    int main()
    {
    	fun(3, 3.14);
    }
    

    用做类型 constraint 时,少传递一个参数,推导出的类型将作为首个参数

    #include <iostream>
    #include <type_traits>
    
    template <typename T, typename T2>
    concept IsAvail = !std::is_same_v<T, T2>;
    
    template <IsAvail<int> T>
    void fun(T input)
    {
    
    }
    
    int main()
    {
    	fun(3.14);
    }
    

requires表达式

注意区分requires表达式与requires从句的含义

  • requires 从句用于模板定义中,它指定了模板参数必须满足的条件。
  • requires 表达式是 requires 从句的一部分,它用于定义概念(Concepts)。
  • 简单表达式:表明可以接收的操作
  • 类型表达式:表明是一个有效的类型
  • 复合表达式:表明操作的有效性,以及操作返回类型的特性
  • 嵌套表达式:包含其它的限定表达式

requires 从句会影响重载解析与特化版本的选取

  • 只有 requires 从句有效而且返回为 true 时相应的模板才会被考虑

    当编译器进行函数调用时,它会尝试找到匹配的函数重载版本。如果一个模板的 requires 从句中的条件不满足,那么即使模板的其他部分与调用匹配,这个模板版本也不会被考虑。这意味着 requires 从句充当了一种编译期的筛选器,确保只有当条件满足时,相应的模板实例才会被考虑。

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    	requires std::is_same_v<T, float>
    void fun(T input)
    {
    	std::cout << "float";
    }
    
    template <typename T>
    	requires std::is_same_v<T, int>
    void fun(T input)
    {
    	std::cout << "int";
    }
    
    int main()
    {
    	fun(3);		//第二个模板将会被调用
    }
    
  • requires 从句所引入的限定具有偏序特性,系统会选择限制最严格的版本

    当存在多个模板特化版本时,编译器会根据 requires 从句所引入的限定来选择最合适的特化。这些限定具有偏序特性

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    concept C1 = std::is_same_v<T, int>;
    
    template <typename T>
    concept C2 = std::is_same_v<T, float> || std::is_same_v<T, int>;
    
    template <C1 T>
    void fun(T input)
    {
    	std::cout << "1";
    }
    
    template <C2 T>
    void fun(T input)
    {
    	std::cout << "2";
    }
    
    int main()
    {
    	fun(3);
    }
    

特化小技巧:在声明中引入“ A||B” 进行限制,之后分别针对 A 与 B 引入特化

#include <iostream>
#include <type_traits>

template <typename T>
	requires std::is_same_v<T, int> || std::is_same_v<T, float>
class B;

template <>
class B<int> {};

template <>
class B<float> {};

int main()
{
	B<double> x;
}

四、模板相关内容

1.数值模板参数与模板模板参数

数值模板参数

  模板可以接收(编译期常量)数值作为模板参数

  • 使用int类型的编译器常量

    其写法为:

    template <int a> 
    class Str;
    

    示例:

    template <int a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<3>(5);
    }
    
  • 使用类型与编译器常量的组合

    这种方式允许你指定一个类型 T 和一个该类型的编译期常量 value

    其语法为:

    template <typename T, T value> 
    class Str;
    

    示例:

    template <typename T, T a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<int, 3>(5);
    }
    
  • 使用 auto 关键字来简化模板参数的定义(C++17)

    在C++17中,可以使用 auto 关键字来简化模板参数的定义,使得模板参数可以自动推断为传递给它的值的类型。

    其语法为:

    template <auto value>
    class Str {
        // ...
    };
    

    示例:

    template <auto a>
    int fun(int x)
    {
        return x + a;
    }
    
    int main()
    {
        fun<3>(5);
        fun<true>(5);
    }
    
  • 接收字面值类对象与浮点数作为模板参数(C++20)

    C++20进一步扩展了模板非类型参数,允许使用字面值类对象和浮点数作为模板参数。

    其语法为:

    template <double value>
    class FloatStr {
        // ...
    };
    

    支持还不完整,有些编译器不支持

模板模板参数

  在 C++ 中,模板可以接收另一个模板作为参数

  • 模板的模板参数(C++17之前)

    即一个模板的模板参数为模板T。在 C++17 之前,模板的模板参数需要显式指定类型说明符 class

    其语法为:

    template <template<typename T> class C>
    class Str {
        // ...
    };
    
  • C++17开始允许省略类型说明符

    C++17 标准放宽了对模板的模板参数的语法要求,允许在模板的模板参数中省略类型说明符 class

    其语法为:

    template <template<typename T> typename C>
    class Str {
        // ...
    };
    

    示例:

    #include <vector>
    
    template <template<typename T> typename C>
    void fun() {
        C<int> tmp;
    };
    
    int main()
    {
        fun<std::vector>();
    }
    
  • C++17 开始,模板的模板实参考虑缺省模板实参

    如:上面的vector类模板实际有两个参数,第二个参数为缺省实参

    支持还不完整,有些编译器不支持

2.别名模板与变长模板

别名模板

  在 C++ 中,using 关键字可以用于引入别名,可以使用 using 引入别名模板。

  • 为模板本身引入别名

    template <typename T>
    class MyClass {
        // ...
    };
    
    // 为模板本身引入别名
    template <typename T>
    using MyAlias = MyClass<T> ;
    int main()
    {
        // 使用别名创建对象
        MyAlias<int> myObject;
    }
    
  • 为类模板的成员引入别名

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        class InnerClass {
            // ...
        };
    
        using InnerType = InnerClass<T>; // 为 InnerClass 模板的特定实例引入别名
    };
    
    int main()
    {
        // 使用别名访问类模板的成员
        MyClass<int>::InnerType myInnerObject;
    }
    
  • 别名模板不支持特化,但可以为基于类模板的特化引入别名,以实现类似特化的功能

    template <typename T>
    class MyClass {
        // ...
    };
    
    // 特化模板
    template <>
    class MyClass<double> {
        // ...
    };
    
    int main()
    {
        // 为特化的模板引入别名
        using MyDoubleClass = MyClass<double>;
    }
    

变长模板

详细内容可参考:https://zh.cppreference.com/w/cpp/language/parameter_pack

  C++中的变长模板(Variadic Templates),也称为参数包(Parameter Packs),是一种强大的模板特性,允许模板接受任意数量的模板参数。

  • 变长模板参数与参数包

    变长模板参数使用省略号(...)来表示,可以与模板参数列表中的其他参数一起使用

    template <typename... Types>
    class Tuple {
        // Types 是一个类型参数包,可以包含任意数量的类型
    };
    
  • 变长模板参数可以是数值、类型或模板

    • 类型参数包:可以用于模板的类型参数。

      #include <iostream>
      
      template <typename ... T>
      void fun(T... args)
      {
          
      }
      
      int main()
      {
          fun<int, double, char>(3, 5.3, 'c');
      }
      

      变长函数模板可以用任意数量的函数实参调用

    • 数值参数包:可以用于模板的非类型参数。

      template <int... Values>
      void printInts() {
          // 使用递归或迭代来打印 Values 中的整数
      };
      
    • 模板参数包:可以用于模板的模板参数。

      template <template <typename> class... Templates>
      class TemplateHolder {
          // Templates 是一个模板参数包
      };
      
  • sizeof... 操作(C++11)

    sizeof... 操作符用于获取参数包中的参数数量

    template<class... Types>
    struct count
    {
        static const std::size_t value = sizeof...(Types);
    };
    
  • 注意变长模板参数的位置

    • 在主类模板中,模板形参包必须是模板形参列表的最后一个形参。特化模板没有这个限制

    • 在函数模板中,模板参数包可以在列表中更早出现,只要其后的所有形参都可以从函数实参推导或拥有默认实参即可:

    template<typename U, typename... Ts>    // OK:能推导出 U
    struct valid;
    // template<typename... Ts, typename U> // 错误:Ts... 不在结尾
    // struct Invalid;
     
    template<typename... Ts, typename U, typename=void>
    void valid(U, Ts...);    // OK:能推导出 U
    // void valid(Ts..., U); // 不能使用:Ts... 在此位置是不推导语境
     
    valid(1.0, 1, 2, 3);     // OK:推导出 U 是 double,Ts 是 {int, int, int}
    

3.包展开与折叠表达式

包展开(C++11):

  通过包展开技术操作变长模板参数。

  • 模式T后随省略号且其中至少有一个形参包的名字会被展开成零个或更多个逗号分隔的模式实例,其中形参包的名字按顺序被替换成包中的各个元素。

    示例:

    template<class... Us>
    void f(Us... pargs) {}
     
    template<class... Ts>
    void g(Ts... args)
    {
        f(&args...); // “&args...” 是包展开
                     // “&args” 是它的模式
    }
    int main()
    {
     	g(1, 0.2, "a"); // Ts... args 会展开成 int E1, double E2, const char* E3
                    // &args... 会展开成 &E1, &E2, &E3
                    // Us... 会展开成 int* E1, double* E2, const char** E3   
    }
    

    编译器会翻译成

    template<class ... Us>
    void f(Us... pargs)
    {
    }
    
    /* First instantiated from: insights.cpp:7 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void f<int *, double *, const char **>(int * __pargs0, double * __pargs1, const char ** __pargs2)
    {
    }
    #endif
    
    
    template<class ... Ts>
    void g(Ts... args)
    {
      f(&args... );
    }
    
    /* First instantiated from: insights.cpp:12 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    void g<int, double, const char *>(int __args0, double __args1, const char * __args2)
    {
      f(&__args0, &__args1, &__args2);
    }
    #endif
    
    
    int main()
    {
      g(1, 0.20000000000000001, "a");
      return 0;
    }
    
  • 如果两个形参包在同一模式中出现,那么它们同时展开而且长度必须相同:

    template<typename...>
    struct Tuple {};
     
    template<typename T1, typename T2>
    struct Pair {};
     
    template<class... Args1>
    struct zip
    {
        template<class... Args2>
        struct with
        {
            typedef Tuple<Pair<Args1, Args2>...> type;
            // Pair<Args1, Args2>... 是包展开
            // Pair<Args1, Args2> 是模式
        };
    };
    
    int main()
    {
        typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
        // Pair<Args1, Args2>... 会展开成
        // Pair<short, unsigned short>, Pair<int, unsigned int> 
        // T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>>
    
        // typedef zip<short>::with<unsigned short, unsigned>::type T2;
        // 错误:包展开中的形参包包含不同长度   
    }
    

使用包展开技术操作变长模板参数的基本应用:

#include <iostream>

void fun()
{

}

template <typename U, typename... T>
void fun(U u, T... args)
{
    std::cout << u << std::endl;
    fun(args...);
}

int main()
{
    fun(1, 2, "hello", "world");
}

运行结果:

1
2
hello
world

折叠表达式(C++17):简化变长模板参数操作

详细内容可参考:https://zh.cppreference.com/w/cpp/language/fold

  • 基于逗号的折叠表达式应用

    示例:对上述代码进行改写

    #include <iostream>
    
    void fun()
    {
    
    }
    
    template <typename... T>
    void fun(T... args)
    {
        ((std::cout << args << std::endl), ...);
    }
    
    int main()
    {
        fun(1, 2, "hello", "world");
    }
    
  • 折叠表达式用于表达式求值,无法处理输入(输出)是类型与模板的情形

4.完美转发与lambda表达式模板

完美转发(C++11): std::forward 函数

  完美转发允许模板函数在转发参数时保留参数的值类别(左值或右值)。这是通过 std::forward 函数和万能引用(也称为转发引用)实现的。

  • 万能引用

    万能引用是一个使用双&&声明的引用类型。它可以接受左值、右值,或者通过模板参数推导为 T&T&&

    template <typename T>
    void func(T&& arg) {
        // arg 是一个万能引用,可以绑定到左值或右值
    }
    
  • std::forward 函数

    std::forward 是一个模板函数,用于实现完美转发。它的作用是:

    • 当模板参数 UT 相同的时候,std::forward<T>(arg)arg 视为 T 类型的左值引用或右值引用,这取决于 arg 在声明时的类型。
    • UT 不同的时候,std::forward<T>(arg)arg 视为 T 类型的右值引用。
    template <typename T>
    void func(T&& arg) {
        // 使用 std::forward 实现完美转发
        someOtherFunction(std::forward<T>(arg));
    }
    
  • 完美转发的使用场景

    完美转发通常用于模板函数或模板类中,特别是那些需要转发其参数给其他函数或构造函数的模板。这样可以保证:

    • 如果原始参数是一个左值,它在转发后仍然是左值。
    • 如果原始参数是一个右值,它在转发后仍然是右值。

示例:

#include <iostream>

void process(int& i) {
    std::cout << "process(int&)" << std::endl;
}

void process(int&& i) {
    std::cout << "process(int&&)" << std::endl;
}

template <typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

int main()
{
    int x = 3;
    wrapper(x);
    wrapper(3);
}

编译器翻译结果:

image-20240607213648027

运行结果:

process(int&)
process(int&&)

在这个示例中,wrapper 函数接受一个万能引用参数。根据传入 wrapper 的参数是左值还是右值,std::forward 将正确地将参数转发给 process 函数,保持其值类别。

lambda表达式模板(C++20):

详细内容可参考:https://zh.cppreference.com/w/cpp/language/lambda

5.消除歧义与变量模板

使用 typename 与 template 消除歧义

  • 使用 typename 表示一个依赖名称是类型而非静态数据成员

    当你在类型上下文中使用依赖名称,并且该名称表示一个类型时,你可以使用 typename 来消除歧义。这通常发生在通过模板参数访问嵌套类型时。

    template <typename T>
    class Outer {
    public:
        template <typename U>
        class Inner {
        };
    
        // 使用 typename 来消除歧义,表示 Inner 是一个类型
        typedef typename Outer<T>::Inner<int> TypedInner;
    };
    

    在这个例子中,Outer<T>::Inner<int> 是一个依赖名称,它依赖于模板参数 T。使用 typename 告诉编译器 Inner<int> 是一个类型

  • 使用 template 表示一个依赖名称是模板

    当你需要指定一个依赖名称是模板时,可以使用 template 关键字。

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        void function() {
            // 使用 template 来消除歧义,表示 function 是一个模板
            MyClass<T>::template function<U>();
        }
    };
    

    在这个例子中,MyClass<T>::function<U>() 是一个依赖名称,它表示一个模板。使用 template 告诉编译器 function 是一个模板,而不是一个静态成员或类型。

  • template 与成员函数模板调用

    成员函数模板是类模板内部定义的模板。当你在类模板外部实例化一个成员函数模板时,你需要使用 template 来指定模板实例化。

    template <typename T>
    class MyClass {
    public:
        template <typename U>
        void memberFunction(U param) {
            // ...
        }
    };
    
    int main() {
        MyClass<int> myObject;
        // 调用成员函数模板,需要使用 template 来指定模板参数
        myObject.template memberFunction<double>(3.14);
    }
    

    编译器翻译成

    template<typename T>
    class MyClass
    {
      
      public: 
      template<typename U>
      inline void memberFunction(U param)
      {
      }
    };
    
    /* First instantiated from: insights.cpp:11 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    class MyClass<int>
    {
      
      public: 
      template<typename U>
      inline void memberFunction(U param);
      
      /* First instantiated from: insights.cpp:13 */
      #ifdef INSIGHTS_USE_TEMPLATE
      template<>
      inline void memberFunction<double>(double param)
      {
      }
      #endif
      
      // inline constexpr MyClass() noexcept = default;
    };
    
    #endif
    
    int main()
    {
      MyClass<int> myObject;
      myObject.memberFunction<double>(3.1400000000000001);
      return 0;
    }
    

    在这个例子中,memberFunctionMyClass 的一个成员函数模板。在 main 函数中,我们使用 template 关键字来指定 memberFunction 的模板参数 double

变量模板(C++14):

  C++14 引入了变量模板,这是一种新的模板类型,允许模板定义变量。

  • 基本形式的变量模板

    template <typename T>
    T pi = T(3.1415926);
    

    pi 是一个变量模板,它对于每个类型 T 都有一个与之对应的实例。注意,这里使用类型转换 T(3.1415926) 来确保值 3.1415926 根据模板参数 T 的类型进行适当的转换。

  • 使用变量模板

    只需要指定所需的类型

    double piDouble = pi<double>; // 使用 double 类型的 pi
    int piInt = pi<int>;          // 使用 int 类型的 pi
    
  • 其他形式的变量模板

    • 编译时常量

    • 类型属性

    • 内联变量模板

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/688713.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Windows上安装VMWare Pro 16.2(虚拟机)并从零安装CentOS 7.6镜像过程记录

本文详细记录了在Windows的VMWare Workstation Pro 16.2中安装CentOS 7.6 的过程,非常适合新手从零开始一步步安装。 文章目录 一、安装VMWare Workstation Pro 16.2并激活二、安装CentOS 7.62.1 下载CentOS7.6镜像文件2.2 创建新的虚拟机2.3 安装CentOS镜像一、安装VMWare Wo…

國際知名榮譽顧問加入台灣分析集團總部,全面升級量子電腦Q系統

近期,國際知名的榮譽顧問正式加入台灣分析集團總部,利用相同的量子數據規格訊息數據庫,進行全方位的系統升級。此次升級後,量子電腦Q系統的精確預測和迅速反應能力提升了3.29%。透過高級的數據處理和技術分析,社群用戶將在瞬息萬變的市場中保持領先地位。 “量子電腦Q系統”由資…

C语言字符、数组指针变量

目录 一、字符指针变量 二、数组指针变量 a.数组指针变量是什么 b.数组指针变量的书写格式 c.数组指针变量如何初始化 d.二维数组传参的本质 一、字符指针变量 在指针的类型中我们知道有一种指针类型为字符指针 char* 。 其一般使用&#xff1a; int main() {char ch w…

【MySQL数据库】my.ini文件参数中文注释

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

LLM的基础模型7:Positional Encoding

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

数据结构笔记1 绪论,线性表

学习视频&#xff1a; 第01周c--1.2基本概念和术语1_哔哩哔哩_bilibili 01《数据结构》绪论_哔哩哔哩_bilibili 数据&#xff1a; 1.数值型的数据&#xff1a;整数&#xff0c;实数 2.非数值型的数据&#xff1a;文字、图像.. 数据元素&#xff1a;&#xff08;元素&#xf…

给孩子的端午节礼物:最新初中数学思维导图大合集+衡水高考学霸笔记,可下载打印!

大家好哇&#xff01;端午节到了&#xff0c;阿星给家里有孩子的伙伴们一份礼物哦&#xff01;今天给大家带来一个超级实用的学习神器——思维导图法&#xff0c;最新版的初中数学思维导图大合集&#xff01; 这可不是我吹哦&#xff0c;连哈佛、剑桥大学都在用的高级学习方法…

JavaScript事件监听之其它事件(页面加载事件、元素滚动事件、页面尺寸事件、M端事件)

目录 1. 页面加载事件(load、DOMContentLoaded)2. 元素滚动事件(scroll)3. 页面尺寸事件3.1 resize3.2 获取元素宽高3.3 获取元素位置(offsetLeft和offsetTop、getBoundingClientRect) 4. M端事件 1. 页面加载事件(load、DOMContentLoaded) load事件&#xff1a; 使用场景: 等…

JVM 虚拟机

JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java 虚拟机&#xff0c;虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。 常见的虚拟机有&#xff1a;JVM、VMwave、Virtual Box等。JVM 是一台被定制过的现实当中不存在的计算…

流量录制学习

AREX Cloud | AREX (arextest.com) 流量录制学习&#xff0c;比vivo的moonbox要好用

【QT5】<总览四> QT常见绘图、图表及动画

文章目录 前言 一、QFile类读写文件 二、QPainter绘简单图形 三、QChart图表 四、QPropertyAnimation属性动画 五、Q_PROPERTY宏简介 六、自定义属性动画 前言 承接【QT5】&#xff1c;总览三&#xff1e; QT常用控件。若存在版权问题&#xff0c;请联系作者删除&#…

C语言过度C++语法补充(面向对象之前语法)

目录 1. C相较于C语言新增的语法 0. C 中的输入输出 1. 命名空间 1. 我们如何定义一个命名空间&#xff1f; 2. 如何使用一个命名空间 3. 命名空间中可以定义什么&#xff1f; 4. 在 相同或者不同 的文件中如果出现 同名的命名空间 会如何&#xff1f; 5. 总结~~撒花~~…

社区服务支持

社区服务支持 原创 小王搬运工 时序课堂 2024-06-07 19:29 四川 &#x1f31f; 邀请函 | 加入我们的时序数据挖掘社区 &#x1f680; 尊敬的数据爱好者们&#xff0c; 我们诚挚地邀请您加入我们的专业社区——时序数据挖掘社区&#xff0c;一个专注于时序数据分析、挖掘与应…

网络空间安全数学基础·同余式

6.1 剩余系&#xff08;掌握&#xff09; 6.2 同余式概念与一次同余式&#xff08;熟练&#xff09; 6.3 中国剩余定理&#xff08;熟练&#xff09; 6.1 剩余系 设m是正整数&#xff0c;模m同余的全体整数是一个模m剩余类&#xff0c;即可表示为a qmr&#xff0c; 0≤r<…

ssti模板注入

一、Flask应用 1、介绍 定义 Flask&#xff1a;是一个使用Python编写的轻量级web应用框架。Flask基于Werkzeug WSGI工具包和Jinja2模板引擎。 特点 良好的文档、丰富的插件、包含开发服务器和调试器、集成支持单元测试、RESTful请求调度、支持安全cookies、基于Unicode。 …

Go微服务: 关于TCC分布式事务

TCC 分布式事务 T: Try 预处理, 尝试执行&#xff0c;完成所有的业务检查&#xff0c;做好一致性&#xff0c;预留必要的业务资源&#xff0c;做好准隔离性C: Confirm 确认&#xff0c;如果所有的分支Try都成功了, 就到了这个阶段, Confirm 是真正执行业务的过程, 不做任何业务…

VCS基本仿真

这里记录三种仿真方式&#xff1a; 第一种是将verilog文件一个一个敲在终端上进行仿真&#xff1b; 第二种是将多个verilog文件的文件路径整理在一个文件中&#xff0c;然后进行仿真&#xff1b; 第三种是利用makefile文件进行仿真&#xff1b; 以8位加法器为例&#xff1a; …

[Bug]使用Transformers 微调 Whisper出现版本不兼容的bug

错误的现象 ImportError Traceback (most recent call last) <ipython-input-20-6958d7eed552> in () from transformers import Seq2SegTrainingArguments training_args Seq2SeqTrainingArguments( output_dir"./whisper-small-…

第九篇——冗余量:《史记》和《圣经》那个信息量大?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 通过信息量的对比&#xff0c;引出来冗余度的概念&#xff0c;又深入浅出…

webman中创建udp服务

webman是workerman的web开发框架 可以很容易的开启udp服务 tcp建议使用gatewayworker webman GatewayWorker插件 创建udp服务: config/process.php中加入: return [// File update detection and automatic reloadmonitor > [ ...........], udp > [handler > p…