Template parameter deduction

演绎的过程

关于模板演绎的一个示例:

#include<iostream>
using namespace std;

template<typename T>
typename T::type FUN(T*  t)
{
	return t[5];
}

void main()
{
	int * p=0;
	int x = FUN(p);
}

上面的示例是错误的,错误原因为:

1    error C2893: 未能使函数模板“T::type FUN(T *)”专用化 (补充:找不到可以满足的模板进行实例化) 
2    IntelliSense:  没有与参数列表匹配的 函数模板 "FUN" 实例
            参数类型为:  (int *)    

因为函数模板的返回类型T::type,只有T类型提供了成员类型type的时候,此函数模板才能被匹配成功,显然int *不能将此模板特化,然后FUN(p)就在别处找满足模板实参int*类型的FUN模板,最终没有找到,于是报错——找不到可以通过实参演绎进行特化(这里的特化指的是实例化,和全局特化/局部特化不是同一个概念,此文中后续的"特化"一般都是这个意思)的模板!

由此可以看出实参演绎的特化过程是:找那些能满足实参演绎的模板进行特化,如果没有满足的模板,则报告找不到!

我们再看一个示例和上面的示例作对比,看看显式特化是怎么实例化模板的:

#include<iostream>
using namespace std;

template<typename T>
typename T::type FUN(T*  t)
{
	return t[5];
}

void main()
{
	int * p=0;
	int x = FUN<int *>(p);
}

上述示例的错误原因为:

1    error C2770: “T::type FUN(T *)”的显式 模板 参数无效   
2    IntelliSense:  没有与参数列表匹配的 函数模板 "FUN" 实例
            参数类型为:  (int *)   
从这里可以一窥显式特化和演绎之间的不同了:显式特化是对现存最可以满足的模板进行特化,如果不能对此模板进行特化(模板参数不匹配),则报模板实参无效的错误!

 

演绎、decay、&

decay(数组或函数到指针的转型)

一个示例:

#include<iostream>
using namespace std;

template<typename T>
void f(T);

template<typename T>
void g(T &);

template<typename T>
void h(T const &, T const & );

double x[20];

int const seven = 7;

void main()
{
	f(x);//发生decay
	g(x);//引用不会decay
	f(seven);//去顶层const
	g(seven);//不会发生去顶层const
	f(7);//此时将去掉顶层cont——7的类型为int const
	//g(7);//error:int &和int const &不匹配(其实是无法找到可以支持实参演绎的模板)


	h("aa","bb");
	//h("aa", "bbb");//error:char const [2]和char const [3]不是同一模板实参
}

由示例总结两条演绎规则:

1,函数模参是引用:不会去掉实参的顶层const类型、不会发生decay

2,函数模参是值:会去掉实参的顶层const类型、会发生decay

(一般参数传递如果函数形参类型不是pointer type或者reference type,形参对于实参的顶层const特性一般都会去掉,原因:如果是值拷贝,那么形参有一个和实参完全独立的拷贝,此时形参的修改不会影响到实参!但是如果是底层const的话不会去掉,原因很simple:一般只有指针和引用具有底层const(引用的底层const是默认的),以指针或者引用传递参数,将受到客户端对所引用内存的访问权的强制指定!)

 

演绎上下文

一个稍显复杂的例子:

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

class X{
public:	
	string(&fun(double(& arr)[10]))[5]{
		
		int i = 0;
		for (auto & elem : arr)
			elem=++i;
	
		static string arrString[5] = {"a","b","c","d","e"};
		return arrString;
	}
};

class C{
public:
	double(&fun(string(&arr)[10]))[5]{
		int i = 0;
		string str = "number-";
		for (auto & elem : arr)
			elem = str + to_string(++i);
		static double arrDouble[5] = { 1,2,3,4,5 };
		return arrDouble;
	}
};

template<int N,int M, typename T1, typename T2, typename T3>
void FUN(T1(&(T2:: *pf)(T3(&)[M]))[N],T2 & object){
	static T3 arr[10];
	int i = 0;
	for (auto & elem : (object.*pf)(arr)){
		cout << arr[i++] << " "<<elem << " " << endl;
	}
}

void main(){
	X x;
	FUN(&X::fun,x);

	C c;
	FUN(&C::fun, c);
}

FUN<5>(&X::fun)这句话中用来演绎的实参的类型是:类X中的一个函数指针,函数的返回类型是数组,函数的参数类型是数组,这个类型包括这几部分:类X基本类型,类X的成员指针类型、函数类型、两个数组类型,匹配模板实参的过程是:从最顶层的构造开始,不断递归各种组成元素!例如:先演绎出X类型,再演绎出X的成员函数指针类型、再演绎出数组类型。。。

 

 

演绎的上下文:类型声明构造(例如:类X基本类型,类X的成员指针类型、函数类型、两个数组类型)

哪些演绎上下文不能用来演绎:

1,受限的类型名称:例如:X<T>::type其中type是受限的类型名!

例子:

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

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

template<typename T>
void FUN(typename X<T>::type i)
{
	cout << i << endl;
}

