摘要訊息 : C++ 14 引入了變數樣板, 使得樣板可以用於變數, 函式和類別.

0. 前言

C++ 11 中, 如果我們想要獲得某個特性萃取的結果, 我們通常會寫成 constexpr auto value = std::is_signed<int>::value;. 為了得到更多類似的 value, 我們可能要對 long, long long, short, signed charchar 都進行類似的宣告. 然而對於剩餘的型別呢? 也要一個一個寫出來嗎?

為了解決上面這個需求, C++ 14 的提案 N3651《Variable Templates》提出了變數樣板, 使用帶有樣板的變數來解決這個需求.

更新紀錄 :

  • 2022 年 4 月 22 日進行第一次更新和修正.

1. 提案內容

變數樣板的基本形式是

template <template-parameter>
constexpr static T variable-name {initializer-list};

其中, template-parameter 是樣板參數, T 是型別, variable-name 是變數的名稱, {initializer-list} 是初始化列表. 當然, 使用等號或者圓括號進行初始化的方式都是可以的. 另外, constexprstatic 是可選的標識.

3.1415926 這樣的常數, 在 C++ 中如果沒有任何後綴, 那麼預設是 double 型別. 而實際使用的時候, 它並不一定是 double 型別, 可能是 long double, float 甚至是用戶自訂型別. 如果沒有辦法確定它的型別, 那麼就可以用 C++ 14 的變數樣板 :

template <typename T>
constexpr T pi {3.1415926};

如果我們需要 float 型別, 那麼我們就可以直接使用 pi<float>. 在第 0 節中的需求, 我們可以寫成

#include <type_traits>

template <typename T>
constexpr auto value = std::is_signed<T>::value;

C++ 14 同樣為標準樣板程式庫 <type_traits> 引入了變數樣板. 對於超函式內有 value 成員變數的, 都加入了一個變數樣板 _v. 例如 std::is_signed_vstd::is_assignable_v 等. 我們在使用的時候, 就可以不需要加 ::value 了.

對於類別內的 static 變數, 同樣也可以使用變數樣板. 但是如果初始化在類別可視範圍之外, 那麼需要重新宣告樣板參數. 另外, 如果變數型別和樣板參數無關, 也就是變數的型別已知, 那麼變數必須在類別可視範圍內進行初始化 :

#include <type_traits>

struct Foo {
    template <typename T>
    static const volatile T value;
};
template <typename T>
const volatile T Foo::value {};

struct Bar {
    template <typename T>
    constexpr static bool value;        // Error
    template <typename T>
    constexpr static bool value {std::is_union<T>::value};       // OK
};

如果閣下熟悉 SFINAE, 那麼需要注意, SFINAE 是不能用於變數樣板中的 :

#include <type_traits>
#include <iostream>

template <int N>
typename std::enable_if<N % 2 == 0, int>::type value {};

template <int N>
typename std::enable_if<N % 2 == 0, int>::type value2 {};
template <int N>
typename std::enable_if<N % 2 == 1, char>::type value2 {'0'};     // redefinition of 'value' with a different type: 'typename std::enable_if<N % 2 == 1, char>::type' vs 'typename std::enable_if<N % 2 == 0, int>::type'

int main(int argc, char *argv[]) {
    std::cout << value<0> << std::endl;       // 輸出結果 : 0
    std::cout << value<1> << std::endl;       // Error : failed requirement '1 % 2 == 0'; 'enable_if' cannot be used to disable this declaration
}

一旦編碼器檢測到 std::enable_if 的第一個樣板引數的結果為 false, 那麼就會立馬擲出編碼錯誤.