enable_if

作者: 404Not_Found | 来源:发表于2022-02-12 15:13 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20220213 12:22
    • 目的: enable_if
    # 编译器的选择规则
    # 替换失败并不是一个错误(SFINAE)
    # enable_if
      ## 基础认识
      ## enable_if 用于函数模板中
      ## enable_if 用于类模板中
    

    编译器的选择规则

    函数模板 与 普通函数重载

    using namespace std;
    
    template <typename T>
    void myfunc(const T &t) {
        cout << "myfunc 函数模板被执行" << endl;
    }
    
    void myfunc(int tmpvalue) {
        cout << "myfunc 普通函数 被执行" << endl;
    }
    
    int main(int argc, char**argv) {
        myfunc(15);
    
        return 0;
    }
    

    结果是,优先调用普通函数。

    如果将myfunc 形参类型修改成

    myfunc(unsigned int tmpvalue) {
    
    }
    

    则优先调用函数模板

    从编译器角度来看,15是有符号 int。如果调用普通函数,则需要一次类型转换。所以编译器认为函数模板更为合适。
    如果传参是 15U,即 myfunc(15U) 则调用普通函数。

    再次修改

    myfunc(unsigned short tmpvalue) {
    
    }
    

    同理会调用函数模板

    所以编译器对于函数的选则来说,有自己的一套规则,并不总是对普通函数优先选择,函数模板靠后选择。

    替换失败并不是一个错误(SFINAE)

    Substitution Failure Is Not An Error
    c++ 语言的一种特性,一种编译器对模板设计的重要原则。

    这个特性是针对 函数模板重载 而言的。

    对于函数模板而言,当用一个具体类型(int)替换函数模板的参数时,可能会产生意想不到的问题。

    1. 比如产生一些毫无意义甚至是看起来语法上有错误的代码。编译器对这些代码并不一定报错。有时候会忽略
      忽略的原因: 编译器认为函数模板不匹配针对本次的函数调用。
      举例,int 带入 上述 myfunc 函数模板中后,进入myfunc,发现myfunc自己会有错误,则编译器会忽略掉。认为这个函数模板不匹配本次函数调用。可以理解为,编译器自行排错。
      就当这个函数模板不存在一样!转而去选择其他更匹配的函数或者函数模板。

    以上即 SFINAE概念。

    举例:

    /*
    typename T::size_type, 类型说明,mydouble 要返回的类型
    */
    
    template <typename T>
    typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
        return t[0] * 2;
    }
    
    int main(int argc, char**argv) {
        mydouble(15);
        return 0;
    }
    
    1. 用int 替换:
    typename int::size_type mydouble(const int &t);
    

    对于int来讲,并不存在 size_type 这个成员。

    由于不合适,所以编译器去找其他重载的。所以提供一个合适的即可:

    /*
    typename T::size_type, 类型说明,mydouble 要返回的类型
    */
    
    template <typename T>
    typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
        return t[0] * 2;
    }
    
    int mydouble(int i) {
        return i * 2;
    }
    
    int main(int argc, char**argv) {
        mydouble(15);
        return 0;
    }
    

    举例:调用函数模板:

    /*
    typename T::size_type, 类型说明,mydouble 要返回的类型
    */
    
    template <typename T>
    typename T::size_type mydouble(const T &t) {//本函数对不对取决于T的类型
        return t[0] * 2;
    }
    
    int mydouble(int i) {
        return i * 2;
    }
    
    int main(int argc, char**argv) {
        vector<int> myvec;
        myvec.push_back(15);
        cout << mydouble(myvec) << endl;
        return 0;
    }
    

    在vector中是存在 T::size_type 这个类型的。

    SFINAE特性:编译器虽然看不出函数模板实例化后的对错(比如一些无效的类型,无效的表达式),但是我能决定是否选择这个函数模板。当编译器觉得这个函数模板不合适的始化,编译器不会报错,但编译器会无视你的存在,从而去找其他函数or函数模板。

    enable_if

    基础认识

    c++11新标准引入的类模板。具有SFINAE特性。定位是一个helper辅助助手模板。
    辅助其他模板设计。
    作用:编译器的分支逻辑(编译器就可以确定哪条分支)。

    enable_if:

    //泛化版本
    template <bool _Test, class _Ty = void>
    struct enable_if {};
    
    //偏特化版本(数量的偏特化,_Test 非类型模板参数)
    //只有偏特化版本存在,才存在一个叫做type类型的别名(类型)
    //偏特化,有点条件分支语句的意思
    template<class _Ty>
    struct enable_if<true, _Ty> {
        using type = _Ty;
    };
    

    注释中的话。

    引例:

    template <typename T>
    struct MEB {
        using type = T;
    };
    
    int main(int argc, char**argv) {
        MEB<int>::type abc = 15;//MEB<int>::type 即int类型
    
    

    enable_if 用于函数模板中

    举例1:

    template <bool _Test, class _Ty = void>
    struct enable_if {};
    
    template<class _Ty>
    struct enable_if<true, _Ty> {
        using type = _Ty;
    };
    
    template <typename T>
    struct MEB {
        using type = T;
    };
    
    int main(int argc, char**argv) {
        //MEB<int>::type abc = 15;//MEB<int>::type 即int类型
        std::enable_if<(3 > 2)>::type *mypoint = nullptr; //void*mypoint1 = nullptr;
        return 0;
    }
    

    编译通过,type是有默认值,这个默认值对偏特化也有效。
    3>2 是true,所以存在type类型。type类型就是 _Ty类型 void。

    std::enable_if<(3<2)>::type* mypiont1 = nullptr;
    

    就注定会报错。对应的泛化版本,没有type功能。即无中生有。

    enable_if 典型应用是作为函数与模板的返回类型。

    template <typename T>
    typename std::enable_if<(sizeof(T) > 2)>::type funceb() {
        //
    }
    
    int main(int argc, char **argv) {
        funceb<int>();
        return 0;
    }
    

    编译通过。int > 2
    上述模板等价于

    void funceb(){}
    

    如果传入的是char就报错,无type类型

    funceb<char>();//报错,type就没有,会继续寻找其他重载函数。
    

    char类型 并不满足 char >2, 所以去寻找了 enable_if 的泛化版本,泛化版本中并没有定义 type 类型。根据 SFNASE,编译器丢掉这个函数模板。
    c++14写法

    template <typename T>
    std::enable_if_t<(sizeof(T) > 2)> funceb() {
    
    }
    

    给 _Ty 赋值

    template <typename T>
    typename std::enable_if<(sizeof(T) > 2), T>::type funceb() {
        T myt = {};
        return myt;
    }
    
    int main(int argc, char **argv) {
        funceb<int>();//int funceb() {}; 0;
        return 0;
    }
    

    如果 不满足 enable_if 的判定条件,编译器则会忽略 这个函数模板。寻找更合适的重载函数。

    enable_if 用于类模板中

    继续讨论完美转发中遇到的拷贝构造函数问题。
    Human myhuman3(myhuman1);
    代码解决办法:
    针对构造函数模板,如果给进来的参数是一个string类型,则用构造函数模板生效。
    否则就让这个构造函数忽略。

    结合 enable_if 即可。
    同时引入与enable_if 一起工作的 std::is_convertible模板。c++11 引入。即判断能否从某个类型,隐式转换到另外一个类型。返回的是一个bool值。

        cout << "string=>float: " << std::is_convertible<string, float>::value << endl; //0
        cout << "float=>int: " << std::is_convertible<float, int>::value << endl;//1
    

    究极写法:

    class Human {
    public:
        
        template <
            typename T,
            typename = std::enable_if_t<std::is_convertible<T, std::string>::value>
            //如果成立,则是 typename = void,则不会被抛弃,否则被抛弃,typename =  实际就是一个辅助
        >
        Human(T&& tmpname) :m_sname(std::forward<T>(tmpname)) {
            cout << "Human(T&& tmpname) 执行" << endl;
        }
    
        //拷贝
        Human(const Human & th) : m_sname(th.m_sname) {
            cout << "Human(const Human &th) copy构造函数执行" << endl;
        }
    
        //移动构造函数
        Human(Human &&th) :m_sname(std::move(th.m_sname)) {
            cout << "Human(Human &&th) 移动构造被执行" << endl;
        }
    private:
    
        string m_sname;
    };
    
    int main(int argc, char **argv) {
    
        cout << "string=>float: " << std::is_convertible<string, float>::value << endl;
        cout << "float=>int: " << std::is_convertible<float, int>::value << endl;
    
        string sname = "ZhangSan";
        Human myhuman1(sname);
        Human myhuman3(myhuman1);//忽略掉构造函数模板
    
        return 0;
    }
    
    template <typename T, typename U = >....
    
    如果U 用不到 可以简化写成
    typename  = ... 
    

    别名简化:

    template <typename T>
    using StrProcType = std::enable_if_t<std::is_convertible<T, std::string>::value>;
    
    class Human {
    public:
        
        template <
            typename T,
            typename = StrProcType<T>
        >
        Human(T&& tmpname) :m_sname(std::forward<T>(tmpname)) {
            cout << "Human(T&& tmpname) 执行" << endl;
        }
    
        //拷贝
        Human(const Human & th) : m_sname(th.m_sname) {
            cout << "Human(const Human &th) copy构造函数执行" << endl;
        }
    
        //移动构造函数
        Human(Human &&th) :m_sname(std::move(th.m_sname)) {
            cout << "Human(Human &&th) 移动构造被执行" << endl;
        }
    private:
    
        string m_sname;
    };
    
    int main(int argc, char **argv) {
    
        cout << "string=>float: " << std::is_convertible<string, float>::value << endl;
        cout << "float=>int: " << std::is_convertible<float, int>::value << endl;
    
        string sname = "ZhangSan";
        Human myhuman1(sname);
        Human myhuman3(myhuman1);
    
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:enable_if

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