模板实参演绎

一、什么是实参演绎

如果我们每次都必须显式的指明模板替换参数类型,例如concat<std::string, int>(s, 3),那么过程将会显得非常繁琐。

如果我们可以concat(s, 3)//之前必须声明s是std::string类型,那么将会向普通的函数调用一样简单,

事实上,C++是允许这样的写法,然后C++编译器会根据实参(s和3)的类型推算出合适的替换类型。

如何演绎?

1 template<typename T>
2 T const& max(T const& a T const& b)
3 {
4     return a < b ? b : a;
5 }
6
7 int g = max(1, 1.0);

上面的演绎会失败,为什么?因为“函数模板对应的参数化类型T”只有一种,但是“调用实参类型”(1和1.0的类型为int型和double型),会导致演绎矛盾,所以失败。

另一种失败的情况是替换的类型导致一个无效的构造。如下:

 1 template<typename T>
 2 typename T::ElementT at(T const& a, int i)
 3 {
 4     return a[i];
 5 }
 6
 7 void f(int* p)
 8 {
 9     int g = at(p, 1);// 这里会演绎失败,因为T被演绎成T*,但是int*没有一个叫做ElementT的替换类型。
10 }

二、灵活的替换规则  

为了方便我们下面的说明,我们来约定

来自调用实参类型 的类型为匹配类型A

来自函数模板对应的参数化类型T 的类型为匹配类型P

(1)如果被声明的参数 是一个带引用的声明(T& 或 T const&),那么匹配类型P(就是T 或 T const), A仍然是实参类型,不会忽略高层次的const和volatile限定符号。

(2)如果被声明的参数 是一个非引用的声明,P就是所声明的参数类型(比如T*),A仍然是实参类型, 同时还会忽略高层次的const和volatile限定符号。

(3)如果被声明的参数 是一个非引用的声明,如果这个实参是数组或者是函数类型(函数指针),那么还会发生decay转换,转换为对应的指针类型,同时还会忽略高层次的const和volatile限定符号。

(4)实参7不能传递给int&

template<typename T> void f(T); //P是 T

template<typename T>void g(T&); //P是T

double x[20];

int const seven = 7;

f(x); //T被演绎成double*,decay转化
g(x);//T被演绎成double[20]
f(seven);//T被演绎成int,忽略了高层的const
g(seven);//T被演绎成int const,不会忽略
f(7);//T被演绎成int
g(7);//T被演绎成int,但是7不能传递给int&

(5)又一个坑:实参为字符串,那么演绎的结果T应该是字符串数组,不是字符串指针char*

template<typename T>
T const& max(T const& a, T const& b);

max("Apple", "Pear");“Apple”的类型是char const[6], “Pear”的类型是char const[5];而且不存在数组到指针的decay转型(因为演绎的参数是引用参数)。因此为了演绎成功,T就同时必须得是char[6]和char[5]。这显然是不可能的,所以产生错误。

三、练手

template<typename T>
void f1(T*);

template<typename E, int N>
void f2(E(&)[N]);

template<typename T1, typename T2, typename T3>
void f3(T1(T2::*)(T3*));

class S{
public:
    void f(double*);
};

void g(int ***ppp)
{
    bool b[42];
    f1(ppp); //演绎T为int***
    f2(b);     //演绎E为bool, N为42
    f3(&S::f); //演绎T1为void, T2为S,T3为double
}

演绎上下文的过程:匹配从最顶层开始,然后不断的递归各种组成元素。

然而有两类构造不能用于演绎上下文。

(1)受限的类型名称,Q<T>::X的类型名称不能用来演绎模板参数T

(2)除了非类型参数外,模板参数还包含其他的成分的非类型表达式,诸如S<I+1>,int(&)[sizeof(S<T>)]类型不能用来演绎I和T

为什么?因为演绎过程并不唯一(甚至不一定有限的情况内演绎完毕)。

template<int N>
class X{
public:
    typedef int I;
    void f(int){}
};

现在如果用 一个int 演绎typename X<N>::I ,那么是不成功的,为什么因为我可以有很多X<1>::i,X<2>::I, X<3>::I …… X<1000>::I,表示,所以C++禁止这种演绎。

