模板的模板参数是C++模板元编程一个重要的基础概念。它允许我们在模板中使用其他模板作为参数,从而实现更复杂的类型和行为。
首先看一段简单的代码示例:
#include <iostream>
#include <tuple>
using namespace std;
template<typename>
struct first_type_getter {};
template<template<typename...> typename Ts, typename T1, typename... TN>
struct first_type_getter<Ts<T1, TN...>> {
using type = T1;
};
// 不能这样子写
// 这样的话,first_type需要提供不止一个模板参数的
// 原本的 first_type<tuple<int, float, double>> 就要变成 first_type<tuple, int, float, double>
// template<template<typename...> typename Ts, typename T1, typename... TN>
// using first_type = typename first_type_getter<Ts<T1, TN...>>::type;
template<typename T>
using first_type = typename first_type_getter<T>::type;
int main(){
cout << is_same_v<first_type<tuple<int, float, double>>, int> << endl; // 1
cout << is_same_v<first_type<tuple<int, float, double>>, float> << endl; // 0
cout << is_same_v<first_type<tuple<int, float, double>>, long> << endl; // 0
}
在这里,我们首先定义了一个通用的模板 first_type_getter
,它接受一个类型参数。然后,我们对这个模板进行了偏特化,通过模板的模板参数使其能够深入获取参数内部的类型。这个偏特化版本提取了模板参数Ts
中的第一个类型 T1
,并将其定义为 type
。在后续使用时,我们就可以通过first_type<std::tuple<int, float, double>>
来获取元组的第一个类型int
。
模板的模板参数语法
需要注意的是,使用模板的模板参数时,正确的语法应该长这样 template<template<typename...> typename Ts>
。其中template<typename...>
表示这个参数是一个模板,然后后面还需要加上typename
以表明这是一个类型参数。
在上述的例子中,即便Ts的实参是一个类型别名,编译器仍然能够正确解析。
比如,如果我们使用using MyTuple = std::tuple<int, float, double>;
,然后调用 first_type<MyTuple>
,编译器仍然能够正确解析出 int
作为第一个类型。
为什么这里需要以偏特化的方式来实现第一个类型获取?
我们先假定不用偏特化的形式,直接定义一个模板:
template<template<typename...> typename Ts, typename T1, typename... TN>
struct first_type_getter {
using type = T1;
};
如此,我们可以使用first_type_getter<std::tuple<int, float, double>>
来获取第一个类型吗?答案是否定的。因为在这种情况下,first_type_getter
的实例化需要提供不止一个模板参数(因为形参列表有多个参数),和我们的本意相悖。由于我们只需要传一个模板实参,且目的是从该模板实参中提取出内部的参数,所以只能先定义一个通用的、接受一个类型参数的模板,然后再对其进行偏特化。