# 一、可变参数模板

#CPP11

# 1. 概述

C++ 的可变参数模板(Variadic Templates)是一种强大的特性,使得模板能够接受可变数量的参数。这一特性引入于 C++11,允许程序员创建更加灵活和通用的代码。

可变参数模板使得函数模板和类模板能够接受任意数量的模板参数,无论是类型参数还是非类型参数。它们为泛型编程提供了更大的灵活性。

# 2. 基本语法

可以通过使用省略号 ... 来定义参数包。

  • 函数模板

    1
    2
    3
    4
    template<typename... Args>
    void func(Args... args) {
    // 处理参数
    }

  • 类模板:同理。

    1
    2
    3
    4
    5
    6
    7
    template<typename... Args>
    class MyClass {
    public:
    MyClass(Args... args) {
    // 构造函数处理
    }
    };

# 3. 参数展开

展开参数包(pack expansion)是将模板参数包展开成单个参数序列。

在函数模板或类模板中,可以使用参数包展开来处理传入的参数。展开的方式通常使用以下语法:

  • 在函数参数中展开:直接使用 args... 。(见下文 递归模板 的举例)
  • 在函数体中展开:使用 (..., expression) 的形式。(即,使用折叠表达式进行展开)

1
2
3
4
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 使用折叠表达式展开参数包
}

# 4. 示例

以下是一个简单的示例,演示如何使用模板参数包:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

template<typename... Args>
void print(Args... args) {
// 逐个打印参数
(std::cout << ... << args) << std::endl; // 展开并输出
}

int main() {
print(1, 2.5, "Hello", 'A'); // 可以传入不同类型的参数
return 0;
}

# 5. 递归模板

可以通过递归模板实现对参数包的处理。例如,计算参数包中所有数值的和:

1
2
3
4
5
6
7
8
9
template<typename T>
T sum(T value) {
return value; // 基础情况
}

template<typename T, typename... Args>
T sum(T value, Args... args) {
return value + sum(args...); // 递归展开
}

# 6. 使用非类型模板参数

除了类型参数,模板参数包也可以使用非类型参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<int... Values>
class IntPack {
public:
static void print() {
((std::cout << Values << ' '), ...); // 输出所有值
std::cout << std::endl;
}
};

int main() {
IntPack<1, 2, 3>::print(); // 输出 1 2 3
return 0;
}

# 7. 结合其他特性

模板参数包可以与其他 C++ 特性结合使用,如 SFINAE(Substitution Failure Is Not An Error)和类型推导,使得编写更加复杂的泛型代码变得可能。

# 二、折叠表达式

#CPP17

# 1. 基本概念

折叠表达式(Fold Expression)是 C++17 引入的一种特性,用于简化对可变参数模板的操作,尤其是在处理参数包时。它允许你对参数包中的元素进行简单的折叠运算(例如加法、乘法、逻辑运算等),减少了编写递归模板的需要。

折叠表达式是通过指定一个二元操作符(如 +*&& 等),对参数包中的所有元素进行运算的简洁方式。

# 2. 语法

折叠表达式有四种主要形式:

  1. 一元右折叠(Unary Right Fold): ( ... op pack )
  2. 一元左折叠(Unary Left Fold): ( pack op ... )
  3. 二元右折叠(Binary Right Fold): ( init op ... op pack )
  4. 二元左折叠(Binary Left Fold): ( pack op ... op init )

具体地,有:

  1. 一元右折叠

    1
    2
    3
    4
    5
    6
    7
    8
    template<typename... Args>
    auto sum(Args... args) {
    return (args + ...); // 等价于 (((args1 + args2) + args3) + ...)
    }

    int main() {
    std::cout << sum(1, 2, 3, 4); // 输出: 10
    }

  2. 一元左折叠

    1
    2
    3
    4
    5
    6
    7
    8
    template<typename... Args>
    auto sum(Args... args) {
    return (... + args); // 等价于 (... + (args1 + (args2 + (args3 + ...))))
    }

    int main() {
    std::cout << sum(1, 2, 3, 4); // 输出: 10
    }

  3. 二元右折叠

    1
    2
    3
    4
    5
    6
    7
    8
    template<typename... Args>
    auto sum(Args... args) {
    return (0 + ... + args); // 初始化值为0,等价于 (0 + ((args1 + args2) + args3) + ...)
    }

    int main() {
    std::cout << sum(1, 2, 3, 4); // 输出: 10
    }

  4. 二元左折叠

    1
    2
    3
    4
    5
    6
    7
    8
    template<typename... Args>
    auto sum(Args... args) {
    return (args + ... + 0); // 初始化值为0,等价于 (... + (args1 + (args2 + (args3 + ... + 0))))
    }

    int main() {
    std::cout << sum(1, 2, 3, 4); // 输出: 10
    }

# 三、示例代码与解析

结合模板参数包和折叠表达式,我们可以编写出非常简洁和强大的模板函数。

例如,一个同时支持不同数据类型的打印函数:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << '\n'; // 使用折叠表达式展开参数包
}

int main() {
print(1, 2, 3); // 输出: 1 2 3
print("Hello", " ", "world!"); // 输出: Hello world!
}

这里我们逐行解释上面的模板函数 print,并详细说明模板参数包和折叠表达式的工作原理。

  1. 模板声明

    1
    template<typename... Args>

    这一行代码声明了一个模板,其中 Args 是一个模板参数包。 typename... Args 表示 Args 可以接受任意数量的模板参数。

  2. 函数定义

    1
    void print(Args... args) {

这一行定义了一个名为 print 的函数。 Args... args 表示 args 是一个函数参数包,对应于模板参数包 Args 。每个 Args 中的类型对应于一个 args 中的参数。

  1. 折叠表达式
    1
    (std::cout << ... << args) << '\n';

    这一行是函数体,使用了折叠表达式来输出所有参数,并在末尾输出一个换行符。
更新于

请我喝杯咖啡吧~

Rick 微信支付

微信支付

Rick 支付宝

支付宝