1. 基本语法
在 C++14 之前,我们只能定义函数模板、类模板。C++14 引入了第三种模板变量模板,它允许我们将变量定义为模板。这使得我们可以创建类型相关的常量值或变量,大大增强了模板元编程的能力。
变量模板的声明方式类似于函数模板或类模板,只是在开头使用了 template <...> 和随后的变量声明。
基本语法格式如下:
1 2 3 4 5
| template<typename T> constexpr T pi = T(3.1415926535897932385L);
template<typename T> T special_value = T(42);
|
1.1 简单的变量模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using namespace std;
template<typename T> constexpr T pi = T(3.1415926535897932385L);
int main() { double d = pi<double>;
int i = pi<int>;
cout << "d = " << d << ", i = " << i << endl; return 0; }
|
在上面的例子中,pi 是一个变量模板。当我们使用 pi<double> 时,编译器会实例化一个类型为 double 的变量;使用 pi<int> 时则实例化一个 int 变量。
1.2 类型相关的常量
numeric_limits 是 C++ 标准库中的一个类模板,位于 <limits> 头文件中。它用于查询各种算术类型(如整数、浮点数)的属性信息。
我们可以使用这个类的静态方法获取类型相关的常量,比如数字极限:
max() - 类型的最大值
min() - 类型的最小值
lowest() - 类型的最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <limits> #include <iostream>
template<typename T> constexpr T max_value = std::numeric_limits<T>::max(); template<typename T> constexpr T min_value = std::numeric_limits<T>::min();
int main() { std::cout << "int max: " << max_value<int> << std::endl; std::cout << "float max: " << max_value<float> << std::endl; std::cout << "int min: " << min_value<int> << std::endl; std::cout << "float min: " << min_value<float> << std::endl; return 0; }
|
1.3 默认参数
在 C++14 中使用变量模板的时候,也可以给它指定默认模板参数,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream>
template<typename T = double> constexpr T pi = T(3.1415926535897932385L);
int main() { std::cout << pi<> << std::endl; std::cout << pi<double> << std::endl; std::cout << pi<float> << std::endl; std::cout << pi<long double> << std::endl; return 0; }
|
如果给变量模板指定了默认参数,以下这两种使用方法是等价的:
使用默认类型 double:pi<>
显式指定 double:pi<double>
关于给定的实际数据(例如上面示例中的3.1415926535897932385L)的类型转换也是有两种书写方式:
2. 变量模板特化
变量模板特化允许我们为特定的模板参数提供定制化的实现。与类模板特化类似,变量模板特化也分为全特化和偏特化两种形式。不论是哪种变量模板的特化一共分为以下几个步骤:
- 声明主模板
- 选择特化类型:全特化 / 偏特化
- 编写特化声明:
- 全特化:
template<>
- 偏特化:
template<修改的参数列表>
- 确保特化可见性(放在使用之前)
- 测试所有特化路径
2.1 全特化
2.1.1 返回固定类型
1 2 3
| template<typename T> constexpr const char* type_name = "unknown";
|
- 模板参数为具体类型:例如
int、double、std::string等
- 返回类型固定:无论
T 是什么类型,都返回 const char*
- 值不同:通过模板特化为不同的类型返回不同的字符串
- 用途:获取类型的字符串表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| template<> constexpr const char* type_name<int> = "int";
template<> constexpr const char* type_name<double> = "double";
template<> constexpr const char* type_name<std::string> = "std::string";
int main() { std::cout << type_name<int> << std::endl; std::cout << type_name<double> << std::endl; std::cout << type_name<std::string> << std::endl; std::cout << type_name<float> << std::endl; return 0; }
|
template<>:表示完全特化(没有剩余模板参数)
constexpr const char*:与主模板一致,返回特定类型
type_name<int>:特化的具体实例,模版参数为具体类型(如 int, double, const char*等)
2.1.2 返回类型依赖模板参数
声明主模版
1 2
| template<typename T> constexpr T default_value = T{};
|
template<typename T>:声明这是一个模板,T 是类型参数(例如int、double、std::string等)
constexpr:C++11 引入,C++14 增强,表示这是编译期常量,如果程序需要可以自行添加
default_value:变量模板名
T{}:值初始化,对于内置类型初始化的默认值为零
编写特化声明
1 2 3 4 5 6 7 8
| template<> constexpr int default_value<int> = -1;
template<> constexpr double default_value<double> = 0.0;
template<> constexpr const char* default_value<const char*> = "default";
|
template<>:表示完全特化(没有剩余模板参数)
constexpr int:必须提为T供具体的特化的类型(如 int, double, const char*等)
default_value<int>:特化的具体实例
= -1:为int类型提供的特殊默认值
让我们扩展上面这个例子,展示更多的 C++14 特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream> #include <type_traits>
template<typename T> constexpr T default_value = T{};
template<> constexpr int default_value<int> = 666;
template<> constexpr double default_value<double> = 3.14;
template<> constexpr const char* default_value<const char*> = "Hello, C++14!";
template<typename T> constexpr auto get_default() { return default_value<T>; }
auto print_default = [](auto type_id) { using T = decltype(type_id); std::cout << "get_default value: " << get_default<T>() << std::endl; std::cout << "default_value: " << default_value<T> << std::endl; };
int main() { std::cout << default_value<int> << std::endl; constexpr long long big_number = 1'000'000'000; print_default(0); print_default(0.0); print_default("hello"); print_default(true); return 0; }
|
对应上面的代码有一个小细节需要大家理解和掌握。有这样一行代码:
1
| constexpr const char* default_value<const char*> = "default";
|
constexpr:这个变量本身(指针变量)是编译期常量(指针值在编译期确定且不可变)
const char*:这是类型,表示指向常量字符的指针
但是这里有个特殊情况!由于有 constexpr 修饰,其实指针本身也变成常量了。所以可以得到如下结论:
1
| constexpr const char* ptr = "Hello";
|
等价于
1
| const char* const ptr = "Hello";
|
因为 constexpr 对象默认是 const 的(C++标准规定)。所以实际上:
- 指针本身不可变(因为有
constexpr)
- 指向的内容也不可变(因为指向
const char)
2.2 偏特化
在 C++14 中,偏特化 是模板编程中的一个核心概念,它允许我们为模板参数的特定模式或特定子集提供自定义的实现,而不是使用通用的主模板。
对于变量模板而言,偏特化意味着:针对某些特定的类型条件(例如指针、数组、特定类型的组合),重新定义该变量的值。
为了详细说明变量模板偏特化的用途,我们将通过几个具体的场景进行演示。
2.2.1 类型特征检查
这是 C++14 引入变量模板最主要的原因之一:为了简化 C++11 中繁琐的 std::some_trait<T>::value 写法,变为 std::some_trait_v<T>。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream>
template<typename T> constexpr bool is_pointer_trait = false;
template<typename T> constexpr bool is_pointer_trait<T*> = true;
template<typename T, typename Class> constexpr bool is_pointer_trait<T Class::*> = true;
int main() { std::cout << std::boolalpha; std::cout << "int: " << is_pointer_trait<int> << std::endl;
std::cout << "int*: " << is_pointer_trait<int*> << std::endl;
std::cout << "const char*: " << is_pointer_trait<const char*> << std::endl;
struct MyClass { int value; }; std::cout << "int MyClass::*: " << is_pointer_trait<int MyClass::*> << std::endl;
return 0; }
|
在这个例子中,当我们使用 int* 实例化 is_pointer_trait 时,编译器发现 int* 符合偏特化 T* 的模式,因此选择了 true,而不是主模板的 false。偏特化的特征如下:
- 模板参数列表不为空
- 特化匹配(如
T*、T&、const T 等)
- 适用于一类类型,而不是单个具体类型
2.2.2 数组类型检测
下面这段代码定义了一个编译时类型特征,用于检测一个类型是否是数组类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream>
template<typename T> constexpr bool is_array_v = false;
template<typename T, std::size_t N> constexpr bool is_array_v<T[N]> = true;
template<typename T> constexpr bool is_array_v<T[]> = true;
template<typename T, std::size_t N, std::size_t M> constexpr bool is_array_v<T[N][M]> = true;
int main() { std::cout << std::boolalpha; std::cout << "int: " << is_array_v<int> << std::endl; std::cout << "int[5]: " << is_array_v<int[5]> << std::endl; std::cout << "int[]: " << is_array_v<int[]> << std::endl; std::cout << "int[3][4]: " << is_array_v<int[3][4]> << std::endl; return 0; }
|
上面程序的工作原理(模板匹配优先级)是这样的,比如当使用 is_array_v<int[5]> 时:
- 编译器首先尝试匹配最特化的版本
T[N] 匹配成功:T → int,N → 5
- 返回
true
2.3 匹配优先级
在使用变量模板的特化的时候可以同时定义了全特化和偏特化,此时我们就需要知道它们的匹配优先级。编译器按以下顺序选择:
- 全特化(最具体)
- 偏特化(次具体)
- 主模板(最通用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <type_traits>
template<typename T> constexpr const char* type_category = "unknown";
template<typename T> constexpr const char* type_category<T*> = "pointer";
template<typename T> constexpr const char* type_category<T&> = "lvalue reference";
template<> constexpr const char* type_category<int*> = "int pointer";
template<> constexpr const char* type_category<const char*> = "C-style string";
template<> constexpr const char* type_category<void> = "void";
int main() { std::cout << "匹配测试:" << std::endl; std::cout << "====================================" << std::endl; std::cout << "int*: " << type_category<int*> << std::endl; std::cout << "double*: " << type_category<double*> << std::endl; std::cout << "int: " << type_category<int> << std::endl; std::cout << "const char*: " << type_category<const char*> << std::endl; std::cout << "void: " << type_category<void> << std::endl; int x = 10; int& ref = x; std::cout << "int&: " << type_category<decltype(ref)> << std::endl; std::cout << "int**: " << type_category<int**> << std::endl; return 0; }
|
type_category<int*>:匹配全特化,全特化优先于偏特化,因此匹配全特化 int pointer
type_category<double*>:匹配偏特化,没有 double*的全特化,匹配偏特化 pointer
type_category<int>:匹配主模板,没有匹配的特化,匹配主模板 unknown
type_category<const char*>:特殊字符串类型,匹配全特化 C-style string
type_category<void>:void 类型,匹配全特化 void
type_category<decltype(ref)>:引用类型,匹配偏特化 lvalue reference
type_category<int**>:多级指针,没有 int** 的全特化,匹配 T* 偏特化 ,结果是 pointer