C++11之类型推导
C++11中使用auto和decltype关键字实现类型推导。
auto关键字
在C++98\03中的语义
在C++98\03中auto是一个存储类型指示符;表示该变量具有自动存储周期,即该变量的生命周期从定义的位置到所在的作用域结束。
auto int i = 0;
static int j = 0;
存储类型指示符包括:auto、static、register、mutable、extern等;
C++11中auto被赋予了新的语义,已不再是一个存储类型指示符。
在C++11中的新语义
C++11中auto是一个类型指示符,用来告诉编译器在编译时对此变量做类型的自动推导。
auto定义的变量必须被初始化。
auto在使用时可以同指针(*)、引用(&)、cv限定符(const、volatile),结合使用。
推导规则
当不声明为指针或引用时,auto 的推导结果和初始化表达式抛弃引用和cv 限定符后类型一致。
当声明为指针或引用时,auto 的推导结果将保持初始化表达式的 cv 属性。
int k = 0;
auto * a = &k; // a -> int*,auto被推导为int
auto b = &k; // b -> int*,auto被推导为int*
auto & c = k; // c -> int&,auto被推导为int
auto d = c; // d -> int ,auto被推导为int
const auto e = k; // e -> const int,auto被推导为int
auto f = e; // f -> int,auto被推导为int
const auto& g = k; // e -> const int&,auto被推导为int
auto& h = g; // f -> const int&,auto被推导为const int
实际应用
使用时机:
1、简化代码;
2、返回值后置.
限制:
auto不能用于函数参数;
auto不能用于非静态成员变量;
auto不能用于定义数组;
auto不能用于推导模板参数;
auto 仅能用于推导static const 整型或者枚举型成员变量;
说明:
1、除了整型或者枚举型其他静态类型在 C++ 标准中无法就地初始化;
2、虽然C++11 中可以接受非静态成员变量的就地初始化,但不支持 auto 类型非静态成员变量的初始化。
void function(auto param){} //error,编译期无法推导
class Test{
public:
auto a = 0; //error,C++11不支持auto类型的非静态成员变量的初始化;
static const auto b = 0; //ok, c++11中仅能推导出static const int或者枚举
};
int array[5] = {0};
auto array1 = array; //ok, array1->int*
auto array2[10] = {0}; //error, auto不能用于定义数组。
template<typename T>
class Temp{};
Temp<int> m;
Temp<auto> n = m;//error,不能用于推导模板参数
decltype关键字
C++11中的语义
C++11 新增了decltype 关键字,用来在编译时推导出一个表达式的类型。它的语法格式 :decltype(exp);其中,exp 表示一个表达式。
类似于 sizeof,decltype 的推导过程是在编译期完成的,并且不会真正计算表达式的值。
int a = 0;
decltype(a) b = 1; // b->int
decltype(a + b) c = 0; // c->int
const int& m = a;
decltype(m) n = b; // j->const int&
const decltype(c) *p = &c; // *p->const int, p->const int*
decltype(c) *q= &c; // *q->int, q->int*
decltype(q)* qq= &p; // *qq->int*, qq->int**
推导规则
1、exp是标识符、类访问表达式时,decltype(exp)和exp的类型一致;
2、exp是函数调用时,decltype(exp)和函数返回值一致,如果返回值是类类型则保留cv,否则不保留cv;
3、其他情况,若exp是左值,则decltype(exp)是exp类型的左值引用,否则和exp类型一致.
举例:
1、标识符表达式和类访问表达式
// 标识符表达式
int i = 0;
volatile const int &j = i;
decltype(i) k = i; //k->int
decltype(j) l = i; //k->volatile const int&
// 类访问表达式
class A{
public:
static const int m = 0;
int x;
};
A a;
decltype(a.x) b = 0; //b->int
decltype(A::m) c = 0; //c->const int
2、函数调用
int& func_int_r(void); // 左值(lvalue,可简单理解为可寻址值)
int&& func_int_rr(void); // x值(xvalue,右值引用本身是一个xvalue)
int func_int(void); // 纯右值(prvalue,将在后面的章节中讲解)
const int& func_cint_r(void); // 左值
const int&& func_cint_rr(void); // x值
const int func_cint(void); // 纯右值
const Foo func_cfoo(void); // 纯右值
// 下面是测试语句
int x = 0;
decltype(func_int_r()) a1 = x; // a1 -> int &
decltype(func_int_rr()) b1 = 0; // b1 -> int &&
decltype(func_int()) c1 = 0; // c1 -> int
decltype(func_cint_r()) a2 = x; // a2 -> const int &
decltype(func_cint_rr()) b2 = 0; // b2 -> const int &&
decltype(func_cint()) c2 = 0; // c2 -> int
decltype(func_cfoo()) ff = Foo(); // ff -> const Foo
需要注意的是:
对于纯右值而言,只有类类型可以携带 cv 限定符,此外则一般忽略掉 cv 限定
3、带括号的表达式和加法运算表达式
class A {
public:
int x;
};
const A a = A();
decltype(a.x) i = 0; // i -> int, 类访问表达式和exp一致
decltype((a.x)) j = i; // j -> const int &,a.x是左值,可知括号也是左值,则decltype是左值引用
int b = 0, c = 0;
decltype(b + c) m = 0; // m -> int, b+c是右值
decltype(m += c) n = c; // n -> int & , m+=c是左值
实际应用
定义类型
标准库中有些类型都是通过 decltype 来定义的:
typedef decltype(nullptr) nullptr_t; // 通过编译器关键字nullptr定义类型nullptr_t
typedef decltype(sizeof(0)) size_t;
抽取类型
decltype 也经常用在通过变量表达式抽取变量类型上,如下面的这种用法:
vector<int> v;
// ...
decltype(v)::value_type i = 0;
获取函数的返回值类型
template <class ContainerT>
class Test{
typename ContainerT::iterator it_; // 类型定义可能有问题
public:
void func(ContainerT& container){
it_ = container.begin();
}
// ...
};
int main(void) {
typedef const std::vector<int> container_t;
container_t arr;
Test<container_t> test; //error, 实例化类型为const,迭代器也应该是const_iterator
test.func(arr);
return 0;
}
//C++98/03的解决方案,提供模板特例
template <class ContainerT>
class A<const ContainerT> {
typename ContainerT::const_iterator it_;
public:
void func(const ContainerT& container){
it_ = container.begin();
}
// ...
};
//C++11标准的解决方法
template <class ContainerT>
class A{
decltype(ContainerT().begin()) it_; //这样做可以防止模板使用const 类型实例化时报错
public:
void func(ContainerT& container) {
it_ = container.begin();
}
// ...
};
返回类型后置语法
auto和decltype的结合使用。
语法格式
//普通函数
auto function_name(int a, int b)->int;
//函数模板
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) { return t + u; }
意义
返回值类型后置语法,是为了解决函数返回值类型依赖于参数而导致难以确定返回值类型的问题。