TMP(2)

作者: Teech | 来源:发表于2021-11-07 17:07 被阅读0次

    深入模板原理

    函数模板,类模板的实参推导

    1. 函数模板的实参推导

      函数模板的实参推导是发生在名字查找之后,和重载决议之前,如果函数模板推导失败,编译器不会直接报错,而是把这个函数从重载集中删除

      template<typename T,typename U> void foo(T,U){}; //#1
      template<typename T> void foo(T,T){};//#2
      void foo(float,int){}; //#3
      foo(1,1.0f);//call #1 由于推导#2失败
      //1. 编译器看到名字为foo的调用
      //2. 编译器找到所有foo的名字的函数和函数模板 3个都符合
      //3. 编译器对于每个函数和函数模板都尝试通过实参(1,1.0f)来推断模板实参
       //#1:T = int ,U = float
       //#2: 推导失败 2被移除重载集
      //4. 编译器对当前的重载集 #1和#3 进行重载决议 选择#1
      //5. 编译器对#1 进行替换(实例化)(T,U)  -》 (int,float)
      
    2. 类模板的实参推导

      实参推导只考虑主模板 不考虑模板特化,如果主模板实参推导失败,编译器直接报错

      template<typename T,typename U> struct S { S(T a,U b ){};}; //#1
      template<typename T> struct S<T,float> { S(T a,T b ){};};   //#2
      template<> struct S<int,int> { S(int a,int b ){};};   //#3
      
      S s(1,1.0f);//call #2 通过主模板构造函数推导出T=int U = float,推导结果拿去匹配特化,匹配最佳特化为#2
      //假设通过#2来做模板实参推导,推导失败 把1,1.0f 带入#2的构造函数就会推导失败,所以也反证了编译器不是用模板特化去推导实参
      //1. 编译器看到变量s的定义
      //2. 编译器通过名字查找找到S的类或者类模板,这里应该只能找到一个,否则会重定义错误
      //3. 对于S的主模板#1,编译器尝试通过构造函数的的实参1,1.0f,去推导模板实参,T = int, U = float
      //4.编译器根据模板的实参去匹配最佳的特化 选择#2
      //5. 编译器对#2进行替换,完成隐式实例化
      
    3. 特化选择

      在所有的模板实参都确定了(可以是显示指定的,可以退推导的 或者从默认实参中获取的),当所有的模板实参都确定了后,编译器就需要在主模板和所有的特化中选择其中一个来进行实例化

      1. 对于每个模板特化,先判断能不能匹配实例化
      2. 如果只有一个模板特化能匹配模板实参,那么就选择这个特化
      3. 如果多个模板特化都可以匹配,那么通过特化的偏序关系来判断哪个模板匹配程度更高,匹配程度最高的特化被选择,
      4. 如果没有任何特化可以匹配,那么主模板就会被选中

      不严谨的说,A的特化比B高,A的特化接受的参数是B接受参的子集。严谨的说对于2个特化A和B,编译器会首先把A和B转换成2个虚构的函数模板FA和 FB,然后模板特化的形参就被转换成函数的形参。

      template<typename T,typename U,typename ...Args> struct S{};
      template<typename T,typename U> struct S<T,U>{};           //#A
      template<typename T> struct S<T,int>{};                    //#B
      //#A -> template<typename T,typename U> void FA(S<T,U>)
      //#B -> template<typename T> void FB(S<T,int>)
      //这样转换后,就转变成了函数模板的重载决议规则(归一化了)
      
    4. 模板的偏序规则

      对于2个函数模板,怎么判断谁的特化程度更高,也是个代入推导的过程

      template<typename T> void foo(T){}; //#1
      template<typename T> void foo(T*){}; //#2
      template<typename T> void foo(const T*){};#3
      const int*p;
      foo(p);
      //对于#1 和 #2的偏序关系
      //1. 尝试用#2代入推导#1,假设给#2传入实参U
           //#1 变成void(T) #2变成void(U*) ,用#2代入#1 T = U* 推导ok T = U*
      //2. 尝试用#1 代入推导#2
           //#1 变成void(U) #2变成void(T*) 用#1代入推导#2 T* = U 推导失败
      //综上 #2的特化程度比#1高
      

      函数模板的重载集是偏序集,直观的说就是集合中并不是所有的元素都可以拿来对比的,模板中并不是所有的模板都可以比较谁的特化程度更高

      template <typename T> void foo(T,T*){};  //#1
      template <typename T> void foo(T,int*); //#2
      //1. 尝试用#2 代入推导1,假设给#2传入实参U
       //#(U,int*) = (T,T*) 推导失败,如果T被推导成U 那么(U,U*) = (U,int*) 显然失败的
      //2. 尝试用#1 代入推导#2 假设给#1传入实参U
       //(U,U*) = (T,int*) 显然失败的
      //无法推导的情况下,编译器无法完成重载决议,就会抛出“ambiguous” 错误。其实如果相互都可以推导成功,也是无法比较的,同样对比编译器无法完成重载决议。
      
      
    5. 重载和特化的关系

      函数模板的每个重载都是主模板,在重载决议的时候 只考虑主模板。模板的特化不在重载集的范围内。对于一个函数调用先进行重载决议,确定使用哪个主模板,然后在考虑要不要使用它的特化。所以先进行重载决议后进行选择特化。做重载决议的时候,特化根本不在编译器考虑范围内

      template <typename T> void foo(T){};    //#1
      template <> void foo(int*){};           //#2
      template <typename T> void foo(T*){};     //#3
      foo((int*)(0)); //call #3  由于#2是#1的特化,重载决议的时候压根看不到#2
      
      //这样#2就会被调用,调换位置后#2变成#3的特化
      template <typename T> void foo(T){};    //#1
      template <typename T> void foo(T*){};     //#3
      template <> void foo(int*){};           //#2
      
    6. SFINAE substitution Is Not An Error

      替换失败并不是一个错误,替换失败指的用实参替换模板形参后,在模板的“立即上下文”中,呈现出“非良构”(ill-formed)

      • 一个类型或者表达式(ill-formed)指的代码违背了语法或者语义的规则
      • 立即上下文简单的说是模板声明中看到的内容
      • “不是一个错误”,如果函数模板在替换失败后,替换失败直接从重载集中移除,编译器会尝试其他重载并不会抛出一个错误。类模板和变量模板偏特化替换失败,这个特化从特化集中移除,编译器继续尝试其他特化,并不会报错。
      template<typename T> 
      typename T::value_type foo(T t) {    //int::value为ill-formed 这里会替换失败 (也就是这个错误不会被直接报错)
        return t::value_type;              //int::value为ill-formed 但是这里不是立即上下文 编译报错
      }
      foo(1);
      

      SFIINAE在函数模板中

      template<typename T> void foo(T) {}             //#1
      template<typename T> void foo(T*) {}            //#2
      template<typename T> typename T::value_type foo(T) {}    //#3
      foo(1); //#3会发生替换失败 int::value_type SFINAE #1h和#2中重载选择#1
      foo(new int); //#3会发生替换失败 int::value_type SFINAE #1h和#2中重载选择#2
      foo<int&&>(1); //#2,#3发生替换失败  SFINAE #1 选中
      

      SFIINAE在类模板偏特化中

      template<typename T,typename U> struct S {};
      template<typename T> struct S <T,typename T::value_type> {};
      
      S<int,int>(); //#2 SFINAE 选择#1
      S<std::true_type,bool>(); //#2
      S<std::true_type,int>(); //#1
      

    回顾下实例化过程

    1. 首先名字查找,编译器首先根据标识符查找同名的模板
      • 如果函数模板,会找到多个模板
      • 如果变量或者类模板,找到唯一的主模板
    2. 确定所有的实参
      • 对于类模板或者变量模板,主模板推导失败了就报错
      • 如果函数模板,如果推导失败了,就从重载集中移除
    3. 对于函数模板要进行重载决议,重载决议只考虑主模板,采用偏序规则,SFINAE发生作用
    4. 特化选择,对于类模板,SFINAE发生作用,对于函数模板,由于只有全特化,直接匹配就可以了。
    5. 对于最终选择的模板进行替换操作,生成真实的代码,放入POI(point of instantiation),生成代码插入的位置

    应用TMP

    enable_if实现

    template<bool,typename T = void>
    struct enable_if:std::type_identity<T>{};
    template<typename T>
    struct enable_if<false,T>{};
    template<typename T> enable_if< std::is_integral_v<T> >::type foo(T) {};        //#1
    template<typename T> enable_if< std::is_floating_point_v<T> >::type foo(T) {};  //#2
    
    foo(1); //匹配 #1 匹配#2的过程中会发生SFINAE enable_if<false,float>::type 出错
    foo(1.0f);//匹配#2 匹配#1过程会发生SFINAE
    //利用SFINAE 我们有了基于逻辑控制函数重载集的能力
    //通过类模板控制函数重载
    template<typename T>
    struct S {
        template<typename U> static enable_if<std::is_same_v<T,int>>::type foo(U) {}; //#1
        template<typename U> static enable_if<!std::is_same_v<T,int>>::type foo(U) {};//#2
    };
    S<int>::foo(1);
    //编译出错#2 由于enable_if<false>::type 虽然是foo的“立即上下文”,但是不是S的立即上下文
    //所以如果想变成S的立即上下文中,要推迟enable_if的计算到实例化foo的时候 
    template<typename ...Args>
    struct always_true:std::true_type{}
    //添加个关于U的表达式 合取表达式,所以就会延迟到foo的实例化的时候在求值enable_if 
    template<typename U> static enable_if<always_true<U> && std::is_same_v<T,int>>::type foo(U) {};
    

    void_t

    template<typename ...>
    using void_t = void
    template<typename,typename = void> struct has_type_member : false_type {};
    template<typename T> struct has_type_member<T,void_t<typename T::type>> : true_type {};
    
    std::cout<<has_type_member<int><<std::endl; // false
    std::cout<<has_type_member<true_type><<std::endl; // true
    std::cout<<has_type_member<type_identity<int>><<std::endl; // true
    
    //实现类似py中has_attr的效果,内部是否有type的member
    

    不求值表达式

    c++中4个运算操作符,操作时不会求值的,typeid,sizeof,noexcept,decltype,这4个操作符只对操作数的编译期进行访问
    
    template<typename T> enable_if<is_integral_v<T>,int> foo(T) {};
    template<typename T> enable_if<is_floating_point_v<T>,float> foo(T) {};
    
    template<typename T> struct {decltype(foo<T>(??)) value_;}; //期望返回foo<T>(??)函数的返回值类型
    //但是这里我们编译器产生不了一个变量啊 
    //可以通过declval来产生一个假象的变量 ,只有申明没有定义,只能用在不求值上下文中
    template<typename T> add_rvalue_reference_t<T> decval() noexcept; //这里没有定义
    //有了decval 就可以“伪造”一个变量传给foo
    template<typename T> struct {decltype(foo<T>(decval<T>())) value_;};
    

    add_reference

    template<typename T> struct add_lvalue_reference:type_identify<T&>{};
    template<typename T> struct add_rvalue_reference:type_identify<T&&>{}
    //问题 void 没有引用类型
    //add_lvalue_reference<void>::type 编译出错
    namespace helper {
      template<typename T> type_identify<T&> try_add_lvalue_reference(int);
      template<typename T> type_identify<T> try_add_lvalue_reference(...);
    };
    template<typename T> struct add_lvalue_reference :decltype(helper::try_add_lvalue_reference<T>(0)) {};
    //当T可以加上左值引用就添加 利用SFINAE
    std::cout<<is_same_v<char&,add_lvalue_reference<char>> <<std::endl;
    std::cout<<is_same_v<void,add_lvalue_reference<void>> <<std::endl;
    

    is_copy_assignable

    //判断一个类型是否可以拷贝赋值
    struct S{ S& operator=(S const &) = delete;};
    std::cout<<is_copy_assignable(int)<<std::endl; //1
    std::cout<<is_copy_assignable(true_type)<<std::endl; //1
    std::cout<<is_copy_assignable(S)<<std::endl; //0
    //假设中有 a = b 这样的表达式,先写出来在说 ,看看是不是良构表达式 同样利用SFINAE
    template<typename T> using copy_assign_t = decltype(declval<T&>() = declval<const T&>());
    template<typename T,typename = void> struct is_copy_assignable : false_type {};
    template<typename T> struct is_copy_assignable<T,void_t<copy_assign_t<T>>> : true_type {};
    
    

    标准库中tuple的实现

    int i= 1;
    auto t = tuple(0,i,'2',3.0f,4ll,std::string("five"));
    template<typename... Args> struct tuple {tuple(Args...) {};}; //主模板
    //特化实现了递归的继承,最终匹配到主模板 递归终止
    template<typename T,typename... Args> struct tuple:tunple<Args...> {
      tuple(T v,Args... params):value_(v),tunple<Args...>(params...) {
      }
      T value_;
    };
    //怎么去读tuple中n个元素
    is_same_v<tuple_element_t<2,decltype(t)>,int);
    //
    template<unsigned N,typename T>
    struct tuple_element;
    
    template<unsigned N,typename T,typename ...Args>
    struct tuple_element<N,tuple<T,Args...>> : tuple_element<N-1,tuple<Args...>>{};
    
    template<typename T,typename ...Args>
    struct tuple_element<0,tuple<T,Args...>> : type_identify<T>{using _tuple_type = tuple<T,Args...>};
    
    // 比如tuple_element[3] 此时_tuple_type的用处就是从元素3以后所有元素构成的tuple的类型
    
    
    template<unsigned N,typename...Args>
    tuple_element_t<N,tuple<Args...>>& get(tuple<Args...>&t){
      using _tuple_type = typename tuple_element<N,tuple<Args...>>::_tuple_type;
      return static_cast<_tuple_type&>(t).value; //返回是左值引用 可以修改的
    }
    cout<<get<1>(t)<<endl;  //1
    cout<<get<5>(t)<<endl;  //five
    

    相关文章

      网友评论

          本文标题:TMP(2)

          本文链接:https://www.haomeiwen.com/subject/cevezltx.html