C/C++编程:模板参数

2023-10-31

现在存在3种模板参数:

  • 类型参数
  • 非类型参数
  • 模板的模板参数

C++设计模板参数的用意在于:尽量将编译可知的因素提取处理,从而进一步抽象代码。无论时代码中的类型、变量地址还是函数地址,只要编译时可知,C++语言就为其一视同仁的提供模板参数支持,以最大限度的降低代码对编译器已知元素的依赖。当代码中有不确定的常数、类型、函数指针或者数据时,就可以用模板参数试着解决。

类型参数

类型参数是通过关键字typename或者class引入的:它们两者几乎是等同的。关键字后面必须是一个简单的标识符,后面用逗号来隔开下一个参数声明,等号=代表接下来的缺省模板实参,一个封装的>表示参数化子句的结束

在模板声明内部,类型参数的作用类似于typedef(类型定义)名称。 比如,如果T是一个模板参数,就不能用class T等形式来修饰:

template<typename Allocator>
class List{
    class  Allocator * allocator;  //错误
    friend class Allocator;       // 错误
};

非类型参数

非类型模板参数表示的是:在编译期或者链接期可以确定的常值。这种参数的类型(也就是这些常值的类型)必须是下面的一种:

  • 整型或者枚举类型
  • 指针类型(包含普通对象的指针类型、函数指针类型、指向成员的指针类型)
  • 引用类型(指向对象或者指向函数的引用都是允许的)

所有其他的类型现今都不允许作为非类型参数使用(将来可能增加浮点型)

另外,在某些情况下,非模板参数的声明也可以使用关键字typename

template<typename T,                         //类型参数
		typename T::Allocator*Allocator>    // 非类型参数
  • 类型参数:typename后面是一个简单的标识符T
  • 非类型参数:typename后面是一个受限的名称

非类型模板参数的声明和变量的声明很像,但是它们不能有static、mutable等修饰符;只有具有constvalatile限定符。但如果这两个限定符限定的如果是最外层的参数类型,编译器将会忽略它们:

template<int const length> class Buffer; // 这里的const没用,会忽略
template<int length> class Buffer; // 和上面等同

最后,非类型模板参数只能是右值:它们不能被取值,也不能被赋值

整数模板参数

非类型模板参数的作用相当于为函数模板或者类模板预定义了一些常量,在生成模板实例时,也要求必须以常量即编译器已知的值为非可选模板参数赋值

相对于常量,非类型模板参数的灵活之处在于:

  • 模板中声明的常量,在模板的所有实例中都具有相同的值,
  • 非类型模板参数则对于在不同的模板实例中可以拥有不同的值来满足不同的需求。

我们来看个例子:如果已知要用到一个长度为10的数组,此时可将数组长度定义为一个常量,代码如下:

template<typename T>
class array{
	static const unsigned size = 10;
	T elems[size];
public:
	T& operator[](unsigned i){
		throw (std::out_of_range){
			// 访问元素时先进行越界检查
			if(i >= size){
				throw std::out_of_range("assess out of range.);
			}else{
				return elems[i];
			}
		}
	}
};

但是当程序中需要多个带越界检查而且长度不同的数组时,当数组长度定义为常量便无法满足要求了。此时,一个解决方法是将长度定义为数组的成员变量,在生成数组类实例时给定并动态申请所需要的内存空间。但是如果数组的长度不变,则记录其长度的成员变量显得有些多余。而将数组长度声明为一个整数型的模板参数,则就能满足要求而且不会有额外的存储以及运行开销。如下:

#include <stdexcept>

//用整形模板参数定义数组长度
template<typename T, unsigned size>
class array{
	T elems[size];
public:
	T& operator[](unsigned i){
		throw (std::out_of_range){
			if(i >= size)
				throw std::out_of_range("array assess out of range");
			else
				return elems[i];
		}
	}
};

int main(){
	array<char, 20> array0;
	array<char, 10> array1;

	array0[10] = 'n';

	try{ array1[10] = 'c';}
	catch(std::out_of_range &e){
		std::cerr << "access out of range.\n";
	}
}

指针以及引用参数模板

