第一版
std::map<int, int> t;
t.emplace(1, 1);
for (const std::pair<int,int>& data : t)
{
int i = 0;
std::ignore = i;
}
中间留一些空格,是因为ms在调试的时候,
尤其是模板比较多的时候,经常断点的行号有问题。
比如第5行的断点,需要打在第4行。
如果代码之间没空行,经常断点会搞错。
把断点下在:for (const std::pair<int,int>& data : t)
虽然是用了const,而且还是引用。
但发现居然调用了构造函数,既然调用了构造函数,那么就存在拷贝的可能性,这里是int所以没关系,如果first和second是class或struct类型,就会发生拷贝动作,对std::pair进行了拷贝初始化:
所调用的std::pair所在的文件位置:
D:\DevTools\VS2017\VC\Tools\MSVC\14.16.27023\include\utility
template<class _Other1,
class _Other2,
enable_if_t<conjunction_v<
is_constructible<_Ty1, const _Other1&>,
is_constructible<_Ty2, const _Other2&>,
is_convertible<const _Other1&, _Ty1>,
is_convertible<const _Other2&, _Ty2>
>, int> = 0>
constexpr pair(const pair<_Other1, _Other2>& _Right)
_NOEXCEPT_COND(is_nothrow_constructible_v<_Ty1, const _Other1&>
&& is_nothrow_constructible_v<_Ty2, const _Other2&>) // strengthened
: first(_Right.first), second(_Right.second)
{
}
看其堆栈:
把文本给拷贝出来,可以看清楚:是用std::pair<int const,int>对std::pair<int,int>类型的对象进行了拷贝初始化,first和second都在做拷贝。
其中,std::pair<int const,int>是编译器推导出来的类型,std::pair<int,int>是for循环里显式指定的类型:
std::pair<int,int>::pair<int,int><int const ,int,0>
(const std::pair<int const ,int> & _Right={...}
)
这个pair构造函数也是个模板,所以上面的<int const ,int,0>就是pair构造函数的参数的推导结果。
template<class _Ty1,
class _Ty2>
struct pair
{
...........
template<class _Other1,
class _Other2,
enable_if_t<conjunction_v<
is_constructible<_Ty1, const _Other1&>,
is_constructible<_Ty2, const _Other2&>,
is_convertible<const _Other1&, _Ty1>,
is_convertible<const _Other2&, _Ty2>
>, int> = 0>
constexpr pair(const pair<_Other1, _Other2>& _Right)
_NOEXCEPT_COND(is_nothrow_constructible_v<_Ty1, const _Other1&>
&& is_nothrow_constructible_v<_Ty2, const _Other2&>) // strengthened
: first(_Right.first), second(_Right.second)
{
}
...........
};
1、其中,std::pair<int,int>::pair<int,int>就是构造函数,就是for (const std::pair<int,int>& data : t)里的std::pair<int,int>& data,显式指定的类型。
2、<int const ,int,0>是编译器推导出来的形参类型,因为构造函数也是模板函数,参数类型也要推导出来。0就是enable_if语句出来的结果。
3、const std::pair<int const ,int>就是根据实参推导出来的类型
思考:
由于构造函数也是个模板,所以它的参数也是推导出来的,参数的类型是const std::pair<int const ,int> & _Right={...}
也就是说推导出来的key的类型是int const,value的类型是int,那么它所对应的std::pair应该是std::pair<int const,int>。而for循环里显式指定了std::pair<int,int>,所以这里面就产生了一个拷贝构造函数的动作。可以认为const std::pair<int,int>是一个临时对象?
改个写法,key上添加const
std::map<int, int> t;
//t.emplace(1, 1);
for (const std::pair<const int,int>& data : t)
{
auto x = std::make_unique<int>();
}
这个时候,就不会走pair的构造函数了,也变相说明,和编译器推导出的类型保持一致了。
打印出来看看吧
template<typename T>
struct TypePrinter;
void main()
{
std::map<int, int> t;
t.emplace(1, 1);
for (auto& data : t)
{
TypePrinter<decltype(data)> a;
}
}
编译结果:
“a”使用未定义的 struct“TypePrinter<std::pair<const _Kty,_Ty> &>”
可以看出,key就是const的
结论
最好的办法是用for(const auto& data : t),这种写法也不会走std::pair的构造函数。这种比较保险,也省事,因为是它推导出来的类型。
这篇文章的目的,是更深入理解编译器在推导时所搞出来的一些隐藏的“坑”。