为什么允许从const到非const的隐式转换?

时间:2017-08-27 23:08:55

标签: c++ c++11 templates const

为什么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中的每个元素。

允许这种隐式转换有什么好处?是否有一些常见的用例,开发人员期望/更喜欢这种隐式转换(而不是获得编译器错误)?

1 个答案:

答案 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 ); 被推断为U1const std::string被推断为U2。 cv限定符intU1具有哪些内容并不重要,因为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;的元素并不重要,因为您只是复制它们。

这也是为什么你应该使用treasureauto&&进行远程循环(甚至可能是一般的?),因为如果你不小心它可以避免副本。