为什么C ++允许编译以下代码?
std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& p: m)
{
// ...
}
根据Scott Meyers' Effective Modern C++(第40-41页):
[...]
std::unordered_map
的关键部分是const
,因此哈希表中std::pair
的类型(std::unordered_map
是什么)不是std::pair<std::string, int>
,而是std::pair <const std::string, int>
。但这不是上面循环中为变量p
声明的类型。因此,编译器将努力找到一种方法将std::pair<const std::string, int>
个对象(即哈希表中的内容)转换为std::pair<std::string, int>
个对象(p
的声明类型)。通过复制p
中的每个对象,然后将引用p绑定到该临时对象,创建m
想要绑定的类型的临时对象,它们将成功。在每次循环迭代结束时,临时对象将被销毁。如果你编写了这个循环,你可能会对这种行为感到惊讶,因为你几乎肯定打算简单地将引用p
绑定到m
中的每个元素。
允许这种隐式转换有什么好处?是否有一些常见的用例,开发人员期望/更喜欢这种隐式转换(而不是获得编译器错误)?
答案 0 :(得分:22)
符合标准的编译器会&#34;参见&#34; for循环如下:
auto&& __range = m;
for (auto __begin = std::begin(m), __end = std::end(m); __begin != __end; ++__begin) {
const std::pair<std::string, int>& p = *__begin;
//loop_statement
}
这基本上归结了您为什么允许使用以下代码的问题:
std::pair<std::string, int> p = std::pair<const std::string, int>{};
请注意,我删除了const&
的{{1}}部分,因为它并不相关。转换是一样的,唯一的区别是临时绑定到引用而不是被复制。
如果您想知道为什么OP的代码段不能使用非const引用,转换就是原因所在。转换的结果是一个临时对象,因为对临时对象的任何更改都是无用的(它的生命周期不会被扩展,因此它会在之后被销毁),所以语言不允许它。
这是允许的,因为p
有一个启用此转换的构造函数。
std::pair
在您的情况下,template< class U1, class U2 >
pair( const pair<U1, U2>& p );
被推断为U1
而const std::string
被推断为U2
。 cv限定符int
和U1
具有哪些内容并不重要,因为U2
的元素会被复制。
其好处与允许这样做的原因相同:
p
例如,请考虑以下非现实示例:
const int zero{};
int zero2 = zero;
现在,如果你说这个转换是出于某种原因不允许的话。程序员必须做什么?
struct {
std::pair<int, int> pos;
} player;
std::pair<const int, const int> treasure{1, 2}; // position of treasure
player.pos = treasure; // ok
如果这也也被禁止,那么也不允许上面带有零的情况,这不是真的有意义,因为你正在复制player.pos.first = treasure.first;
player.pos.second = treasure.second;
,所以如果你能修改它就不重要,因为这是一个完全不同的操作。
如果允许这样做,那么为什么zero
被禁止,因为它唯一能做的就是复制?如上所述,您是否可以更改player.pos = treasure;
的元素并不重要,因为您只是复制它们。
这也是为什么你应该使用treasure
或auto&&
进行远程循环(甚至可能是一般的?),因为如果你不小心它可以避免副本。