void main()
{
	//演绎
	//FUN(5);//error

	//显式特化
	FUN<int>(5);
}

失败的原因在于,5是一个int类型,而要靠int类型来演绎出X<T>::type中的T明显不可能!(况且type根本就是一个未知物——局部特化可以使其不存在)

——这种错误属于由一个基本类型推断出一个泛化类型!——Compiler will say NO!

 

2,除了非类型参数以外,模板参数还包括其他成分的非类型表达式:例如:X<N+1>

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

template<int N>
class X
{
public:
	typedef int type;
};

template<int N>
void FUN(X<N +1>);

void main()
{
	FUN(X<4>());
}

错误原因:

1    error C2783: “void FUN(X<N+1>)”: 未能为“N”推导 模板 参数  
假设上例能成功,X<4>类型和X<N+1>类型匹配,最终两者类型必须都是X<4>(因为形参实参类型相匹配才能演绎),那么按理N就会是3,但问题是:编译器怎么知道N的类型要4的基础上减去1呢?编译器无法知道:因为<>内是N+1,所以最后的N就是N+1-1!(虽然我们知道!)

 

演绎过程不唯一——怎样演绎出受限的类型名称!

一个例子:

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

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

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


void main()
{
	fppm(&X<33>::f);
}

示意图:

总结一句话:低级演绎上下文的模板参数在高级演绎上下文中已经被推倒出来!

 

另外,我们试图从低演绎上下文中推断出高演绎上下文,结果失败!

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

template<int N>
class X;

template<int N>
class C
{
public:
	void f(C<N>);
};

template<int N>
class X
{
public:
	typedef C<N> type;
};

template<int N>
void fppm(void (typename X<N>::type:: *)(C<N>));

//void(X<4>::type:: )(C<4>)

void main()
{
	fppm(&(X<4>::type::f));
}

其中C<N>属于低级演绎上下文,X<N>::type属于高级演绎上下文,X<N>::type的N想通过C<N>演绎出来结果失败!

 

两种特殊的演绎:

1,赋值时演绎

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

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

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

void main()
{
}

此处void(*pf)(char, char) = &f需要一个void()(char, char)类型的函数,所以模板的T被演绎出了char!

2,转型时演绎

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

class S
{
public:
	template<typename T>
	operator T&();
};

void f(int &);

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

此例中s对象在匹配f函数参数的时候,将自己转型为in&,此时发生了演绎以支持转型操作!

 

演绎中几种可接受的类型转型

 

1,当模板声明参数是引用参数子

#include<iostream>
using namespace std;

template<typename T>
void FUN(T & a)
{
	++a;
}

void main()
{
	int a = 0;
	FUN(a);
	cout <<a << endl;
}

