【C++泛型学习笔记】万能引用、完美转发和常用标准库模板

学习参考书籍:王健伟《C++新经典:模板与泛型编程》

万能引用

万能引用是一种类型,同int一样。万能引用既可以接受左值,又可以接受右值。

右值引用:

1
2
int a = 3;
int &b = a; // 左值引用类型变量b

左值引用:

1
int &&b = 3;  // 右值引用类型变量b

左值和右值的区分:

  • 可以取地址的(如存在内存中),有名字的(有标识符),非临时的就是左值;
  • 不能取地址的(如存在寄存器中),没有名字的(没有标识符),临时的就是右值;

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
void func(int &&tmprv)	// 参数tmprv是一个右值引用类型(int &&),只能传入右值引用类型的实参,传入左值引用类型实参将会报错
{
cout << tmprv << endl;
return;
}

template<typename T>
void func(T&& tmprv) // 参数tmprv是一个万能引用类型(T&&),既可以传入右值引用类型参数,也可以传入左值引用类型参数
{
cout << tmprv << endl;
return;
}

参数成为万能引用(也叫做未定义引用)类型必须满足如下条件:

  • 必须是函数模板
  • 必须是发生了模板参数类型推断并且函数模板参数形如T&&。

T&& 前加 const 关键字进行修饰,会将万能引用退化为右值引用。

完美转发

完美转发概念:从主函数中调用函数A,通过函数A调用函数B,这个过程叫做转发。函数A作为跳板函数,将主函数出入的参数传递给函数B。在普通转发过程中,参数的某些类型信息(如const属性、左值或右值属性)会丢失。而完美转发能够使在转发过程中参数的类型信息不丢失。

函数形参的完美转发:

1
2
3
4
5
template<typename F, typename T>
void A(F B, T&& t) // t为万能引用类型
{
B(std::forward<T>(t));
}

上述完美转发实现方式为:通过一个函数模板A,可以把任意函数名B、任意类型参数传递给函数模板A,从而达到间接调用任意函数B的目的。std::forward<T>(t)是C++标准库中的函数,作用是保持函数模板输入原始实参的左值性或右值性,实现函数形参的完美转发。

常用标准库模板

  • std::declval

    作用:

    1. 将一个类型转换成右值引用类型。
    2. 配合decltype,让在decltype表达式中不必经过类类型的构造函数(不创建类对象)就能使用该类型的成员函数。
    1
    decltype(std::declval<A>().Afunc());
  • std::true_type和std::false_type

    std::true_type/std::false_type为类型别名(类类型),代表一个类型。

    1
    2
    using true_type = integral_constant<bool, true>
    using false_type = integral_constant<bool, false>

    作用:该类型在trait技术中应用广泛。

  • std::void_t

    C++17别名模板,应用:

    • 判断类中是否存在某个类型别名

      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
      struct NoInnerType
      {
      int m_i;
      };

      struct HaveInnerType
      {
      using type = int;
      void func() {}
      };

      // 类模板泛化版本
      template<typename T, typename U = std::void_t<>>
      struct HasTypeMem : std::false_type
      {

      };

      // 偏特化版本
      template<typename T>
      struct HasTypeMem<T, std::void_t<typename T::type>> : std::true_type
      {

      };

      int main()
      {
      cout << HasTypeMem<NoInnerType>::value << endl; // 调用泛化版本
      cout << HasTypeMem<HaveInnerType>::value << endl; // 调用偏特化版本
      }
    • 判断类中是否存在某个成员变量

      改写上述偏特化版本实现,其他代码不变

      1
      2
      3
      4
      5
      6
      // 偏特化版本
      template<typename T>
      struct HasTypeMem<T, std::void_t<decltype(T::m_i)> : std::true_type
      {

      };
    • 判断类中是否存在某个成员函数

      1
      struct HasTypeMem<T, std::void_t<decltype(std::declval<T>().func())> : std::true_type
  • std::conditional

    C++11类模板,表现编译期的分支逻辑。实现代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<bool b, class T, class U>
    struct conditional
    {
    using type = T;
    };

    template<class T, class U>
    struct conditional<false, T, U>
    {
    using type = U;
    };

    std::conditional<true, A, B>::type返回A;std::conditional<false, A, B>::type返回B,类似于if-then-else逻辑。

  • std::function

    可调用对象包装器,C++11类模板。

  • std::remove_all_extents

    C++11类模板,功能为把一个数组中的数组类型部分移除,只保留元素类型。即int[10]变为int。

    1
    2
    int a[10];
    std::remove_all_extents<decltype(a)>;