Skip to content

变量模板

本节将介绍 C++14 变量模板

初识变量模板

变量模板不是变量,只有实例化的变量模板,编译器才会生成实际的变量。

变量模板实例化后简单的说就是一个全局变量,所以也不用担心生存期的问题。

定义变量模板

cpp
template<typename T>
T v;

就这么简单,毫无难度。

当然了,既然是变量,自然可以有各种的修饰,比如 cv 限定,比如 constexpr ,当然也可以有初始化器,比如 {}= xxx

cpp
template<typename T>
constexpr T v{};

使用变量模板

cpp
template<typename T>
constexpr T v{};

v<int>;     // 相当于 constexpr int v = 0;

我们知道 constexpr 附带了 const 类型,所以其实:

cpp
std::cout << std::is_same_v<decltype(v<int>),const int> << '\n';

std::is_same_v 其实也是个变量模板,在 C++17 引入。这里用来比较两个类型是否相同,如果相同返回 1,不相同返回 0。暂时不用纠结它是如何实现的,后续会手搓。

会打印 1,也就是 v<int> 的类型其实就是 const int


我们再提出一个问题,v<int>v<double> 有什么关系吗?

最早在函数模板中,我们强调了“同一个函数模板生成的不同类型的函数,彼此之间没有任何关系”,这句话放在类模板、变量模板中,也同样适用。

cpp
std::cout << &v<int> << '\n';
std::cout << &v<double> << '\n';

以上示例打印的地址不会相同。

有默认实参的模板形参

变量模板和函数模板、类模板一样,支持模板形参有默认实参。

cpp
template<typename T = int>
constexpr T v{};

int b = v<>;        // v 就是 v<int> 也就是 const int v = 0

与函数模板和类模板不同,即使模板形参有默认实参,依然要求写明 <>

非类型模板形参

变量模板和函数模板、类模板一样,支持非类型模板形参。

cpp
template<std::size_t N>
constexpr int v = N;

std::cout << v<10> << '\n'; // 等价 constexpr int v = 10;

当然,它也可以有默认值:

cpp
template<std::size_t N = 66>
constexpr int v = N;

std::cout << v<10> << '\n';
std::cout << v<> << '\n';

可变参数变量模板

变量模板和函数模板、类模板一样,支持形参包与包展开。

cpp
template<std::size_t...values>
constexpr std::size_t array[]{ values... };

int main() {
    for (const auto& i : array<1, 2, 3, 4, 5>) {
        std::cout << i << ' ';
    }
}

array 是一个数组,我们传入的模板实参用来推导出这个数组的大小以及初始化。

{values...} 展开就是{1, 2, 3, 4, 5}

cpp
std::cout << std::is_same_v<decltype(::array<1, 2, 3, 4, 5>), const std::size_t[5]>; // 1

在 msvc 与 gcc14 会输出 1,但是 gcc14 之前的版本、clang,却会输出 0msvc 与 gcc14 是正确的。 gcc 与 clang 不认为 array<1, 2, 3, 4, 5>const std::size_t[5] 类型相同;它们认为 array<1, 2, 3, 4, 5>const std::size_t[] 类型相同,这显然是个 bug

可以参见 llvm issues.

类静态数据成员模板

在类中也可以使用变量模板。

类静态数据成员

讲模板之前先普及一下静态数据成员的基本知识,因为网上很多资料都是乱讲,所以有必要重复强调一下。

cpp
struct X{
    static int n;
};

n 是一个 X 类的静态数据成员,它在 X 中声明,但是却没有定义,我们需要类外定义。

cpp
int X::n;

或者在 C++17 以 inline 或者 constexpr 修饰。

因为 C++17 规定了 inline 修饰静态数据成员,那么这就是在类内定义,不再需要类外定义。constexpr 在 C++17 修饰静态数据成员的时候,蕴含了 inline

cpp
struct X {
    inline static int n;
};

struct X {
    constexpr static int n = 1;      // constexpr 必须初始化,并且它还有 const 属性
};

模板

与其他静态成员一样,静态数据成员模板的需要一个定义。

cpp
struct limits{
    template<typename T>
    static const T min; // 静态数据成员模板的声明
};
 
template<typename T>
const T limits::min = {}; // 静态数据成员模板的定义

当然,如果支持 C++17 你也可以选择直接以 inline 修饰。

变量模板分文件

变量模板和函数模板、类模板一样,通常写法不支持分文件,原因都一样。

总结

变量模板其实很常见,在 C++17,所有元编程库的类型特征均添加了 _v 的版本,就是使用的变量模板,比如 std::is_same_vstd::is_pointer_v 等;我们在后面会详细讲解。

如果学到这里了,如果你注意到,函数模板、类模板、变量模板,很多语法是共通的,是越学越简单,代表你思考了。

后续还有很多内容是一起的,比如模板偏特化、全特化、显式实例化等。