• Effective Modern C++[实践]->理解decltype


    何谓decltype

    decltype 是“declare type”的缩写,译为“声明类型”。
    decltype说明符是c++ 11引入的另一个新特性。decltype检查实体的声明类型,或表达式的类型和值类别。
    语法如下:

    decltype ( 实体 ) 	  (1) 	  (C++11)
    decltype ( 表达式 ) 	  (2) 	  (C++11)
    
    • 1
    • 2

    推导规则说明:

    规则1

    1. 如果实参是没有括号的标识表达式或没有括号的类成员访问表达式,那么 decltype 产生以该表达式命名的实体的类型。如果没有这种实体或该实参指名了一组重载函数,那么编译错误。
    • 示例1
    #include <vector>
    int main() {
    	int x = 42;
        std::vector<decltype(x)> v(100, x); // v 是一个 vector<int>
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    生成的代码如下

    #include <vector>
    int main(){
      int x = 42;
      std::vector<decltype(x)> v = std::vector<int, std::allocator<int> >(100, x, std::allocator<int>());
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 示例2
    struct S {
        int x = 42;
    };
    const S s;
    decltype(s.x) y; // Y的类型是int,尽管s.x是const
    
    • 1
    • 2
    • 3
    • 4
    • 5

    生成的代码如下

     struct S{
      int x;
      // inline constexpr S() noexcept = default;
    };
    int main(){
      const S s = S();
      int y;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 示例3
    int func(){
        return 0;
    }
    int func(int a){
        return 0;
    }
    int main(){
        int i = 4;
        // 不正确的使用。Func为重载函数命名
        decltype(func) var1;
        // 正确的使用。重载操作没有歧义
        decltype(func(i)) var2;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    规则2

    1. 如果实参是其他类型为 T 的任何表达式,且
      1. 如果 表达式 的值类别是亡值,将会 decltype 产生 T&&
      2. 如果 表达式 的值类别是左值,将会 decltype 产生 T&
      3. 如果 表达式 的值类别是纯右值,将会 decltype 产生 T。该类型不需要是完整类型或拥有可用的析构函数,而且类型可以是抽象的。此规则不适用于其子表达式:decltype(f(g())) 中,g() 必须有完整类型,但 f() 不必。

    注意如果对象的名字带有括号,那么它会被当做通常的左值表达式,从而 decltype(x)decltype((x)) 通常是不同的类型。在难以或不可能以标准写法进行声明的类型时,decltype 很有用,例如 lambda 相关类型或依赖于模板形参的类型。
    示例1

    int f() { return 42; }
    int& g() { static int x = 42; return x; }
    
    int main() {
    	int x = 42;
    	decltype(f()) a = f(); // a的类型是int
    	decltype(g()) b = g(); // b的类型是int&
    	decltype((x)) c = x;   // c的类型是int&,因为x是左值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    生成的代码如下:

    int f(){ return 42;}
    int & g(){
      static int x = 42;
      return x;
    }
    int main(){
      int x = 42;
      int a = f();
      int & b = g();
      int & c = x;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    综合示例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    struct A { double x; };
    
    int&& f1(void);  //返回值为 int&&
    
    int main() {
        int var = 1;
        
        const A *a = new A();
        decltype(f1()) a1 = 0 ;
    	decltype(var) a2 ;
    	decltype(a->x) a3;
        decltype((a->x)) a4 =11;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    编译生成代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    
    using namespace std;
    
    struct A
    {
      double x;
    };
    int && f1();
      //返回值为 int&&
    
    int main()
    {
      int var = 1;
      const A * a = new A();
      
      int && a1 = 0;//decltype(f1()) a1 = 0 ;
      int a2;//decltype(var) a2 ;
      double a3;//decltype(a->x) a3;
      const double & a4 = 11;//decltype((a->x)) a4 =11;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    decltype类型说明
    decltype(f1()) a1const int&& 一个指向const int对象的右值引用。
    decltype(var);int 变量var的类型。
    decltype(a->x); double成员访问的类型。
    decltype((a->x)) a4const double&内部圆括号导致语句被作为表达式而不是成员访问进行计算。由于a被声明为const指针,该类型是const double类型的引用。

    应用

    应用1

    c++11中,比较典型的应用示例是decltypetype\using的合用。

    using size_t = decltype(sizeof(0));
    using ptrdiff_t = decltype((int*)0-(int*)0);
    using nullptr_t = decltype(nullptr);
    
    • 1
    • 2
    • 3

    应用2

    1. c++11
    template<typename T, typename U>
    auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }
    
    int main() {
    	int x = 42;
      	double y = 77.22;
      	add(x,y);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    生成代码如下:

    template<typename T, typename U>
    auto add(T&& t, U&& u) -> decltype(t+u) { return t + u; }
    
    /* First instantiated from: insights.cpp:8 */
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    double add<int &, double &>(int & t, double & u)
    {
      return static_cast<double>(t) + u;
    }
    #endif
    int main()
    {
      int x = 42;
      double y = 77.219999999999999;
      add(x, y);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. c++14
    template<typename T, typename U>
    decltype(auto) add1(T&& t, U&& u){ return t + u; }
    
    int main() {
    	int x = 42;
      	double y = 77.22;
      	add1(x,y);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    生成的代码如下:

    template<typename T, typename U>
    decltype(auto) add1(T&& t, U&& u){ return t + u; }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    double add1<int &, double &>(int & t, double & u){
      return static_cast<double>(t) + u;
    }
    #endif
    int main(){
      int x = 42;
      double y = 77.219999999999999;
      add1(x, y);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    书中示例研习

    缺陷代码1

    template<typename T, typename U>
    auto authAndAcess(T &t,U u){
    	return t[u];
    }
    
    • 1
    • 2
    • 3
    • 4

    此代码调用authAndAcess(d,5)后推导代码如下:

    template<>
    int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
    {
      return t.operator[](u);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    原因为:auto关键字进行类别推导时会忽略引用特性,因此虽然d[5]返回的时int &,但是最终却是int的结果。因此无法执行authAndAcess(d,5)=10

    缺陷代码2

    //推导为int &,但是无法接收右值容器
    template<typename T, typename U>
    decltype(auto) authAndAcess1(T &t,U u){
    	return t[u];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此代码调用authAndAcess(d,5)后推导代码如下:

    template<>
    int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
    {
      return t.operator[](u);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以实现赋值了,但是形参第一个无法接收右值

    c++14的最终版本如下

    #include <iostream>
    #include <deque>
    using namespace std;
    //推导为int ,无法对返回值赋值
    template<typename T, typename U>
    auto authAndAcess(T &t,U u){
    	return t[u];
    }
    //推导为int &,但是无法接收右值容器
    template<typename T, typename U>
    decltype(auto) authAndAcess1(T &t,U u){
    	return t[u];
    }
    //c++ 14的最终版本
    template<typename T, typename U>
    decltype(auto) 
    authAndAcess2(T &&t,U u){
    	return std::forward<T>(t)[u];
    }
    
    std::deque<std::string> makeStringDeque(){
    	return {"1","2","3","4","5"};
    }
    int main() {
    	std::deque<int> d={0,1,2,3,4,5,6,7};
      
      	authAndAcess(d,5);
      	authAndAcess1(d,5)=10;
        //note: candidate function [with T = std::deque<std::basic_string<char>>, U = int] not viable: expects an lvalue for 1st argument
      	//authAndAcess1(makeStringDeque(),2);//第一个参数是左值,不能传入右值
      	auto s = authAndAcess2(makeStringDeque(),5);
      	authAndAcess2(d,5)= 12;
      	//std::deque<std::string> makeStringDeque();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    编译后代码生成的代码如下:

    #include <iostream>
    #include <deque>
    using namespace std;
    //推导为int ,无法对返回值赋值
    template<typename T, typename U>
    auto authAndAcess(T &t,U u){
    	return t[u];
    }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    int authAndAcess<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
    {
      return t.operator[](u);
    }
    #endif
    
    //推导为int &,但是无法接收右值容器
    template<typename T, typename U>
    decltype(auto) authAndAcess1(T &t,U u){
    	return t[u];
    }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    int & authAndAcess1<std::deque<int, std::allocator<int> >, int>(std::deque<int, std::allocator<int> > & t, int u)
    {
      return t.operator[](u);
    }
    #endif
    
    //c++ 14的最终版本
    template<typename T, typename U>
    decltype(auto) authAndAcess2(T &&t,U u){
    	return std::forward<T>(t)[u];
    }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    std::basic_string<char> & authAndAcess2<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >, int>(std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > && t, int u)
    {
      return std::forward<std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > >(t).operator[](static_cast<unsigned long>(u));
    }
    #endif
    
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    int & authAndAcess2<std::deque<int, std::allocator<int> > &, int>(std::deque<int, std::allocator<int> > & t, int u)
    {
      return std::forward<std::deque<int, std::allocator<int> > &>(t).operator[](static_cast<unsigned long>(u));
    }
    #endif
    
    
    std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > > makeStringDeque()
    {
      return std::deque<std::basic_string<char>, std::allocator<std::basic_string<char> > >{std::initializer_list<std::basic_string<char> >{std::basic_string<char>(std::basic_string<char>("1", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("2", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("3", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("4", std::allocator<char>())), std::basic_string<char>(std::basic_string<char>("5", std::allocator<char>()))}, std::allocator<std::basic_string<char> >()};
    }
    
    int main()
    {
      std::deque<int> d = std::deque<int, std::allocator<int> >{std::initializer_list<int>{0, 1, 2, 3, 4, 5, 6, 7}, std::allocator<int>()};
      authAndAcess(d, 5);
      authAndAcess1(d, 5) = 10;
      std::basic_string<char> s = std::basic_string<char>(authAndAcess2(makeStringDeque(), 5));
      authAndAcess2(d, 5) = 12;
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
  • 相关阅读:
    C++丨初识C++像极了C语言
    想知道电脑录屏软件哪个好用?这三个工具轻松实现屏幕录制
    微信小程序积分活动有哪些玩法
    HarmonyOS NEXT应用开发之Axios获取解析网络数据
    C++的文件操作
    YOLOv5算法进阶改进(5)— 主干网络中引入SCConv | 即插即用的空间和通道维度重构卷积
    Ajax无刷新
    C语言-循环语句
    制作一个企业网站——html华为官网购物商城项目的设计与实现
    code:-9907磁盘不足如何处理?帮你整理了几个必备的!
  • 原文地址:https://blog.csdn.net/MMTS_yang/article/details/125488459