  • 以指针以及引用作为模板参数时,其作用与整数型模板参数类似,相当于为函数或类声明一个常量指针或引用。
  • 注意,只有指向全局变量、外部变量(extern修饰)、类静态变量的指针及引用才可以作为模板参数。函数的局部变量、类成员变量等均不能作为模板参数。
  • 这是因为模板参数值必须是编译时已知的。对于指针及引用,需要在编译时确定其所指向或者所引用的内存中的地址,而局部变量有可能被编译器分配到函数调用栈上,内存地址不固定,故而无法作为模板参数使用。
#include <iostream>

template<int *p>
struct wrapper1{
    int get(){return *p;}
    void set(int v){*p = v;}
};

template<int &p>
struct wrapper2{
    int get(){return p;}
    void set(int v) {p = v;}
};

int global_variable = 0;

int main() {
    wrapper1<&global_variable> g1;
    g1.set(1);
    printf("%d\n", g1.get());

    wrapper2<global_variable> g2;
    g2.set(2);
    printf("%d\n", g2.get());

	// int local_variable; // 局部变量的指针和引用无法用于模板参数
    return 0;
}

成员函数指针参数。

成员函数指针的声明方法:如果某个类some_class有多个函数都接受一个int值参数并返回一个int值结果的话,则指向这一系列成员函数的指针mfp可以定义为:

int (some_class::* mfp)(int)

上面,除了mfp是变量名外,其他部分都用于描述所指函数的各个细节。一般我们会给类型起个别名:

typedef int (some_value::* some_value_mfp)(int);
some_value_mfp mfp;

使用成员函数指针调用函数时,需要用到操作.*->*,这两个操作符的优先级都很低,因此要用到()以保证正确:

some_class a;
std::cout << (a.*mfp)(0) << "\n";
some_class* p(&a);
std::cout << (p->*mfp)(0) << "\n";

我们来看个完整的例子:

#include <iostream>

class some_value;

typedef int (some_value::* some_value_mfp)(int);

template <some_value_mfp func>  //func是一个成员函数指针模板参数
int call(some_value &value, int op){
    return (value.*func)(op);
}

class some_value{
     int value;
public:
    some_value(int _value) : value(_value) {}
    int add_by(int op){ return value += op;}
    int sub_by(int op){ return value -= op;}
    int mult_by(int op){ return value *= op;}
};

int main() {
    some_value v0(0);
    printf("%d\t", call<&some_value::add_by>(v0, 1));
    printf("%d\t", call<&some_value::sub_by>(v0, 2));
    printf("%d\t", call<&some_value::mult_by>(v0, 3));
    return 0;
}

成员函数指针的本意在于提供一种在运行时类行为可变的多态机制。但当以成员函数指针为模板时,则将原本的动态绑定变为静态绑定,其作用相当于直接调用所绑定成员函数。如此,其目的时从调用不同成员函数的操作中提取出共性,提到代码重用率

函数指针模板参数

函数和数组类型可以被指定为非模板参数,但是要把它们先隐式的转换为指针类型,这种转型也称为decay:

template<int buf[5]> class Lexer; //buf实际上是一个int *类型
template<int* buf> class Lexer; // 正确:这是上面的重新声明

作用:相当于回调函数

#include <iostream>

template<typename T, void (*f)(T &v)>
void foreach(T array[], unsigned size){
    for(unsigned  int i = 0; i < size; i++){
        f(array[i]);
    }
}


template<typename T>
void inc(T &v){
    ++v;
}
template<typename T>
void dec(T &v){
    --v;
}
template<typename T>
void print(T &v){
    std::cout << '\t' << v;
}
int main() {
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8};
    using namespace std;
    foreach<int, print<int>> (array, 8);
    printf("\n");

    foreach<int, inc<int>> (array, 8);
    foreach<int, print<int>> (array, 8);
    printf("\n");

    foreach<int, dec<int>> (array, 8);
    foreach<int, print<int>> (array, 8);
    printf("\n");
    return 0;
}

在同一对<>内部,位于后面的模板参数声明可以引用前面的模板参数名称(但前面的不能引用后面的):

template<typename T,
		T *root,
		template<T*> class Buf>
		class Structure;