template<int N>
void fppm(void (X<N>::*p)(typename X<N>::I));

int main()
{
    fppm(&X<33>::f);//可以演绎成功,N=33
}

在函数模板fppm()中,子构造X<N>::I不是一个可以演绎的上下文,但是可以通过成员指针类型(X<N>::*p)的成员部分X<N>演绎上下文。

四、特殊情况的演绎

存在两种情况,其中演绎实参-参数对(A,P)并不是分别来自函数调用的实参,函数模板参数。

template<typename T>
void f(T, T);

void (*pf)(char, char) = &f;

(1)如上,第一种情况是取函数模板的地址的时候, A就是void(char, char), P就是void(T, T),

T被演绎成char,同时pf被初始化为“特化f<char>“的地址

------------------------我是华丽的分割线╮( ̄▽ ̄")╭------------------------------------

class S{
public:
    template<typename T, int N> operator T[N]&(); //我是转型运算符,转换成T[N]类型
};

void f(int(&)[20]); //参数的类型是20个元的数组

void g(S s)
{
    f(s);
}

我们试图把S转换成int(&)[20];因此类型A是int(&)[20], 类型P为T[N],于是用int替换T,用20替换N。演绎类型就是成功的。

五、再谈灵活的替换规则

(1)对于模板参数-实参对(P, A),有两种情况下,P可以比A多一个const或volatile限定符。

①原来声明的参数是一个引用参数子,那么P类型可以比A类型多出一个const或volatile

②原来的声明参数是不是一个引用参数子没关系,A类型是指针或成员指针类型,那么P类型也可以比A类型多出一个const或volatile。

(2)当演绎过程不涉及转型运算符模板时,被替换的P类型可以是A类型的基类,或者是当A是指针类型时,P可以是一个指针类型,P指向的类型是A所指向类型的基类, 只有在不精确匹配情况下才会出现这中宽松匹配。

template<typename T>
class B{};

template<typename T>
class D:public B<T>{
};

template<typename T>
void f(B<T>*);

void g(D<long> dl)
{
    f(&dl); //成功,用long替换T
}

六、警告:类模板参数不能用于实参演绎

 

七、警告:函数模板的缺省实参不能用于实参演绎,即使实参不是依赖型的实参

template<typename T>
void f(T x = 42)
{}

int main()
{
   f<int>();  //正确,实例化
   f();//错误,不能用于实参演绎
}

八、一个小技巧 Barton-Nackman方法

当时这种方法被创建出来基于以下几个原因:

(1)当时函数模板不能被重载

(2)运算符==如果重载在类模板里面那么,根据上面的那些灵活的转换方式(指向基类,指向子类之云云),第一个实参(this指针指向),第二个实参的转型规则可能不一样。

现在定义一个模板类Object,那如果要定义这个类的operator ==,那么这个operator==不能定义在类内部(根据(2)),

也不能定义在全局或类之外的命名空间,如template<typename T> bool operator ==(Array<T> const& a, Array<T> const& b){……},(根据(1))

Barton和Nackman将这个运算符作为类的普通友元函数定义在类的内部。如下

#include <iostream>
using namespace std;

template<typename T>
class Object{
    public:
    int a;
    Object(int n):a(n){}
    friend bool operator == (Object<T> const&lt, Object<T> const& rt)
    {
        return equal(lt, rt);    //根据参数类型调用重载的函数
    }
};

bool equal(Object<int> const& lt, Object<int> const& rt) //这是一个普通函数,可以被随便重载
{
    return lt.a == rt.a;
}

int main()
{
    Object<int> s(1);
    Object<int> s1(1);
    cout << (s == s1) << endl;
    return 0;
}

最后顺利编译通过,运行成功。

注:这些代码的运行环境都是在mingw下进行的,VS2013估计自己重新实现了模板名字查找,很多书上说名称找不到的情况,VS2013都找得到(-__-)b,所以为了更好的学习《C++Templates》转投MinGW,编辑器是codeblocks。

编辑整理:Claruarius,转载请注明出处。

时间: 2024-08-28 18:32:07

模板实参演绎的相关文章

C++ template —— 实例化和模板实参演绎(四)

本篇讲解实例化和模板实参演绎------------------------------------------------------------------------------------------------------------第10章 实例化------------------------------------------------------------------------------------------------------------模板实例化是一个过程,它

template_11实参演绎

1,演绎过程匹配类型A(来自实参的类型),参数化类型P(行参参数声明)如果被声明的参数是一个引用声明g(T& )那么P就是所引用类型T:f(T)中P就是所声明的参数类: decay指从数组和函数类型隐式转换为指针类型.如果实参的类型是数组或函数类型,则会发生decay,此时还会忽略高层次的const和volatile限定符. template <class T>T const& max(T const& a, T const& b) T被要求同时是char[7]

C++模板:实参演绎

在C++函数模板中,实参演绎是一非常种灵活的机制,实参演绎的字面意思就是:通过实参的类型来推断函数模板的类型参数,举个简单的函数模板的例子: template<typename T> T& func1(T &a, T &b) { cout<<typeid(T).name()<<endl; } 因为有了实参演绎,我们可以像使用普通函数一样来使用模板函数,如下: int a,b; func1(a,b); 也就是说,虽然我们没有为func指定T,编译器

C++模板实参推断

1 类型转换与模板实参 1)自动转换的只有:const转换, 数组及函数到指针的转换 注:不同大小相同元素类型是不同的类型 2)相同模板参数名对应的实参类型必须相同 3)不同模板参数名对应的实参类型可以不同,但必须兼容 2 函数模板的返回值问题 函数模板只会对函数参数列表的类型进行推断不会对返回值推断 解决方法: 1) 显示模板参数 注: 显示指定了模板类型参数在类型转换上和普通函数参数一样 template <typename T1, typename T2, typename T3> T1

