C++模板推断函数类型

    借助模板特例化和偏特化可以完成函数返回类型、输入参数类型的推断。我们直接步入主题:

    首先预热编译器对偏特化的类型匹配。C++模板库中,std::is_same可判断两个类型是否相同,这里我们可以借助偏特化自己实现一个is_same模板:

template<typename T,typename U>
class is_same
{
public:
    static constexpr bool value = false;
};

template<typename T>  //偏特化
class is_same<T,T>
{
public:
    static constexpr bool value = true;
};

       利用编译器对模板类型匹配的特点,如果两个模板参数的类型相同,则实例化偏特化模板,其value成员为true;反之实例化一般模板,其value成员为false。

      下面是另一个模板类型匹配的例子:

template<typename T>
class GG
{
public:
    typedef T type;
};

template<typename T,typename U> //偏特化
class GG<T(U)>
{
public:
    typedef T return_type;
    typedef U arg_type;
};

int main()
{
    GG<int(int)>::return_type d; //is int
    GG<int(int)>::arg_type f; //is int
}

      这里使用了两个类型T和U构建了函数类型T(U),以此作为模板GG的偏特化类型。编译器实例化GG后,可以得到T和U的类型。

     C++模板可表示三种可执行类型,其依次为:

     1.  T (*)(U...)    函数指针;

     2.  T(&)(U...)    函数引用;

     3.  T(U...)         函数类型。表示函数,常用于tr1::function、bind等模板的类型表示。

      通过上述形式的偏特化模板,我们可以推断函数指针的返回类型:

template<typename T>
class GG
{
public:
    typedef T type;
};

template<typename T,typename ...U> //偏特化
class GG<T(*)(U...)>
{
public:
    typedef T(&pFunc)(U...);
    GG(pFunc p):g(p){}
    typedef T return_type; //函数返回类型
    pFunc g;
};

typedef int(*p)(int,int);

int getcs(int s, int f)
{
    cout<<s<<f<<endl;
    return 0;
}

GG<p> v(getcs);

        对于函数的输入参数类型,借助偏特化和模板实例化递归可以推断:

template<signed A,typename ...T>
class GETARG
{
};

template<signed A,typename T,typename ...U>
class GETARG<A,T,U...>
{
public:
    typedef typename GETARG<A-1,U...>::type type; //递归实例化直至A==0
    //每递归一次,剥离一个参数类型用T表示
};

template<typename T,typename ...U>  //设置递归结束
class GETARG<0,T,U...>
{
public:
    typedef T type;
};


template<typename T>
class GG
{
public:
    typedef T type;
};

template<typename T,typename ...U> //偏特化类型匹配
class GG<T(*)(U...)>
{
public:
    typedef T return_type;
    template<signed A> //以此获得输入参数的类型
    class ARG
    {
    public:
        typedef typename GETARG<A,U...>::type type;
    };
};

GG<int(*)(int,double)>::ARG<0>::type //is int
GG<int(*)(int,double)>::ARG<1>::type //is double

       如需获得可变类型参数的个数,利用模板实例化递归可以完成:

1. 传统实现方式,使用static常量或enum类型:

template<typename T, typename ...U>
class GETNUM 
{
public:
    static constexpr int num = GETNUM<U...>::num+1; //递归实例化
};

template<typename T>
class GETNUM<T>
{
public:
    static constexpr int num = 1;
};

template<typename ...U>
class NUM
{
public:
    static constexpr int num = GETNUM<U...>::num; //参数个数
};

这种方式占用内存可能较大(与编译优化有关),每递归一层都要生成一个GETNUM类型,每个类型都存在一个static常量。

2. 改进的实现方式,减少static常量个数:

template<signed A, typename T, typename ...U>
class GETNUM : public GETNUM<A+1,U...>
{
};

template<signed A,typename T>
class GETNUM<A,T>
{
public:
    static constexpr int num = A; //num为参数个数,此处为最上层父类
};

template<typename ...U>
class NUM
{
public:
    static constexpr int num = GETNUM<1,U...>::num; //参数个数
};

通过继承的方式完成递归实例化,GETNUM只存在一个static常量。


题外话:

       C++模板编译时进行的Two-phase name lookup过程(第一次匹配模板内部非Dependent 名称,在模板实例化后再匹配Dependent 名称),要求第一次检查模板即划分Dependent名称和非Dependent 名称,再确定所有非Dependent名称的使用是否合法。经验而言,第一次模板检查,必须确定所有涉及的名称是否为指针、引用、函数、class对象;如果编译器在检查名称时无法推断是否为指针、引用、函数、class对象,则需手工添加typename、template标识,否则模板检查出错。Dependent types依赖模板参数T可能产生多种形式(指针、引用、函数、class对象),阻碍名称检查;非Dependent types根据模板参数T只能产生固定的形式,不阻碍名称检查。

        函数类型作为模板参数时,模板内只能间接使用该类型,如进行函数声明、作为函数指针或函数引用,不能用作创建函数对象。函数声明需注意Dependent types的影响:

template<typename T>
class HH
{
    T *pFunc;  //定义pFunc为T指针
    T &func;   //定义func为T引用
    //T foo;   //实例化时报错,提示声明的foo函数类型无效。
    //friend T foo1; //模板检查时报错, foo1不是函数或类成员函数
    typedef T (FUNC)(T);
    FUNC foo2; //声明成员函数foo2
    friend FUNC foo3; //声明友元函数
public:
    HH(T &a):pFunc(nullptr),func(a){}
};

int get(int a)
{
    cout<<a<<endl;
    return 0;
}
HH<int(int)> obj(get);
//或如下表示
typedef int(FUNC)(int); //表示函数类型,等同于模板类型int(int)
FUNC get; //声明get函数
FUNC *p = get;
FUNC &q = get;

T foo错误原因是C++标准的如下规定:

取自《Information technology — Programming  languages — C++》ISO/IEC 14882:2011,C++14大体没有变化。

也可参考stackoverflow上关于Dependent 名称的深入讨论:https://stackoverflow.com/questions/34696351/template-dependent-typename

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值