TMP的困境


有这样一个需求,给定一个类型int,再给定一堆类型string, const char*,float,然后在这堆类型中找出能够对给定类型正确赋值的类型,这里显然是float才可能“正确”赋值。然而当你写下下面的代码并编译时,编译器会告诉你,你错了!

static_assert(std::is_assignable<std::string, int>::value);

编译上面代码,编译器并不会报错,但是,顺利的通过编译就意味着是正确的吗? 从上面的代码看来,显然不是!大致看看一下std::is_assignable的实现就会发现,得到这个value并没有经过演算,只是简单的判断了下declval<To>() = declval<From>()是否是well-formed,而declval也只是个强制返回右值引用的函数而已。


遇到这个问题是在编写variant的过程中。准确的情况是想要在编译期判断一个对象是否能够被赋值,如果是非POD类型那么就判断它是否有operator=,如果是POD类型,则判断它们的裸类型是否相同(拒绝隐式转换,例如: int x = 2.33)。但是,对于非POD类型来说,它的operator=可能有很多个重载,而这些重载中可能会包含隐式转换(比如string会有string& operator= (const sring&),这样的话,传入const char*或者char[N]也都会接受),然而,即使有这样的隐式转换,我们也不能将他们与其它的重载区分开来,或者说,即使能够区分开,必然会付出极大的努力!


对于C++17之前的标准来说,在编写variant的过程中,深刻的理解到了没有编译器的帮助的情况下构建向后兼容代码的困难。各种hack,这些都是C++中的屠龙绝技啊!然而,在使用这些技巧的过程中,非但没有唯我独尊的感觉,反而觉得编写的代码越来越危险,各种后怕,这些技巧稍不注意就会造成UB,毕竟模板元编程这部分C++标准都能含糊其辞。然而,C++模板是图灵完备的,加上元代码运行于编译期没有运行时开销,当然被给予厚望。为了榨干C++的性能,更有甚者不惜利用UB来达到目的。简单来说,大量使用模板元编程,除了会带来编译时间增加以及代码膨胀外,更危险的是很难判定所写代码的正确性以及是否存在UB。


前面提到,元程序运行于编译期,其结果是产生运行时所需的代码(多数时候是某个类型或者值)。那么,编写元程序的人就很难得到有效的错误提示,就目前来看,编译器也无法做到有效的错误提示,更不用说能调试元程序简直是一种奢望了。换句话说,就是一下回到解放前,什么都得靠自己,人脑充当元程序的编译器。下面上一张图
编译器叫你吔屎啦


如你所见,红色的部分是库程序里面的,编译器并没有高亮我们所写代码的错误,假使我们写的代码有上千行,我们还能轻松的找出错误的地方吗?


最后,如果你是编写业务代码的话,元编程你可能永远都不会接触到,这相当于给这个语言的使用者划分成了几个等级,当然“精通”C++是不可能的了。如果你稍微留意一下C++的最近几个版本(C++11,C++14,C++17)新增的特性,你就会发现C++的重心已经移向编译期计算,为了进一步压榨性能不惜变得更灵活(复杂),这真的很C++。对于这样的C++,我们仍满怀期待,编译期反射,编译期字符串分析,编译期正则什么的统统都扔过来吧!



转载请注明:Serenity » TMP的困境

上一篇

下一篇