变量模板和非类型模板参数

变量模板

变量模板(Variable Templates)是C++14引入的一个新特性,它允许我们定义模板化的变量。我们可以创建一个变量,其类型或值依赖于模板参数。

基本语法

变量模板的声明和定义类似于函数模板。基本语法如下:

1 // 变量模板
2 template<typename T>
3 constexpr T pi = T(3.14159265358979323846);

用法

定义编译期常量

 1 // 定义变量模板
 2 template<typename T>
 3 constexpr T max_value = std::numeric_limits<T>::max();
 4 
 5 template<typename T>
 6 constexpr T pi_v = T(3.14159265358979323846);
 7 
 8 template<typename T>
 9 constexpr T e_v = T(2.71828182845904523536);
10 
11 int main()
12 {
13     std::cout << "Max int: " << max_value<int> << std::endl;
14     std::cout << "Max double: " << max_value<double> << std::endl;
15 
16     std::cout << "Pi as float: " << pi_v<float> << std::endl;
17     std::cout << "Pi as double: " << pi_v<double> << std::endl;
18 
19     std::cout << "e as float: " << e_v<float> << std::endl;
20     std::cout << "e as double: " << e_v<double> << std::endl;
21     return 0;
22 }

输出如下:

Max int: 2147483647
Max double: 1.79769e+308
Pi as float: 3.14159
Pi as double: 3.14159
e as float: 2.71828
e as double: 2.71828

变量模板特化

 1 // 主模板
 2 template<typename T>
 3 constexpr T default_value = T{};
 4 
 5 // 全特化
 6 template<>
 7 constexpr int default_value<int> = 42;
 8 
 9 template<>
10 constexpr double default_value<double> = 3.14;
11 
12 
13 // std::string 不能用作 constexpr,下面代码有错
14 // template<>
15 // constexpr std::string default_value<std::string> = "Hello";
16 
17 template<>
18 constexpr const char* default_value<const char*> = "Hello";
19 
20 
21 // 偏特化(通过重载实现类似效果)
22 template<typename T>
23 constexpr T* default_value<T*> = nullptr;
24 
25 int main()
26 {
27     std::cout << "Default int: " << default_value<int> << std::endl;
28     std::cout << "Default double: " << default_value<double> << std::endl;
29     std::cout << "Default const char*: " << default_value<const char*> << std::endl;
30     std::cout << "Default int*: " << default_value<int*> << std::endl;
31     return 0;
32 }

输出:

Default int: 42
Default double: 3.14
Default const char*: Hello
Default int*: 0

非类型模板参数

非类型模板参数是指模板参数不是类型,而是具体的值。例如,下述代码用于定义 Fibonacci 数列。

template<int N>
constexpr int fibonacci = fibonacci<N - 1> +fibonacci<N - 2>;

template<>
constexpr int fibonacci<0> = 0;

template<>
constexpr int fibonacci<1> = 1;

上述代码中,constexpr 关键字表示这是编译期常量,确保变量模板可以在编译期使用。

目前,非类型模板参数只能是以下几种类型(C++17后放宽):

  • 整型(包括枚举)
  • 指针类型
  • 引用类型
  • 指向成员的指针

注意:不支持浮点类型和类类型。

简单用法

基本用法

 1 // 整型类型
 2 template<int N>
 3 class Array 
 4 {
 5 public:
 6     int data[N];
 7     int size() const { return N; }
 8 };
 9 
10 // 指针类型
11 template<const char* Str>
12 class StringWrapper 
13 {
14 public:
15     const char* get() const { return Str; }
16 };
17 
18 // 引用类型
19 template<int& Ref>
20 class ReferenceWrapper 
21 {
22 public:
23     int get() const { return Ref; }
24     void set(int value) { Ref = value; }
25 };
26 
27 // 枚举类型
28 enum class Color { Red, Green, Blue };
29 template<Color C>
30 class ColorTemplate 
31 {
32 public:
33     static constexpr Color value = C;
34 };

编译时计算

 1 template<int N>
 2 constexpr int factorial = N * factorial<N-1>;
 3 
 4 template<>
 5 constexpr int factorial<0> = 1;
 6 
 7 template<int Base, int Exp>
 8 constexpr int power = Base * power<Base, Exp - 1>;
 9 
10 template<int Base>
11 constexpr int power<Base, 0> = 1;
12 
13 int main()
14 {
15     std::cout << "Factorial(5): " << factorial<5> << std::endl;
16     std::cout << "2^5 = " << power<2, 5> << std::endl;
17     return 0;
18 }

输出如下:

Factorial(5): 120
2^5 = 32

非类型模板参数的链接性

先看一段代码:

1 constexpr int arr[3] = {1, 2, 3};
2 template<const int* P> struct ArrayPtr {};
3 
4 int main()
5 {
6     constexpr int arr2[3] = {1, 2, 3}; 
7     ArrayPtr<arr> a;
8     ArrayPtr<arr2> b; // error: 'arr2' is not a valid template argument of type 'const int*' because 'arr2' has no linkage
9 }

ArrayPtr<arr2> b 在使用居然报错了。这个问题涉及到 C++ 模板非类型参数和链接性的规则。arr 和 arr2 的链接性是不相同的:

  • arr 是全局变量,具有外部链接。
  • arr2 是局部变量(在 main 函数内),无链接。

C++ 标准对非类型模板参数的要求:

  1. 整型(包括 int, char, bool, enum 等):
  • 只需要是常量表达式,可以是局部 constexpr 变量。
  1. 指针/引用:
  • 必须指向具有外部或内部链接的实体。
  • 不能指向局部变量(无链接)

要让 arr2 可用作模板参数,可以使用 static 关键字或者使用命名空间:

 1 // 使用 static
 2 int main()
 3 {
 4     static constexpr int arr2[3] = {1, 2, 3};  // 内部链接
 5     ArrayPtr<arr2> b;  // 现在合法
 6 }
 7 
 8 // 使用命名空间作用域
 9 namespace 
10 {
11     constexpr int arr2[3] = {1, 2, 3};  // 内部链接
12 }
13 
14 int main()
15 {
16     ArrayPtr<arr2> b;  // 合法
17 }

这个限制主要是为了确保模板实例化的一致性:

  • 有链接的实体在程序的整个生命周期中都有唯一的地址。
  • 无链接的实体(如局部变量)可能在不同的翻译单元或上下文中有不同的实例。
  • 模板需要在编译时确定唯一的标识符。

 

posted @ 2026-04-13 16:47  西兰花战士  阅读(3)  评论(0)    收藏  举报