函数模板中的类型归纳
一个非常简单但很有用的例子:
//: :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()这个方法了。