模板

可变参数模板

可变参数模板(Variadic Templates)是C++11引入的一种强大特性,它允许模板接受可变数量的模板参数或函数参数。可变参数模板为编写通用性更强的代码提供了极大的灵活性,特别是在处理多种类型或不确定数量的参数时。

  • 模板参数包
    • 定义template<typename... Args> 中的 Args... 表示一个模板参数包(template parameter pack),它可以容纳零个或多个类型参数。
    • 展开Args... 表示对参数包的展开,通常在函数定义或调用时使用。
  • 函数参数包
    • 定义void func(Args... args) 中的 args... 是一个函数参数包(function parameter pack),表示可以接受零个或多个函数参数。
    • 展开args... 可以在函数体内展开,用于递归调用、初始化列表或其他操作。
template <typename... Arguments> returntype functionname(Arguments... args);
template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);
template <typename... Arguments> returntype functionname(const Arguments&... args);

可变参数模板使用 sizeof...() 运算符

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

展开参数包(Parameter Pack Expansion):

  • 展开参数包的基础形式:args... 是一个参数包,你可以将其展开到各种上下文中,如函数调用、构造函数调用、表达式等。

    template<typename... Args>
    void function(Args... args) {
        // 这里需要展开 args... 参数包
    }
    
  • 递归展开:每次递归调用 print,会处理一个参数并将剩余的参数继续传递下去,直到参数包为空。这时,调用的是不带参数的基函数 print(),这就是递归的终止条件。

    void print() {
        std::cout << "End of recursion" << std::endl;
    }
    
    template<typename T, typename... Args>
    void print(T first, Args... rest) {
        std::cout << first << std::endl;
        print(rest...);  // 递归展开参数包
    }
    
    int main() {
        print(1, 2, 3, "Hello", 4.5);
        return 0;
    }
    
  • 使用初始化列表展开:C++11 中,也可以利用初始化列表 {} 进行参数包展开,常用于执行一些表达式的副作用,如输出或计算。这里 (std::cout << args << std::endl, 0)... 是参数包展开的关键部分,它将 args... 中的每个参数依次应用到 std::cout << args << std::endl 表达式中。

    template<typename... Args>
    void print(Args... args) {
        (void)std::initializer_list<int>{(std::cout << args << std::endl, 0)...};
    }
    
    int main() {
        print(1, 2, 3, "Hello", 4.5);
        return 0;
    }
    
  • C++17 Fold 表达式:C++17 引入了 fold 表达式,这是展开参数包的最简洁方法。fold 表达式(std::cout << ... << args) 会将参数包 args... 中的每个参数依次应用到 << 操作符中。最终结果是将所有参数依次输出。

    template<typename... Args>
    void print(Args... args) {
        (std::cout << ... << args) << std::endl;
    }
    
    int main() {
        print(1, 2, 3, "Hello", 4.5);
        return 0;
    }
    

类型萃取

基本类型判断萃取

  • std::is_integral<T>:判断类型 T 是否为整数类型(如 intchar 等)。

    std::cout << std::is_integral<int>::value << std::endl;  // 输出: 1 (true)
    std::cout << std::is_integral<float>::value << std::endl;  // 输出: 0 (false)
    
  • std::is_floating_point<T>:判断类型 T 是否为浮点数类型(如 floatdouble 等)。

    std::cout << std::is_floating_point<double>::value << std::endl;  // 输出: 1 (true)
    
  • std::is_pointer<T>:判断类型 T 是否为指针类型。

    std::cout << std::is_pointer<int*>::value << std::endl;  // 输出: 1 (true)
    std::cout << std::is_pointer<int>::value << std::endl;  // 输出: 0 (false)
    

类型修饰符萃取

  • std::add_const, std::remove_const:用于添加或移除类型的 const 限定符。
  • std::add_volatile, std::remove_volatile:用于添加或移除类型的 volatile 限定符。
  • std::remove_cv, std::add_cv:同时处理类型的 constvolatile 修饰符。它们提供了一种方便的方法,可以同时移除或添加这两个修饰符。
  • std::remove_reference<T>:移除类型 T 的引用限定符(包括左值和右值引用)。std::add_lvalue_reference<T>:将左值引用限定符添加到类型 Tstd::add_rvalue_reference<T>:将右值引用限定符添加到类型 T

条件选择萃取

根据条件选择不同的类。

  • std::conditional<Condition, T1, T2>:根据 Condition 选择 T1T2 作为类型。
  • std::enable_if<Condition, T>:当 Conditiontrue 时,类型为 T,否则此模板不参与重载。

类型转换萃取

用于在类型之间进行转换

  • std::make_signed<T>:将类型 T 转换为对应的有符号整数类型。
  • std::make_unsigned<T>:将类型 T 转换为对应的无符号整数类型。

类型判断萃取

判断两个类型之间的关系。

  • std::is_same<T1, T2>:判断类型 T1T2 是否相同。
  • std::is_base_of<Base, Derived>:判断 Base 是否为 Derived 的基类。