天天看点

C++11/14/17 auto & decltypeC++98/03中的autoC++11中的auto与decltypeC++14中的auto与decltypeC++17中的auto与decltype

C++98/03中的auto

C++98/03中的auto存储标识符,用来标识临时变量。现已经废止:

// C++98/03
auto int x = ;

// C++11开始已经废止
auto int x = ;  // error
           

C++11中的auto与decltype

auto

C++11将auto存储标识符的语义废止,改成了类型指示符,在变量声明中,表示用变量的初始化表达式来推导变量的类型。在函数声明中,起占位符作用,表示函数包含尾置返回类型(trailing-return-type)。

C++11引入auto以后,带来了很多好处和改进,例如:

  • 简化过长的类型名:
    // C++98/03 
    // 过长的类型名称不仅书写麻烦,还很容易忘记。
    // 而且很多时候不需要知道具体的类型,像这个例子一样,
    // 只需要知道通过调用std::find()得到的是一个迭代器,
    // 并且通过这个迭代器可以进行比较,解引用等操作即可。
    std::vector<std::string> vec;
    // ...
    std::vector<std::string>::iterator iter = 
        std::find(vec.begin(), vec.end(), "something");
    if (iter != vec.end())
    {
        // ...
    }
    
    // C++11
    std::vector<std::string> vec;
    // ...
    // 用auto声明变量,无需手写复杂的类型
    auto iter = std::find(vec.begin(), vec.end(), "something");
    if (iter != vec.end())
    {
        // ...
    }
               
  • 用auto确定模板参数的计算结果:
    // C++98/03
    // 无法确定模板参数的计算结果
    template <typename T, typename U>
    void f(T t, U u)
    {
        ??? v = t + u;  // v 应该是什么类型?
    }
    
    // C++11
    // 通过auto声明变量,可以用任何表达式推导出变量的类型,
    // 包括模板参数所组成的表达式
    template <typename T, typename U>
    void f(T t, U u)
    {
        auto v = t + u;  // 自动推导 t+u 结果的类型
    }
               
  • 用auto和decltype推断函数模板的返回值类型(decltype见下文):
    // C++98/03
    // 无法确定函数模板的返回值类型
    template <typename T, typename U>
    ??? f(T t, U u)  // 返回值应该是什么类型?
    {
        return t + u;
    }
    
    // C++11
    // 用auto告知编译器模板的返回类型需要自动推导
    // 用decltype(expression)告知编译器返回类型根据expression推导
    // -> decltype(expression) 这种表示法叫做尾置返回类型
    // C++14对这种情况进行了改进,见下文
    template <typename T, typename U>
    auto f(T t, U u) -> decltype(t + u)
    {
        return t + u;
    }
               

