樣板 (C++)

範本是 C++ 中泛型程式設計的基礎。 C++ 是強型別語言,需要所有變數都有特定類型,由程式設計人員明確宣告或編譯器推導。 不過,無論其運作的類型為何,許多資料結構和演算法看起來都相同。 範本可讓您定義類別或函式的作業,並讓使用者指定那些作業應該處理的具體類型。

定義和使用範本

範本是建構,根據使用者為範本參數提供的引數,在編譯時期產生一般類型或函式。 例如,您可以定義如下所示的函式範本:

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

上述程式碼描述具有單一類型參數 T 之泛型函式的範本,其傳回值和呼叫參數 (lhs 和 rhs) 都是此類型。 您可以命名任何您想要的類型參數,但依照慣例,最常使用單一大寫字母。 T 是範本參數; typename 關鍵字表示此參數是類型的預留位置。 呼叫 函式時,編譯器會將 的每個實例 T 取代為使用者指定的具體型別引數,或編譯器推論。 編譯器從範本產生類別或函式的程式稱為 範本具現化 ; minimum<int> 是範本 minimum<T> 的具現化。

在其他地方,使用者可以宣告針對 int 特製化的範本實例。假設 get_a() 和 get_b() 是傳回 int 的函式:

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

不過,由於這是函式範本,而且編譯器可以從 和 b 引數 推斷 的 T 型別,所以您可以像一般函式一樣呼叫它:

int i = minimum(a, b);

當編譯器遇到最後一個語句時,它會產生新的函式,其中範本中每次出現的 T 都會取代為 int

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

編譯器如何在函式範本中執行型別推算的規則,是以一般函式的規則為基礎。 如需詳細資訊,請參閱 函數範本呼叫 的多載解析。

型別參數

在上述範本中 minimum ,請注意,類型參數 T 在函式呼叫參數中使用之前,不會以任何方式限定,其中會新增 const 和參考限定詞。

類型參數數目沒有實際限制。 以逗號分隔多個參數:

template <typename T, typename U, typename V> class Foo{};

關鍵字 class 相當於 typename 在此內容中。 您可以將上述範例表示為:

template <class T, class U, class V> class Foo{};

您可以使用省略號運算子 (...) 來定義採用任意數目零或多個類型參數的範本:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

任何內建或使用者定義型別都可以當做類型引數使用。 例如,您可以在標準程式庫中使用 std::vector 來儲存類型 int 為 、 double std::string MyClassMyClassconst 、*、 MyClass& 等的變數。 使用範本時的主要限制是類型引數必須支援套用至類型參數的任何作業。 例如,如果我們 minimum 呼叫 using MyClass ,如下列範例所示:

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

會產生編譯器錯誤,因為 MyClass 未提供 運算子的多 < 載。

沒有任何固有需求,任何特定範本的類型引數全都屬於相同的物件階層,不過您可以定義強制執行這類限制的範本。 您可以將物件導向技術與範本結合;例如,您可以將 Derived* 儲存在向量 < Base* > 中。 請注意,引數必須是指標

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

和其他標準程式庫容器對 元素 T 施加的基本需求 std::vectorT 可複製和可複製建構。

非類型參數

不同于其他語言的泛型型別,例如 C# 和 JAVA,C++ 樣板支援 非類型參數 ,也稱為值參數。 例如,您可以提供常數整數值來指定陣列的長度,如同這個範例,與標準程式庫中的 std::array 類別類似

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

請注意範本宣告中的語法。 此值 size_t 會在編譯時期以樣板引數的形式傳入,而且必須是 constconstexpr 運算式。 您可以使用它,如下所示:

MyArray<MyClass*, 10> arr;

其他類型的值,包括指標和參考,都可以以非型別參數的形式傳入。 例如,您可以傳入函式或函式物件的指標,以自訂範本程式碼內的某些作業。

非類型樣板參數的類型推算

在 Visual Studio 2017 和更新版本中,在 /std:c++17 模式或更新版本中,編譯器會推算以 auto 宣告的非類型樣板引數類型:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

範本作為範本參數

範本可以是範本參數。 在此範例中,MyClass2 有兩個範本參數:typename 參數 T 和範本參數 Arr

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

因為 Arr 參數本身沒有主體,因此不需要其參數名稱。 事實上,從 主體 MyClass2 內參照 Arr 的 typename 或類別參數名稱是錯誤的。 因此, 可以省略 Arr 的類型參數名稱,如下列範例所示:

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

預設範本引數

類別和函式範本可以有預設引數。 當範本有預設引數時,您可以在使用範本時將其保留為未指定。 例如,std::vector 範本具有配置器的預設引數:

template <class T, class Allocator = allocator<T>> class vector;

在大部分情況下,預設 std::allocator 類別是可接受的,因此您可以使用如下的向量:

vector<int> myInts;

但如有必要,您可以指定自訂配置器,如下所示:

vector<int, MyAllocator> ints;

若有多個樣板引數,則第一個預設引數之後的所有引數都必須有預設引數。

使用所有預設參數的範本時,請使用空角括弧:

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

範本特製化

在某些情況下,範本不可能或想要針對任何類型定義完全相同的程式碼。 例如,您可能想要定義只有在類型引數是指標、std::wstring 或衍生自特定基類的類型時,才要執行的程式碼路徑。 在這種情況下,您可以定義 該特定類型的範本特製化 。 當使用者具現化具有該類型的範本時,編譯器會使用特製化來產生 類別,而針對所有其他類型,編譯器會選擇較一般範本。 特製化的所有參數都是 完整的特製化 。 如果只有部分參數是特製化的,則稱為 部分特製化

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

只要每個特製化類型參數是唯一的,範本就可以有任意數目的特製化。 只有類別範本可以部分特製化。 範本的所有完整和部分特製化都必須在與原始範本相同的命名空間中宣告。

如需詳細資訊,請參閱 範本特製化