decltype 和 auto

返回类型推导 decltype

 

decltype 推导规则

编译器将根据以下三条规则得出结果:

  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。
【实例】exp 是左值,或者被( )包围:
using namespace std;
class Base{
public:
    int x;
};
int main(){
    const Base obj;
    //带有括号的表达式
    decltype(obj.x) a = 0;  //obj.x 为类的成员访问表达式,符合推导规则一,a 的类型为 int
    decltype((obj.x)) b = a;  //obj.x 带有括号,符合推导规则三,b 的类型为 int&。
    //加法表达式
    int n = 0, m = 0;
    decltype(n + m) c = 0;  //n+m 得到一个右值,符合推导规则一,所以推导结果为 int
    decltype(n = n + m) d = c;  //n=n+m 得到一个左值,符号推导规则三,所以推导结果为 int&
    return 0;
}

用法

在难以或不可能以标准写法进行声明的类型时,decltype 很有用,例如 lambda 相关类型或依赖于模板形参的类型。C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

据我自己的理解,这个关键字经常被用在模板编程中,模板在使用时,可能会出现不知道应该声明是什么类型的状况

template?type?  fun(T1 a,T2 b){    ?type? aplusb=a+b;    return aplusb;}

在上述情况中,我们事先并不知道aplusb的类型,无法对其进行声明。
但是C++11中新增加的关键字decltype解决了这个问题

templateauto fun(T1 a,T2 b) ->decltype(a+b) //auto 马上会讲{    return aplusb=a+b;}

为什么函数的返回值是auto嘞?由于在提供返回类型之前,还未声明变量a,b所以无法对返回类型设置为decltype(a,b)
这时候在C++11中提供了一个解决方案,就是后置返回类型。简单的说就是把返回值后置。

例子

#include struct A { double x; };const A* a;decltype(a->x) y;       // y 的类型是 double(其声明类型)decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)???templateauto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参{                                     // C++14 开始可以推导返回类型    return t+u;}int main() {    int i = 33;    decltype(i) j = i * 2;    std::cout << "i = " << i << ", "              << "j = " << j << '\n';    auto f = [](int a, int b) -> int    {        return a * b;    };    decltype(f) g = f; // lambda 的类型是独有且无名的    i = f(2, 2);    j = g(3, 3);    std::cout << "i = " << i << ", "              << "j = " << j << '\n';}

输出:

i = 33, j = 66i = 4, j = 9

decltype 的实际应用

auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便,本节仅演示只能使用 decltype 的情形。
我们知道,auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了。下面是一个模板的定义:


#include 
using namespace std;

template 
class Base {
public:
    void func(T& container) {
    m_it = container.begin();
}

private:
    typename T::iterator m_it; //注意这里
};

int main()
{
    const vector v;
    Base> obj;
    obj.func(v);

    return 0;
}

单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出一大堆错误信息。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。

要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

template 
class Base {
public:
    void func(T& container) {
    m_it = container.begin();
}

private:
    decltype(T().begin()) m_it; //注意这里
};

看起来是不是很清爽?

注意,有些低版本的编译器不支持T().begin()这种写法,以上代码我在 VS2019 下测试通过,在 VS2015 下测试失败。

 

类型推导之 auto

用法 

C++11中,auto不再是一个存储类型指示符,而是一个自动推导变量的类型。auto并非是一种类型的声明,而是一个类型声明时的“占位符”编译器在编译期间会将auto替换为变量实际的类型

我觉得auto给我们带来的最大改变有两方面。

  1.  解放程序员双手。有了auto这个神器,妈妈再也不用担心我的键盘手了。auto可以代替以前要打的很长很长的变量类型

  2. 还是用在模板推导,下边的例子如果没有auto,x*y就没有办法填写类型了。

template void Multiply(_Tx x, _Ty y){    auto v = x*y;    std::cout << v;}

需要注意的是:

  • auto 变量必须在定义时初始化,这类似于const关键字。

  • 如果初始化表达式是引用,则去除引用语义

  • 如果初始化表达式为const或volatile(或者两者兼有),则除去const/volatile语义

  • 如果auto关键字带上&号,则不去除const语意

const int a2 = 10;auto &b2 = a2;//因为auto带上&,故不去除const,b2类型为const int
  • 初始化表达式为数组时,auto关键字推导类型为指针

  • 若表达式为数组且auto带上&,则推导类型为数组类型

  • 函数或者模板参数不能被声明为auto

void func(auto a)  //错误{    //... }
  • 时刻要注意auto并不是一个真正的类型。auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid

cout << sizeof(auto) << endl;//错误cout << typeid(auto).name() << endl;//错误

例子

#include #include templateauto add(T t, U u) { return t + u; } // 返回类型是 operator+(T, U) 的类型// 在其所调用的函数返回引用的情况下// 函数调用的完美转发必须用 decltype(auto)templatedecltype(auto) PerfectForward(F fun, Args&&... args) {     return fun(std::forward(args)...); }template // C++17 auto 形参声明auto f() -> std::pair // auto 不能从花括号初始化器列表推导{    return {n, n};}int main(){    auto a = 1 + 2;            // a 的类型是 int    auto b = add(1, 1.2);      // b 的类型是 double    static_assert(std::is_same_v);    static_assert(std::is_same_v);    auto c0 = a;             // c0 的类型是 int,保有 a 的副本    decltype(auto) c1 = a;   // c1 的类型是 int,保有 a 的副本    decltype(auto) c2 = (a); // c2 的类型是 int&,为 a 的别名    std::cout << "a, before modification through c2 = " << a << '\n';    ++c2;    std::cout << "a, after modification through c2 = " << a << '\n';    auto [v, w] = f<0>(); // 结构化绑定声明    auto d = {1, 2}; // OK:d 的类型是 std::initializer_list    auto n = {5};    // OK:n 的类型是 std::initializer_list//  auto e{1, 2};    // C++17 起错误,之前为 std::initializer_list    auto m{5};       // OK:C++17 起 m 的类型为 int,之前为 initializer_list//  decltype(auto) z = { 1, 2 } // 错误:{1, 2} 不是表达式    // auto 常用于无名类型,例如 lambda 表达式的类型    auto lambda = [](int x) { return x + 3; }; //  auto int x; // 于 C++98 合法,C++11 起错误//  auto x;     // 于 C 合法,于 C++ 错误}

可能的输出:

a, before modification through c2 = 3a, after modification through c2 = 4

区别

auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

auto varname = value;
decltype(exp) varname = value;

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。

auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。 

你可能感兴趣的:(C++11,c++11)