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);