目录
  • 引子
  • 标准演进
  • C++11
  • C++14
    • 放宽对不完整类型的限制
    • decltype(auto)
  • C++17
    • 总结

      引子

      在编程过程中,有时我们需要根据表达式的类型来声明变量,尤其是在涉及模板编程和泛型编程时,经常会遇到这样的问题:(1)、有些泛型类型由模板参数决定,但是却很难或根本无法表示;(2)、需要在编译时确定变量的类型。

      除此之外,我们知道auto在自动类型推导时,会忽略类型的修饰符。如此会导致auto推导的类型会与原表达式的类型存在不一致问题。

      为了更好的解决这些问题,从C++11标准开始,C++引入了decltype关键字,其作用是让编译器在编译时识别表达式的类型,方便的的进行类型推导,同时也解决泛型编程和模板编程中变量类型表示的问题。

      标准演进

      decltype是declare type的缩写。C++11标准引入了decltype的核心功能和推导规则,C++11以后的各标准都本别对C++11自定的规则进行扩容和改进。具体演进过程如下所示:

      • C++11:引入关键字,并引入decltype的核心功能,用于根据表达式推导出变量的类型;
      • C++14:引入两个重要改进
      • 引入decltype(auto)语法,此语法可用于函数返回值类型的推导。基于decltype(auto)语法,函数的返回值类型可通过函数体的返回值表达式来推导,从而简化函数返回值类型的声明。
      • 放宽了对不完整类型的限制:在 C++11 中,如果 decltype 推导的表达式结果是一个不完整类型,那么会导致编译错误。而在 C++14 中,对不完整类型的处理更加宽松,允许使用decltype 推导不完整类型的变量。
      • C++17:decltype(atuo)支持非类型模板形参占位符。

      C++11

      引入关键字,并引入decltype的核心功能,用于根据表达式推导出变量的类型;当使用decltype(e) 推导表达式 e(类型为T)的类型时,C++11标准定义decltype的推导规则如下:

      • 如果是一个未加括号的标识符表达式或类成员访问,那么decltype(e)的推导结果为e类型T;假如不存在这样的实体或e是一组重载函数,那么decltype(e)无法推导。而且推导过程const/volatile 限定符会被忽略;
      • 如果e是一个可调用对象,那么decltype(e)推导为可调用对象返回值的类型;
      • 如果e是一个左值,decltype(e)推导为T&。const/volatile 限定符不能忽略;
      • 如果e是一个将亡值,decltype(e)推导为T&&,const/volatile 限定符不可忽略;
      • 如decltype(e)无法命中上述4情况,decltype(e)将会推导为e的类型T;

      为了让大家更形象的理解这5条规则,下面我们通过一些示例来说明这五条推导规则。

      示例 1: 未加括号标识符表达式

      int x = 42;
      decltype(x) y; // 推导结果是 int,满足第1条规则

      示例 2: 加括号的标识符表达式

      int x = 42;
      decltype((x)) y = x; // 推导结果是 int&,满足第三条规则

      示例3:未加括号的类成员访问

      struct MyClass {
          int member;
      };
      const MyClass obj;
      decltype(obj.member) result = obj.member; // 推导结果是 int, 忽略const/volatile 限定符,满足第1条规则

      示例4:加括号的类成员访问

      struct MyClass {
          int member;
      };
      const MyClass obj;
      decltype((bj.member)) result = obj.member; // 推导结果是 const int&, const/volatile 限定符不能忽略,满足第3条规则

      示例 5: 可调用对象表达式

      int add(int a, int b)
      {
          return a + b;
      }
      decltype(add(1, 2)) result; // 推导结果是 int,满足第2条规则

      示例 6: 将亡值

      int x = 42;
      decltype(std::move(x)) result = std::move(x); // 推导结果为int&&,std::move(x) 为将亡值

      示例 7: 右值表达式

      int x = 42;
      decltype(x + 1) result; // 推导结果是 int(右值表达式 x + 1 的类型是 int)

      示例8:右值引用变量

      int&& i = 500;
      decltype(i) x2;           // x2的类型是int&&,满足第5条

      C++14

      C++14主要引进了两个重要改进,他们分别是:放宽对不完整类型的限制;引入decltype(auto)语法。

      放宽对不完整类型的限制

      C++11标准要求decltype在使用时,推导的表达式必须是完整类型。如果decltype推导的表达式是一个不完整类型,例如某个类的声明但尚未定义,那么会导致编译错误。C++14对这个限制进行了放宽,允许使用decltype推导不完整类型的变量。这使得编写一些特定的模板代码更加方便,因为在某些情况下,可能需要推导出不完整类型。

      但是,虽然C++14放宽了对不完整类型的限制,但仍然要求推导的表达式在使用时必须是可见的,即需要在推导时至少对类型进行了前向声明。否则,将会导致编译错误。

      以下是一个示例,演示如何在泛型编程中使用 decltype 推导不完整类型:

      template <typename T>
      struct Container
      {
          using ValueType = decltype(*std::declval<T>()); // 使用 decltype 推导不完整类型
          // 其他成员和函数...
      };
      int main()
      {
          Container<std::vector<int>> container;
          using ValueType = typename decltype(container)::ValueType; // 推导结果为 int&
          return 0;
      }

      decltype(auto)

      除了放宽对不完整类型的限制,C++14还有一个特色就是decltype(auto)decltype(auto)作用是告诉编译器auto的推导规则遵循decltype而非auto。不过有一点需要注意就是decltype(auto)必须单独声明,不能与其他相结合。所以下述声明是不合法的:decltype(auto)*const decltype(auto), volatile decltype(auto)

      decltype(auto) 的推导规则如下:

      • 如果初始化表达式是一个标识符表达式,那么decltype(auto)推导为表达式的类型(const/volatile 限定符和引用修饰符不能忽略);
      • 如果初始化表达式是一个函数调用表达式,那么decltype(auto)推导为函数调用表达式的返回类型;
      • 如果初始化表达式是一个左值表达式(如变量名、数组名、成员访问等),那么decltype(auto)推导为对应左值类型的引用类型(const/volatile 限定符和引用修饰符不能忽略)。
      • 如果初始化表达式是一个右值表达式(如字面值、临时对象、表达式的结果等),那么decltype(auto)推导为对应右值的类型(const/volatile 限定符和引用修饰符不能忽略)。
      • 如果初始化表达式是一个将亡值(如移动赋值),那么decltype(auto)推导为对应类型的右值引用

      示例 1:标识符表达式

      int x = 42;
      decltype(auto) y = x; // 推导结果是 int(x 的类型)

      示例 2:函数调用表达式

      int add(int a, int b)
      {
          return a + b;
      }
      decltype(auto) result = add(1, 2); // 推导结果是 int(add 函数返回类型)

      示例 3:左值表达式

      const int x = 42;
      decltype(auto) ref = (x); // 推导结果是 const int&(x 的引用类型)

      示例4:右值表达式

      decltype(auto) x2 = 50; // 推导结果是 int

      示例4:将亡值

      int x2 = 50;
      decltype(auto) x3 = std::move(x2); // 推导结果为int&&

      除了变量类型推导以外,在C++14中引入了decltype(auto)作为一种返回类型的语法。它用于在函数声明中指定返回类型,该返回类型将从函数体中的表达式推导而来。

      为了更好的理解decltype(auto)作为一种返回类型的语法,我们参考下面三种函数返回类型自动推导定义方式。

      第一种: C++14 基于auto新特性返回值类型自动推导

      template<typename Container, typename Index>
      auto accessOrUpdate(Container& c, Index i) { 
        return c[i];  // 返回类推导为c[i]的类型,而且会异常引用限制      
      }
      std::vector<int> v{1,2,3,4,5};
      accessOrUpdate(v,2) = 10;      // 编译错误,不允许赋值

      第二种:C++14 基于auto和decltype实现返回值类型推导

      template <typename Container, typename Index>
      auto accessOrUpdate(Container &c, Index i) -> decltype(c[i]) {
        return c[i];
      }
      std::vector<int> v{1,2,3,4,5};
      accessOrUpdate(v,2) = 10;

      第三种:C++14 decltype(auto)实现返回值类型推导

      template <typename Container, typename Index>
      decltype(auto) accessOrUpdate(Container &c, Index i) {
        return c[i];
      }
      std::vector<int> v{1,2,3,4,5};
      accessOrUpdate(v,2) = 10;

      对比上述三种函数返回值类型推导,decltype(auto)可让编译器根据表达式的类型自动推导函数的返回类型,而不需要显式地指定返回类型。这种方式可简化代码,而且推导更加灵活。

      C++17

      为与auto交相辉映,C++17开始支持decltype(auto)非类型模板。但是需特别注意的是在C++17标准中,非类型模板参数类型必须是整理类型(int, short, long等),枚举类型,指针类型,左值引用类型和std::nullptr_t,而自定义类型,浮点数和字符串则不允许作非类型模板参数。

      template<decltype(auto) n>  // C++17 decltype(auto)形参声明
      auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
      {
          return {n, n};
      }
      f<5>();      // n为int
      f<(5)>();    // n为int&
      f<'a'>();    // n为char
      f<('a')>();  // n为char&
      f<1.0>();    // 编译失败double不能作为模板参数,double不允许做非类型模板参数。

      C++20允许字面量类类型作为非类型模板参数。例如在C++20之前,下述代码无法编译通过,而在C++20中则可以编译通过。

      class A {};
      template <A a>
      class B {};
      A a;
      B<a> b;  // C++20 前编译失败,C++20 可以编译成功。

      总结

      本文从泛型编程中经常会遇到2个常见问题入手,循序渐进的分析了从C++11开始引入的关键字decltype,希望本文可以对大家有所帮助。

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。