C++模板杂谈

在模板编程中,有几个常用的技术:模板(偏)特化,特性萃取,标签分派,匹配失败不是错误。其中模板(偏)特化是基础,匹配失败不是错误(SFINAE)应用最为广泛。

 

现代C++对模板编程做了更多的加强,boost.hana又结合constexpr和lambda把类型与值的计算统一了起来。放眼C++世界,尤其是C++库,几乎都是使用模板的泛型编程。

 

话说在C++的世界中(并且几乎所有语言中)函数的作用是最为明显的,试想:没有类也可以完成编程任务,但没有函数却不好说。另一方向只使用变量和语句也能完成少量的工作,但不进行函数的封装终难成大事。这几年函数式编程赿来赿流行,与函数的重要性不无关系。所以无论是用类也罢,用函数也罢,或者使用C++新增的变量模板,我们的思路是始终围绕着把它们向函数上靠就好。从MPL的元函数开始就试图使类尽可能像函数,我们在模板编程中也要使模板类、模板变量、乃至模板函数都向“函数”靠拢。这里函数加上引号我想表达的是包括但不限于constexpr函数。

 

一、先从类型计算将用类实现来转为用函数实现起。将类型计算与值计算统一起来,以例子说话:

 

 1 #include <type_traits>
 2 
 3 //1、先定义一个辅助类,为了将类型表示为对象
 4 template<typename T>
 5 struct type_t
 6 {
 7     using type = T;
 8 };
 9 
10 //2、装饰类型,这里给类型添加指针
11 template<typename T>
12 constexpr auto add_pointer(type_t<T>)
13 {
14     return type_t<T*>{};
15 }
16 
17 //3、为测试提供支持
18 template<typename T>
19 constexpr auto is_pointer(type_t<T>)
20 {
21     return std::false_type{};
22 }
23 
24 template<typename T>
25 constexpr auto is_pointer(type_t<T*>)
26 {
27     return std::true_type{};
28 }
29 
30 //4、测试
31 type_t<int> int_t{};
32 auto p = add_pointer(int_t);
33 
34 auto is_ptr = is_pointer(p);
35 static_assert(is_ptr.value,"");
36 
37 int main()
38 {
39     return 0;
40 }

 

我们没有用元函数式方法实现,因为STL库中实现好了。现在用函数的方式也可以实现了,仔细体会代码,函数把类型和值的计算统一了起来,看起来函数的作用更加强大了。针对以上例子,我们需要类型的时候可以这样

 

1 // --vs2017rc仍然不能编译--
2 //template<typename T>
3 //using point_type =typename decltype(add_pointer(type_t<T>{}))::type;
4 
5 using point_type = typename decltype(p)::type;

 

稍嫌繁琐,但可以进一步封装。

 

1 //将以上代码装到namespace中
2 namespace detail {
3     //...
4 }
5 
6 template<typename T>
7 uisng add_pointer_t = typename decltype(detail::add_pointer(type_t<T>{}))::type;

 

 

二、把类函数化。还是以例子说话:

 

 1 struct test {};
 2 
 3 //1、古老的仿函数
 4 template<typename T>
 5 struct func
 6 {
 7     template<typename... Args>
 8     auto operator()(Args&&... args) const
 9     {
10         return T{ std::forward<Args>(args)... };
11     }
12 };
13 
14 func<test> func_c;
15 auto test_t=func_c();
16 
17 //2、元函数,就不写例子了,STL中很多

 

func明明是类,但我们把它用出了函数的感觉有没有。

 

三、把变量模板函数化

 

上面的代码感觉用起来还是不爽,主要是因为还要先生成一个类对象,我们把它也封装起来,再看个例子

 

 1 struct test2
 2 {
 3     explicit test2(int x)
 4         : x_(x)
 5     {
 6 
 7     }
 8 
 9     int x_;
10 };
11 template<typename T>
12 constexpr func<T> func_c{};
13 
14 auto test2_t = func_c<test2>(1);

 

看起来就像在调用函数了吧。

 

总之,不管是什么,只管往函数上靠就对了。

 

本来想要写sfinae来着,却强烈地想函数的事,现在记录一种利用sfinae的方式吧。

 

首先,当然是模板特化了。特化前先说说形式完整性这个概念:

有一个别名声明:

1 template<typename...> 
3 using void_t=void;

 

这个别名声明的意思是不管你给我传多少个模板参数,可能是有效的参数我都统统把它们当做void,除非任意一个模板参数不是有效的,那样的话另说(通常是编译器会给你点颜色看看)。

 

今天我就是要给你无效参数了怎么地?哼哼,我有sfinae大法在手,给我脸色我还不尿你呢!

有了sfinae,编译器会妥协,不管模板参数有效无效它都不敢给你脸色啦。

 

嗯,我想想哈,举个标签的例子吧。设想是如果一个类有标签,那么我们获取它的标签,如果没有,就什么也不做。

 

 1     template<typename T, typename = void>
 2     struct tag_of;
 3 
 4     template<typename T>
 5     struct tag_of<T, void_t<typename T::tag>>
 6     {
 7         using type = typename T::tag;
 8     };
 9 
10     template<typename T>
11     using tag_of_t = typename tag_of<T>::type;
12 
13     template<typename T, typename Tag>
14     struct is_tagof : std::false_type
15     {
16 
17     };
18 
19     template<typename T>
20     struct is_tagof<T, tag_of_t<T>> : std::true_type
21     {
22 
23     };

 

模板(偏)特化会优先得到解析。上面is_tagof判断类型T是否有嵌入的tag,如果有,is_tagof就是true_type,如果没有的话,编译器在匹配void_t<typename T::tag>会失败,编译器不认为这是错误而是回头匹配主模板,is_tagof就是false_type

 

posted @ 2017-02-16 22:30  frereze  阅读(614)  评论(1编辑  收藏  举报