Effective Modern C++ - 1: 类型推断

part1 类型推断

C++98
    只有1种: 函数模板 实参推断
    
C++11 
    auto 
    decltype
    
好处 
    不用拼写 明显或多余的类型
    code 自适应: code 某一点上更改类型, 自动通过类型推导传播到其他位置
    
弊端 
    有时代码更难理解 
    
大多情况下, auto出现在decltype表达式中

C++14用decltype(auto) 构造 

item1 模板类型推断: 即 函数模板实参推断

规则非常自然

remember: 模板类型推导中

(1) 引用 传递的实参: 忽略 实参的引用性

(2) 万能引用 传递的实参: 左值实参 得到 特殊待遇(T 和 ParamType 都被推断为 左值引用)

(3) pass-by-value 实参: 依次忽略 实参(expr)本身的 reference -> const -> volatile 特性

(4) 数组/函数名 实参: 衰变为指针, 用于 初始化 数组/函数 引用 T (&rArr)[N])/ReturnType (&rFunc)(argType) 时才有意义

Note

(1) 函数模板的形参类型 ParamType 只应该为 3类4种形式:

[1] 值传递 T param: 不应该加 const/volatile, 实参的 const/volatile 特性会被去掉

[2/3] 引用传递 T& param 或 const T& param

[4] 万能引用传递 T&& param: 不应该加 const, 否则就不是万能引用了

(2) 引用传递 + ParamType 不显式含 const 的模板(T& parameter): 编译器不允许 实参(arg) 为 右值

引用传递 + ParamType 显式含 const 的模板(const T& parameter): 编译器允许 实参(arg) 为 右值

#include 

template
void f(T& param) // param is a reference
{
    std::cout << param << std::endl;
}

int main()
{
    f(2); // VS2019下, compile error: 无法将参数 1 从“int”转换为“T &”
}
#include 

template
void f(const T& param) // param is a reference
{
    std::cout << param << std::endl;
}

int main()
{
    f(2); // VS2019下, comiple/run ok
}

分析

    template
    void f(ParamType param);

    f(expr); // call f with some expression
             // 从 expr 推导出  T 和 ParamType

T 的类型推导 依赖于2点: [1] expr 的类型 [2] ParamType 的形式

3 种 cases 

ParamType 是

1 指针或引用 类型, 但 not 万能(universal)引用

工作原理

(1) 若 expr 的类型是引用, 则 忽略 expr 的引用部分

(2) 模式匹配 expr 的类型ParamType, 以确定 T, 再由 T 推断出 ParamType

[1] 引用传递 + ParamType 不显式含 const 的模板: T& parameter

需要将 const作为 T 的一部分 来推导

template
void f(T& param); // param is a reference

int x = 27;         // 1] x is an int
const int cx = x;   // 2] cx is a const int
const int& rx = x;  // 3] rx is a const int& -> 忽略引用部分: 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] => 常量对象 引用传递(由 ParamType 决定)给模板安全的, 无论 ParamType 是否显式含 const

原因: 实参(arg)的 const 特性会被推断出来, 作为 T 的一部分

[2] 引用传递 + ParamType 显式含 const 的模板: const T& param

不再需要将 const作为 T 的一部分 来推导 => 实参(arg)的 const 特性被忽略

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&

[3/4] param 是 pointer 或 pointer to const, 推理完全同上

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*

2 万能引用

详见 item24

类型推断 区分 实参(expr)左值还是右值

工作原理:

(1) expr 是左值, 则 T 和 ParamType 都被推断为 左值引用 -> 双重不寻常

[1] 这是模板类型推导中的 唯一情况, 其中 T 被推断为 引用

[2] 尽管 ParamType 声明使用右值引用的语法, 其推导类型是 左值引用

(2) expr 是右值,则 "正常"(Case1)规则适用

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&&

3 既非 指针, 也非 引用

工作原理

依次忽略 实参(expr)本身的 reference -> const -> volatile 特性, 但保持 实参(若为 指针/引用)所指对象的 const 特性

原因: 值传递 -> copy: 副本没有 reference/const/volatile 特性

3.1 指针/引用 实参

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
template
void f(T param); // param is still passed by value
                 // T is const char* => param is const char*

const char* const ptr =     // ptr is const pointer to const object: const char* const
    "Fun with pointers";
 
f(ptr); // pass arg of type const char * const

3.2 数组实参: 对类型推断而言, 与指针类型不同

指向常量的指针 const char*常量数组 const char[13] 不同: 所指的1个元素/数组所有元素 const

const char name[] = "J. P. Briggs"; // name's type is const char[13]
const char * ptrToName = name;      // array decays to pointer

(1) 常量数组(const char[])值传递给模板: T/ParamType 被推断为 指向常量的指针 const char*/const char*

template
void f(T param); // template with by-value parameter

f(name); // name is array, but T deduced as const char*

