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采用万能引用的基础上,若手动给出参数类型,反而会因为引用参数类型不匹配造成编译错误。