C++泛型编程指南08 auto decltype

### 文章目录

  • [@[TOC]](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [第3章:`auto`占位符(C++11~C++17)](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.1 `auto`关键字的重新定义](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.2 类型推导规则](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.3 何时使用`auto`](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.4 返回类型推导](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.5 在Lambda表达式中使用`auto`](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [3.6 非类型模板参数占位符](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [总结](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [第4章 `decltype`说明符(C++11~C++17)](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.1 回顾 `typeof` 和 `typeid`](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.2 使用 `decltype` 说明符](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.3 推导规则](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.4 cv限定符的推导](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.5 `decltype(auto)`](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [4.6 `decltype(auto)` 作为非类型模板形参占位符](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [总结](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [第5章 函数返回类型后置与推导](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [5.1 使用函数返回类型后置声明函数](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [5.2 推导函数模板返回类型](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)
  • [总结](#文章目录 @[TOC] 第3章:auto占位符(C++11~C++17) 3.1 auto关键字的重新定义 3.2 类型推导规则 3.3 何时使用auto 3.4 返回类型推导 3.5 在Lambda表达式中使用auto 3.6 非类型模板参数占位符 总结 第4章 decltype说明符(C++11~C++17) 4.1 回顾 typeoftypeid 4.2 使用 decltype 说明符 4.3 推导规则 4.4 cv限定符的推导 4.5 decltype(auto) 4.6 decltype(auto) 作为非类型模板形参占位符 总结 第5章 函数返回类型后置与推导 5.1 使用函数返回类型后置声明函数 5.2 推导函数模板返回类型 总结)

第3章:auto占位符(C++11~C++17)

3.1 auto关键字的重新定义

虽然auto关键字自C++98标准以来就已经存在,用于声明自动变量,但C++11为其赋予了新的意义:根据初始化表达式自动推断变量类型,或作为函数返回值类型的占位符。例如:

cpp 复制代码
auto i = 5; // 推断为int
auto str = "hello auto"; // 推断为const char*
auto sum(int a1, int a2) -> int { return a1 + a2; } // 返回类型后置,使用auto作为占位符

重要的是,当编译器无法推导出类型时,使用auto会导致编译失败。

这里有几点需要注意:

  1. 多变量声明 :使用单个auto关键字声明多个变量时,编译器会依据最左边的初始化表达式来推导auto的具体类型:

    cpp 复制代码
    int n = 5;
    auto *pn = &n, m = 10; // pn: int*, m: int

    如果尝试将不同类型的值赋给同一auto声明的不同变量,会导致编译错误:

    cpp 复制代码
    int n = 5;
    auto *pn = &n, m = 10.0; // 编译失败,类型不匹配
  2. 条件表达式初始化 :在使用条件表达式初始化auto变量时,编译器会选择表达能力更强的类型:

    cpp 复制代码
    auto i = true ? 5 : 8.0; // i的数据类型为double
  3. 成员变量声明限制 :尽管C++11允许在声明成员变量时进行初始化,但不允许用auto声明非静态成员变量。然而,在C++11中,可以用const限定符声明静态成员变量,并且在C++17中,即使没有const限定符也可以这样做:

    cpp 复制代码
    struct sometype {
        static const auto i = 5; // C++11
        static inline auto j = 5; // C++17
    };
  4. 函数参数限制 :在C++20之前,不能在函数形参列表中使用auto声明形参(但在C++14中,auto可用于lambda表达式的形参):

    cpp 复制代码
    void echo(auto str); // C++20之前编译失败
3.2 类型推导规则
  1. 忽略cv限定符 :对于按值初始化的auto变量,推导类型时会忽略常量和volatile限定符:

    cpp 复制代码
    const int i = 5;
    auto j = i; // auto推导为int,而非const int
  2. 引用属性被忽略 :如果目标对象是引用,auto推导类型时会忽略引用属性:

    cpp 复制代码
    int i = 5;
    int &j = i;
    auto m = j; // auto推导为int,而非int&
  3. 万能引用 :使用auto&&声明变量时,对于左值auto会被推导为引用类型,而对于右值则不会:

    cpp 复制代码
    int i = 5;
    auto&& m = i; // auto推导为int&(引用折叠)
    auto&& j = 5; // auto推导为int
  4. 数组与函数指针 :当目标对象是数组或函数时,auto会被推导为对应的指针类型:

    cpp 复制代码
    int i[5];
    auto m = i; // auto推导为int*
    
    int sum(int a1, int a2);
    auto j = sum; // auto推导为函数指针类型
  5. 列表初始化 :结合auto和列表初始化时,有特定的规则需遵守(C++17标准):

    • 使用直接列表初始化时,列表中必须是单元素,否则编译失败。
    • 使用等号加列表初始化时,列表可以包含一个或多个相同类型的元素,此时auto类型会被推导为std::initializer_list<T>
    cpp 复制代码
    auto x1 = { 1, 2 }; // x1类型为 std::initializer_list<int>
    auto x2{ 3 }; // x2类型为int

通过上述内容,我们可以更好地理解和运用auto关键字,提高代码的灵活性和可读性。接下来的部分将探讨何时使用auto以及更多高级特性。

3.3 何时使用auto

合理使用auto可以提高代码的灵活性和可读性,尤其是在处理复杂类型或模板时。以下是几种推荐使用auto的情况:

  1. 一眼能看出初始化类型的变量 :当变量的初始化表达式已经清楚地表明了其类型时,使用auto可以使代码更简洁。

    cpp 复制代码
    std::map<std::string, int> str2int;
    // 填充str2int的代码
    for (auto it = str2int.cbegin(); it != str2int.cend(); ++it) {
        // 处理每个元素
    }
    // 或者使用范围for循环
    for (auto &it : str2int) {
        // 处理每个元素
    }
  2. 复杂的类型 :对于那些类型声明冗长或难以书写的类型(如lambda表达式、std::bind等),直接使用auto可以简化代码。

    cpp 复制代码
    auto l = [](int a1, int a2) { return a1 + a2; }; // lambda表达式
    
    int sum(int a1, int a2) { return a1 + a2; }
    auto b = std::bind(sum, 5, std::placeholders::_1); // 使用std::bind
  3. 模板编程:在模板编程中,类型推导可以帮助减少重复代码,特别是在需要处理多种类型的场景下。

  4. 避免显式的类型转换 :使用auto可以避免不必要的显式类型转换,使代码更加自然和易读。

3.4 返回类型推导

C++14引入了对函数返回类型自动推导的支持,允许将返回类型声明为auto。这使得编写泛型代码变得更加简单:

cpp 复制代码
auto sum(int a1, int a2) {
    return a1 + a2; // 编译器会根据返回值推导出返回类型为int
}

需要注意的是,如果一个函数有多个返回路径,并且这些路径返回不同类型的值,则编译器无法进行类型推导,会导致编译错误。

3.5 在Lambda表达式中使用auto

C++14还扩展了lambda表达式的功能,允许在lambda表达式的形参列表中使用auto,从而创建泛型lambda表达式:

cpp 复制代码
auto l = [](auto a1, auto a2) { return a1 + a2; };
auto retval = l(5, 5.0); // a1被推导为int,a2被推导为double,retval被推导为double

此外,lambda表达式还可以通过auto返回引用类型:

cpp 复制代码
auto l = [](int &i) -> auto& { return i; };
auto x1 = 5;
auto &x2 = l(x1);
assert(&x1 == &x2); // 检查是否指向同一内存地址

这是让lambda表达式返回引用类型的唯一方法。

3.6 非类型模板参数占位符

C++17进一步扩展了auto的功能,使其可以用作非类型模板参数的占位符。这意味着可以在模板声明中使用auto作为参数类型:

cpp 复制代码
#include <iostream>

template<auto N>
void f() {
    std::cout << N << std::endl;
}

int main() {
    f<5>(); // N为int类型
    f<'c'>(); // N为char类型
    // f<5.0>(); // 错误:模板参数不能为double
}

这个特性允许模板函数接受任何类型的常量作为参数,只要该类型支持作为模板参数。

总结

通过本章的学习,我们了解了auto关键字在现代C++中的重要作用及其丰富的应用场景。从简化复杂类型声明到支持泛型编程,再到与lambda表达式的结合使用,auto极大地增强了C++代码的灵活性和可维护性。合理利用这些特性,可以帮助开发者编写更加高效、简洁的代码。希望这些内容能帮助你在日常开发中更好地运用auto,提升编码效率和代码质量。

第4章 decltype说明符(C++11~C++17)

4.1 回顾 typeoftypeid

在C++11标准发布之前,GCC提供了一个名为typeof的扩展运算符,用于获取操作数的具体类型。例如:

cpp 复制代码
int a = 0;
typeof(a) b = 5; // b被推导为int类型

除了使用typeof,C++标准还提供了typeid运算符来获取与目标操作数类型相关的详细信息。这些信息存储在一个std::type_info对象中,可以通过调用其成员函数name()来获取类型的名称。例如:

cpp 复制代码
int x1 = 0;
double x2 = 5.5;
std::cout << typeid(x1).name() << std::endl; // 输出 "int"
std::cout << typeid(x1 + x2).name() << std::endl; // 输出 "double"
std::cout << typeid(int).name() << std::endl; // 输出 "int"

关于typeid的几点重要特性:

  1. 生命周期typeid的返回值是一个左值,其生命周期持续到程序结束。

  2. 引用和指针std::type_info删除了复制构造函数,因此只能通过引用或指针保存其结果:

    cpp 复制代码
    auto t1 = typeid(int); // 编译失败,没有复制构造函数
    auto &t2 = typeid(int); // 编译成功,t2推导为const std::type_info&
    auto t3 = &typeid(int); // 编译成功,t3推导为const std::type_info*
  3. 忽略cv限定符typeid忽略类型的cv限定符,即typeid(const T)等同于typeid(T)

4.2 使用 decltype 说明符

C++11引入了decltype说明符,用于获取对象或表达式的类型。其语法类似于typeof

cpp 复制代码
int x1 = 0;
decltype(x1) x2 = 0; // x2被推导为int类型
double x3 = 0;
decltype(x1 + x3) x4 = x1 + x3; // x4被推导为double类型
// decltype({1, 2}) x5; // 编译失败,{1, 2}不是表达式

auto不同,decltype可以在非静态成员变量中使用:

cpp 复制代码
struct S1 {
    int x1;
    decltype(x1) x2; // x2被推导为int类型
    double x3;
    decltype(x2 + x3) x4; // x4被推导为double类型
};

在函数形参列表中使用decltype

cpp 复制代码
int x1 = 0;
decltype(x1) sum(decltype(x1) a1, decltype(a1) a2) {
    return a1 + a2;
}
auto x2 = sum(5, 10); // x2被推导为int类型

为了更好地讨论decltype的优势,考虑以下例子:

cpp 复制代码
auto sum(int a1, int a2) -> int {
    return a1 + a2;
}

在C++11中,auto作为占位符无法使编译器自动推导函数返回类型,必须使用返回类型后置的形式指定返回类型。如果想泛化这个函数以支持各种类型运算,需要使用模板:

cpp 复制代码
template<class R, class T1, class T2>
R sum(T1 a1, T2 a2) {
    return a1 + a2;
}
auto x3 = sum<double>(5, 10.5); // x3被推导为double类型

为了简化代码,可以进一步优化:

cpp 复制代码
template<class T1, class T2>
auto sum(T1 a1, T2 a2) -> decltype(a1 + a2) {
    return a1 + a2;
}
auto x4 = sum(5, 10.5); // x4被推导为double类型

在C++14中,可以直接使用auto进行返回类型推导:

cpp 复制代码
template<class T1, class T2>
auto sum(T1 a1, T2 a2) {
    return a1 + a2;
}
auto x5 = sum(5, 10.5); // x5被推导为double类型

尽管C++14支持对auto声明的返回类型进行推导,但在某些情况下仍然需要使用decltype,例如返回引用类型时:

cpp 复制代码
template<class T>
auto return_ref(T& t) -> decltype(t) {
    return t;
}
int x1 = 0;
static_assert(std::is_reference_v<decltype(return_ref(x1))>); // 编译成功
4.3 推导规则

decltype(e)(其中e的类型为T)的推导规则如下:

  1. 标识符表达式 :如果e是一个未加括号的标识符表达式(结构化绑定除外)或者未加括号的类成员访问,则decltype(e)推断出的类型是e的类型T。如果不存在这样的类型,或者e是一组重载函数,则无法进行推导。
  2. 函数调用 :如果e是一个函数调用或者仿函数调用,那么decltype(e)推断出的类型是其返回值的类型。
  3. 左值 :如果e是一个类型为T的左值,则decltype(e)是T&。
  4. 将亡值 :如果e是一个类型为T的将亡值,则decltype(e)是T&&。
  5. 其他情况 :除去以上情况,则decltype(e)是T。

示例:

cpp 复制代码
const int&& foo();
int i;
struct A {
    double x;
};
const A* a = new A();

decltype(foo()); // const int&&
decltype(i); // int
decltype(a->x); // double
decltype((a->x)); // const double&

更多示例:

cpp 复制代码
int i;
int *j;
int n[10];
const int&& foo();
decltype(static_cast<short>(i)); // short
decltype(j); // int*
decltype(n); // int[10]
decltype(foo); // 函数类型
struct A {
    int operator() () { return 0; }
};
A a;
decltype(a()); // int

复杂表达式示例:

cpp 复制代码
int i;
int *j;
int n[10];
decltype(i=0); // int&
decltype(0,i); // int&
decltype(i,0); // int
decltype(n[5]); // int&
decltype(*j); // int&
decltype(static_cast<int&&>(i)); // int&&
decltype(i++); // int
decltype(++i); // int&
decltype("hello world"); // const char(&)[12]
4.4 cv限定符的推导

通常情况下,decltype(e)所推导的类型会同步e的cv限定符:

cpp 复制代码
const int i = 0;
decltype(i); // const int

当e是未加括号的成员变量时,父对象表达式的cv限定符会被忽略:

cpp 复制代码
struct A {
    double x;
};
const A* a = new A();
decltype(a->x); // double

当e是加括号的数据成员时,父对象表达式的cv限定符会同步到推断结果:

cpp 复制代码
struct A {
    double x;
};
const A* a = new A();
decltype((a->x)); // const double&
4.5 decltype(auto)

C++14引入了decltype(auto)组合,它告诉编译器使用decltype的推导规则来推导auto。需要注意的是,decltype(auto)必须单独声明,不能结合指针、引用以及cv限定符:

cpp 复制代码
int i;
int&& f();
auto x1a = i; // int
decltype(auto) x1d = i; // int
auto x2a = (i); // int
decltype(auto) x2d = (i); // int&
auto x3a = f(); // int
decltype(auto) x3d = f(); // int&&
auto x4a = { 1, 2 }; // std::initializer_list<int>
// decltype(auto) x4d = { 1, 2 }; // 编译失败, {1, 2}不是表达式
auto *x5a = &i; // int*
// decltype(auto)*x5d = &i; // 编译失败,decltype(auto)必须单独声明

简化返回类型后置的语法:

cpp 复制代码
template<class T>
decltype(auto) return_ref(T& t) {
    return t;
}
int x1 = 0;
static_assert(std::is_reference_v<decltype(return_ref(x1))>);
4.6 decltype(auto) 作为非类型模板形参占位符

在C++17中,decltype(auto)也能作为非类型模板形参的占位符,其推导规则与前面介绍的一致:

cpp 复制代码
#include <iostream>

template<decltype(auto) N>
void f() {
    std::cout << N << std::endl;
}

static const int x = 11;
static int y = 7;

int main() {
    f<x>(); // N为const int类型
    f<(x)>(); // N为const int&类型
    // f<y>(); // 编译错误,y不是一个常量
    f<(y)>(); // N为int&类型
}

总结

通过本章的学习,我们了解了decltype及其变体decltype(auto)在现代C++中的重要作用及其应用场景。合理利用这些特性,可以帮助开发者编写更加高效、简洁和灵活的代码。无论是简化复杂的类型声明,还是处理泛型编程中的细节问题,decltype都提供了强大的支持。希望这些内容能帮助你在日常开发中更好地运用decltype,提升编码效率和代码质量。

第5章 函数返回类型后置与推导

5.1 使用函数返回类型后置声明函数

C++11引入了函数返回类型后置的语法,允许在函数声明中将返回类型放在参数列表之后。这种语法特别适用于返回类型复杂的场景,如返回函数指针类型。以下是基本示例:

cpp 复制代码
auto foo() -> int {
    return 42;
}

对于更复杂的返回类型,比如返回一个函数指针,返回类型后置可以显著提高代码的可读性:

cpp 复制代码
int bar_impl(int x) {
    return x;
}

typedef int(*bar)(int);

bar foo1() {
    return bar_impl;
}

auto foo2() -> int(*)(int) {
    return bar_impl;
}

int main() {
    auto func = foo2();
    func(58);
}

在这个例子中,foo2 使用返回类型后置的方式定义了一个返回类型为 int(*)(int) 的函数。

5.2 推导函数模板返回类型

在C++11标准中,函数返回类型后置的一个重要用途是推导函数模板的返回类型。通过结合 decltype 说明符,可以在编译时根据参数类型推导出返回类型。例如:

cpp 复制代码
template<class T1, class T2>
auto sum1(T1 t1, T2 t2) -> decltype(t1 + t2) {
    return t1 + t2;
}

int main() {
    auto x1 = sum1(4, 2);
}

这里,decltype(t1 + t2) 用于推导 sum1 函数的实际返回类型。需要注意的是,decltype(t1 + t2) 不能写在函数声明之前,因为编译器在解析返回类型时还未解析到参数部分,因此无法识别 t1t2

另一种方法是直接在函数声明前使用 decltype,但这会导致需要调用类型的默认构造函数,这在某些情况下可能会导致编译失败。例如:

cpp 复制代码
class IntWrap {
public:
    IntWrap(int n) : n_(n) {}
    IntWrap operator+ (const IntWrap& other) {
        return IntWrap(n_ + other.n_);
    }

private:
    int n_;
};

template<class T1, class T2>
decltype(T1() + T2()) sum2(T1 t1, T2 t2) {
    return t1 + t2;
}

int main() {
    // 编译失败,IntWrap没有默认构造函数
    sum2(IntWrap(1), IntWrap(2));
}

为了克服这个问题,可以使用以下两种方法:

  1. 使用指针转换和解引用
cpp 复制代码
template<class T1, class T2>
decltype(*static_cast<T1*>(nullptr) + *static_cast<T2*>(nullptr)) 
sum3(T1 t1, T2 t2) {
    return t1 + t2;
}

这种方法通过将 nullptr 转换为 T1T2 类型的指针,并解引用它们来进行求和操作。由于编译器不会真正执行这些操作,因此不会引发运行时错误。

  1. 使用 std::declval

std::declval 是标准库提供的一个工具函数,它将类型 T 转换为引用类型,而不需要实际构造对象。这使得在 decltype 中使用表达式成为可能,而无需依赖默认构造函数。

cpp 复制代码
#include <utility> // 包含 std::declval

template<class T1, class T2>
decltype(std::declval<T1>() + std::declval<T2>()) 
sum4(T1 t1, T2 t2) {
    return t1 + t2;
}

int main() {
    sum3(IntWrap(1), IntWrap(2));
    sum4(IntWrap(1), IntWrap(2));
}

这两种方法都能有效地解决在推导复杂返回类型时遇到的问题,同时保持代码的清晰和简洁。

总结

通过本章的学习,我们了解了如何使用C++11中的函数返回类型后置语法来简化复杂返回类型的声明。此外,我们还探讨了如何利用 decltypestd::declval 来推导函数模板的返回类型,从而避免因默认构造函数缺失而导致的编译错误。合理运用这些特性,可以使代码更加简洁、易读且功能强大。希望这些内容能帮助你在日常开发中更好地处理复杂返回类型的场景。

相关推荐
power-辰南10 分钟前
技术架构师成长路线(2025版)
java·架构师·学习路线·技术专家
二十雨辰11 分钟前
[Java基础]面向对象
java·开发语言
栗豆包1 小时前
w187社区养老服务平台的设计与实现
java·spring boot·后端·spring·tomcat
violin-wang1 小时前
如何在Intellij IDEA中识别一个文件夹下的多个Maven module?
java·spring boot·spring·maven·intellij-idea
假客套1 小时前
Java小白入门教程:LinkedList
java·开发语言
被AI抢饭碗的人1 小时前
c++:list
开发语言·c++
lingllllove2 小时前
Maven的三种项目打包方式——pom,jar,war的区别
java·maven·jar
雨 子2 小时前
Maven jar 包下载失败问题处理
java·maven·jar
为美好的生活献上中指2 小时前
java每日精进1.31(SpringSecurity)
java·开发语言·微服务
星如雨グッ!(๑•̀ㅂ•́)و✧2 小时前
Spring Boot 2 快速教程:WebFlux处理流程(五)
java·spring boot·后端