C++模板函数make_pair的问题


C++模板函数make_pair的问题

问题引入

  前几天在群里看到有人写堆优化dijkstra最短路的时候出现了问题,问题定位在make_pair的代码上,日常写这个的时候一般没有指定模板的类型,能够正常编译和运行,但这里指定之后为什么会存在编译问题呢。原问题抽象一下如下。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0), cout.tie(0);

    int s = 1;
    pair<int,int> pr = make_pair<int, int>(s, 0);

    return 0;
}
  报错为 - error: no matching function for call to 'make_pair<int, int>(int&, int)'


函数模板的特性

  函数重载、函数模板是C++的一个重要功能,利用泛型的方式由编译器编译时检测静态类型可以极大地便于编程。在使用模板函数时,可以不给出类型,交由编译器推导;也可以显式地指定类型,但必须与所带参数的类型一致,否则会报错。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

template<typename T>
T myMinus(T a, T b)
{
    return a - b;
}

int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0), cout.tie(0);

    int a = 1;
    cout << myMinus(a, -1) << endl;
    cout << myMinus<double>(2.4, 0.5) << endl;

    return 0;
}
  可以正常运行得到结果如下:

输出结果
2
1.9

考察make_pair原码

  在看了报错原因之后不禁疑惑,我给定的参数不应该是(int, int)为什么被推断成了(int&, int),这里就要引出make_pair函数的改良了,这个竟然是在C++11时代经过改良的一个函数。 - 注意:#if __cplusplus >= 201103L 根据这个宏表明按照C++11标准的编译,而__cplusplus为 199711L则按照旧标准编译

  // 181.  make_pair() unintended behavior
#if __cplusplus >= 201103L
  // NB: DR 706.
  template<typename _T1, typename _T2>
    constexpr pair<typename __decay_and_strip<_T1>::__type,
                   typename __decay_and_strip<_T2>::__type>
    make_pair(_T1&& __x, _T2&& __y)
    {
      typedef typename __decay_and_strip<_T1>::__type __ds_type1;
      typedef typename __decay_and_strip<_T2>::__type __ds_type2;
      typedef pair<__ds_type1, __ds_type2> 	      __pair_type;
      return __pair_type(std::forward<_T1>(__x), std::forward<_T2>(__y));
    }
#else
  template<typename _T1, typename _T2>
    inline pair<_T1, _T2>
    make_pair(_T1 __x, _T2 __y)
    { return pair<_T1, _T2>(__x, __y); }
#endif

  对比两个版本,最大的不同点在于给定参数模板时T被改为了T&&,这里就要谈一谈C++11中和右值引用相关的概念。


关于C++11新的引用标准

  在C++11中提出了右值引用的概念。我们一般习惯意义上的变量a,b,c还有对象object,指针p这些都是一个左值(严格意义上并不是定义依据,只是习惯理解)。右值分为两类,纯右值和将亡值。纯右值一般是字面量或表达式,而将亡值来源于临时对象即将销毁的情况。这两种情况都可以用右值引用的方式来获取。

int num = 10;
//int && a = num;  //右值引用不能初始化为左值
int && a = 10;
a = 100;
cout << a << endl;
  这里输出a的值为100。将亡值这里不再举例。右值引用的主要作用是为了减少程序开销的一种方式,一般都会利用其接管资源的性质。C++更有关于move,forward的实用用途,不妨查阅相关资料深入理解。这里更重要的是在右值引用基础上,C++做了一个万能引用(Universal Reference)。在指定template<typename T>模板的情况,可以使用T&&作为万能引用,具体推导见下面的例子。
template<typename T>
void f(T&& args){

}
int a = 0;
f(a);                   //传入左值,推断为左值引用
f(1);                   //传入右值,推断为右值引用
int &la = a;
f(a);                    //传入左值引用,经折叠,推断为左值引用
int &&ra = 0;
f(ra);                   //传入右值引用,经折叠,推断为右值引用
  对于本例,已经很好地说明了问题,在不采用C++11新标准的情况下,使用make_pair是重新构造了新的变量来存储,所以能够正常运行。而在C++11采用万能引用的基础上,若手动给出参数类型,反而会因为引用参数类型不匹配造成编译错误。


文章作者: Commander
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Commander !
  目录