模板型模板参数

引入

当函数指针作为模板时, 函数指针参数目标有两个目标参数,第一个T是数组元素类型,第二个函数指针参数标明针对每一个元素的操作。由于操作必然是针对类型为T的元素数组,所以指针所指函数必须接收一个T型引用作为参数。函数指针目标参数例子中将三种操作都包装为三个模板函数。因此,在生成模板实例时,需要先生成三个操作的函数实例,然后再作为foreach的模板参数传入,也就是说,需要这样写:

foreach<int, print<int>> (array, 8);
foreach<int, dec<int>> (array, 8);
foreach<int, inc<int>> (array, 8);

这样写比较麻烦,既然print、dec、inc都是函数模板,是否可以直接将模板传入,然后再foreach中自动生成实例呢?也就是引入了模板型模板参数(必须将三个模板函数改为函数类模板):

  • 模板型模板参数是代表类模板的占位符
  • 函数类(functor)是一种特殊的类,通过重载若干括号操作符函数使得函数类实例可以模板函数的调用
#include <iostream>

// Func是一个模板型函数模板,保证foreach要对每个元素进行的操作
template<template<typename TT> class Func, typename T>  
void foreach(T array[], unsigned size){
    Func<T> func;
    for(unsigned i = 0; i < size; i++){
        func(array[i]);
    }
}

// 三种操作都保证成函数类模板,可以通过拷贝操作符的调用
template<typename T>
struct inc{
    void operator()(T &v) const {
        v++;
    }
};
template<typename T>
struct dec{
    void operator()(T &v) const {
        v--;
    }
};
template<typename T>
struct print{
    void operator()(T &v) const {
       std::cout << ' ' << v;
    }
};
int main(){
    using namespace std;
    int array[] = {1, 2, 3, 4, 5, 6, 7, 8};
    foreach<print>(array, 7); // 不需要多写一遍int,在foreach自动完成
    printf("\n");

    foreach<inc>(array, 7);
    printf("\n");
}

比较foreach<print>(array, 7);foreach<int, print<int>> (array, 8);,是不是简单了很多。

  • 这里是故意将foreach的参数都颠倒的,将类型模板参数T放在参数列表末尾以便自动推导。很多时候都会写成template<typename T, template<typename TT> class Func>
  • 另外,正如在声明函数时其参数名可以省略一样,作为参数的模板其参数名也可以省略,也就是说TT可以省略,即template<typename T, template<typename > class Func>

定义

模板型模板参数:故名思意,就是模板的参数是另一个模板。其声明形式类似如下:

template<typename T, template<typename TT0, typename TT1> class A>
struct FOO(A<T, T> bar);

上面所声明的第二个模板参数A就是一个模板。

  • 要注意参数声明中的关键字class是必须的,也就是说只有类模板可以作为模板参数。此处的class不能用struct和union代替,即:
template <template<typename X> class C> //正确
void f(C<int> *p);

template <template<typename X> struct C> //error
void f(C<int> *p);

template <template<typename X> union C> //error
void f(C<int> *p);
  • 另外模板的模板参数的参数(比如下面的A)可以由缺省的模板实参。显然,只有在调用时没有指定该参数的情况下才会应用缺省模板实参:
template<template<typename T,
				typename A = MyAllocator> class Container>
class Adaptation{
	Container<int> storage; //隐式等同于Container<int, MyAllocator>
};
  • 对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明所使用。比如下面:
template<template<typename T, T*> class Buf>
class Lexer{
	static char storage[5];
	Buf<char, &Lexer<Buf>::storage[0]>buf;
};

template<template<typename T> class List>
class Node{
	static T* storage; // 错误
};

缺省的模板实参

目前,只有类模板声明才能有缺省模板实参(可能会变)。任何类型的模板参数都可以有一个缺省实参,只要该缺省实参能够匹配这个参数就可以。显然,缺省实参不能依赖自身的参数,但是可以依赖前面的参数

template<typename T, typename Allocator = allocator<T>> //依赖前面的参数T,不能依赖自己Allocator 
class List;

