一、使用 B o o s t Boost Boost库来借助推断
通常,我们可以使用 t y p e i d ( ) typeid() typeid()来推断一个类型,但是有时候 t y p e i d typeid typeid不够准确,因此,我们借助 B o o s t Boost Boost库里面的 t y p e _ i d _ w i t h _ c v r < > type\_id\_with\_cvr<> type_id_with_cvr<>来帮助推断。
关于 B o o s t Boost Boost库的安装可以参考这篇文章:Boost库的安装
安装、配置完成后,写入以下代码,编译成功说明配置正确了。
#include<iostream>
#include<boost/type_index.hpp>
template<typename T>
void myfunc1(const T& tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
}
二、函数模板类型推断
2.1 引用类型
如果参数类型时引用,但不是万能引用时,我们看下面的推断代码:
2.1.1 普通引用类型
//引用类型
template<typename T>
void myfunc2(T& tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
}
void Test2() {
int i = 18; //T = int , tmprv = int &
const int j = i;//T = int const, tmprv = const int&
const int& k = i;//T = int const, tmprv = const int&
myfunc2(i);
myfunc2(j);
myfunc2(k);
}
推断结果如下:
2.1.2 c o n s t const const修饰的引用类型
如果形参上带有 c o n s t const const,并且有引用类型呢,我们看下面的代码:
template<typename T>
void myfunc3(const T& tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
}
void Test3() {
int i = 18; //T = int, tmprv = const int &
const int j = i;//T = int , tmprv = const int&
const int& k = i;//T = int , tmprv = const int&
myfunc3(i);
myfunc3(j);
myfunc3(k);
}
推断结果如下:
2.2.3 总结和分析
通过简单分析我们发现:
(
1
)
(1)
(1)带有引用类型的形参传入后,引用类型会被忽略。
(
2
)
(2)
(2)无论形参是否带有
c
o
n
s
t
const
const限定,只有实参具有
c
o
n
s
t
const
const限定,那么推导出来的形参
t
m
p
r
v
tmprv
tmprv就一定具有
c
o
n
s
t
const
const限定
通过上面的范例,总结一下编码技巧。
&①&形参中的引用有两个作用:可以通过对形参的修改来修改实参﹔传递引用比传值效率更高。试想,如果实参是一个对象,那么形参是引用类型可以避免对象的拷贝构造。所以,一般来说,函数模板中的形参建议优先考虑“ T & t m p r v T\& \ tmprv T& tmprv”这样的形态,就不怕实参中的引用被忽略掉而导致开发者想通过对形参的修改达到修改实参的本意无法达成。
② ② ②如果既想享受形参为引用带来的效率上的提高,又不希望通过形参修改实参,则函数模板中的形参建议考虑“ c o n s t T & const \ T\& const T&”这样的形态。
3.万能引用类型
我们知道,万能引用类型可以接受左值也能接受右值,那么我们现在就看看万能引用中参数的推断情况吧,参考下方代码:
//万能引用类型
template<typename T>
void myfunc5(T&& tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
}
void Test5() {
int i = 10; //T = int, tmprv = int&
const int j = i;// T = int const, tmprv = int const&
const int& k = i;//T = int const& , tmprv = int const&
myfunc5(i);
myfunc5(j);
myfunc5(k);
myfunc5(100);//T = int, tmprv = int&&
int&& x = 10;
myfunc5(x);
}
参考推断结果:
可以发现,万能引用可以正确推导出左值和右值的类型,同样会保留形参的
c
o
n
s
t
const
const修饰。
注意这里
m
y
f
u
n
c
(
x
)
myfunc(x)
myfunc(x)传入的右值引用是左值,因此,推导为
t
m
p
r
v
=
i
n
t
&
tmprv = int\&
tmprv=int&是没问题的。
4.指针类型
指针类型与引用类型相似,但有细微差异。
参考下方代码:
//指针类型
template<typename T>
void myfunc4(T* tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
}
void Test4() {
int i = 18;
const int* pi = &i;
myfunc4(&i); //T = int ,tmprv = int*
myfunc4(pi);//T = int const, tmprv = int const*
}
同样是实参存在 c o n s t const const限定,则推断出的形参一定有 c o n s t const const限定
5. 传值类型
5.1 一般传值方法
形参不带任何修饰
(
c
o
n
s
t
,
∗
,
&
(const,*,\&
(const,∗,&…),那么就称为传值类型:
参考下方代码:
void Test6() {
int i = 18; //T =int , tmprv = int
const int j = i;//T = int, tmprv = int;
const int& k = i; //T = int, tmprv = int;
myfunc6(i);
myfunc6(j);
myfunc6(k);
}
推断结果:
可以发现:
(
1
)
(1)
(1)如果实参具有
c
o
n
s
t
const
const限定,那么
c
o
n
s
t
const
const限定会被忽略。
( 2 ) (2) (2)如果实参为引用,那么引用也会被忽略。
5.2 显式调用模板
不过,如果使用显式的调用模板,可以推导出 c o n s t const const限定或 & \& &引用类型,当然一般情况下,不推荐这样写:
//显式指定推导为引用
myfunc6<const int&>(k); //T = int const&, tmprv = int const&
int& m = i;
myfunc6<int&>(m);//T = int&, tmprv = int&
推断结果:
5.3 传入指针
当我们向传值模板中传入常量指针,那么会有一个细节,参考下方代码:
//如果是指针
char mystr[] = "I love C++ !";
const char* p = mystr; //T = char const* , tmprv = char const*
myfunc6(p);
在模板中加入以下语句:
//增加修改语句
tmprv = 0;
*tmprv = 'Y'; //指针指向的内容不可被修改
其中 t m p r v = 0 tmprv = 0 tmprv=0合法,而 ∗ t m p r v = ′ Y ′ *tmprv ='Y' ∗tmprv=′Y′非法,我们可以看看下面的推断结果:
可以发现,指针的限定
c
o
n
s
t
const
const被忽略了,但是指针指向的数组的常量性会被保留,这是其特殊的一面。
5.4 传值的引申
如果我们想在传值方法中传入引用或者
c
o
n
s
t
const
const限定,除了显式的调用模板(不推荐)
,我们也可以使用标准库中的
s
t
d
:
:
r
e
f
std::ref
std::ref和
s
t
d
:
:
c
r
e
f
std::cref
std::cref,分别对应了
&
\&
&和
c
o
n
s
t
&
const \ \&
const &。
具体来说,模板实际上是被推断为 s t d : : r e f e r e n c e _ w r a p p e r < > std::reference\_wrapper<> std::reference_wrapper<>类型和其的一个 c o n s t const const限定版本,而 s t d : : r e f std::ref std::ref可以隐式地转换为 & \& &类型。
参考下方代码:
//传值对象的引申
template<typename T>
void myfunc7(T tmprv) {
std::cout << "-----------------begin-----------------\n";
using boost::typeindex::type_id_with_cvr;
std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << "\n";
std::cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << "\n";
std::cout << "-----------------end-----------------\n";
//tmprv = 12; //错误,std::reference_wrapper<T>没有赋值运算符
int& tmpvaluec = tmprv; //正确,隐式转换为引用类型
tmpvaluec = 0; //修改tmprv
}
void Test7() {
int i = 100;
myfunc7(std::ref(i)); //推导为std::reference_wrapper<int>类型
//T = class std::reference_wrapper<int>
tmprv = class std::reference_wrapper<int>
std::cout << i << "\n";
//无法被修改
const int j = 100;
myfunc7(std::cref(j));//推导为std::reference_wrapper<const int>类型
//T = class std::reference_wrapper<int const >
tmprv = class std::reference_wrapper<int const >
std::cout << j << "\n";
}
值得注意的是, s t d : : r e f e r e n c e _ w r a p p e r < i n t > std::reference\_wrapper<int> std::reference_wrapper<int>类内没有重载 o p e r a t o r = operator = operator=,因此无法被显式地赋值,但是可以使用 i n t & int\& int&来隐式地转换,然后修改 i n t & int\& int&的值,从而达到修改的目的。
同样的,通常 s t d : : r e f std::ref std::ref包装的类型通常为值,如 i n t , l o n g , d o u b l e int,long,double int,long,double等没有修饰的类型,而 s t d : : c r e f std::cref std::cref包装的类型为 c o n s t i n t , c o n s t l o n g const \ int, const\ long const int,const long等含有 c o n s t const const限定的类型。
6.函数名作为实参
这里讨论的函数名是全局函数,即函数指针,而如匿名函数、成员函数等暂时不讨论。
参考下方代码:
//函数名作为实参
void testFunc() {}
void Test9() {
myfunc8(testFunc); //传值
//T = void (__cdecl*)(void)
//tmprv = void(__cdecl*)(void)
myfunc2(testFunc); //传引用
//T = void __cdecl(void)
//tmprv = void(__cdecl&)(void)
}
这里,我们分别尝试对函数指针进行值传递,引用传递和指针传递,推导结果如下:
可以发现:
值传递的模板推导出了指针
(
∗
)
(*)
(∗)。引用传递的模板推导为了函数引用,
v
o
i
d
(
&
)
(
v
o
i
d
)
void(\&)(void)
void(&)(void),忽略了指针。而指针传递的模板由于存在
∗
*
∗,因此直接忽略了指针。
7.数组作为实参
数组和指针类似,有时会被当做地址被传递,这里我们分别尝试将数组按值传递和按引用传递,参考下方代码:
//数组作为实参
void Test8() {
const char mystr[] = "I Love C++ !";
myfunc8(mystr); //传值
//T = char const *
// tmprv = char const*
myfunc2(mystr);//传引用
//T = char const [13]
//tmprv = char const (&__ptr64)[13]
}
推断结果如下:
我们发现:
在值传递版本中,数组直接被推断为了指针,因此就被当做了指针传递
在引用传递版本中,数组被推断为了数组引用,包含了数组的大小
[
13
]
[13]
[13],按引用传递保持了数组自身的性质,而不会退化为指针。
8.初始化列表作为实参
要使用初始化列表作为实参,需要加入头文件: # i n c l u d e < i n i t i a l i z e r l i s t > \#include<initializer_list> #include<initializerlist>,下面我们使用初始化列表进行传值和传引用,参考下方代码:
//初始化列表作为实参
void Test10() {
myfunc10({ 1,2,3,4 }); //传值
//T = int
//tmprv = class std::initializer_list<int>
std::initializer_list<int> list = { 1,2,3,4 };
myfunc11(list); //传引用
//T = int
//tmprv = class std::initializer_list<int> &
}
可以发现,初始化列表正常的被推断为了值版本和引用版本: