浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第19课 类型萃取(3)_类型选择的traits

Posted on 2017-11-04 09:37  浅墨浓香  阅读(1752)  评论(0编辑  收藏  举报

1. std::conditional

(1)原型:template <bool Cond, class T, class F> struct conditional;

//根据条件获取T或F类型
template<bool Cond, class T, class F> //泛化
struct conditional { typedef T type; };

template<class T, class F>  //偏特化
struct conditional<false, T, F> { typedef F type; };

(2)说明:

  ①当cond为true时,conditional::type被定义为T类型

  ②当cond为false时,conditional::type被定义为F类型

【编程实验】std::conditional

#include <iostream>
#include <typeinfo>
using namespace std;

int main()
{
    typedef std::conditional<true, int, float>::type A;                 //int
    typedef std::conditional<false, int, float>::type B;                //float
    typedef std::conditional<is_integral<A>::value, long, int>::type C; //long
    typedef std::conditional<is_integral<B>::value, long, int>::type D; //int
    
    //比较两个类型,输出较大的类型
    typedef std::conditional<(sizeof(long long) > sizeof(long double)),
                              long long,
                              long double>::type max_size_t;
    cout << typeid(max_size_t).name() << endl; //long double
    return 0;
}

2. std::enable_if

(1)enable_if的作用:

  ①当某个 condition 成立时,enable_if可以提供某种类型

  ②具备限定模板参数的作用,可以在编译期检查输入的模板参数是否有效。

  ③可以用来控制重载函数是否可用,以实现强大的重载机制。

(2)std::enable_if的原型

  ①原型: template<bool cond, class T = void> struct enable_if;

//enable_if的可能实现
template<bool Cond, typename T = void>
struct enable_if {}; //注意,没有type类型

template<typename T> //偏特化,注意T的默认值为void
struct enable_if<true, T> { typedef T type; };

  ②在 condition 为真的时候,由于偏特化机制,第2个结构体模板明显是一个更好的匹配,所以 std::enable_if<>::type 就是有效的。

  ③当condition 为假的时候,只有第一个结构体模板能够匹配,所以std::enable_if<>::type 是无效的,会被丢弃。同时,编译器会报错:error: no type named ‘type’ in ‘struct std::enable_if<false, bool>。

【编程实验】利用std::enable_if检查模板参数

#include <iostream>
#include <typeinfo>
using namespace std;

//1. 模板参数只能是arithmetic(整型和浮点型)
template<typename T>
typename std::enable_if<is_arithmetic<T>::value, T>::type
foo1(T t)
{
    return t;
}

//2. 限定入参类型:
template<typename T> //注意限制的是foo1的第2个形参,只能是整型
T foo2(T t, typename std::enable_if<std::is_integral<T>::value, int>::type = 0)
{
    return t;
}

//3. 限定模板参数T的类型 (注意限制的是模板的T参数:为intergral类型)、
//   如果T是非integral类型,is_integral<T>::value为false,enable_if<false>::type将报错
template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type>
T foo3(T t) 
{
    return t;
}

//4. 类模板特化时,参数的限定
//前向声明,A为类模板
template<class T, class Enable = void> class A; 

template<class T>  //模板特化
class A<T, typename std::enable_if<std::is_floating_point<T>::value>::type> //对模板参数的限定
{};

int main()
{
    //1. 
    auto r1 = foo1(1);      //返回整数1
    auto r2 = foo1(1.2);    //返回浮点数1.2
    //auto r3 = foo1("test"); //error
    
    //2. 限定入参类型
    foo2(1, 2); //ok
    //foo2(1, ""); //error,第二个参数应为integral类型
    
    //3.限定模板参数
    foo3(1);
    //foo3(1.2); //error,模板参数的类型应为integral类型z z
    
    //4.类模板特化时,参数的限定
    A<double> a1;  //ok,先匹配特化模板,所以a的类型为A<double, double>
    //A<double, double> a2; //error, 显式指式两个参数。因此匹配的是第1个模板,但由于这里只是声明
                            //而未定义类(注意class A和class A{}的区别),所以会报A是个未完整类的错误。
    //A<int>  a3; //先匹配特化模板(失败)。再匹配A<int, void>模板,但由于class A只是声明,会与a2一样。
                  //的错误。
          
    return 0;
}

(3)注意事项

  ①T的默认值为void类型,即enable_if的第2个模板参数不指定时,当cond为真,默认会获取到的类型为void。

  ②当cond为假时,由于std::enable_if<>::type是无效的,因此编译器会报错。

【编程实验】利用std::enable_if根据条件选择重载函数

#include <iostream>
using namespace std;

//利用std::enable_if根据条件选择重载函数

/********************************************************************************************/
//利用std::enable_if来选择重载的模板函数foo
//(注意,两个模板函数仅返回值不同!而模板参数从形式上看虽然相同,但实参推导后T类型是不同的!)

//1. 模板函数的参数相同,返回值不同函数的重载。(注意,实际推导后形参其实是不同的!)
template <class T>
typename std::enable_if<std::is_arithmetic<T>::value>::type  //T为arithmetic类型时,返回值void
foo(T& t) //两个foo函数,模板参数相同。但实际推导后这里是arithmetic类型。
{
    return;
}

template <class T>
typename std::enable_if<std::is_class<T>::value, T>::type&  //T为class时,T&
foo(T& t)
{
    return t;
}

//2. 模板函数的形参相同,返回值相同的函数重载。(注意,实际推导后形参其实是不同的!)
//函数功能:将输入的参数转为string类型
//(对于arithemic类型调用std::to_string(t),对于string类型返回其本身)
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, string>::type  //返回值string
toString(T& t)
{
    return std::to_string(t);
}

template<class T>
typename std::enable_if<std::is_same<T, string>::value, string>::type //返回值
toString(T& t)
{
    return t;
}

class Test{};

/********************************************************************************************/
//3. 可调用对象包装器的实现
//3.1 无返回值的情况:
template<class FT,class...Args>
auto call(FT&& f, Args&&...args)->   //返回值为void
typename std::enable_if<std::is_void<typename std::result_of<FT(Args...)>::type>::value, void>::type
{
    f(std::forward<Args>(args)...);
}

//3.2 有返回值的情况
template<class FT, class...Args>
auto call(FT&& f, Args&&...args)->   //当f有返回值时,则返回f原来的返回类型
typename std::enable_if<!std::is_void<typename std::result_of<FT(Args...)>::type>::value, 
                        typename std::result_of<FT(Args...)>::type>::type
{
    return f(std::forward<Args>(args)...);    
}

//3.3 万能包装器(统一以上两种情况)
template<typename FT, class...Args>
auto FuncWrap(FT&& func, Args&& ...args)->decltype(func(std::forward<Args>(args)...))
{
    return func(std::forward<Args>(args)...);
}

int func(int a, int b)
{
    cout << "int func(int a, int b):" << a + b <<  endl;
    return a + b;
}

int main()
{
    //1. 选择foo重载函数(返回值不同)
    int x = 1;
    foo(x);  //匹配第1个模板,返回void类型
    Test t;
    foo(t);  //匹配第2个模板,返回Test&
    
    //2. 选择toString重载函数(返回值相同)
    cout << toString(x) << endl;
    string s("abc");
    cout << toString(s)<< endl;
    
    //3. 可调用对象包装器
    auto lda = [](){cout << "do anything you want!" << endl;};
    call(lda); //无返回值
    call([](int a){cout << "a = " << a << endl;}, 1);
    call(func, 1, 2); //带返回值
    
    FuncWrap(lda);  //无返回值
    FuncWrap(func, 1, 2); //带返回值
    
    return 0;
}
/*输出结果
e:\Study\C++11\19>g++ -std=c++11 test3.cpp
e:\Study\C++11\19>a.exe
1
abc
do anything you want!
a = 1
int func(int a, int b):3
do anything you want!
int func(int a, int b):3
*/

(4)SFINAE时使用非类型参数

//当使用非类型参数(F)时,错误的使用方式。如下,编译func1时,会出现违反One Definition规则的错误
template<bool F, typename = std::enable_if_t<F>> 
auto func1() -> int { return 0; }
template<bool F, typename = std::enable_if_t<!F>>
auto func1() -> int { return 0; }

//当使用非类型参数(F)时,正确的使用方式。如下,func2正常编译!
template<bool F, std::enable_if_t<F, int> = 0>
auto func2() -> int { return 0; }
template<bool F, std::enable_if_t<!F, int> = 0>
auto func2() -> int { return 0; }

【分析】编译func1时,SFINAE失败只会删除模板参数的默认值

//当F为true时,func1两个模板会被推导为以下两个函数。
template <bool F, typename = void> //具有默认值void
int func1() { return 0; }

template <bool F, typename> // 无默认值,但仍是有效的。
int func1() { return 0; }

    注意:第1个函数模板具有默认值(void),第2个则无默认值。因为默认值并不是函数的签名,因此上面两个函数会出现重定义错误,最终导致编译失败!
【分析】编译func2时,当SFINAE时,会删除一个模板参数(而不是默认值)!因此,也就销毁了该函数,所以并不存在冲突,编译通过!

template <bool F, int = 0>
int func2() { return 0; }

// template<bool F, ...>  // 函数被删除!
// int func2() { return 0; }