与缺省的函数调用参数的约束一样:对于任一个模板参数,只有在之后的模板参数都提供了缺省实参的前提下,才能具有缺省模板实参。后面的缺省值通常是在同个模板声明中提供的,也可以在前面的模板声明中提供:

template<typename T1, typename T2, typename T3,
		typename T4 = char, typename T5 = char>
class Quituple; // 正确

template<typename T1, typename T2, typename T3 = char,
		typename T4 , typename T5>
class Quituple; // 正确,根据前面的模板声明,T4和T5都有缺省值了

template<typename T1 = char, typename T2, typename T3,
		typename T4 , typename T5>
class Quituple; // error:T1不能具有缺省实参,因为T2还没有缺省实参

另外,缺省实参不能重复声明:

template<typename T = void>
class Value;

template<typename T = void>
class Value; // error:重复出现的缺省实参

模板声明要引入参数化子句,模板参数就是在该子句中声明的。这类声明可以把模板参数的名称省略不写(在后面不会引用该名称的前提下)

template<typename, int> // 省略不写
class X;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++编程:模板参数 的相关文章

  • Dapper 强类型查询返回默认对象值

    刚刚开始使用 Dapper 并喜欢它 我遇到了问题 它返回正确数量的对象 但它们的属性都有默认值 using var dbConnection Connection await dbConnection OpenAsync const st
  • 使用内部构造函数实例化类

    我有一个类 其构造函数被定义为内部 这意味着我无法实例化它 虽然这可能有道理 但出于调试和研究目的 我仍然愿意做一次 是否可以通过反射来做到这一点 我知道我可以访问私有 内部成员 但是我可以调用内部构造函数吗 或者 由于构造函数没有做任何重
  • 警告:从指针目标类型中丢弃“const”限定符

    没有const char s意味着 s 是一个指向常量 char 的指针 那么为什么它给我这个警告 我并不是想改变价值观 在第一个函数中警告是return discards const qualifiers from pointer tar
  • boost::interprocess 准备好迎接黄金时间了吗? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我正在开发一个由内存映射文件支持的线
  • 在两个 .cpp 文件之间定义全局变量 [重复]

    这个问题在这里已经有答案了 如何在 A cpp 和 B cpp 之间共享 全球化 bool 变量 其中它们都不包含其他 h 文件 他们有其他联合头文件 但彼此没有 我可以在这些共享标头中定义全局变量吗 Thanks 我可以在这些共享标头中定
  • 何时对向量进行归一化?

    我正在学习 XNA 并且在几乎所有的教育套件中都可以找到http creators xna com en US http creators xna com en US 我总是看到向量上对 Normalize 的调用 我知道归一化基本上将向量
  • C/C++ 中随机数生成器的实现[重复]

    这个问题在这里已经有答案了 我对 C 中随机数生成器的实现有点困惑 它也与 C 中的明显不同 如果我理解正确 对 srand seed 的调用会以某种方式初始化可通过 rand 访问的隐藏变量 种子 该变量又将函数指向预先生成的序列 例如例
  • 此插件导致 Outlook 启动缓慢

    我正在使用 C NET 4 5 开发 Outlook Addin 项目 但部署后 有时 Outlook 会禁用我的插件 并显示此消息 这个插件导致 Outlook 启动缓慢 我不知道我的插件出了什么问题 这只有很少的代码 并且ThisAdd
  • 可选参数“必须是编译时常量”

    我有一个类分为两个部分文件 如下所示 public partial class PersonRepository BaseRepository
  • 使用 QSet 作为 Qt 地图容器中的键

    我需要一个映射 其中键是唯一的 并且每个键都是一组或自定义 POD 结构 其中包含 3 个数据项 这些值只是指向对象实例的指针 从阅读Qt 的 QMap 与 QHash 的文档 http qt project org doc qt 4 8
  • Type_traits *_v 变量模板实用程序顺序无法编译

    看过了这个答案 https stackoverflow com a 31763111 7151494 我试图想出一个变量模板从中获取代码的实用程序 template
  • ASP MVC 5 - 403 customError 不起作用

    我正在尝试为我的应用程序创建自定义错误页面 它在大部分情况下都有效 但不适用于403 errors 我的网络配置
  • 如何使用 C# 代码使用超链接的 onClick 事件?

    我正在尝试为页面中的超链接添加条件 而不是仅仅使用特定的链接 例如 a href help Tutorial html Tutorial a 我想为不同的用户显示不同的页面 例如 如果用户以管理员身份登录 他们将看到与普通用户不同的链接 我
  • 打破条件变量死锁

    我遇到这样的情况 线程 1 正在等待条件变量 A 该变量应该由线程 2 唤醒 现在线程 2 正在等待条件变量 B 该变量应该由线程 1 唤醒 在我使用的场景中条件变量 我无法避免这样的死锁情况 我检测到循环 死锁 并终止死锁参与者的线程之一
  • 语义问题 Qt Creator:命名空间“std”中没有名为“cout”的成员

    我开始使用 Qt Creator 编写代码 对于 C 文件 我遇到很多语义问题 99 是 命名空间 yyy 中没有名为 xxx 的成员cpp文件构建 编译和输出没有问题 如果我点击例如cout 我已链接到 iostream 我是否需要在 Q
  • 如何使用eclipse构建C++应用程序

    我已经从以下位置下载了 Eclipse Juno for C here http www eclipse org downloads download php file technology epp downloads release ju
  • 检索 Autofac 容器以解析服务

    在 C WindowForms 应用程序中 我启动一个 OWIN WebApp 它创建另一个类 Erp 的单例实例 public partial class Engine Form const string url http 8080 49
  • 获取大于某个数字的元素个数

    我正在尝试解决以下问题 数字被插入到容器中 每次插入数字时 我需要知道容器中有多少元素大于或等于当前插入的数字 我相信这两个操作都可以以对数复杂度完成 我的问题 C 库中有标准容器可以解决这个问题吗 我知道std multiset可以在对数
  • 无效的模板相关成员函数模板推导 - 认为我正在尝试使用 std::set

    我有一个继承自基类模板的类模板 基类模板有一个数据成员和一个成员函数模板 我想从我的超类中调用它 我知道为了消除对成员函数模板的调用的歧义 我必须使用template关键字 我必须明确引用this在超级班里 this gt base mem
  • C 中的静态和动态绑定(严格来说是 C,而不是 C++)是什么?

    我最初对发布这个问题感到担忧 以免它重复 但即使在谷歌搜索了许多关键字之后 我在 StackOverflow 上找不到任何解释 C 的静态和动态绑定的链接 尽管有 C 的问题和答案 但是都涉及classes以及显然不适合 C 的东西 Sta

