专业编程基础技术教程

网站首页 > 基础教程 正文

C++模板 - 8(非类型模板参数)(c++模板类和类模板)

ccvgpt 2024-08-03 12:32:05 基础教程 16 ℃

前面说过的函数模板和类模板,它们的模板参数并非一定是某个类型,还可以是值。跟类型一样,模板实例化时给定不同的值,编译器就会生成不同版本的函数或类代码。不过有一点不一样,非类型模板参数无法推断,使用时必须显式给出。

template<int v>
struct Int2Type {
    enum { value = v };
};

Int2Type<3>::value; // 3

上面是《C设计新思维》(Modern C Design)给出的一个例子(很难想象这本书是在2001年写的,放在今天也非常值得一读)。

C++模板 - 8(非类型模板参数)(c++模板类和类模板)

定义很简单,将整数常量映射为类型。有人可能会好奇,这个类有什么用?这不是脱裤子放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
}

非模板参数相对简单,今天就到这里吧。

Tags:

最近发表
标签列表