1 模板推导规则
1.1 函数的形参是一个指针或引用但不是通用引用
函数模板推导时,推导出的T会省略函数模板中形参类型有的部分(const、*、&)。
实参类型有,函数模板中形参类型没有的部分,推导出的T则会保留这一部分。
最后推导出的param的类型,将会是实参类型和函数模板中形参类型的并集。
demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <iostream> #include <boost/type_index.hpp>
template<typename T> void test1(T& param) { std::cout<< "T:" << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
std::cout<< "形参:" << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
template<typename T> void test2(const T& param) { std::cout<< "T:" << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
std::cout<< "形参:" << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
template<typename T> void test3(const T* param) { std::cout<< "T:" << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
std::cout<< "形参:" << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
int main() { int a = 10; const int b = a; const int& c = a; const int* p = &a;
std::cout << "test1:" << std::endl; test1(a); test1(b); test1(c);
std::cout << "test2:" << std::endl; test2(a); test2(b); test2(c);
std::cout << "test3:" << std::endl; test3(&a); test3(p);
return 0; }
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| test1: T:int 形参:int& T:int const 形参:int const& T:int const 形参:int const& test2: T:int 形参:int const& T:int 形参:int const& T:int 形参:int const& test3: T:int 形参:int const* T:int 形参:int const*
|
1.2 函数的形参是通用引用
demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <boost/type_index.hpp>
template<typename T> void test4(T&& param) { std::cout<< "T:" << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
std::cout<< "param:" << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
int main() { int a = 10; const int b = a; const int& c = a;
std::cout << "T&&:" << std::endl; test4(a); test4(b); test4(c); test4(10);
return 0; }
|
结果:
1 2 3 4 5 6 7 8 9
| T&&: T:int& param:int& T:int const& param:int const& T:int const& param:int const& T:int param:int&&
|
1.3 函数的形参是值传递
demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> #include <boost/type_index.hpp>
template<typename T> void test5(T param) { std::cout<< "T:" << boost::typeindex::type_id_with_cvr<T>().pretty_name() << std::endl;
std::cout<< "param:" << boost::typeindex::type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
int main() { int a = 10; const int b = a; const int& c = a;
const int* p1 = &a; int* const p2 = &a; const char* const p3 = "既是常量指针,也是指针常量";
std::cout << "值传递" << std::endl;
std::cout << "T param:" << std::endl; test5(a); test5(b); test5(c); test5(p1); test5(p2); test5(p3);
return 0; }
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 值传递 T param: T:int param:int T:int param:int T:int param:int T:int const* param:int const* T:int* param:int* T:char const* param:char const*
|
1.4 其他:数组作为实参
由于即使函数的形参声明为数组:void myFunc(int param[])
,也会被视为指针声明:void myFunc(int* param)
,因此以数组为实参通过值传递传入函数模板中,则依旧会被推导为指针。
1 2 3 4
| int nums[] = {1, 2, 3, 4}; template<typename T> void f(T param);
|
而如果形参为引用传递,传入数组实参则会被推导为数组。
1 2 3 4
| int nums[] = {1, 2, 3, 4}; template<typename T> void f(T& param);
|
1.5 其他:函数作为实参
一般函数作为参数传入函数时,是会退化为指向函数的指针,也就是通过函数指针方式传入。
于是针对以下函数传入以下的函数模板中,如果是值传递,param就会被推导为函数指针,如果是引用传递,则会是函数的引用。
1 2 3 4 5 6 7 8 9 10
| void someFunc(int, double);
template<typename T> void f1(T param)
template<typename T> void f2(T& param)
f1(someFunc); f2(someFunc);
|
注意:
函数和数组因为会被推导为指针,所以有左const也不会被省略。
数组被推导为指针时会丢失大小信息,因此最好还是用STL容器。
2 auto
2.1 与模板推导规则相同点
- 情况一:变量不是引用、指针、通用引用,不是函数,不是数组。
1 2 3
| auto x = 27; const auto cx = x; const auto& rx = cx;
|
- 情况二:变量是引用。类似模板推导中的值传递,传带引用性的实参,引用性被忽略。
1 2 3
| int x = 27; int& rx = x; auto ax = rx;
|
1 2 3 4 5 6 7
| auto x = 27; const auto cx = x; const auto& rx = cx;
auto&& uref1 = x; auto&& uref2 = cx; auto&& uref3 = 27;
|
1 2 3
| const char name[] = "hanmeilin"; auto arr1 = name; auto& arr2 = name;
|
1 2 3
| void someFunc(int, double); auto func1 = someFunc; auto& func2 = someFunc;
|
2.2 与模板推导规则不同点:对统一初始化的推导
- auto针对统一初始化,会推导为
std::initializer_list
1 2 3 4 5 6 7
| auto x1 = {27}; auto x2{27};
auto x3 = {1, 2, 3.0};
|
1 2 3 4
| template<typename T> void f(T param);
f({11, 2, 9});
|
- 在模板中将T指定为
std::initializer_list<T>
,则能使模板类型推导正常工作。
1 2 3 4
| template<typename T> void f(std::initializer_list<T> initList);
f({11, 22, 9});
|
2.3 C++14中auto作为函数返回值或lambda函数形参
这里auto的实际工作机制是模板类型推导,所以此时用auto推导花括号的统一初始化,反而会报错。
auto做函数返回值:
1 2 3 4
| auto createInitList() { return {1, 2, 3}; }
|
auto做lambda函数形参:
1 2 3 4 5
| std::vector<int> v ... auto resetV = [&v] (const auto& newValue) {v = newValue;}; ... resetV({1, 2, 3});
|
2.4 使用auto的好处
- 省略冗长的声明类型,并且有些表达式或函数返回的类型只有编译器知道,也可以用auto声明。
1 2 3 4 5 6 7 8 9 10 11 12 13
| template<typename It> void dwim(It b, It e) { typename std::iterator_traits<It>::value_type currValue1 = *b;
auto currValue2 = *b; }
|
- 对于保存闭包,相对于
std::function
语法更加简洁,并且内存消耗可能会更少。因为auto会直接使用与闭包相同大小的存储空间,而实例化std::function
时会有一个固定的大小,如果这个大小不足以存储一个闭包,则std::function
的构造函数会在堆上分配内存来存储,因此会消耗更多的内存。
闭包由要执行的代码块和作用域(计算环境)两个部分组成,在C++中,通常由lambda表达式生成。
1 2 3 4
| auto x1 = [](int x, int y){return x + y;}
std::function<int(int, int)> x2 = [](int x, int y){return x + y;}
|
demo1:
1 2 3 4 5 6 7
| std::vector<int> v;
unsigned sz = v1.size(); auto sz = v2.size();
|
demo2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
std::unordered_map<std::string, int> m; for (const std::pair<std::string, int>& p : m) { }
for (const auto& p : m) { }
|
2.5 使用static_cast强制auto推导出想要的结果
使用此操作的问题背景:
1 2 3 4 5 6 7
| std::vector<bool> features(const Widget& w); void processWidget(const Widget& w, bool highPriority); Widget w;
auto highPriority = features(w)[5];
processWidget(w, highPriority);
|
解决:
1 2 3
|
auto highPriority = static_cast<bool>features(w)[5];
|
3 decltype
作用:decltype(变量)
可以获取到变量的类型。
3.1 decltype(auto)
C++14后添加
作用: decltype(auto)
会根据decltype的规则推导出变量完整的类型。这里的auto表示这个类型将会被推导。
demo1:
1 2 3 4
| Widget w; const Widget& cw = w; auto myWidget1 = cw; decltype(auto) myWidget2 = cw;
|
demo2:
1 2 3 4 5 6 7 8 9 10 11
| template<typename T> decltype(auto) func(T& t) { return t[i]; }
int main() { std::deque<int> d; func(d); }
|
3.2 特殊情况:对于不是单纯变量名的左值表达式
对于不是单纯变量名的左值表达式(能出现在等号左边的都是左值表达式,包括可以被赋值的变量),decltype推导的结果总是引用类型。
demo:
因此有些情况会引发一些未定义行为的错误
情况1:
因为(i)作为左值表达式会被推导为int&
,返回了临时变量的引用,导致错误。
1 2 3 4
| decltype(auto) func(int i) { return (i); }
|
情况2:
同理,++i
是一个表达式,所以会被推导为int&
。i++
不会出错,因为i++
本质返回的就是变量i
,推导的是int
。
1 2 3 4
| decltype(auto) func(int i) { return ++i; }
|
4 尾置返回类型
使用场景: 一般函数返回值的类型需要在函数声明时指定,而使用尾置返回类型可以根据函数使用时的形参来推断返回值类型。这里的auto是告诉函数,返回值类型将由后续推导决定。
语法:
1 2 3 4 5 6 7 8 9 10 11
| auto function_name(parameters) -> 返回的类型 { }
template<typename T> auto function_name(T& t) -> decltype(t[i]) { return t[i]; }
|
这里demo只用auto做返回值会出现什么问题?
1、从2.2可知,这里的auto做返回值实际是用的模板推导的规则,如果传入花括号实参(统一初始化),则会报错。
2、如果传入的容器是std::deque<int> d;
,d[i]
返回的数据类型则是int&
,而从2.1可知,auto会把他推导为int
,而这个int
是拷贝的一个临时对象,是右值,无法被赋值。这时如果这样调用:function_name(d) = 5
,就会出问题。