C++98
有一组用于类型推断的规则: 用于函数模板的规则。C++11
对这个规则集进行了一些修改,并添加了两个新的规则集,一个用于auto
,另一个用于decltype
。然后C++14
扩展了auto
和decltype
可能被使用的上下文。类型推断的使用场合越来越广,这使您不再需要手写那些明显或冗余的类型。它使C++
代码的适应性更强,因为在源代码中的某一点更改类型会自动通过类型推断传播到其他位置。但是,它会使代码更难于推理,因为编译器推导出的类型可能和您希望的不同。
如果对于类型推导操作没有一个扎实的理解,要想写出有现代感的C++程序是不可能的。类型推导随处可见:在函数模板调用中,在auto出现的地方,在decltype表达式出现的地方,以及C++14
的decltype(auto)
中。
1. 理解模板类型推断
假设有下面的代码:
1 2 3
| int x=27; const int cx = x; const int& rx = x;
|
情形一: 参数是引用或指针
1 2 3 4
| template<typename T> void f(T& param); f(x); f(cx); f(rx);
|
1 2 3 4
| template<typename T> void f(const T& param); f(x); f(cx); f(rx);
|
1 2 3 4 5
| template<typename T> void f(T* param); const int* px = &x;
f(&x); f(px);
|
情形二: 参数是通用引用(Universal Reference)
普通函数上的&&
是通用引用,既支持左值引用也支持右值引用.
1 2 3 4 5
| template<typename T> void f(T&& param); f(x); f(cx); f(rx); f(27);
|
情形三: 参数既不是指针也不是引用
1 2 3 4
| template<typename T> void f(T param); f(x); f(cx); f(rx);
|
数组类型的参数
1 2 3 4 5 6 7 8 9
| template<typename T> void f1(T param); template<typename T> void f2(T& param);
const char* const ptr = "hello world"; const char name[] = "hello world";
f1(ptr); f1(name); f2(name);
|
我们可以定义一个取数据数量的函数
1 2 3 4
| template<typename T, std::size_t N> constexpr auto arraySize(T (&)[N]) { return N; }
|
函数类型的参数
1 2 3 4 5 6 7
| template<typename T> void f1(T param); template<typename T> void f2(T& param);
void someFunc(int, double);
f1(someFunc); f2(someFunc);
|
2. auto 推断
假设有下面的代码:
1 2 3 4 5
| int x=27; const int cx = x; const int& rx = x; const char name[] = "hello world"; void someFunc(int, double);
|
1 2 3 4 5 6 7 8 9 10
| auto&& uref1 = x; auto&& uref2 = cx; auto&& uref3 = rx; auto&& uref4 = 27;
auto arr1 = name; auto& arr2 = name;
auto func1 = someFunc; auto& func2 = someFunc;
|
坑
注意小括号和大括号的区别
1 2 3 4 5 6 7 8 9 10
| auto x1=27; auto x2(27); auto x3={27}; auto x4{27};
template<typename T> void f1(T param); template<typename T> void f2(std::initializer_list<T> param);
f1({11,23,9}); f2({11,23,9});
|
3. decltype 推断
1 2 3 4 5 6 7 8
| const int i = 0; std::vector<int> v;
const Widget& cw = w; auto myWidget1 = cw; decltype(auto) myWidget2 = cw;
|
坑
auto
推断会把引用去除, 所以下面auto作为返回值时不会返回int&
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template <typename C, typename I> auto getItem(C &c, const I &i) { return c[i]; }
template <typename C, typename I> decltype(auto) getItem2(C &c, const I &i) { return c[i]; }
std::vector<int> v = {1,2,3}; getItem(v, 2) = 10; getItem2(v, 2) = 10;
|
巨坑
注意上面的getItem2
函数, 当参数c
是右值时, 会产生一个临时对象, 这时返回的是已经析构的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template <typename C, typename I> decltype(auto) getItem2(C &c, const I &i) { return c[i]; }
std::vector<Widget> getVec() { return std::vector<Widget>(10); }
int main(int, char**) { Widget& w = getItem2(getVec(), 5); w.show(); return 0; }
|
正确的版本是要能处理右值, 所以应该用通用引用作为参数
1 2 3 4 5
| template<typename C, typename I> decltype(auto) getItem2(C&& c, const I &i) { return std::forward<C>(c)[i]; }
|
表达式(x)
会被当作引用, 所以下面的代码有坑
1 2 3 4 5 6 7 8 9 10 11 12
| decltype(auto) ff1() { Widget x; return x; }
decltype(auto) ff2() { Widget x; return (x); } 经测试, `ff2`返回的是`Widget&`, 但是这个引用的对象已经析构了
|
4. 怎样看编译器推断的类型?
- 看IDE
- 写个错误的模板类等编译出错时看
- 用boost::type-index
1 2 3 4 5 6 7 8 9 10 11
| #include <boost/type_index.hpp> using boost::typeindex::type_id_with_cvr; template<typename T> void f2(T& param) { std::cout << "T = " << type_id_with_cvr<T>().pretty_name() << std::endl; std::cout << "param = " << type_id_with_cvr<decltype(param)>().pretty_name() << std::endl; }
|