随机推荐

  • [系统安全] 四十二.Powershell恶意代码检测系列 (4)论文总结及抽象语法树(AST)提取

    您可能之前看到过我写的类似文章 为什么还要重复撰写呢 只是想更好地帮助初学者了解病毒逆向分析和系统安全 更加成体系且不破坏之前的系列 因此 我重新开设了这个专栏 准备系统整理和深入学习系统安全 逆向分析和恶意代码检测 系统安全 系列文章会更
  • 如何正确使用线程池

    具体请参考原创 Java线程池实现原理及其在美团业务中的实践 Java 线程池及参数动态调节详解 一 为何要使用线程池 降低资源消耗 线程的创建和销毁会造成一定的时间和空间上的消耗 线程池可以让我们重复利用已创建的线程 提高响应速度 线程池
  • Deform软件无法启动

    启动时卡在这个界面然后闪退 打开 提示 许可证被禁止了 点击star
  • [转]unity--角度调整和摄像头位置调整

    我之前在使用unity的时候大量的时间花在了把物体拉近镜头 把场景角度和摄像机角度调到一致上 这里总结下怎么做会比较快 关于物体的位移和缩放场景操作 右上角的标志是表示当前xyz轴方向的 为了方便调整 我们统一调成z轴朝上 y轴向前 x轴向
  • 如何实现浮点数立方根?

    给一个浮点数num 如何求其立方根ans 首先 0 lt ans lt num 对于浮点数区间的海量数据 若采用加法枚举判断 那绝对把CPU能累死 计算精度越高 时间复杂度越高 上述方法 只是简单的加法性线性探测 如果采用对数级别线程探测
  • AcWing 1250. 格子游戏 并查集模板题

    题 参考 并查集常用一维 所以对于坐标 x y 转换为x n y xy都要从0开始 其实就是3x3的转换为 0 1 2 3 4 5 6 7 8 这种 include
  • c++ STL map 结构体

    点击打开链接 点击打开链接 Map是STL的一个关联容器 它提供一对一 其中第一个可以称为关键字 每个关键字只能在map中出现一次 第二个可能称为该关键字的值 的数据处理能力 由于这个特性 它完成有可能在我们处理一对一数据的时候 在编程上提
  • 【华为OD机试真题2023B卷 JAVA&JS】比赛的冠亚季军

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 比赛的冠亚季军 知识点数组编程基础链表分治 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 有N 3 lt N lt 10000 个运动员 他们的id为0到N 1 他们
  • LeetCode:验证回文串(c++)

    题目内容 给定一个字符串 验证它是否是回文串 只考虑字母和数字字符 可以忽略字母的大小写 说明 本题中 我们将空字符串定义为有效的回文串 示例 1 输入 A man a plan a canal Panama 输出 true 示例 2 输入
  • How to Install Android Studio under Ubuntu 16.04

    If you find difficulties installing Android Studio under Ubuntu 16 04 1 LTS Xenial Xerus you can follow this tutorial th
  • PyCharm如何安装torch

    运行Pycharm中的代码时候提示ModuleNotFoundError No module named torch 试了很多种方法都不行 然后进入官网查了下具体的安装方法 附上网址https pytorch org get started
  • Oracle的高可用

    快速浏览了一下Oracle官方的网页以及非官方的ppt 简单了解了一下Oracle提供的高可用方案 主要有三种 1 RAC RAC Real Application Clusters 多个Oracle服务器组成一个共享的Cache 而这些O
  • Python读取cfg文件

    mysql HOST 127 0 0 1 PORT 3306 USER root PWD 123456789 DB employees CHARSET utf8 redis redis配置 暂时写在这里 线下配置 线上一定要从新配置 并且不
  • Android入门教程

    EditText 监听回车 使用EditText时 有时候我们会需要监听输入的回车 以做出一些操作 或者需要把回车变成 搜索 发送 或 完成 等等 EditText 为我们提供了一个属性 imeOptions 用来替换软键盘中 enter
  • 怎样简便的使用vw完成移动端rem适配

    怎样简便的完成移动端rem适配 了解一些必要的单位 px 像素 进行页面开发的基础单位 em 相对单位 rem 相对单位 vw 相对宽度 vh 相对高度 如何进行简单的px rem转换 了解一些必要的单位 px 像素 进行页面开发的基础单位
  • 数据库第十五课-------------非关系型数据库----------Redis

    作者前言 作者介绍 作者id 老秦包你会 简单介绍 喜欢学习C语言和python等编程语言 是一位爱分享的博主 有兴趣的小可爱可以来互讨 个人主页 小小页面 gitee页面 秦大大 一个爱分享的小博主 欢迎小可爱们前来借鉴 Redis的简单
  • 通信工程专业论文毕设选题推荐

    文章目录 1前言 2 如何选题 2 1 移动通信方向 2 2 嵌入式开发方向 2 3 人工智能方向 2 4 物联网方向 2 5 算法研究方向 2 6 移动应用开发方向 2 7 网络通信方向 2 8 学长作品展示 4 最后 1前言 近期不少学
  • html视频自动播放

    音频
  • Java如何让CPU利用率达到100%

    一 背景 记得有一次去面试Java软件开发工程师 面试官问了我一个关于Java如何让CPU利用率到达百分百的问题 我当时下意识的回答到让程序死循环就可以了 这源于我之前的工作中有一次无意间写了死循环 当时电脑卡的简直不能动 我都关机了 可是
  • C/C++编程:模板参数

    现在存在3种模板参数 类型参数 非类型参数 模板的模板参数 C 设计模板参数的用意在于 尽量将编译可知的因素提取处理 从而进一步抽象代码 无论时代码中的类型 变量地址还是函数地址 只要编译时可知 C 语言就为其一视同仁的提供模板参数支持 以