1. 程式人生 > >C++非型別模板引數

C++非型別模板引數

非型別模板參看,顧名思義,模板引數不限定於型別,普通值也可作為模板引數。在基於型別的模板中,模板例項化時所依賴的是某一型別的模板引數,你定義了一些模板引數(template<typename T>)未加確定的程式碼,直到模板被例項化這些引數細節才真正被確定。而非型別模板引數,面對的未加確定的引數細節是指(value),而非型別。當要使用基於值的模板時,你必須顯式地指定這些值,模板方可被例項化。

非型別類模板引數

這裡我們使用一個新版本的Stack類模板,這類模板的底層容器是一個一維陣列,陣列的元素型別由模板型別引數typename T指定,而一位陣列在初始化時必須指定其大小,這個大小便可通過一個非型別的模板引數int MAXSIZE

指定,當然也可通過建構函式傳遞。

template<typename T, int MAXSIZE>
class Stack
{
public:
    Stack():idx(0){}
    bool empty() const { return idx == 0;}
    bool full() const { return idx == MAXSIZE;}
    void push(const T&);
    void pop();
    T& top();
    const T& top() const;
private:
    int idx; 
    T elems[MAXSIZE];
}

template
<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::push(const T& elem) { if (full()) throw std::out_of_range("Stack<>::push(): full stack"); elems[idx++] = elem; } template<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::pop() { if (!empty()) idx--; else
throw std::out_of_range("Stack<>::pop(): empty stack") } template<typename T, int MAXSIZE> T& Stack<T, MAXSIZE>::top() { if (empty()) throw std::out_of_range("Stack<>::top(): empty stack"); return elems[idx-1]; } template<typename T, int MAXSIZE> const T& Stack<T, MAXSIZE>::top() const { if (empty()) throw std::out_of_range("Stack<>::top(): empty stack"); return elems[idx-1]; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

客戶端程式:

try
{
    Stack<int, 10> int10Stack;
    Stack<int, 20> int20Stack;
    int20Stack.push(7);
    ...
}
catch(std::exception& ex)
{
    cout << ex.what() << endl;
    return EXIT_FAILURE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

每個模板例項都有自己的型別,int10Stackint20Stack屬於不同的型別,這兩種型別之間也不存在顯示或隱式的型別轉換。

同樣地,也可以為非型別模板引數指定預設值:

template<typename T, int MAXSIZE = 20>
class Stack
{
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5

這樣在呼叫時:

Stack<int> intStack;        // == Stack<int, 20>
  • 1

非型別函式模板引數

同樣地也可以為函式模板定義為非型別引數,如下的函式模板定義一組用於增加特定值的函式:

template<typename T, int VAL>
T addValue(const T& x)
{
    return x + VAL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

當然這樣做純粹為了演示,意義不大,我們可以將非型別引數的型別(這話有點繞)定義為型別引數:

template<typename T, T VAL>
T addValue(const T& x)
{
    return x+VAL;
}
  • 1
  • 2
  • 3
  • 4
  • 5

千萬不要小瞧這樣的簡單機制(整個計算機理論的恢弘大廈的本質也還是0/1呢),如果把函式(仿函式)或者某一操作當做引數傳遞給某一函式,這些實現了一些簡單的功能的函式將產生巨大的作用。例如,藉助標準模板庫(STL),可以將這個函式模板的一個例項傳遞給集合中的每一個元素,將集合中的每一個值都增加一個預設的值。

#include <algorithm>
std::vector<int> ivec;
std::transform(src.begin(), src.end(),      // 原容器(待轉換)的起點和終點 
                    dst.begin(),            // 目標容器的起點
                    addValue<std::vector<int>::value_type, 10>);    // 操作或者函式(也可以是仿函式)
  • 1
  • 2
  • 3
  • 4
  • 5

這裡要想成為STL <algorithm>演算法中某一函式的引數(尤其是在內部作為函式使用的),需要滿足一定的要求,比如本例中的std::transform(),傳遞進來的在內部作為函式使用的第四個引數,只能接受一個引數,且需返回一個值(返回值的型別就是目標容器的元素型別),這是基本要求。

另外還有一個問題需要注意,addValue<int, 5>是一個函式模板的例項化版本,而函式模板的例項化通常被視為用來命名一組過載函式的集合(即使該集合中只有一個元素)。在一些較老的C++標準裡,過載函式的集合不能用於模板引數(如本例的transform())的演繹。於是,必須顯式地將函式模板的實參強制轉換為具體的型別:


std::transform(ivec.begin(), ivec.end(), dst.begin(),
                (int(*)(const int& ))addValue<int, 5>);
  • 1
  • 2
  • 3

一個完整的演示程式即是:

int arr[] = {1, 2, 3, 4, 5};
vector<int> src(arr, arr+5), dst(5);
typedef vector<int>::value_type value_type;
transform(src.begin(), src.end(), dst.begin(), 
            (value_type (*)(const value_type&)addValue<value_type, 5>);
copy(dst.begin(), dst.end(), ostream_iterator<value_type>(cout, " "));
        // ostream_iterator 在<iterator>的std名稱空間中
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

非型別模板引數的限制

非型別模板引數是有型別限制的。一般而言,它可以是常整數(包括enum列舉型別)或者指向外部連結物件的指標

浮點數和類物件(class-type)不允許作為非型別模板引數:

template<double VAL>            // ERROR: 浮點數不可作為非型別模板引數
double process(double v)
{
    return v * VAL;
}

template<std::string name>      // ERROR:類物件不能作為非型別模板引數
class MyClass
{}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

稍作變通,我們即可使編譯通過:

template<double* PVAL>
double process(const double& x)
{
    return x * (*PVAL);
}

template<const char* name>
class MyClass
{
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這樣可順利通過編譯,但如果想在當前檔案中使用這兩個模板,還需要動一些手腳:

double val = 10;
double res = process<&val>(20);     // ERROR: 表示式必須含有常量值

MyClass<"hello"> x;                 // ERROR: 模板引數不能引用非外部實體

const char* s  = "hello";
MyClass<s> x;                       // ERROR: 表示式必須含有常量值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這裡就點出另外一點注意事項,也就是非型別模板引數的限制,非型別模板引數可以是指標,但該指標必須指向外部連結物件,還記得在A.cpp中如何引用B.cpp中的全域性變數嗎,在A.hpp中使用extern關鍵字對外部變數加以引用。

// B.cpp
double val = 3.14159265;
char str[] = "hello";
  • 1
  • 2
  • 3
// A.hpp
extern double val;
extern char str[];
  • 1
  • 2
  • 3
  • 4
// A.cpp
#include "A.hpp"

double res = process<&val>(10);
MyClass<str> x;
  • 1
  • 2
  • 3
  • 4
  • 5