2,当模板实参是指针,那么模板声明参数可以加const/volatile(只能是顶层const支持转型

#include<iostream>
using namespace std;


//顶层const可以
template<typename T>
void FUN(T * const a)
{
	++(*a);
}

//底层const不行
template<typename T>
void FUN(const T * a)
{
	a = 0x11;
}

//用于验证对于底层const普通参数转型可以但是泛型转型不支持
void fun(const int const  * a)
{
}

void main()
{
	int a = 0;
	FUN(&a);
	fun(&a);
	cout << a << endl;
}

3,派生类的引用、指针可以向基类转型

#include<iostream>
using namespace std;

template<typename T>
class Base
{
};

template<typename T>
class Drived :public Base<T>
{
};

template<typename T>
void FUN(Base<T> *);

template<typename T>
void FUN(Base<T> &);

void main()
{
	FUN(&Drived<int>());
	FUN(Drived<int>());
}

注意:上面必须提供类模板的定义(涉及到转型、创建对象必须看到类的定义!具体细节这篇文章开头就有论述《模板的实例化剖析》)

 

演绎不适用于类模板!

#include<iostream>
using namespace std;

template<typename T>
class X
{
public:
	X(T b) :a(b){}
private:
	T a;
};

void main()
{
	X(10);
}

上面示例想通过X的构造函数演绎模板参数T,失败!

 

演绎与缺省调用实参

——可以有缺省调用实参,并且缺省调用实参可以依赖于模板参数

#include<iostream>
using namespace std;

class X
{
public:
	X(int, int);//1
};

template<typename T>
void FUN(T a=T()){//2
}

void main()
{
	FUN(X(2,2));
}

编译器在编译模板的时候,有这样一条策略:假定缺省实参不会被使用(对于该函数缺省实参可能会导致的语义错误,编译器假定该缺省实参不会被用到,可在本文中详细了解:《模板的实例化剖析 》)(上面示例中的1、2位置可以验证这条策略!)

 

不能靠缺省调用实参演绎模板参数

#include<iostream>
using namespace std;

template<typename T>
void FUN(T a=2){//2
}

void main()
{
	//FUN();//error
	FUN(3);
}

上例试图使用3缺省调用实参演绎T,但是错误!

 

 

Barton-Nackman方法

Barton-Nackman面临的问题:想为两个类模板定义自己的operator ==,但是在不支持函数模板重载的当时,不能定义两个函数名都是operator==但是参数不一样的函数模板

Barton-Nackman的解决办法:利用边缘实例化、ADL查找、友元

示例:

#include<iostream>
using namespace std;

//用于处理X的成员是否相等的函数
template<typename T>
bool ProcessForX(T & t1,T & t2){
	return  t1.GetFirst() == t2.GetFirst()&& t1.GetSecond() == t2.GetSecond();
}

template<typename T>
class X
{
public:
	X(T t1, T t2) :a(t1), b(t2){}

	//限制的模板扩展
	friend bool operator == (X<T> & x1,X<T> & x2){
		return ProcessForX(x1, x2);
	}

	T const & GetFirst(){return a;}
	T const & GetSecond(){return b;}
private:
	T a;
	T b;
};

void main()
{
	X<int> x1(2, 2);
	X<int> x2(2, 2);

	if (x1 == x2)
		cout << "x1==x2" << endl;
	else
		cout << "x1!=x2" << endl;
}

特点:

1,此时友元函数operator == 不是函数模板,可以被重载

2,operator == 是内联函数,等于直接调用ProcessForX(ProcessForX是X类模板相等的外部实现)

缺点:

上述友元函数operator ==被在类模板中声明了,该友元函数在外部可见与否要视情况而定(是否发生ADL,详情在《模板的实例化剖析 》)

缺点示例:

#include<iostream>
using namespace std;

//用于处理X的成员是否相等的函数
template<typename T>
bool ProcessForX(T & t1,T & t2){
	return  t1.GetA() == t2.GetA();
}

class C{
};

template<typename T>
class X
{
public:
	X(T t1) :a(t1){}

	//限制的模板扩展
	friend bool operator == (X<T> & x1,X<T> & x2){
		return ProcessForX(x1, x2);
	}

	T const & GetA(){return a;}
private:
	T a;
};

void main()
{
	C c1,c2;
	X<C> x1(c1);
	X<C> x2(c2);

	if (x1 == x2)
		cout << "x1==x2" << endl;
	else
		cout << "x1!=x2" << endl;
	/*
	错误	1	error C2678: 二进制“==”: 没有找到接受“const C”类型的左操作数的运算符(或没有可接受的转换)	
	*/
}

只有当operator == 函数参数是和X类相关的时候,才能在X内的内部找到operator == 函数,但是C类型的参数不是和X相关(你可能以为C可以转型为X所以C和X还是相关的,但是如果这也算相关,那么还有什么类型不是和X相关呢?),所以根本找不到operator == 函数(虽然C类型可以匹配operator == 的形参X<C> & 类型)!

 

Barton-Nackman面临问题的现今解决之道(利用函数模板重载):

#include<iostream>
#include<vector>
#include<list>
using namespace std;

//为两个类模板设定的==操作符
template<typename T>
bool operator==(vector<T> & v1, vector<T> & v2)
{
	cout << "the operator == for vector!" << endl;
	return true;
}

template<typename T>
bool operator==(list<T> & l1, list<T> & l2)
{
	cout << "the operator == for list!" << endl;
	return true;
}

void main()
{
	vector<int> v1{ 1, 2, 3 };
	vector<int> v2{ 1, 2, 3 };
	list<int>l1{ 1, 2, 3 };
	list<int>l2{ 1, 2, 3 };
	v1 == v2;
	l1 == l2;

	/*
	输出:
	the operator == for vector!
	the operator == for list!
	*/
}

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
模板参数推导(template argument deduction)是C++11中引入的一个新特性,它可以自动推导模板参数,避免手动指定模板参数。 在使用模板函数或模板类时,通常需要手动指定模板参数,例如: ``` template<typename T> void foo(T t); int main() { foo<int>(1); // 指定模板参数为int return 0; } ``` 在上面的代码中,我们调用了模板函数`foo`,并手动指定了模板参数为int。这种方式比较繁琐,容易出错。 模板参数推导可以自动推导模板参数,避免手动指定模板参数。例如: ``` template<typename T> void foo(T t); int main() { foo(1); // 自动推导模板参数为int return 0; } ``` 在上面的代码中,我们调用了模板函数`foo`,并没有手动指定模板参数,编译器会自动推导模板参数为int。 模板参数推导可以用于函数模板和类模板的实例化过程中。它可以根据函数参数或对象成员的类型,自动推导出模板参数的类型。例如: ``` template<typename T, int N> class Array { public: T data[N]; }; int main() { Array arr{1, 2, 3}; // 自动推导模板参数为int和3 return 0; } ``` 在上面的代码中,我们定义了一个类模板`Array`,它有两个模板参数,一个是类型参数T,一个是整型参数N。在实例化`Array`对象时,我们只传入了一个参数{1, 2, 3},编译器会自动推导模板参数为int和3。 需要注意的是,模板参数推导只能用于函数模板和类模板的实例化过程中,不能用于模板定义中。在模板定义中,必须手动指定模板参数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值