使用和注意事项

  • auto声明中初始化表达式可以是任何合法的表达式:
    auto x = ;  // 常量
    auto y = x + ;  // 算数表达式
    
    std::vector<std::string> vec;
    auto iter = vec.begin();  // 函数调用表达式
    
    auto f = [](const std::string &) { /*...*/ };  // lambda表达式
               
  • 由于auto需要用初始化表达式推导变量类型,因此auto声明必须初始化:
  • auto声明的变量不能出现在初始化表达式中:
    int x = x;  // ok but useless
    auto y = y;  // error
               
  • auto类型指示符可以与其他类型指示符(const, volatile等)合用:
    const auto x = ;
    volatile auto y = ;
    inline auto x = ;  // C++17
    
    constexpr int getBufferSize() { return ; }
    constexpr auto buffer_size = getBufferSize();
               
  • 声明符(declarator)可以指定该变量是指针、引用等类型:
    int i = ;
    auto &ri = i;  // ri 是引用类型
    auto *pi = &i;  // pi 是指针类型
               
  • 可以在声明中包含声明符列表(多个声明符),但是推导的类型必须一致:
    int y = ;
    auto x = , *py = &y, &ry = y;  // ok,所有推导类型一致
    
    float f = ;
    auto r = , *pf = &f;  // 错误!auto由 r = 5 推导为int,
                           // 由 *pf = &f 推导为float,两者不一致
               
  • auto会将初始值列表推导为std::initializer_list:
    auto x = {, };  // x 的类型为 std::initializer_list<int>
    auto y{, }; // y 的类型为 std::initializer_list<double>
    auto z{};        // z 的类型为 std::initializer_list<int>
               
  • 任何可以声明变量的地方都能使用auto:
    //
    // 在条件语句的声明中使用auto
    // 
    
    if (auto result = getResult())
    {
        // ...
    }
    
    switch (auto x = result())
    {
        // ...
    }
    
    
    //
    // 在循环语句的声明中使用auto
    //
    
    for (auto iter = vec.begin(); iter != vec.end(); ++iter)
    {
        // ...
    }
    
    // C++ range for
    for (const auto &str : vec)  
    {
        // ...
    }
    
    while (auto running = isRunning())
    {
        // ...
    }
    
    
    //
    // 在类中声明静态数据常量时使用auto
    //
    
    class Foo
    {
        static const auto bar = ;
    };
               
  • 在new表达式中使用auto:
    auto pi = new auto();
               
  • auto标识函数包含尾置返回类型:
    template <typename T, typename U>
    auto f(T t, U u) -> decltype(t + u)
    {
        return t + u;
    }
               
  • 注意用auto推导一个返回代理类的表达式时可能会有危险。例如

    std::vector<bool>::operator[]

    会返回一个代理类

    std::vector<bool>::reference

    来表示

    std::vector<bool>

    中某个索引下的bool引用:
    std::vector<bool> vec = { true, false };
    
    vec[] = false;  // 将第一个元素的值改为false
    // 等价于:
    std::vector<bool>::reference &&r = vec[];  // 返回一个代理对象,表示索引为0的bool值
    r = false;  // 通过代理对象改变索引为0的bool值,需要注意的是
                // std::vector<bool>::reference保留了std::vector<bool>内部数据的引用
    
    
    bool status = vec[];  // 将第一个元素的值赋值给status
    // 等价于:
    std::vector<bool>::reference &&r = vec[];  // 返回一个代理对象,表示索引为0的bool值
    bool status = r;  // std::vector<bool>::reference定义了operator bool以转换成bool值
               
    如果

    std::vector<bool>

    是一个临时对象,则不可将

    operator[]

    的返回值赋值给任何

    std::vector<bool>::reference

    类型的变量,包括auto推导的

    std::vector<bool>::reference

    类型的变量:
    std::vector<bool> getResults()
    {
        std::vector<bool> vec = { true, false };
        return vec;
    }
    
    std::vector<bool>::reference 
        r = getResults()[];  // getResults()返回临时std::vector<bool>对象,
                              // getResults()[0]返回std::vector<bool>::reference对象,该对象
                              // 保留了临时std::vector<bool>对象内部数据的引用,
                              // 赋值给r之后,r同样保留了临时std::vector<bool>对象内部数据的引用,
                              // 执行完该语句之后临时std::vector<bool>对象被销毁,
                              // r引用了无效的数据
    bool status = r;  // 未定义行为,r内部引用的数据已经无效
    
    // 等价于:
    auto r = getResults()[];  // auto 推导为 std::vector<bool>::reference
    bool status = r;  // 未定义行为,r内部引用的数据已经无效
               
    解决方法是隐式或显式转型,从而抛弃代理对象:
    bool status = getResults()[];  // 将std::vector<bool>::reference隐式转型为bool
    auto r = static_cast<bool>(getResults()[]);  // 显式转型
               

类型推导规则

  1. 类型标识符是指针或引用,但不是forwarding reference(universal reference by Scott Meyers)。则:忽略初始化表达式引用符号(&),再进行模式匹配:
    const int& x = ; // 忽略&再匹配
    auto& a = x;  // auto deduced to const int, a is const int&
    
    int y = ;
    const auto& b = y; // auto deduced to int, b is const int&
    
    const int* z = nullptr;
    auto* c = z;  // auto deduced to const int, c is const int*
    const auto* d = z;  // auto deduced to int, d is const int*
               
  2. 类型标识符是forwarding reference。则:如果初始化表达式是左值,则匹配为左值引用;如果初始化表达式是右值,则匹配为右值引用:
    int x = ;
    auto&& a = x;  // auto deduced to int&, a is int&
    auto&& a2 = std::move(x);  // auto deduced to int&&, a2 is int&&
               
  3. 类型标识符既不是指针也不是引用。则:如果初始化表达式为引用,则忽略引用符号(&),忽略后,继续忽略顶级(top-level)const和volatile,再进行模式匹配:
    const int& x = ;  // 忽略&,然后忽略顶级const
    auto a = x;  // auto deduced to int, a is int
    
    const int *const px = &x;  // 忽略顶级const
    const auto b = px;  // auto deduced to int*, b is const int*
               

