• C++20:换了“心“的auto关键字


    何爲换“心”

    auto关键字是C++98就引入的关键字,C++98标准中,auto声明的变量为自动变量,就是说他具备自动生命周期。由于C++98规定函数代码段中声明的变量默认是auto变量,经常大家在声明变量时直接忽略auto。所以可以看出C++98中auto关键字是一个可有可无的关键字,无任何实质功能。但是从C++11开始,auto从原来可有可无转变成具备实质功能的C++关键字。这就是所谓的auto换“心”。

    C++11标准委员会赋予了auto两个能力:第一,变量定义时可根据初始化表达式自动类型推导的能力;第二,声明函数返回值的占位符,允许函数后置返回类型。

    C++14标准,C++17标准和C++17标准都分别对auto进行了升级扩展;C++14对auto函数占位符进行了简化,lambda形参声明允许使用auto;C++17将auto引入到非类型模板的声明和结构化绑定。C++20允许函数形参声明使用auto。

    auto的标准演进

    C++11

    C++11标准委员会为auto引入了两个能力:第一,变量定义时可根据初始化表达式自动类型推导的能力;第二,声明函数返回值的占位符,允许函数后置返回类型。例如:

    auto value = 1;   // 推导为int
    auto name = "liuguang"; // 推导为const char *
    
    auto min(int a, int b) -> int   // auto为int的占位符,这种返回类型后置是C语言早期的一种函数声明方式。
    {
    	return (a < b) ? (a): (b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由于auto会在编译过程中自动完成类型推导,因此在编码过程中要确保代码类型可推导。例如:

    auto i;  // 编译失败,编译期无法决策i的类型
    
    • 1

    此处之所以会编译错误,原因在于没有对i进行初始化,编译器无法确认i的具体类型。

    C++14

    C++14引入三个重要特性:第一,decltype(auto) ;第二,支持函数或 lambda 表达式的返回类型声明为auto,允许return语句的操作数推导返回auto类型。第三,允许lambda表达式使用auto形参声明。

    auto x = 1 + 2;
    decltype(auto) y = x;           // y的类型是int,持有x的副本          
    decltype(auto) z = (x);         // z的类型int&, z是x的引用  
    
    template<class T, class U>
    auto add(T t, U u)              // 返回类型是 operator+(T, U) 的类型
    { 
    	return t + u; 
    }
    
    auto lambda = [](int x) { return x + 3; };
    
    auto lambda1 = [](auto a, auto b) {return a + b;}; // lambda1的返回类型为decltype(a+b)
    auto value = lambda1(1, 3.0);   // value 被推导为double
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    C++17

    从C++17标准开始,auto开始支持非类型模板的声明,auto非类型模板同样需要遵守非类型模板标准,即推导出的类型必须是可作为模板实参的。

    template<auto n>   // C++17 auto 形参声明
    auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
    {
        return {n, n};
    }
    
    f<5>();      // n为int
    f<'a'>();    // n为char
    f<1.0>();    // 编译失败double不能作为模板参数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    除非类型模板声明,C++17标准还允许auto 说明符应用于结构化绑定声明。例如:

    auto [v, w] = f<100>(); // 结构化绑定声明
    
    • 1

    C++20

    函数形参声明允许使用auto关键字 ,这种函数声明可简化函数模板。

    void f1(auto); // 与 template void f(T) 相同
    
    auto min(auto a, auto b)
    {
    	return (a < b) ? (a) : (b);
    }
    
    auto val1 = min(2, 5.0);  // val1推导为double
    auto val2 = min(2.0, 5.0);// val2推导为double
    auto val3 = min(2.0, 5);  // val3推导为double
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    auto推导规则

    使用auto占位符声明变量必须初始化变量,由于初始化表达式存在多种形式,不同形式的表达式auto的推导规则也不尽相同,此部分主要介绍auto的推导规则。

    按值初始化忽略cv限定符

    在auto声明变量时,如果没有使用引用也没有使用指针。那么编译期推导时会忽略cv限定符。但是如果auto声明时使用了指针或引用,那么cv限定符不会忽略。例如:

    const int i = 12;
    auto j = i;   // j会被推导为int
    auto &k = i;  // k声明时使用&,auto会被推导为const int,k类型为const int&
    auto *x = &i; // x声明使用指针* auto会被推导为const int, x类型为const int*
    const auto y = i; // auto 推导为int, y类型为const int
    
    • 1
    • 2
    • 3
    • 4
    • 5

    按引用初始化忽略引用属性

    如果使用一个引用对象去初始化auto类型对象,那么原对象的引用属性会被忽略。例如:

    const int i = 12;
    auto &k = i;  // k声明时使用&,auto会被推导为const int,k类型为const int&
    auto j = k;   // j 会推导为int 因为此处k的引用属性将会被忽略
    
    • 1
    • 2
    • 3

    auto与&&

    在此需要特别说明的是auto与&&结合时,&&是万能引用而非右值引用。

    如果初始化表达式为左值,根据&&引用折叠原理auto会决策为引用,如果初始化表达式为右值,auto会推导为表达式字面值类型。

    int i = 100;
    auto&& j = i;     // 引用折叠,auto&& 推导为int&, 所以j的类型为int&
    auto&& k = 200;   // 引用折叠,auto&& 推导为int&&, 所以k的类型为int&&
    
    • 1
    • 2
    • 3

    auto同数组和函数

    auto与数组结合,数组会退化为数组指针,auto与函数结合,函数也会退化为函数指针。

    int array[100] = {0};
    auto i = array;   // i推导为int*
    
    auto min(int a, int b) -> int 
    {
    	return (a < b) ? (a): (b);
    }
    
    auto fn = min;   // fn 推导的类型为int (*)(int, int)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    &&与三目运算符

    &&与三目运算符结合进行类型推导,编译期总是使用数据范围更大的类型。例如:

    auto min = (true) ? 100, 2.3;  // min会被推导为double,因为double比int类型范围更大。
    
    • 1

    auto与多变量

    如果一个auto关键字声明多个变量,那么auto将会使用左结合性,也就是就近结合类型推导。此时只有满足声明类型统一才可以通过编译。否则会提示编译失败。

    int value = 8;
    auto *pValue = &value, n = 10;  // auto 推导为int, n也为int,可以编译通过
    
    auto *pValue = &value, m = 10.0; // auto 推导为int, m也为int,m不能赋值10.0
    
    • 1
    • 2
    • 3
    • 4

    auto与成员声明初始化

    虽然C++11支持类成员变量声明初始化,但是auto却无法应用于类非静态成员初始化。仅可以应用于const static 成员变量。

    struct Person
    {
    	auto age = 15;  // 编译失败,不允许这么声明
    };
    
    struct Person
    {
    	static const auto age = 15; // C++11可以编译通过 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在C++17标准,进一步放松限制,允许static auto成员变量声明初始化。

    struct Person
    {
    	static auto age = 15; // C++17可以编译通过 
    };
    
    • 1
    • 2
    • 3
    • 4

    auto与列表初始化

    C++11标准下,直接列表初始化和等号赋值列表初始化,均推导为std::initializer_list。

    auto d = {1, 2};  // d的类型是 std::initializer_list
    auto n = {5};     // n 的类型是 std::initializer_list
    auto e{1, 2};     // C++11标准,e的类型是std::initializer_list
    auto m{5};        // C++11标准,m的类型是std::initializer_list
    
    • 1
    • 2
    • 3
    • 4

    C++17标准下,等号赋值列表初始化推导为std::initializer_list,直接赋值初始化仅允许单个元素。

    auto d = {1, 2};  // d的类型是 std::initializer_list
    auto n = {5};     // n 的类型是 std::initializer_list
    auto e{1, 2};     // C++17标准,编译报错误
    auto m{5};        // C++11标准,m的类型是int
    
    • 1
    • 2
    • 3
    • 4

    两份代码一模一样,读者可对比C++11和C++17 auto列表初始化类型推导差异。

    decltype(auto)

    decltype(auto) 是C++14标准引入的占位约束。意义在于通过decltype推导表达式规则来推导auto。

    auto a = 1 + 2;          // a 的类型是 int
    decltype(auto) c1 = a;   // c1 的类型是 int,保有 a 的副本
    decltype(auto) c2 = (a); // c2 的类型是 int&,它是 a 的别名
    
    • 1
    • 2
    • 3

    C++20中decltype(auto)增加的类型约束功能。关于类型约束笔者会在decltype相关的博客中详细论述。这里就不赘述,仅给出一个例子。

    // 在它调用的函数返回引用的情况下
    // 函数调用的完美转发必须用 decltype(auto)
    template<class F, class... Args>
    decltype(auto) perfectForward(F fun, Args&&... args) 
    { 
        return fun(std::forward<Args>(args)...); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    auto使用场景

    在编码过程中,关于何时使用auto,不同的开发人员会有不同的见解和看法。但是这里面依然存在某些规律可寻。这里为大家介绍几种大家广泛认可的使用场景。

    • 明确知晓初始化类型时可使用auto,例如:
    auto i = 2;
    
    • 1
    • 复杂的类型,例如:lambda表达式,bind以及STL迭代器。lambda和bind类型有时候我们自己都很难描述,而STL迭代器则是因为类型太长而影响代码阅读。
    std::unordered_multimap<std::string, int> unorderedMap;
    
    unorderedMap.insert(std::make_pair("li", 10));
    unorderedMap.insert(std::make_pair("liu", 50));
    unorderedMap.insert(std::make_pair("wu", 49));
    
    std::unordered_multimap<std::string, int>::iterator iter = unorderedMap.find("li"); 
    //  auto iter = unorderedMap.find("li");  使用auto iter定义可简化为auto iter;
    if (iter != unorderedMap.end()) //查找成功
    {
        std::cout << "name: " << iter->first << "age: " << iter->second;
    }
    
    // 你可以准确的描述fnIsEven的具体类型吗? 在此只有auto最简单。
    auto fnIsEven = [](int value) -> bool {
    	return (0 == value % 2);
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    总结

    本文详细为大家介绍了auto关键字的演进过程以及auto关键字的类型推导规则。希望对你有帮助。

  • 相关阅读:
    [激光原理与应用-33]:典型激光器 -5- 不同激光器的全面、综合比较
    vue中绑定class样式和条件渲染
    单片机进阶---PCB开发之照葫芦画瓢(二)
    前端面试中小型公司都考些什么
    Open3D(C++) 深度图像与彩色图像转三维点云
    汽车三元催化器的废品项目详解,三元催化再生项目的回收技术教学
    HJ86 求最大连续bit数
    【Kaggle比赛常用trick】K折交叉验证、TTA
    Linux ALSA驱动之Control设备创建流程源码分析(5.18)
    系统学习区块链、Solidity 和前后端全栈 Web3 开发
  • 原文地址:https://blog.csdn.net/liuguang841118/article/details/128195097