扩展模板库的新方法 - 善用函数的ADL查找
2017-04-17 08:04
260 查看
问题
随着C++语言的发展,越来越多的C++类库都使用模板库提供给大家使用。模板库通常使用基于对象的设计,相比传统的面向对象的设计而言,前者的每个模块可以单独使用,而不依赖框架。整个模板库的框架,只是一个胶水层,让各个组件之间按照某种协议,组合到一起,形散而神不散,发挥出强大的功能。而且模板库非常好扩展。模板库的扩展通常有以下三种模式:函数重载;
Policy Based Design, 按照框架规定好的协议导出指定的type,计算好特定的静态常量和实现特定的函数;
特化traits或者一些特定模板组件的特化;
由于前两种方式比较简单,还要就是没有找到比较好的素材,鄙文暂时主要讨论第三种扩展方式。在实现iguana的过程中,我们发现模板特化和宏的结合,在碰到命名空间的时候,我们一筹莫展。案例如下,
namespace client { struct person { int age; std::string name; }; REFLECTION(persion, age, name); }
REFELCTION宏实质上生成了一个在全局命名空间下的类模板的特化的代码。但是由于宏是预处理阶段,生成的代码在client命名空间下。C++的语言规范不允许在一个命名空间中特化另一个命名空间的类模板,也不允许在一个命名空间下特化全局命名空间的类模板(全局的情况MSVC能编过,我是服气的)。这里是一个参考链接。
那么我们库中遇到的问题,用下列简化的代码给出,有兴趣的读者可以到各个编译环境下测试。
namespace lib { template <typename> struct to_extend; } template <typename> struct to_extend_global; // OK template <> struct lib::to_extend<struct foo> { using type = foo; }; // OK template <> struct to_extend_global<struct foo> { using type = foo; }; namespace client { // Illegal template <> struct ::lib::to_extend<struct fee> { using type = fee; }; // Illegal template <> struct ::to_extend_global<struct fee> { using type = fee; }; }
其实这个限制并不是无解,只要你保证REFLEACTION宏在全局命名空间下,就不会出错。例如BOOST_FUSION_ADAPT_STRUCT宏,也是用相同的方法规避的。但是你会发现这样对使用来说非常不便,你偶尔不得不把一个完整的命名空间的代码block拆成两个。我们得找另外一种方法来扩展我们的模板库,而不受限于命名空间。
解决方案
什么样的扩展方法,可以跨越命名空间的限制?仔细阅读过template C++的读者应该不难发现,就是函数的ADL查找。我们用如下代码来说明,如何利用函数的ADL查找// 这里是你的库类中需要扩展的组件 namespace lib { void to_extend(...) {} } // 客户处的扩展代码 namespace client { struct foo {}; // 下面的代码通常会被宏来生成 auto to_extend(foo const&) { struct foo_extend { static constexpr char const* name() { return "foo"; } }; return foo_extend{}; } } // 在库中,可能是这样集成组件的 namespace lib { constexpr char const* apply_impl(std::true_type, ...) { return "null"; } template <typename T> constexpr char const* apply_impl(std::false_type, T) { return T::name(); } template <typename T> constexpr char const* apply(T const& t) { using type = decltype(to_extend(t)); // 探测有没有对应的to_extend函数,ADL查找保证 using is_void = std::is_same<type, void>; return apply_impl(is_void{}, type{}); } } // 在client中可能是这样使用的 namespace client { void some_function() { foo f; std::cout << lib::apply(f) << std::endl; } }
简单解释一下,下面的代码是如何工作的。首先,函数的ADL查找,是apply函数尝试调用to_extend的时候,不仅会查找lib命名空间下的符号,也会去查找T类型所在命名空间的符号。我们定义的to_extend函数在client命名空间下,foo类型也在client命名空间下。那么ADL查找肯定可以找到to_extend函数的符号。然后,我们没有选择类模板的特化,而是选择了使用to_extend函数,返回一个它内部定义的类型作为policy的功能。这个思想有点类似Andrei Alexandrescu大神的MDC中第11章节的Multimethod的蹦床函数,trampoline funciton. 有了这些,我们就可以在任意命名空间下来扩展我们的模板库,当然配合宏使用也没有问题了。在iguana中具体实现的小细节,可以参考reflection.hpp里面的实现。
相关文章推荐
- linux 下面查找函数的方法
- Linux下查找函数说明的方法
- 扩展方法之二分查找
- JQuery模拟三------利用extend函数扩展jQuery自身的方法和浏览器判断
- 在jQuery定义自己的扩展方法函数
- [Silverlight] 写了两个方便 DOM 查找的扩展方法
- 扩展原生函数 建立方法
- MFC - 通过spy++查找应用程序菜单对应函数的方法
- 开辟一个二维数组,有10*8个元素,用随机数填充,按照下面的方法用函数实现查找一个数是否存在
- Sqlite3 的扩展:实现 REGEXP函数和包含字符串查找的函数 CONTAIN
- 转贴 有关js 函数验证和字符串查找替换的方法
- VS2005通过指令地址查找其所在函数的方法
- 扩展方法之二分查找
- Mootools中使用extend和implement给你的函数扩展功能或方法
- 一个string不区分大小写查找的方法(函数)
- 通过spy++查找应用程序菜单对应函数的方法
- Silverlight两个方便 DOM 查找的扩展方法
- delphi通过函数名动态调用函数的方法(方便功能的扩展)
- 开辟一个二维数组,有10*8个元素,用随机数填充,按照下面的方法用函数实现查找一个数是否存在