STL基础(五)可变参数模板

发布于:2024-04-27 ⋅ 阅读:(26) ⋅ 点赞:(0)

在学习这一篇内容之前,我们需要了解模板的一个规则:我们不能声明两个同名但模板参数不同的类模板

template <typename T>
class test;

// 异常,与前面的声明不匹配
//template <typename T1, typename T2>
//class test;

这里就会有一个问题了,如何让一个模板可以接收不同数量的参数呢?

模板函数可以通过重载实现:

template <typename T>
void print();

template <typename T1, typename T2>
void print();

模板类就没有办法实现了,如果要接收不同数量的参数,必须要声明不同名称的模板类:

template <typename T1, typename T2, typename T3>
class test;

template <typename T1, typename T2>
class test1;

template <typename T1>
class test2;

无论是上面的函数模板重载还是声明不同名称的模板类,都不太适合处理未知数量参数的情况。

1、可变参数模板

C++为我们提供了可变参数模板,顾名思义,模板的参数个数是不固定的,语法如下:

template <typename... Args>
class Test;

template <typename... Args>
void func(Args...);

语法中的typename...表示一个类型参数包,函数签名中的Args...表示值参数包。

接下来一起了解可变参数模板应该如何使用:

1.1、可变参模板函数

可变参模板函数通常和递归一起使用,举个例子:

template <typename T>
void print(T arg) {
    std::cout << arg << std::endl;
}

template <typename T, typename... Rest>
void print(T arg, Rest... rest) {
    std::cout << arg << std::endl;
    print(rest...);
}

声明了两个模板函数print,它们间的关系为重载,如果我们进行以下调用:

int main() {
    print('1', 2, "three", 4.0);
}
// 1
// 2
// three
// 4

第二个print模板种包含有一个递归,每次执行print都会将第一个参数打印出来。typename... Rest是模板参数包,函数签名中的Rest... rest表示一个值参数包,rest...表示将参数包展开。

可能会有人不理解参数包是什么,参数包是如何展开的,下面会有例子帮助理解。

上例中有一点要注意,只有一个参数的模板声明需要放在变参模板声明的上面,否则将会出现找不到合适模板的错误。

1.2、可变参模板类

template <typename... Ts>
class Tuple;

template <typename T, typename... Ts>
class Tuple<T, Ts...> : Tuple<Ts...> {
    T value;
};

可变参数模板类用的可能会比较少一些,C++中的容器元组就是用可变参模板类实现的,可以包含不同类型元素的容器,这个我们后续再学习。

2、可变参数模板的常见应用场景

  1. 构造函数转发:我们可以用可变参数模板来转发参数给构造函数,比较常见的就是只能指针的实现:
template <typename T, typename... Args>
unique_ptr<T> make_unique(Agrs&&... args) {
    return unique_ptr<T>(new T(forward<Args>(args)...));
}

我们在这里理解模板参数包和参数包的展开,先来理解模板参数包,Args表示任意数量的模板类型参数,例如:

std::string str = "hello";
auto p = make_unique<std::string>(str);

这个例子中T代表了std::string,Args 代表了 std::string&

再来一个例子:

auto p = make_unique<std::string>("hello");

T代表了std::string,Args 代表了 const char (&)[6]

上面两个例子中Args代表了一个参数,接下来举一个Agrs代表多个参数的例子:

class MyClass {
public:
    MyClass(int x, double y)
        : x_(x), y_(y) {
    }
private:
    int x_;
    double y_;
};

int main() {
    auto p = std::make_unique<MyClass>(42, 3.14);
    return 0;
}

Args表示int, double

Args是参数包,Args...表示展开,展开后相当于:

make_unique<MyClass, int, double>(int&& arg1, double&& arg2) {
    return unique_ptr<MyClass>(new MyClass(forward<int>(arg1), forward<double>(arg2)));
}

Args&&... args 可以看作是将所有的参数同时声明为相同格式

forward<Args>(args)...这里面有两个参数包,展开时类型参数和值参数需要一一对应。

还有一点我们需要了解,make_unique它有两个模板参数:一个是要分配的对象的类型,另一个是构造这个对象所需参数的类型,实际使用中我们只指定了第一个模板参数,其他参数都是编译器推导出来的。

为模板函数提供了足够的信息后,编译器可以推导出未明确指定的模板参数

  1. 元组
  2. 类型特性:在编译时获取类型信息,计算可变参数模板中的类型数量,举个例子:
template <typename... Args>
struct Count;

template<>
struct Count<> {
    enum {
        value = 0
    };
};

template <typename T, typename... Rest>
struct Count<T, Rest...> {
    enum {
        value = 1 + Count<Rest...>::value
    };
};

int main() {
    std::cout << Count<int, float, double, char>::value;
}
// 4
  1. 日志的格式化:类似我们在可变参模板函数一节中举的例子,使用可以变模板参数的递归。

网站公告

今日签到

点亮在社区的每一天
去签到