Effective Modern C++-第一章 类型推断

Hi!这里是山幺幺的Effective Modern C++系列。在对c++有基本了解之后,通过这本书来继续进阶~因为看的是英文书,所以笔记是中英夹杂的。

发展历史


  • c++ 98:只有function template有类型推断
  • c++ 11:增加了auto和decltype
  • c++ 14:extends the usage contexts in which auto and decltype may be employed 【比如decltype(auto)】

Item1:Template类型推断


template
void f(ParamType param);
f(expr); // deduce T and ParamType from expr

Case 1:ParamType是指针/引用,但不是universal reference(即:若形参是T&&,实参只能是右值)

  • 类型推断过程
    1. 若expr是引用,则ignore its reference part
    2. 用expr的类型与ParamType比较并推断出T(const会被推断入T)
  • 栗子之T&(T&&同样)
template
void f(T& param);

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&
  • 栗子之const T&
template
void f(const T& param); // param is now a ref-to-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&
  • 栗子之T*
template
void f(T* param); // param is now a pointer

int x = 27; // as before
const int *px = &x; // px is a ptr to x as a const int

f(&x); // T is int, param's type is int*
f(px); // T is const int, param's type is const int*

Case 2:ParamType是universal reference(即:形参是T&&,实参是左值)

  • 类型推断过程
    • (此时expr是左值,ParamType是T&&)T 和 ParamType 都被推断为左值引用(const会被推断入T)
  • 栗子
template
void f(T&& param); // param is now a universal reference

int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before

f(x); // x is lvalue, so T is int&, param's type is also int&
f(cx); // cx is lvalue, so T is const int&, param's type is also const int&
f(rx); // rx is lvalue, so T is const int&, param's type is also const int&
f(27); // 27 is rvalue, so T is int, param's type is therefore int&&

Case 3:ParamType不是指针/引用(传值)

  • 类型推断过程
    1. 若expr是引用,则ignore its reference part
    2. 若expr是const/volatile,则ignore对应part(因为是传值!)
  • 栗子
template
void f(T param); // param is now passed by value

int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before

f(x); // T's and param's types are both int
f(cx); // T's and param's types are again both int
f(rx); // T's and param's types are still both int

const char* const ptr = "Fun with pointers"; // ptr is const pointer to const object
f(ptr); // T's and param's types are const char*

特殊Case:expr是数组

  • 背景知识
    • array-to-pointer conversion
    const char name[] = "J. P. Briggs"; // name's type is const char[13]
    const char * ptrToName = name; // array decays to pointer to its first element
    
    • 形参不能是数组
    void myFunc(int param[]); // 实际上被视为:
    void myFunc(int* param); // same function as above
    
  • 类型推断规则:
    1. 若ParamType不是引用,T被推断为指针;
    2. 若ParamType是引用,T被推断为array,param被推断为指向array的引用
  • 栗子们
const char name[] = "J. P. Briggs";

template
void f1(T param); // template with by-value parameter
template
void f2(T* param);
template
void f3(T& param);

f1(name); // param and T deduced as const char*
f2(name); // param: const char*, T: const char
f3(name); // param: const char (&)[13], T: const char [13]
  • 栗子之求数组大小
template 
constexpr std::size_t arraySize(T (&)[N]) noexcept {
  return N;
}

int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
int mappedVals[arraySize(keyVals)]; // so does mappedVals
// we prefer a std::array to a built-in array
std::array mappedVals; // mappedVals' size is 7

特殊Case:expr是函数

  • 类型推断规则与array相同
  • 栗子们
void someFunc(int, double); // someFunc is a function; type is void(int, double)

template
void f1(T param); // in f1, param passed by value
template
void f2(T* param); 
template
void f3(T& param); 

f1(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double)
f2(someFunc); // param deduced as ptr-to-func; type is void (*)(int, double)
f3(someFunc); // param deduced as ref-to-func; type is void (&)(int, double)

Item2:auto类型推断


auto类型推断本质上就是template类型推断,遵守Item 1中介绍的规则

  • auto x = 27; 等价于
template // conceptual template for deducing x's type
void func_for_x(T param);
func_for_x(27); // conceptual call: param's deduced type is x's type
  • const auto cx = x; 等价于
template // conceptual template for deducing cx's type
void func_for_cx(const T param); 
func_for_cx(x); // conceptual call: param's deduced type is cx's type
  • const auto& rx = x; 等价于
template // conceptual template for deducing rx's type
void func_for_rx(const T& param); 
func_for_rx(x); // conceptual call: param's deduced type is rx's type

但有一个例外

  • auto的特殊规则:When the initializer for an auto-declared variable is enclosed in braces, the deduced type is a std::initializer_list. If such a type can’t be deduced (e.g., because the values in the braced initializer are of different types), the code will be rejected
  • 栗子
auto x1 = 27; // type is int, value is 27
auto x2(27); // ditto
auto x3 = { 27 }; // type is std::initializer_list, value is { 27 }
auto x4{ 27 }; // ditto

auto x5 = { 1, 2, 3.0 }; // error! can't deduce T for std::initializer_list
// 这里实际上发生了两步:①通过auto deduction把x5推断为std::initializer_list;②通过template deduction推断T,这一步失败了
  • 更清楚地看auto deduction和template deduction的区别:auto assumes that a braced initializer represents a std::initializer_list, but template type deduction doesn’t
auto x = { 11, 23, 9 }; // x's type is std::initializer_list