[014]模板-模板实参推导

对于函数模板,编译器利用调用中的函数实参来确定其函数模板,从函数实参来确定模板实参的过程就被叫做是模板实参推导. 比如: 1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 template <class T> 6 int compare(const T &v1, const T &v2) { 7 if (v1 > v2) { 8 cout << &quo

C++ primer-&gt;16.2 模板实参推断

一.类型转换与模板类型参数 1.如果一个函数形参的类型使用了模板类型参数,那么它采用特殊的初始化规则.只有很有限的几种类型转换会自动地应用于这些实参. ①.顶层const无论是在形参中还是在实参中,都会被忽略. ②.const转换:可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参. ③.数组或函数指针转换:如果函数形参不是引用类型,则可以将对数组或函数类型的实参应用于正常的指针转换. 如下程序所示: 1 template<typename T> T fobj(

【C++ Primer 第16章】2. 模板实参推断

模板实参推断:对于函数模板,编译器利用调用中的函数实参来确定模板参数,从函数实参来确定模板参数的过程被称为模板实参推断. 类型转换与模板类型参数 与往常一样,顶层const无论在形参中还是在是实参中,都被会忽略. • const转换:可以将一个非const对象的引用(或指针)传递给const的引用(或指针)形参. • 数组或函数指针转换:一个数组实参可以转换为一个指向其首元素的指针.类似的,一个函数实参可以抓转换一个该函数类型的指针. 1 template <typename T> T fob

模板与泛型编程——模板实参推断

一.模板实参推断 对于函数模板,编译器利用调用中的函数实参来确定其模板参数.从函数实参来确定模板实参的过程被称为模板实参推断.在模板实参推断过程中,编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数与给定的函数调用最为匹配. 1.类型转换与模板类型参数 与非模板函数一样,我们在一次调用中传递给函数模板的实参被用来初始化函数的形参.如果一个函数形参的类型使用了模板类型参数,那么它采用特殊的初始化规则.只有很有限的几种类型转换会自动地应用于这些实参.编译器通常不是对实参进行类型转

C++11 局部和匿名类型作模板实参

[1]C++11支持局部的类型和匿名类型做模板的实参 在C++98中,标准对模板实参的类型还有一些限制. 具体地讲,局部的类型和匿名的类型在C++98中都不能做模板类的实参. 而在C++11标准中,支持做模板的实参.示例如下: 1 template<typename T> class X {}; 2 template<typename T> void TempFun(T t) {}; 3 4 struct A { } a; 5 struct { int i; } b; // b是匿