Item 1:Understand template type deduction.
Case 1: ParamType is a Reference or Pointer, but not a Universal Reference
通用的引用的形式:const T&可以绑定到左值变量(T&)和右值变量(T&&)。
- ref-模板的定义
template<typename T> void f(T& param); // param is a reference
模板的实例化的参数(例如T)会包含const限定符。但是传递给f的参数的引用与普通变量是没有区别的。
int x = 27; // x is an int const int cx = x; // cx is a const int const int& rx = x; // rx is a reference to x as a const int f(x); // T is int, param's type is int& f(cx); // T is const int, // param's type is const int& f(rx); // T is const int, // param's type is const int&
2. cons-ref模板定义
template<typename T> void f(const T& param); // param is now a ref-to-const
模板类型的实参变量的const通用会被抛弃。
int x = 27; // as before const int cx = x; // as before const int& rx = x; // as before f(x); // T is int, param's type is const int& f(cx); // T is int, param's type is const int& f(rx); // T is int, param's type is const int&
3. 指针类型同上。
Case 2: ParamType is a Universal Reference
模板的参数类型使用T &&来进行引用折叠。这样出入的参数的左/右值属性得以保留。T的const限定符予以保留。
Case 2: ParamType 既不是指针也不是引用
template<typename T> void f(T param); // param is now passed by value
在模板的实例化中,param会丢弃cons/volatilet限定符(cv-qualifier)(即使实参是const限定的)。param会被拷贝,所以也不会推导为引用。
Array Arguments(数组参数)
例如处理一个字符串的函数f
template<typename T> void f(T& param); // template with by-reference parameter
如果使用引用那么T的类型会被推导为char[n]。如果使用参数T param,那么会被推导为(退化为)char*。
这个函数可以在编译期得到数组的长度
// return size of an array as a compile-time constant. (The // array parameter has no name, because we care only about // the number of elements it contains.) template<typename T, std::size_t N> // see info constexpr std::size_t arraySize(T (&)[N]) noexcept // below on { // constexpr return N; // and }
Function Arguments(函数参数)
函数作为模板的参数T&,T被推导为函数的签名。T 推导为函数指针类型。
注意:
Things to Remember
• During template type deduction, arguments that are references are treated as
non-references, i.e., their reference-ness is ignored.
• When deducing types for universal reference parameters, lvalue arguments get
special treatment.
• When deducing types for by-value parameters, const and/or volatile argu‐
ments are treated as non-const and non-volatile.
• During template type deduction, arguments that are array or function names
decay to pointers, unless they’re used to initialize references.
Item 2: Understand auto type deduction.
单纯使用auto推导的是表达式的类型。不包括cv限定符以及引用的属性。所以需要自行加入限定符、引用属性。如果加上了&或&&,那么引用折叠还是会发生的。
分为三种情况:
Case1:表达式是引用以及非引用情况
auto x = 27; // case 3 (x is neither ptr nor reference) //没有限定符、引用。但是指针是正常的。函数也退化为指针 const auto cx = x; // case 3 (cx isn't either)// const auto& rx = x; // case 1 (rx is a non-universal ref.)//非通用的引用。总是为左值引用
Case2:通用引用
auto&& uref1 = x; // x is int and lvalue, // so uref1's type is int& auto&& uref2 = cx; // cx is const int and lvalue, // so uref2's type is const int& auto&& uref3 = 27; // 27 is int and rvalue, // so uref3's type is int&&
Case3:数组参数
加引用推导为数组的类型:T[n];不带有引用的auto退化为指针类型T*。函数指针也是。
花括号列表初始器在auto下的推导:
auto x1 = 27; // type is int, value is 27 auto x2(27); // ditto auto x3 = { 27 }; // type is std::initializer_list<int>, // value is { 27 } auto x4{ 27 }; // ditto 同上
初始化列表:
auto x5 = { 1, 2, 3.0 }; // error! can't deduce T for // std::initializer_list<T>
{}在类型推导过程中推导为std::initializer_list<T>类型。
所以模板的参数定义
auto x = { 11, 23, 9 }; // x's type is // std::initializer_list<int> template<typename T> // template with parameter void f(T param); // declaration equivalent to // x's declaration f({ 11, 23, 9 }); // error! can't deduce type for T However, if you specify in the template that param is a std::initializer_list<T> for some unknown T, template type deduction will deduce what T is: template<typename T> void f(std::initializer_list<T> initList); f({ 11, 23, 9 }); // T deduced as int, and initList's // type is std::initializer_list<int>
C++14 auto作为匿名函数的参数的类型
std::vector<int> v; … auto resetV = [&v](const auto& newValue) { v = newValue; }; // C++14 … resetV({ 1, 2, 3 }); // error! can't deduce type // for { 1, 2, 3 }
Things to Remember
• auto type deduction is usually the same as template type deduction, but auto
type deduction assumes that a braced initializer represents a std::initializer_list, and template type deduction doesn’t.
• auto in a function return type or a lambda parameter implies template type deduction, not auto type deduction.
Item 3: Understand decltype
可以在编译期推导表达式的类型。他不会计算表达式。
例如编写一个使用[]运算符返容器的某个元素的引用时,使用decltype可以避免返回一些意外的代理类(例如std::vector<bool>的operator[])
//C++14 版本 template<typename Container, typename Index> // C++14; auto authAndAccess(Container& c, Index i) // not quite { // correct authenticateUser(); return c[i]; // return type deduced from c[i] } //尾置返回类型 template<typename Container, typename Index> // works, but auto authAndAccess(Container& c, Index i) // requires -> decltype(c[i]) // refinement { authenticateUser(); return c[i]; }
这样设计就会导致这样的问题出现authAndAccess(容器,1)=5的出现。如果容器是const限定的,由于auto抛弃cv运算符。这就会导致无法赋值的问题。
面临的问题:
- 容器本身的引用限定。
- 返回值的引用类型或者cv限定。
解决方案:
- 使用通用的引用:T&&
- decltype(auto)可以确保返回的类型与return语句返回的类型是一致的(cv、引用)。decltype(auto)通用可以应用在变量声明上。
- return std::forward<Container>(c)[i];语句确保函数的返回类型一致。 std::forward<Container>返回的是表达式。而decltype((x))一定是一个引用!所以返回的是表达式而非变量。这样decltype(auto)才是有效的
(注意:C++不使用delctype(auto)) 这样声明即可
auto // C++11 authAndAccess(Container&& c, Index i) // version -> decltype(std::forward<Container>(c)[i])
#include <iostream> #include <vector> class Widget { public: Widget() { } Widget(int) { } operator bool() { return true; } }; template<typename Container, typename Index> auto authAndAccess(Container &c, Index i)->decltype(c[i]) //c i 在函数名定义之前是不可用的 { if (i >= 0 && i<c.size()) return c[i]; else if (i < 0) return c[-i % c.size()]; else return c[i % c.size()]; } //C++14 从函数中推导 template<typename Container, typename Index> decltype(auto) authAndAccess1(Container &c, Index i) //c i 在函数名定义之前是不可用的 { if (i >= 0 && i<c.size()) return c[i]; else if (i < 0) return c[-i % c.size()]; else return c[i % c.size()]; } //改进支持lvalue和rvalue template<typename Container, typename Index> decltype(auto) authAndAccess2(Container &&c, Index i) //c i 在函数名定义之前是不可用的 { if (i >= 0 && i<c.size()) return c[i]; else if (i < 0) return c[-i % c.size()]; else return c[i % c.size()]; } //改进的,支持返回指定的类型 template<typename Container, typename Index> decltype(auto) authAndAccess3(Container &&c, Index i) //c i 在函数名定义之前是不可用的 { if (i >= 0 && i<c.size()) return std::forward<Container>(c)[i]; else if (i < 0) return std::forward<Container>(c)[-i % c.size()]; else return std::forward<Container>(c)[i % c.size()]; } //C++11 版本:改进的,支持返回指定的类型 template<typename Container, typename Index> auto authAndAccess4(Container &&c, Index i)->decltype(std::forward<Container>(c)[i]) //c i 在函数名定义之前是不可用的 { if (i >= 0 && i<c.size()) return (c[i]); else if (i < 0) return (c[-i % c.size()]); else return (c[i % c.size()]); } std::vector<int> makeIntVector() { return std::vector<int>{2, 3}; } int main() { std::vector<int> vec{ 1,2,3 }; std::cout << authAndAccess(vec, 2) << std::endl; std::cout << authAndAccess(vec, -4) << std::endl; /*----------------*/ authAndAccess1(vec, -4) = 50; std::cout << authAndAccess1(vec, -4) << std::endl; //如果传入的是临时的对象 auto &s = authAndAccess3(std::move(makeIntVector()), 1); s = 20; std::cout << s << std::endl; //C++11 版本的调用 auto s1 = authAndAccess4(makeIntVector(), 2); s1 = 21; std::cout << s1 << std::endl; std::cout << (authAndAccess4(makeIntVector(), 2) = 0) << std::endl;//也是右值引用 auto io = makeIntVector(); auto &&ij = std::forward<int>(6+6); auto &&iij = std::forward<std::vector<int>>(std::move(io));//iij本身是右值引用 system("pause"); return 0; }
Things to Remember
• decltype almost always yields the type of a variable or expression without
any modifications.
• For lvalue expressions of type T other than names, decltype always reports a
type of T&.
• C++14 supports decltype(auto), which, like auto, deduces a type from its
initializer, but it performs the type deduction using the decltype rules.
Item 4: Know how to view deduced types.
运行时typeid关键字返回的类型在模板函数:
#include <typeinfo> #include <iostream> #include <vector> struct ww{ ww(int){} }; template<typename T> void f(const T ¶m) { using std::cout; cout<<"T ="<<typeid(T).name()<<"\n";//类型显示的是一样的,因为typeid是模板函数,推导的时候使用T,所以const、volatile cout<<"param="<<typeid(param).name()<<"\n"; } std::vector<ww> createVec() { return std::vector<ww>{2,3,5}; } int main() { const auto vw = createVec(); if(!vw.empty()) { f(&vw[0]); } system("pause"); }
显示T =PK2ww,param=PK2ww。但是param的类型与T完全不同所以,typeid的返回的结果并不一定准确。
typeid 运算符在应用于多态类类型的左值时执行运行时检查,其中对象的实际类型不能由提供的静态信息确定。 此类情况是:
- 对类的引用
- 使用 * 取消引用的指针
- 带下标的指针(即 [ ])。(请注意,通常情况下,将下标与指向多态类型的指针一起使用不安全。)
typeid在多态的情况下:类有虚函数->运行时求值。否则返回的是静态类型。
如果typeid(exp)的表达式:
- exp为指针则会返回<指针所指的静态类型>*(编译时),返回指针的type_info。不管类是否有虚函数。指针并不是类类型
- exp为指针的解引用,检查所指对象的静态类型,是否有虚函数。然后决定什么时候求值【不包含虚函数的类型:静态编译,且不计算*exp;否则,动态编译】。动态编译时,如果指针是nullptr,typeid 将引发 bad_typeid 异常
- 如果 expression 既不是指针也不是对对象的基类的引用,则结果是表示 expression 的静态类型的 type_info 引用。 表达式的 static type 将引用在编译时已知的表达式的类型。 在计算表达式的静态类型时,将忽略执行语义。 此外,在确定表达式的静态类型时,将忽略引用(如果可能):
// expre_typeid_Operator_2.cpp #include <typeinfo> int main() { typeid(int) == typeid(int&); // evaluates to true 必须是编译时,所以int&也变成了int }
- 对类的引用,返回的总是不带有引用的类型(引用本身是一个别名)
引用&参考:
- 《Effective Modern C++》
- (StackOverflow)decltype(auto)
- (MSDN)typeid的使用
- 《C++ Primer(第五版)》