函數模板中的類型歸納
一個非常簡單但很有用的例子:
//: :arraySize.h
// Uses template type induction to
// discover the size of an array
#ifndef ARRAYSIZE_H
#define ARRAYSIZE_H
template<typename T, int size>
int asz(T (&)[size]) { return size; }
#endif // ARRAYSIZE_H ///:~
沒有使用sizeof()操作,卻能在編譯階段指出數組的大小,你可以有更多簡明的方法來計算編譯時間數組的大小。
//: C03:ArraySize.cpp
// The return value of the template function
// asz() is a compile-time constant
#include "../arraySize.h"
int main()
{
int a[12], b[20];
const int sz1 = asz(a);
const int sz2 = asz(b);
int c[sz1], d[sz2];
} ///:~
當然,程式正常啟動並執行前提是:數組在定義時就已經給出了大小。
取得一個已經執行個體化的函數模板的地址
有很多地方,你都需要取得函數入口地址,例如,你可能有一個函數,它的參數是一個指向另一個函數的指標,當然了,
這個被指的函數有可能是從一個模板中產生的,所以,你需要一種方法來取得這樣的函數地址。
//: C03:TemplateFunctionAddress.cpp
// Taking the address of a function generated
// from a template.
template <typename T> void f(T*) {}
void h(void (*pf)(int*)) {}
template <class T>
void g(void (*pf)(T*)) {}
int main()
{
// Full type exposition:
h(&f<int>);
// Type induction:
h(&f);
// Full type exposition:
g<int>(&f<int>);
// Type inductions:
g(&f<int>);
g<int>(&f);
} ///:~
這個例子講述了許多的不同的主題。首先,即使你在使用模板,類型必須匹配——函數h()取了一個指向函數指標,這個函
授接受int類型的參數返回void類型。而這正是f的特點。第二,需要函數指標作為參數的這個函數本身也可以是一個模板,就
像g一樣。在main()函數中,你可以看到類型歸納同樣也在這裡工作。第一次調用h()明確的給出了模板參數f,但是從h()中看
到它僅僅處理使用int型變數做參數的函數的函數指標,那一部分可以由編譯器來歸納。G()的使用還要有意思些,因為這裡有兩
個模板要使用。編譯器不能歸納出來這個類型,也就什麼都不會做,但如果f和g都賦成int,其它的對編譯器來說也就好辦了。
模板中的成員類
向STL系列中應用函數
假設你想使用一個STL系列的容器,並且向容器中包含的所有對象應用一個函數,我之所以這樣說是因為一個vector可以包
含各種類型的對象,你需要一個函數可以同vector協同工作,處理它所包含的各種對象:
//: C03:applySequence.h
// Apply a function to an STL sequence container
// 0 arguments, any type of return value:
template<class Seq, class T, class R>
void apply(Seq& sq, R (T::*f)())
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)();
it++;
}
}
// 一個參數,傳回值不定
template<class Seq, class T, class R, class A>
void apply(Seq& sq, R(T::*f)(A), A a)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)(a);
it++;
}
}
//兩個參數,傳回值不定
template<class Seq, class T, class R,
class A1, class A2>
void apply(Seq& sq, R(T::*f)(A1, A2),
A1 a1, A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
{
((*it)->*f)(a1, a2);
it++;
}
}
// 諸如此類, 傳遞最多的類似的參數 ///:~
apply()函數模板使用了對容器類的一個引用和一個容器類內部成員函數的成員指標。它使用一個iterator在Stack中移動
定位向各個對象套用這個函數。
注意這裡沒有任何STL的標頭檔包含在applySequence.h中,所以它在與STL系列共同使用時,沒有限制。當然,在使用的時
候,考慮到iterator的特殊性,我們也只能把它應用到STL系列中了(別忘了,我們必須使用iterator)。
你可以看到,這裡有不止一個的apply(),因此它可以重載函數模板,每一個版本都使用了不同數量的參數,因為它是一個
模板,這些參數可以為任何類型,唯一的限制是:這不是超級模板可以為你建立出新的模板。
測試一下我們不同版本的apply()。
//: C03:Gromit.h
// The techno-dog. Has member functions
// with various numbers of arguments.
#include <iostream>
class Gromit
{
int arf;
public:
Gromit(int arf = 1) : arf(arf + 1) {}
void speak(int)
{
for(int i = 0; i < arf; i++)
std::cout << "arf! ";
std::cout << std::endl;
}
char eat(float)
{
std::cout << "chomp!" << std::endl;
return 'z';
}
int sleep(char, double)
{
std::cout << "zzz..." << std::endl;
return 0;
}
void sit(void) {}
}; ///:~
現在,函數apply()可以同vector<Gromit*>協同使用來製作一個容器,調用成員函數和被包含的對象:
//: C03:applyGromit.cpp
// Test applySequence.h
#include "Gromit.h"
#include "applySequence.h"
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<Gromit*> dogs;
for(int i = 0; i < 5; i++)
dogs.push_back(new Gromit(i));
apply(dogs, &Gromit::speak, 1);
apply(dogs, &Gromit::eat, 2.0f);
apply(dogs, &Gromit::sleep, 'z', 3.0);
apply(dogs, &Gromit::sit);
} ///:~
儘管apply的定義有些複雜,新手未必能完全理解,但它的使用簡潔明了,新手也知道該怎麼用,我想,這就是那些為自己
的程式而奮鬥所為達到的目標吧:無需知道細節,只需知道實現自己的目標即可。
模板的模板
//: C03:TemplateTemplate.cpp
#include <vector>
#include <iostream>
#include <string>
using namespace std;
// As long as things are simple,
// this approach works fine:
template<typename C>
void print1(C& c)
{
typename C::iterator it;
for(it = c.begin(); it != c.end(); it++)
cout << *it << " ";
cout << endl;
}
// Template-template argument must
// be a class; cannot use typename:
template<typename T, template<typename> class C>
void print2(C<T>& c)
{
copy(c.begin(), c.end(),
ostream_iterator<T>(cout, " "));
cout << endl;
}
int main()
{
vector<string> v(5, "Yow!");
print1(v);
print2(v);
} ///:~
成員函數模板
事實上,我們也可以把apply()作為一個類中的成員函數模板,這樣可以使聲明更加清晰:
dogs.apply(&Gromit::sit);
作為容器類中的一個成員,apply()的定義被證明是非常清晰的,為了完成這個,我們需要從現存STL系列容器中繼承一個
新的容器,把我們的新函數加到這個容器中去。當然,為了具有最好的適應性,我們將使用STL系列的容器,並且,必須使用模
板的模板來做這項工作,告訴編譯器一個模板參數是即上是一個模板,而它自身作為一個型別參數也可以被初始化。看下面:
//: C03:applyMember.h
// applySequence.h modified to use
// member function templates
template<class T, template<typename> class Seq>
class SequenceWithApply : public Seq<T*>
{
public:
// 0 arguments, any type of return value:
template<class R>
void apply(R (T::*f)())
{
iterator it = begin();
while(it != end())
{
((*it)->*f)();
it++;
}
}
// 1 argument, any type of return value:
template<class R, class A>
void apply(R(T::*f)(A), A a)
{
iterator it = begin();
while(it != end())
{
((*it)->*f)(a);
it++;
}
}
// 2 arguments, any type of return value:
template<class R, class A1, class A2>
void apply(R(T::*f)(A1, A2),
A1 a1, A2 a2)
{
iterator it = begin();
while(it != end())
{
((*it)->*f)(a1, a2);
it++;
}
}
}; ///:~
因為他們是類的成員,所以apply()函數就不需要那麼多參數了,並且iterator所屬類也不需要被特殊指定。當然,begin
()和end()也是新類的成員函數了,這一切看上去都那麼清晰,明了。然而,基本的代碼仍是一樣的
//: C03:applyGromit2.cpp
// Test applyMember.h
#include "Gromit.h"
#include "applyMember.h"
#include <vector>
#include <iostream>
using namespace std;
int main()
{
SequenceWithApply<Gromit, vector> dogs;
for(int i = 0; i < 5; i++)
dogs.push_back(new Gromit(i));
dogs.apply(&Gromit::speak, 1);
dogs.apply(&Gromit::eat, 2.0f);
dogs.apply(&Gromit::sleep, 'z', 3.0);
dogs.apply(&Gromit::sit);
} ///:~
從概念上講:你現在是從dogs這個容器中調用apply()這個方法了。