template // template with parameter
void f(T param); // declaration equivalent to x's declaration
f({ 11, 23, 9 }); // error! can't deduce type for T

template
void f(std::initializer_list initList);
f({ 11, 23, 9 }); // T deduced as int, and initList's type is std::initializer_list

C++ 14新特性

  • C++ 14允许在函数返回类型和lambda parameter declarations中使用auto,但这些情形下的auto没有上面所说的特殊规则
auto createInitList() {
  return { 1, 2, 3 }; // error: can't deduce type for { 1, 2, 3 }
} 

std::vector v;
auto resetV = [&v](const auto& newValue) { v = newValue; }; // C++14
resetV({ 1, 2, 3 }); // error! can't deduce type for { 1, 2, 3 }

Item 3:理解decltype


通常情况decltype返回the exact type of the name or expression

const int i = 0; // decltype(i) is const int

bool f(const Widget& w); // decltype(w) is const Widget&; decltype(f) is bool(const Widget&)

struct Point {
  int x, y; // decltype(Point::x) is int; decltype(Point::y) is int
}; 

Widget w; // decltype(w) is Widget

if (f(w)) … // decltype(f(w)) is bool

vector v; // decltype(v) is vector

if (v[0] == 0) … // decltype(v[0]) is int&

PS:For std::vector, operator[] does not return a bool&. Instead, it returns a brand new object

decltype用作trailing return type

  • 背景知识
    1. trailing return type的好处是return type可以使用形参的信息
    2. trailing return type不是类型推断!
  • 栗子:注意这里的auto只是表明这是个trailing return type,与auto类型推断无关!
template 
auto authAndAccess(Container& c, Index i)  -> decltype(c[i]) {
  authenticateUser();
  return c[i];
}

decltype用作return type deduction

  • 背景知识
    1. C++ 11允许为single-statement lambdas推断返回类型,C++ 14允许为所有lambda和所有函数推断返回类型
    2. decltype(auto)是C++ 14新特性
    3. C++ 14允许在函数返回类型和lambda parameter declarations中使用auto,但这些情形下的auto没有Item2所说的auto的特殊规则

PS:以下代码都是C++ 14下的

  • 栗子1:只使用auto做返回类型推断,不完全正确,因为根据Item1,c[i]的reference part会被忽略,而对vector来说,大多数情况下[]需要返回一个引用
template 
auto authAndAccess(Container& c, Index i) { 
  authenticateUser();
  return c[i]; // return type deduced from c[i]
}
  • 栗子2:使用decltype(auto)做返回类型推断,正确;auto specifies that the type is to be deduced, and decltype says that decltype rules(返回exact type) should be used during the deduction;for the common case where c[i] returns a T&, authAndAccess will also return a T&, and in the uncommon case where c[i] returns an object, authAndAccess will return an object
template  
decltype(auto) authAndAccess(Container& c, Index i)  { 
  authenticateUser();
  return c[i];
}

decltype(auto)用作变量声明时的类型推断

  • 作用:使用apply the decltype type deduction rules(即推断为exact type) to the initializing expression
  • 栗子
Widget w;
const Widget& cw = w;

auto myWidget1 = cw; // auto type deduction: myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction: myWidget2's type is const Widget&

对authAndAccess的改进

  • authAndAccess的问题:c必须是左值
  • 改进1:使用universal reference,从而c可以是右值
template // c is now a universal reference
decltype(auto) authAndAccess(Container&& c, Index i); 
  • 改进2:使用forward,Item25会解释为什么要apply std::forward to universal references
template // final C++ 14 version
decltype(auto) authAndAccess(Container&& c, Index i) {
  authenticateUser();
  return std::forward(c)[i];
}

template // final C++ 11 version
auto authAndAccess(Container&& c, Index i) -> decltype(std::forward(c)[i]) {
  authenticateUser();
  return std::forward(c)[i];
}

decltype也可能不返回exact type

  • 对左值表达式,会返回绑定该类型的引用(即if an lvalue expression other than a name has type T, decltype reports that type as T&)
  • 栗子1:decltype(x) is int;而decltype((x)) is int&
  • 栗子2
decltype(auto) f2() {
  int x = 0;
  …
  return (x); // decltype((x)) is int&, so f2 returns int&
}

Item 4:如何得到deduced type


在写代码时得到

  • 在IDE中把鼠标hover on entity,一般可以看到类型

在编译时得到

  • use entity in a way that leads to compilation problems,这样在报错中可以看到它的类型,比如实例化一个只声明而未定义的类:
    template // declaration only for TD;
    class TD; // TD == "Type Displayer"
    
    TD xType; // elicit errors containing
    TD yType; // x's and y's types
    
    编译器会显示:
    error: 'xType' uses undefined class 'TD'
    error: 'yType' uses undefined class 'TD'
    

在运行时得到

  • typeid
    • typeid的结果不可靠,因为它会对传给它的entity进行template deduction,即会忽略reference/const/volatile
  • Boost.TypeIndex
    • 结果可靠
    • pretty_name()返回std::string
    • 栗子
    #include 
    template
    void f(const T& param) {
      using std::cout;
      using boost::typeindex::type_id_with_cvr;
      // show T
      cout << "T = "
        << type_id_with_cvr().pretty_name()
        << '\n';
      // show param's type
      cout << "param = "
        << type_id_with_cvr().pretty_name()
        << '\n';
      …
    }
    

你可能感兴趣的:(Effective Modern C++-第一章 类型推断)