decltype

C++11引入的decltype用来推导表达式的类型。在变量声明中,可以用这个推导出的类型的作为变量的类型,而不必用该表达式作为变量的初始值。在函数(尤其是函数模板)声明中,可以用这个推导出的类型作为返回值。

  • 用decltype声明变量:
    decltype(foo()) bar;  // bar的类型为foo()的返回类型
                          // 在推断foo()返回类型时,不对foo()求值
               
  • decltype用在尾置返回类型中:
    template <typename T, typename U>
    auto f(T t, U u) -> decltype(t + u)  // 函数返回值为decltype(t + u)
    {                                    // auto为占位符,表示类型在参数列表后面
        return t + u;
    }
               

使用和注意事项

  • decltype的类型推导规则和auto不同,decltype在类型推导时完全复制表达式的类型:
    const int &x = ;
    decltype(x) y = ;  // y 的类型为 const int &
    
    std::vector<int> w;
    decltype(w) vec;  // vec 的类型为 std::vector<int>
               
  • decltype应用于产生左值的表达式则推导的结果为引用:
    int x = ;
    decltype(x) y = ;  // y 的类型是 int
    decltype(*px) rx = x;  // *px是产生左值的表达式,rx 类型为 int&
    decltype((x)) z = x;  // (x)是产生左值的表达式,因此 z 的类型为 int&
               
  • 声明函数模板时,用于推导由参数类型决定的返回值类型:
    template <typename T, typename U>
    auto f(T t, U u) -> decltype(t + u)
    {                               
        return t + u;
    }
               

C++14中的auto与decltype

C++14丰富了auto与decltype的用法:

  • 用auto声明函数返回类型:
    template <typename T, typename U>
    auto sum(T x, U y)  // 根据return后面的表达式自动推断返回类型
    {                   // 注意这里是以auto的类型推导规则来推断返回类型
        return x + y;
    }
               
  • auto可用于lambda表达式的参数:
    // C++11
    std::vector<std::map<std::string, std::string>> vec;
    // 必须指明lambda表达式参数类型
    auto f = [](const std::vector<std::map<std::string, std::string>> &vec){ /*...*/ };
    f(vec);
    
    // C++14
    std::vector<std::map<std::string, std::string>> vec;
    // 用auto推导参数类型
    auto f = [](const auto &vec){ /*...*/ };
    f(vec);
               
  • 用decltype(auto)声明函数返回类型:
    // C++11
    template <typename T, typename U>
    auto sum(T x, U y) -> decltype(x + y)  // 尾置返回类型
    {
        return x + y;
    }
    
    // C++14
    template <typename T, typename U>
    decltype(auto) sum(T x, U y)  // 根据return后面的表达式自动推断返回类型
    {                             // 注意这里是以decltype的类型推导规则来推断返回类型
        return x + y;
    }
               

C++17中的auto与decltype

  • C++17明确了用初始值列表初始化auto变量的语义:
    // C++11
    auto x = {, };  // x 的类型为 std::initializer_list<int>
    auto y{, }; // y 的类型为 std::initializer_list<double>
    auto z{};        // z 的类型为 std::initializer_list<int>
    
    // C++17
    auto x = {, };  // x 的类型为 std::initializer_list<int>
    auto y{, }; // error! 直接列表初始化时列表中只能有一个值
    auto z{};        // 直接列表初始化,z 的类型为int
               
  • C++17可以在if和switch中声明变量,因此在if和switch里也可以使用auto:
    if (auto iter = vec.begin(); iter != vec.end())  // c++
    {
        // ...
    }
    
    switch (auto status = getResponse(); status.code)  // c++
    {
        // ...
    }
               
  • 在结构化绑定(Structured binding)声明时使用auto:
    struct S { int x1 : ; volatile double y1; };
    S f();
    const auto [ x, y ] = f();
               

继续阅读