前面说过的函数模板和类模板,它们的模板参数并非一定是某个类型,还可以是值。跟类型一样,模板实例化时给定不同的值,编译器就会生成不同版本的函数或类代码。不过有一点不一样,非类型模板参数无法推断,使用时必须显式给出。
template<int v>
struct Int2Type {
enum { value = v };
};
Int2Type<3>::value; // 3
上面是《C设计新思维》(Modern C Design)给出的一个例子(很难想象这本书是在2001年写的,放在今天也非常值得一读)。
定义很简单,将整数常量映射为类型。有人可能会好奇,这个类有什么用?这不是脱裤子放P吗?它可能还真的有用。比如说代码的路径选择(if-else)。如果是在运行时,这个大家都用过(在运行时虽然只会执行某一分支的代码,但最终的执行文件中两个分支的代码都存在)。但是如果你需要在编译时让编译器只生成某一路径的代码(这样只有有效路径的代码在最后的执行文件中,或者两个分支在指定条件下只能有一个代码是有效的,另一个分支的代码是无效的),这个通常称为静态派发(static dispatching)。
不过上面的写法是C++11之前的方式,使用了匿名枚举定义。C++11之后的定义方式就堂堂正正了。
template<int v>
struct Int2Type {
static constexpr int value = v;
};
对于这个类,对象并不重要(其实它的某个实例化类的所有对象都是完全一样的),重要的是类本身。
我们还是使用之前的Stack这个类模板吧,看看真正有用的非类型模板参数的用途。
template<typename T, std::size_t MaxSize>
class Stack {
private:
T elems_[MaxSize];
};
Stack<int, 10> s;
Stack<int, 20> s2;
这次我们不预先固定Stack的内部数组的长度,而是在使用时指定数组的长度(注意,s和s2是不同类型的对象,不同相互赋值,或基于其中某个对象创建另一个对象)。
下面看看函数模板中的非类型模板参数。
template<int Val, typename T>
T max(const T x) {
return Val < x ? x : Val;
};
int val = 30;
max<10>(val); // 30
这次的max比较的两个参数,一个由非类型模板参数给出,另一个是调用参数。使用时我们只显式给出了非类型参数,类型参数还是由编译器推断得出。下面我们给出更加简便的定义,正好趁机简单介绍非类型模板参数的auto类型定义、类型参数的缺省值和非类型参数的缺省值(C++总是在不停地进化中)。
template<auto Val, typename T = decltype(Val)>
T max(const T x) {
return Val < x ? x : Val;
};
max<10>(3); // 10
template<typename T, T Val = T{}>
T max(const T x) {
return Val < x ? x : Val;
};
max(4); // 4
注意上面两个定义的模板参数顺序不同,所以使用时也不一样(后者如果需要指定Val为非缺省值时,需要同时给出T的类型,因为T在前面定义的嘛)。
先看第一个例子,这回Val的类型是auto,编译器会根据你指定的值来推断出类型,decltype(Val)操作符得到Val的类型,然后作为T的缺省值。
第二个例子中Val的缺省值使用了T{}这样的统一的缺省构造方式,这个适用所有类型(包括内置类型和自定义类型)。
不过有一点需要注意,并不是所有类型的值都可以用作非类型参数的,其实只有整型(包括bool,枚举,指针,引用,它们底层都是整型数据)才可以。大家在实际使用中可能会遇到的是字符串类型,const char*,使用时能否通过编译,跟字符串本身的链接特性(外部链接、内部链接)有关系。下面定义一个greet,它想跟谁打招呼不是通过函数参数,而是模板参数。
template<const char* Who>
void greet() {
std::cout << "hello, " << Who << std::endl;
}
extern char const s1[] = "world";
char const s2[] = "world";
greet<"world">(); // Error, string literal
greet<s1>(); // OK, external linkage
greet<s2>(); // OK, internal linkage
{
static const char s3[] = "world"; // local static, no linkage
greet<s3>(); // OK since C++17
}
非模板参数相对简单,今天就到这里吧。