【转载】C++ 变量模板(variable template)

476 阅读2分钟

原文地址:C++基础::变量模板(variable template)

以下是正文,我对排版和错别字进行了一点修改

正文

既然允许 C++ 模板类(class template)的存在,允许 C++ 函数模板(function template)的存在,也应当允许变量模板(variable template)的存在。

引入

变量模板,标准 C++14(C++11 是 C++14 的一个 subset 子集,)的一个新的语法特性。C++ 新标准引入变量模板的主要目的是为了简化定义(simplify definitions)以及对模板化常量(parameterized constant)的支持。

C++14 之前的语法规则不允许使用模板声明的方式声明一个变量

template <typename T>
T var;        // not allowed in pre-C++14
var<int> = 5; // == (int var = 5;)

它足够新,以至于相当多的编译器还未完全支持,比如 Windows 下的 vs2013 。本文涉及的代码统一在 vs2019 中进行编译(选择这个编译器的目的主要在于它对 C++14 标准的支持)

条条大陆通罗马,此路不通(C++14 标准之前),我们虽然有一些另外的替代方案,但这些方案,往往冗余度过高且形式比较复杂。

方案 1

第一种替代方案是,使用类模板的 constexpr static 数据成员的方式

template <typename T>
struct PI
{
    constexpr static T pi = T(3.1415926535897932385);
    // 这里必须使用关键字 constexpr,而不可以是 const
    // const 常量必须在编译器得到确定
    // 自 C++11 起,constexpr 可以让表达式核定于编译期
}

// duplicate declaration
template <typename T>
constexpr T PI<T>::pi;

int main(int, char **)
{
    std::cout << PI<int>::pi << std::endl; // 3
    std::cout << PI<double>::pi << std::endl;// 3.14159
    return 0;
}

这种做法,因为 constant 是一种 ODR(One Definition Rule) 的定义规则。对 constant 的两次声明是有必要的,一次在类模板体内,一次在类模板体外,如上代码所示。

方案 2

另外一种解决方案是使用 constexpr 函数模板 的形式,该函数返回期待的类型。

template <typename T>
T PI()
{
    constexpr T pi = T(3.1415926535897932385);
    return pi;
}

int main(int, char **)
{
    std::cout << PI<int>() << std::endl;
    std::cout << PI<double>() << std::endl;
    return 0;
}

输出结果为

image.png

variable template

现在,有了 C++ 新标准对变量模板的支持,操纵一个 可变类型的常量 可以得到显著的简化,

// old version
PI<int>::pi // constexpr static data member
PI<int>()   // constexpr function

// new version
PI<int>
template <typename T>
constexpr T PI = T(3.1415926535897932385);
template <typename T>
T area(T r)
{
    return PI<T> * r * r;
}

int main(int, char **)
{
    std::cout << PI<int> << std::endl;
    std::cout << area(2.0) << std::endl; // 函数模板的自动类型推导
    return 0;
}

输出结果为

image.png

当然我们亦可以将变量模板应用到一个非常量变量上来;

tempalte<typename T>
T val = T(3.1415926535897932385);

int main(int, char **)
{
    val<float> = 0.6180339887498948482;
    std::cout << val<float> << std::endl; // 使用新赋的值
    std::cout << val<double> << std::endl; // 使用默认值
    return 0;
}

输出结果为

image.png

参考

<Introduction to Variable Templates of C++14>