地球人都知道C++里有一个typeid操作符可鉯用来获取一个类型/表达式的名称:
但是这个name()的返回值是在vc和gcc中打印出来的结果如下:
一个稍微长一点的类型名称,比如:
(话说gcc您的返回结果真是。)
当然了想在gcc里得到和微软差不多显示效果的方法也是有的,那就是使用:
先不说不同编译器下的适配问题来看看丅面这个会打印出啥:
可爱的cv限定符和引用都被丢掉了=.=
如果直接在typeid的结果上加上被丢弃的信息,对于一些类型而言(如逻辑函数是什么指針引用)得到的将不是一个正确的类型名称
想要获得一个类型的完整名称,并且获得的名称必须要是一个正确的类型名称应该怎样做呢?
我们需要一个泛型类用特化/偏特化机制静态检查出C++中的各种类型,并且不能忽略掉类型限定符(type-specifiers)和各种声明符(declarators)
先来考虑一個最简单的类模板:
假如在它的基础上特化,需要写多少个版本呢我们可以稍微实现下试试:
这还远远没有完。有同学可能会说了我們不是有伟大的宏嘛,这些东西都像是一个模子刻出来的弄一个宏批量生成下不就完了。
实际上当我们真的信心满满的动手去写这些宏嘚时候才发现适配上的细微差别会让宏写得非常痛苦(比如&和*的差别,[]和[N]的差别还有逻辑函数是什么类型、逻辑函数是什么指针、逻輯函数是什么指针引用、逻辑函数是什么指针数组、类成员指针、……)。当我们一一罗列出需要特化的细节时不由得感叹C++类型系统的複杂和纠结。
但是上面的理由并不是这个思路的致命伤
不可行的地方在于:我们可以写一个多维指针,或多维数组类型是可以嵌套的。总不可能为每一个维度都特化一个模板吧
不过正由于类型其实是嵌套的,我们可以用模板元编程的基本思路来搞定这个问题:
一个简單的继承就让特化变得simple很多。因为当我们萃取出一个类型比如T *,之后的T其实是携带上了除*之外所有其他类型信息的一个类型那么把這个T再重复投入check中,就会继续萃取它的下一个类型特征
可以先用指针、引用的萃取来看看效果:
很漂亮,是不是当然,在gcc里这样输出void会变成v,所以gcc下面要这样写check模板:
我们可以简单的这样修改check让它同时支持vc和gcc:
但是到目前为止check的输出结果都是无法保存的。比较好的方式是可以像typeid(T).name()一样返回一个字符串这就要求check能够把结果保存在一个std::string对象里。
当然了我们可以直接给check一个“std::string& out”类型的构造逻辑函数是什麼,但是这样会把输出的状态管理、字符的打印逻辑等等都揉在一起因此,比较好的设计方法是实现一个output类负责输出和维护状态。我們到后面就会慢慢感觉到这样做的好处在哪里
output类的实现可以是这样:
为了让外部的使用依旧简洁,实现一个外敷逻辑函数是什么模板是佷自然的事情:
如果我们想实现表达式的类型输出使用decltype包裹一下就行了。
不知道看到这里的朋友有没有注意到check在gcc下的输出可能会出现問题。原因是abi::__cxa_demangle并不能保证永远返回一个有效的字符串
我们来看看这个逻辑函数是什么的:
在bracket里,不仅实现了圆括号的输出其实还实现叻一个编译期if的小功能。当不输出圆括号时我们可以给bracket指定一个其它的输出内容。
当然不实现bracket,直接在check的类型特化里处理括号逻辑也鈳以但是这样的话逻辑就被某个check特化绑死了。我们可以看到bracket的逻辑被剥离出来以后后面所有需要输出圆括号的部分都可以直接复用这個功能。
然后是[]的输出逻辑考虑到对于[N]类型的数组,还需要把N的具体数值输出来因此输出逻辑可以这样写:
输出逻辑需要写在bound类的析構,而不是构造里原因是对于一个数组类型,[N]总是写在最后面的
这里在输出的时候直接使用了运行时的if-else,而没有再用特化来处理是洇为当N是一个编译期数值时,对于现代的编译器来说“if (N == 0) ; else ;”语句会被优化掉只生成确定逻辑的汇编码。
最后是逻辑函数是什么参数的输絀逻辑。逻辑函数是什么参数列表需要使用变参模板适配用编译期递归的元编程手法输出参数,最后在两头加上括号
我们可以先写出遞归的结束条件:
parameter在析构的时候,析构逻辑函数是什么的scope就是bracket的影响范围后面的其它显示内容,都应该被包括在bracket之内因此bracket需要显式定義临时变量bk;
check的调用理由很简单,因为我们需要显示出每个参数的具体类型;
最下面是parameter的递归调用在把out_丢进去之前,我们需要思考下具體的显示效果是希望打印出(P1, P2, P3)呢,还是(P1 , P2 , P3)
在这里我们选择了逗号之前没有空格的第一个版本,因此给parameter传递的是out_.compact()
对parameter的代码来说,看起来不奣显的就是bracket的作用域了check和parameter的调用其实是被bracket包围住的。为了强调bracket的作用范围同时规避掉莫名其妙的“(void)bk;”手法,我们可以使用lambda表达式来凸顯逻辑:
这样bracket的作用域一目了然并且和check、parameter的定义方式保持一致,同时也更容易看出来out_.compact()的意图
下面考虑带cv限定符的类成员逻辑函数是什麼指针。在开始书写后面的代码之前我们需要先思考一下,cv限定符在类成员逻辑函数是什么指针上的显示位置是哪里答案当然是在逻輯函数是什么的参数表后面。所以我们必须把cv限定符的输出时机放在T(pact()("::*"); \
上面这段代码先定义了一个at_destruct用来在析构时执行“输出cv限定符”的动莋;同时把原本处在基类位置上的T(/markl22222/article/details/
1.楼主的下标操作符重载的返回值昰一个引用这时毋容置疑的
2.重载下标操作符[]时,应该返回的是一个引用这样返回值既可以用作右值,也可以用作左值