(2) 常量数组(const char[])引用传递给模板: T/ParamType 被推断为 数组的实际类型(char[N=...])/数组的引用类型(const char(&)[N])

template
void f(T& param); // template with by-reference parameter

f(name); // name is 数组的引用 const char(&)[13], T deduced as 数组的实际类型 char[13]) 

声明数组的引用的模板, 支持 推导 数组包含的元素数 + constexpr -> 结果可用在编译期

// return size of an array as a 编译期常量
// 数组参数无名, 因为我们只关心数组包含的元素个数
template 
    constexpr std::size_t 
arraySize(T (&)[N]) noexcept    // below on
{                               // constexpr
    return N;                   // and
}                               // noexcept

int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; // keyVals has 7 elements
 
int mappedVals[ arraySize(keyVals) ]; // so does mappedVals

更 modern的做法: std::array

std::array mappedVals; // mappedVals' size is 7

3.3 函数实参

衰变为指针的2种东西: 数组 / 函数 -> 指针/函数指针

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); // in f2, param passed by ref

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

这在实践中没有意义

item2 auto

remember

(1) 通常, auto 与 模板类型推断 本质相同, 但 auto 类型推断 假定 带大括号的 初始化 表示 初始化列表(std::initializer_list)

(2) 函数返回类型lambda参数中的 auto 表示 模板类型推断, 而非 auto 类型推断

1 通常: 编译器推断规则同 模板实参推断(expr -> T + ParamType)

auto 扮演的角色 : T

type specifier(类型限定符) : ParamType 中除 T部分之外的 限定符

auto x = 27;

const auto cx = x;

const auto& rx = x;
template        // conceptual template for
void func_for_x(T param);   // deducing x's type

func_for_x(27); // conceptual call: param's
                // deduced type is x's type
                
template             // conceptual template for
void func_for_cx(const T param); // deducing cx's type

func_for_cx(x); // conceptual call: param's
                // deduced type is cx's type
 
template              // conceptual template for
void func_for_rx(const T& param); // deducing rx's type

func_for_rx(x); // conceptual call: param's
                // deduced type is rx's type

Case3/1

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&&

数组/函数

const char name[] = // name's type is const char[13]
    "R. N. Briggs";
    
auto arr1 = name;   // arr1's type is const char*

auto& arr2 = name;  // arr2's type is
                    // const char (&)[13]
 
void someFunc(int, double); // someFunc is a function;
                            // type is void(int, double)
                            
auto func1 = someFunc;      // func1's type is
                            // void (*)(int, double)
                            
auto& func2 = someFunc;     // func2's type is
                            // void (&)(int, double)

2 auto 和 模板类型推断 唯一真正的区别: auto 假设 带大括号({})的初始值初始化列表 (std::initializer_list), 模板类型推断 则不

(1) auto 用于初始化

[1] 初始初始化单个元素

auto x1 = 27;       // type is int, value is 27
auto x2(27);        // ditto

[2] 初始化列表 std::initializer_list

auto x3 = { 27 };   // type is std::initializer_list
                    // value is { 27 }
auto x4{ 27 };      // ditto

(2) 模板类型推断: 模板函数的形参 不含/含 初始化列表, 不可/可推断出 初始化列表类型

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

3 auto 作 returnType/ lambda的paraType: 意味着模板类型推断 => 此时, 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 }

item3 decltype

推断 名称表达式 的 类型

1 decltype VS. auto

auto 用于 类型推断时, 修饰的是变量, 且 变量必须初始化; 无需/不能定义变量时, 用 decltype: 编译时推断 表达式的类型 decltype(expr)

2 decltype

C++11中, decltype 主要用途是 声明 函数模板, 其中returnType 取决于 para

函数名之前用 auto 与 类型推断无关, 表示用 尾部返回类型, 即 返回类型 在 -> 之后 声明, 此时, 类型推断由 decltype 实现

(1) 仅支持左值容器: 操作 + 将 operator[] 包装1层返回时, 去掉引用特性

template // works, but
auto authAndAccess(Container& c, Index i)    // requires
    -> decltype(c[i])                        // refinement
{
    authenticateUser();
    return c[i];
}

(2) Note: 下标操作符 operator[] 返回 引用, 但 尾部返回类型 相应的类型推断会去掉引用特性

=> 函数返回值 不再是引用 => 不能被赋值

std::deque d;

// …

authAndAccess(d, 5) = 10;   // authenticate user, return d[5]: 是 int&, 但函数返回类型是 int 
                            // then assign 10 to it;
                            // this won't compile!

(3) 支持左/右值容器: 工厂函数返回容器(临时容器对象) -> operator[] 取其第 n 个元素 -> copy 返回: pass by value

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

// copy 5th element of deque returned from makeStringDeque
auto s = authAndAccess(makeStringDeque(), 5);

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