001-EMC 深入解读-理解模板型别推导

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了001-EMC 深入解读-理解模板型别推导相关的知识,希望对你有一定的参考价值。

EMC 第一章条款一,一上来就出个王炸——有关于函数模板的推导规则。

1. 问题

似乎你在使用的时候并未关注过这些事情,因为一切看起来相当自然,你也用的很爽。比如:

template <typename T>
T add(T a, T b) 
    return a + b;

这是一个计算两数之和的函数,你可以传递 float, double, int 等支持 operator+ 类型的参数,而不用为每一种类型都写一份 add 函数。

但是,上面的函数模板你也可以写成:

template <typename T>
T add(T& a, T& b) 
    return a + b;


template <typename T>
T add(const T& a, const T& b) 
    return a + b;


template <typename T>
T add(T&& a, T&& b) 
    return a + b;

然而,上面这 3 种写法在 C++11 中都好使。到底哪种比较好?或者说,有什么区别?从直觉上来说,写在 add(T a, T b) 是值传递,而带 & 的则是引用传递,带 const 则表示希望对象保持常量属性。你的直觉是对的。

那么 T&& 是什么?这是 C++11 出现的特有语法,你也许听说过右值引用,很抱歉,看起来 T&& 确实像右值引用,不过,当 && 出现在模板参数 T 后面时,则称为 Universal Reference(通用引用、万能引用)。接下来我们给一个比较正式的定义:

函数模板中持有型别形参 T 时,Universal Reference 写作 T&&

现在将问题抽象一下,对于下面的模板:

template<typename T>
void f(ParamType param)

问题:在不同的模板参数里,传递不同类型的参数,T 会被编译器推导为什么?ParamType 又是什么?

例如:

template<typename T>
void f(const T& param) // ParamType = const T&

int x = 27;
const int cx = x;
const int& rx = x;

当你调用 f(x), f(cx), f(rx) 时,T 是什么,ParamType 是什么?

事实上,上面的 3 种情况,T 全部被推导为 int,所以,ParamType = const T& = const int&.

如果模板是:void f(T& param),情况又是怎样呢?如何在你的编译器中验证?

2. 实验

我们没一个比较好的直接的方法在程序里打印出推导的类型,但是可以利用链接器的错误提示来看到实际推导类型。

2.1 void f(T& param)

编译以下代码,命名为 deduce01.cpp

  • 推导 f(x)
// deduce01.cpp
template <typename T>
void f(T& param); // 故意不写 f 的定义

int main() 
    int x = 27;
    const int cx = x;
    const int& rx = x;

    f(x);
    // f(cx);
    // f(rx);
    return 0;

使用下面方式编译:

$ g++ -std=c++11 deduce01.cpp



图1 调用 f(x)

可以看到,f(x) 里,T 被推导为 int,而 ParamType 被推导为 int &.

  • 推导 f(cx)

如果调用 f(cx),再进行编译:

// deduce.01.cpp
template <typename T>
void f(T& param); // 故意不写 f 的定义

int main() 
    int x = 27;
    const int cx = x;
    const int& rx = x;

    // f(x);
    f(cx);
    // f(rx);
    return 0;



图2 调用 f(cx)

可以看到,调用 f(cx) 时,T 被推导为 int const, ParamType 被推导为 int const&.

  • 推导 f(rx)

如果调用 f(rx),再进行编译:

// deduce.01.cpp
template <typename T>
void f(T& param); // 故意不写 f 的定义

int main() 
    int x = 27;
    const int cx = x;
    const int& rx = x;

    // f(x);
    // f(cx);
    f(rx);
    return 0;



图3 调用 f(rx)

推导的类型和调用 f(cx) 一模一样。

2.2 void f(const T& param)

同样的,我们编译 deduce02.cpp,使用上述方法编译:

// deduce02.cpp
template <typename T>
void f(const T& param); // 故意不写 f 的定义

int main() 
    int x = 27;
    const int cx = x;
    const int& rx = x;

    // 这里一次调用 3 次 f
    f(x);
    f(cx);
    f(rx);
    return 0;



图4 调用 f(x), f(cx), f(rx)

你发现,只报了一个链接错误,这意味着这 3 次调用,推导的结果是一样的。T 被推导为 int,而 ParamType 被推导为 int const&.

3. 总结

  • 掌握查看推导类型的方法

后文我们继续讨论具体的规则:

  • ParamType 是引用或指针,但不是通用引用
  • ParamType 是通用引用
  • ParamType 既非指针也非引用

以上是关于001-EMC 深入解读-理解模板型别推导的主要内容,如果未能解决你的问题,请参考以下文章

002-EMC 深入解读-理解模板型别推导

auto类型推导

深入理解函数模板

支持向量机SVM原理解读,以及PyQt5融合SVM的代码实现,没有公式推导(已经很成熟,很难改公式,数学专业的可以推导学习)

第57课 深入理解函数模板

深入了解C++:auto与函数模板之推导规则辨析