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