[翻译]看老夫如何调戏C++编译器
这依旧是一篇翻译的文章,当初在reddit上面看到的,写得比较有趣,而且也确实介绍了C++模板中比较tricky的地方,原文在这里,但是需要梯子。所以我翻译在这里,方便那些没有条件搭梯子的童鞋。
咳咳,废话少说,咱们开始了啊。
最初的时候,将模板引入C++是为了支持泛型编程,例如,实现针对任意类型都可以使用的算法。但偶然间C++的码农们却发现这货是图灵完全的!也就是说,任何理论上可以进行的计算都可以通过C++模板来完成,而且,是在编译阶段!
因为图灵完全允许无终结型计算(non-terminating computation,即永远不会结束的计算)的定义,由此可以得出一个重要推论:我们是可以写出永远不会编译结束的代码来的!这也暗示了编译器在处理这种情况时既不能报错,也没办法生成目标代码(尼玛,当个编译器我容易吗!)。实际上,编译器都会有一个模板实例化最大深度的限制,以防止有脑残写出编译永远不会结束的代码。(翻译君表示有遇到过模板实例化深度超限的情况,clang默认的最大深度为128,但是,boost的某个库会超过这个限制,导致编译直接报错。你们现在知道boost是有多丧心病狂了吧!解决办法是加大这个限制,设置参数 -ftemplate-depth=256
为256或者更高)
下面,我就来演示一些包含无终结计算的代码片段,以及GCC(g++ 4.8.2)和Clang(clang++ 3.3)是如何来处理这些好玩但很脑残的代码的。(翻译君表示,原作者只测试了gcc和clang两种编译器,把巨硬家的MSVC给忽略了,而翻译君翻译的时候正好手边没有Windows环境,没法测试。不过,有热心人把在MSVC2013上的测试结果贴原文评论里了,翻译君看了一下,全部报错,而且错误信息莫名其妙,才知道作者不测试MSVC是有深刻原因的好吗!)
递归展开
在这个例子中,为了计算 InfRec<T>::value
,我们需要知道 InfRec< InfRec<T> >::value
,因此又需要知道 InfRec< InfRec< InfRec<T> > >::value
,由此无限循环下去……
#include <iostream> template<typename T> struct InfRec { static const int value = InfRec< InfRec<T> >::value; }; int main() { std::cout << InfRec<int>::value << std::endl; return 0; }
在这个例子中,clang和GCC都因为达到最大模板实例化深度的错误而结束。
自我递归
在这个例子中,为了计算 SelfRec<T>::value
,我们需要知道 SelfRec<T>::value
,也就是说,它的定义就是其本身。
#include <iostream> template<typename T> struct SelfRec { static const int value = SelfRec<T>::value; }; int main() { std::cout << SelfRec<int>::value << std::endl; return 0; }
在这个例子中,虽然GCC报错了,但是没有能够终止编译…… :-)
selfrec.cpp:5:22: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘SelfRec<int>::value’ static const int value = SelfRec<T>::value; ^ selfrec.cpp:5:22: recursively required from ‘const int SelfRec<int>::value’ selfrec.cpp:5:22: required from ‘const int SelfRec<int>::value’ selfrec.cpp:9:32: required from here selfrec.cpp:5:22: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘SelfRec<int>::value’ selfrec.cpp:5:22: recursively required from ‘const int SelfRec<int>::value’ selfrec.cpp:5:22: required from ‘const int SelfRec<int>::value’ selfrec.cpp:9:32: required from here selfrec.cpp:5:22: error: initializer invalid for static member with constructor selfrec.cpp:5:22: error: (an out of class initialization is required) selfrec.cpp:5:22: error: ‘SelfRec<int>::value’ cannot be initialized by a non-constant expression when being declared
而clang则编译通过,没出任何问题,程序最终输出了0。我对clang的做法深表怀疑,因为,上面的代码其实和下面的代码是等价的:
#include <iostream> const int var = var; int main() { std::cout << var << std::endl; return 0; }
互相递归
这个例子中,为了计算 <MutualRec<A, B>::value
,我们需要知道 MutualRec<B, A>::value
,然后,就没有然后了……
#include <iostream> template<typename A, typename B> struct MutualRec { static const bool value = MutualRec<B, A>::value; }; int main() { std::cout << MutualRec<float,int>::value << std::endl; return 0; }
和上一个例子一样,GCC报了相同的错误,而且又没能终止编译。 :-)
而clang则认为上述初始化的不是常量表达式,所以报错了。但实际上,至少在我看来, value
绝对是常量,因为它前面有那么大两个关键字 static const
!
utualrec.cpp:5:44: error: in-class initializer for static data member is not a constant expression static const bool value = MutualRec<B,A>::value; ~~~~~~~~~~~~~~~~^~~~~ mutualrec.cpp:5:28: note: in instantiation of template class 'MutualRec<int, float>' requested here static const bool value = MutualRec<B,A>::value; ^ mutualrec.cpp:9:15: note: in instantiation of template class 'MutualRec<float, int>' requested here std::cout << MutualRec<float,int>::value << std::endl; ^ 1 error generated.
自相矛盾
从广义上来讲,这个例子中的定义并不是自相矛盾的,除了两个模板参数相同的情况。即 Contra<T, T>::value
被定义为 Contra<T, T>::value
的取反操作结果。
#include <iostream> template<typename U, typename V> struct Contra { static const bool value = not Contra<V,U>::value; }; int main() { std::cout << Contra<int,int>::value << std::endl; return 0; }
这次GCC又报错了,不过换了点花样:它认为初始化的是非常量表达式,但随后它自己都被搞糊涂了,然后它要求提一个bug,就退出了。
contradict.cpp:5:28: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘Contra<int, int>::value’ static const bool value = not Contra<V,U>::value; ^ contradict.cpp:5:28: recursively required from ‘const bool Contra<int, int>::value’ contradict.cpp:5:28: required from ‘const bool Contra<int, int>::value’ contradict.cpp:9:32: required from here contradict.cpp:5:28: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) instantiating ‘Contra<int, int>::value’ contradict.cpp:5:28: recursively required from ‘const bool Contra<int, int>::value’ contradict.cpp:5:28: required from ‘const bool Contra<int, int>::value’ contradict.cpp:9:32: required from here contradict.cpp:5:20: error: initializer invalid for static member with constructor static const bool value = not Contra<V,U>::value; ^ contradict.cpp:5:20: error: (an out of class initialization is required) contradict.cpp:5:20: error: ‘Contra<int, int>::value’ cannot be initialized by a non-constant expression when being declared contradict.cpp:11: confused by earlier errors, bailing out Preprocessed source stored into /tmp/ccAf1N1I.out file, please attach this to your bugreport.
而clang则通过了编译,并且程序输出了1。
罗素悖论(天哪,罗素悖论都出来了,翻译君压力山大……)
M是所有集合的集合,这些集合都不会包含自身为其元素。那么……请问,M包不包含它自己呢?
#include <iostream> template<typename U, typename V> struct HasElement { static const bool value = false; }; class M {}; template<typename T> struct HasElement<M,T> { static const bool value = not HasElement<T,T>::value; }; int main() { std::cout << HasElement<M,M>::value << std::endl; return 0; }
GCC这次表现又和上次一样:报错,自己被绕晕,要求提bug。
而clang的答案是毫无疑问的YES。 :-D
翻译到此结束。插个花絮:看评论时,有一哥们儿说了句大实话:你们所说的这些问题,对我大VB6来说都不是问题!:-D