特性、策略与标签
    利用迭代器,我们可以实现很多通用算法,迭代器在容器与算法之间搭建了一座桥梁。适用所有类型的求和函数模板如下:

    1. #include <iostream>
    2. #include <vector>
    3. template<typename iter> //要求我们输入的模板类型iter是个迭代器类型
    4. //用迭代器实现所有类型的求和
    5. typename iter::value_type mysum(iter begin, iter end)
    6. {
    7. typename iter::value_type sum(0);
    8. for(iter i=begin; i!=end; ++i)
    9. sum += *i;
    10. return sum;
    11. }
    12. int main()
    13. {
    14. std::vector<int> v;
    15. for(int i = 0; i<100; ++i)
    16. v.push_back(i);v.push_back(i);
    17. std::cout << mysum(v.begin(), v.end()) << '\n';
    18. }

    程序编译输出:4950。


    我们想让 mysum() 对指针参数也能工作,毕竟迭代器就是模拟指针,但指针没有嵌套类型 value_type,可以定义 mysum() 对指针类型的特例,但更好的办法是在函数参数和 value_type 之间多加一层特性(traits)

    1. template<typename iter>
    2. class mytraits //标准容器通过这里获取容器元素的类型
    3. {
    4. public: typedef typename iter::value_type value_type;
    5. };
    6. template<typename T>
    7. // mytraits的指针偏特化版本
    8. class mytraits<T*> //数组类型的容器,通过这里获取数组元素的类型
    9. {
    10. public: typedef T value_type;
    11. };
    12. template<typename iter>//iter可以是个迭代器 也可以是个指针类型
    13. typename mytraits<iter>::value_type mysum(iter begin, iter end)
    14. {
    15. typename mytraits<iter>::value_type sum(0);//如果iter是指针类型,则其中value_type就是iter去掉*后的类型
    16. for(iter i=begin; i!=end; ++i)
    17. sum += *i;
    18. return sum;
    19. }
    20. int main()
    21. {
    22. int v[4] = {1,2,3,4};
    23. //传入v,自动推导出iter的类型为int*,因为是iter是指针类型,所以调用mytraits的指针特化版本在这个特化版本中 value_type被定义为int类型
    24. std::cout << mysum(v, v+4) << '\n';
    25. return 0;
    26. }


    程序输出:10
    其实,C++ 标准定义了类似的 traits (可同时适用于指针和迭代器的遍历), std::iterator_trait(另一个经典例子是 std::numeric_limits)
    traits特性对类型的信息(如 value_type、 reference)进行包装,使得上层代码可以以统一的接口访问这些信息。


    C++ 模板元编程会涉及大量的类型计算,很多时候要提取类型的信息(typedef、 常量值等),如果这些类型信息的访问方式不一致(如上面的迭代器和指针),我们将不得不定义特例,这会导致大量重复代码的出现(另一种代码膨胀),而通过加一层特性(如上面的mytraits统一了接口)可以很好的解决这一问题。

    另外,特性不仅可以对类型的信息进行包装,还可以提供更多信息,当然,因为加了一层,也带来复杂性。特性是一种提供元信息的手段。

    策略(policy)一般是一个类模板,典型的策略是 STL 容器的分配器(如std::vector<>,完整声明是template> class vector;)(这个参数有默认参数,即默认存储策略), 策略类将模板的经常变化的那一部分子功能块集中起来作为模板参数,这样模板便可以更为通用,这和特性的思想是类似的。

    标签(tag)一般是一个空类,其作用是作为一个独一无二的类型名字用于标记一些东西,典型的例子是 STL 迭代器的五种类型的名字。
    input_iterator_tag
    output_iterator_tag
    forward_iterator_tag
    bidirectional_iterator_tag
    random_access_iterator_tag (随机访问迭代器tag)

    实际上,std::vector::iterator::iterator_category就是random_access_iterator_tag, 可以使用type_traits的特性is_same来判断类型是否相同。

    1. #include <iostream>
    2. #include <vector>
    3. #include <type_traits>
    4. int main()
    5. {
    6. std::cout << is_same<std::vector<int>::iterator::iterator_category, std::random_access_iterator_tag >::value << std::endl;
    7. return 0;
    8. }


    程序输出:1。
    有了这样的判断,还可以根据判断结果做更复杂的元编程逻辑(如一个算法以迭代器为参数,根据迭代器标签进行特例化以对某种迭代器特殊处理)。标签还可以用来分辨函数重载。

    整型数值类型

    1. template <class T, T v>
    2. struct integral_constant {
    3. static const T value = v;
    4. typedef T value_type;
    5. typedef integral_constant type;
    6. };

    integral_constant 模板同时包含了整数的类型和数值,而通过这个类型的 value 成员我们又可以重新取回这个数值。有了这个模板的帮忙,我们就可以进行一些更通用的计算了



    上面讲的内容都没有离开c++98,下面讲在现代c++中的做法
    编译期类型推导
    C++ 标准库在 头文件里定义了很多工具类模板,用来提取某个类型(type)在某方面的特点(trait)。和上一节给出的integral_constant相似,这些特点既是类型,又是常值。
    1 true_type or false_type
    为了方便地在值和类型之间转换,标准库定义了一些经常需要用到的工具类。上面描述的 integral_constant 就是其中一个(上面写的定义实际有所简化)。为了方便使用,针对布尔值有两个额外的类型定义

    1. typedef std::integral_constant<bool, true> true_type;
    2. typedef std::integral_constant<bool, false> false_type;

    //本质上是结构体
    这两个标准类型 true_type 和 false_type 经常可以在函数重载中见到。有一个工具函数常常会写成下面这个样子

    1. template <typename T>
    2. class SomeContainer {
    3. public:
    4. //很多容器里会有一个destory函数,通过指针来析构某个对象
    5. static void destroy(T* ptr)
    6. {
    7. _destroy(ptr,
    8. is_trivially_destructible<
    9. T>());// 两个重载版本在下面 destroy(T* ptr, true_type or false_type)
    10. //is_trivially_destructible 模板来用于判断类是否是可平凡析构的——也就是说,
    11. //不调用析构函数,不会造成任何资源泄漏问题(即判断此类是否有管理堆上资源)。
    12. //--- 其实只要显示定义或重载了析构函数 那么那个类T就无法平凡析构,说着T类中有非POD成员(即类成员)则也无法平凡析构
    13. }
    14. private:
    15. static void _destroy(T* ptr,
    16. true_type)
    17. {}// 类可以平凡析构 即T类中没有重新定义类的析构函数 即只有默认析构函数 那么可以什么也不做 因为默认析构本身就是什么也不做
    18. static void _destroy(T* ptr,
    19. false_type)
    20. {
    21. ptr->~T();/ 类不可以平凡析构 T类中重新定义类的析构函数 重写了析构 那么我们必须调用T的析构函数来释放资源
    22. }
    23. //这两个重载根据形参的类型不同来区分,但都没有为形参取名(仅仅用于区分重载的形参 在函数中并没有用到 所以可以不取形参名)。
    24. };


    is_trivially_destructible() (调用该类型的构造函数,让编译器根据类型来选择合适的重载 不需要 使用is_trivially_destructible::value取得false_type or true_type中的布尔值)判断T类型中是否有自定义的析构函数,假如有返回 false_type,没有则返回true_type。在编译期根据其返回直接决定了调用哪个重载版本的_destroy函数,不需要运行时再决定

    像 is_trivially_destructible 这样的 trait 类有很多,可以用来在模板里决定所需的特殊行为:is_array,is_enum,is_function,is_pointer,is_reference,is_const,has_virtual_destructor
    都是返回调用这些模板的实例化等同于 true_type or false_type
    这些特殊行为判断可以是像上面这样用于决定不同的重载(调用构造的方式得到true_type or false_type),也可以是直接用在模板参数甚至代码里(记得我们是可以直接得到布尔值的 取::value)。


    2 remove_const
    除了得到布尔值和相对应的类型的 trait 模板,我们还有另外一些模板,可以用来做一些类型的转换。以一个常见的模板 remove_const 为例(用来去除类型里的 const 修饰),它的定义大致如下:

    1. template <class T>
    2. struct remove_const {
    3. typedef T type;//通用版本 如果remove_const<常类型外的其他类型>调用这个版本,可以看到下面type就是T 因为T本身就是不带const的 符合我们给予它的行为
    4. };
    5. template <class T>
    6. struct remove_const<const T> {// 常类型特化版本 如果remove_const<一个常类型>会调用这个特化版本 可以看到下面type为T 是去掉cosnt的类型
    7. typedef T type;
    8. };
    9. remove_const<const string>::type 等价于 string

    细节! 对于指针的 顶层(常指针 指向不可改) 和 底层 const(常数指针 内容不可改)。对顶层指针remove_const是可以去掉顶层cosnt的,因为本质是指针 去常。
    但是对底层const是去不掉的,原因是,const char 是指向 const char 的指针,而不是指向 char 的 const 指针。本质是因为 指针不是常的
    const string&应用remove_const后还是const string&(只有当同时出现指针概念和const才会出现顶层和底层const的区别 因为而引用本质是顶层const const string&中的const是修饰string的是底层const 所以这里无法去掉修饰string的这个底层const),const string(只有一个cosnt 都视为顶层const 可以直接去掉)应用remove_const后是string。
    对::取内容的简化

    如果你觉得写 *is_trivially_destructible::value 和 remove_const::type 非常啰嗦
    的话,那你绝不是一个人。在当前的 C++ 标准里,前者有增加 _v 的编译时常量,后者有增加 _t 的类型别名:

    1. template <class T>
    2. inline constexpr bool
    3. is_trivially_destructible_v = //增加_v的类型别名 不需要再写::value, 而是直接写_v取value
    4. is_trivially_destructible<
    5. T>::value;
    6. //is_trivially_destructible<T>::value == is_trivially_destructible_v<T>
    7. template <class T>
    8. using remove_const_t =
    9. typename remove_const<T>::type; //增加_t的类型别名 不需要再写::type, 而是直接写_t取type
    10. //remove_const<T>::type == remove_const_t<T>

    using 是现代 C++ 的新语法,功能大致与 typedef 相似,但 typedef 只能针对某个特定的类型,而 using 可以生成别名模板。目前我们只需要知道,在你需要 trait 模板的结果数值和类型时,使用带 _v 和 _t 后缀的模板可能会更方便,尤其是带 _t 后缀的类型转换模板。

    const T& 等同于 T const&,但和 T& const 不同。前者是一个指向常量的引用(T const&==const T&这里明确写出的const为底层const,引用的那个隐式cosnt为顶层const),后者是一个常引用(T& const 引用的那个隐式cosnt为顶层const,这里加上的const没用—-不存在所谓的常引用,引用一定是顶层cosnt的,引用的类型是不能改的)。只有后者才被看作是一个“常量”。

    例子通用的fmap函数模板

    下面我们演示一个 map 函数(当然,在 C++ 里它的名字就不能叫 map 了,这个map实际指的是映射—对传入函数的容器内容做统一的函数映射),其中用到了目前为止我们学到的多个知识点:

    1. template <
    2. template <typename, typename>
    3. class OutContainer = vector,//缺省使用 vector 作为返回值的容器,但可以通过模板参数改为其他容器
    4. typename F, class R>
    5. auto fmap(F&& f, R&& inputs)
    6. {
    7. typedef decay_t<decltype(
    8. f(*inputs.begin()))>
    9. //用 decltype 来获得用 f 来调用 inputs 元素的类型,调用一次我们要做的函数映射f(*inputs.begin()) 并通过decltype取得函数映射返回值的类型
    10. //用 decay_t (_t后缀)来把获得的类型变成一个普通的值类型
    11. result_type;
    12. OutContainer<
    13. result_type,
    14. allocator<result_type>>
    15. result;//输出的结果容器
    16. //使用基于范围的 for 循环来遍历 inputs,对其类型不作其他要求
    17. for (auto&& item : inputs) {
    18. result.push_back(f(item));//存放结果的容器需要支持 push_back 成员函数
    19. //对万能引用使用完美转发是否好点,
    20. //result.push_back(std::forward<decltype(f(item))>(f(item)))或//result.emplace_back(std::forward<decltype(f(item))>(f(item)));;
    21. }
    22. return result;//自动推导result 类型
    23. }


    1. vector<int> v{1, 2, 3, 4, 5};
    2. int add_1(int x)
    3. {
    4. return x + 1;
    5. }
    6. auto result = fmap(add_1, v);

    在 fmap 执行之后,我们会在 result 里得到一个新容器,其内容是 2, 3, 4, 5, 6。

    一个多重模板的例子

    1. template <int num1, int num2>
    2. struct Add_
    3. {
    4. const static int res = num1 + num2;
    5. };
    6. template <int num1, int num2>
    7. struct Sub_
    8. {
    9. const static int res = num1 - num2;
    10. };
    11. template <bool Condition>
    12. struct If_;
    13. template <>
    14. struct If_ <true>
    15. {
    16. template<int num1, int num2>
    17. using type = Add_<num1, num2>;
    18. };
    19. template <>
    20. struct If_ <false>
    21. {
    22. template<int num1, int num2>
    23. using type = Sub_<num1, num2>;//相当于两个参数都给定的全特化
    24. };
    25. template<int num1, int num2>
    26. template<bool Condition>
    27. using If = typename If_<Condition>::template type<num1, num2>;
    28. template<int num1, int num2>
    29. using True = If<true>;
    30. template<int num1, int num2>
    31. using False = If<false>;
    32. { // 更好的写法
    33. template <bool Condition, int num1, int num2>
    34. using If = typename If_<Condition>::
    35. template type<num1, num2>;
    36. template <int num1, int num2>
    37. using True = If<true, num1, num2>;
    38. template <int num1, int num2>
    39. using False = If<false, num1, num2>;
    40. }