为什么允许隐式转换从const到non-const ?

时间:2021-10-18 03:12:43

Why does C++ allow the following code to compile?

为什么c++允许编译以下代码?

std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& p: m)
{
    // ...
}

According to Scott Meyers' Effective Modern C++ (p. 40-41):

根据Scott Meyers有效的现代c++(第40-41页):

[...] the key part of a std::unordered_map is const, so the type of std::pair in the hash table (which is what a std::unordered_map is) isn’t std::pair<std::string, int>, it's std::pair <const std::string, int>. But that's not the type declared for the variable p in the loop above. As a result, compilers will strive to find a way to convert std::pair<const std::string, int> objects (i.e., what’s in the hash table) to std::pair<std::string, int> objects (the declared type for p). They’ll succeed by creating a temporary object of the type that p wants to bind to by copying each object in m, then binding the reference p to that temporary object. At the end of each loop iteration, the temporary object will be destroyed. If you wrote this loop, you'd likely be surprised by this behavior, because you'd almost certainly intend to simply bind the reference p to each element in m.

[…] std:::unordered_map的关键部分是const,所以哈希表中的std:::pair (std:::unordered_map就是这样的)不是std::pair ,它是std:::pair< const std::string, int>。但这不是上面循环中为变量p声明的类型。因此,编译器将努力寻找一种方法来转换std::pair 对象(即。它们通过复制m中的每个对象来创建p想要绑定的类型的临时对象,然后将引用p绑定到那个临时对象。在每次循环迭代结束时,临时对象将被销毁。如果您编写这个循环,您可能会对这种行为感到惊讶,因为您几乎肯定希望将引用p绑定到m中的每个元素。

What is the benefit of allowing this implicit conversion? Is there some common use case where the developer would expect / prefer this implicit conversion (rather than getting a compiler error)?

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

1 个解决方案

#1


22  

A standards compliant compiler would "see" the for loop as follows:

符合标准的编译器会“看到”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 
}

Which basically boils down your question to why the following code is allowed:

这基本上就把你的问题归结为为什么以下代码是允许的:

std::pair<std::string, int> p = std::pair<const std::string, int>{};

Note that I dropped the const& part of p, because it isn't relevant. The conversion is the same, the only difference is that the temporary is bound to a reference instead of being copied.

注意,我省略了p的const&part,因为它不相关。转换是相同的,唯一的区别是临时变量被绑定到引用而不是被复制。

If you're wondering why OP's snippet doesn't work with a non-const reference, the conversion is the reason why. The result of the conversion is a temporary object, and because any change to the temporary will be useless (its lifetime isn't extended and so it is destroyed right after), so the language disallows it.

如果您想知道为什么OP的代码段不使用非常量引用,那么转换就是原因。转换的结果是一个临时对象,因为对临时对象的任何更改都是无用的(它的生存期没有扩展,所以它在之后被销毁),所以语言不允许它。

This is allowed because std::pair has a constructor that enables this conversion.

这是允许的,因为std::pair有一个支持这种转换的构造函数。

template< class U1, class U2 >
pair( const pair<U1, U2>& p );

In your case, U1 is deduced as const std::string and U2 as int. It doesn't actually matter what cv qualifiers U1 and U2 have, because p's elements get copied.

在你的例子中,U1被推断为const std::string和U2作为int,实际上cv限定符U1和U2是什么并不重要,因为p的元素会被复制。

The benefits are the same as to why this is allowed:

其好处和为什么允许这样做是一样的:

const int zero{};
int zero2 = zero;

For example, consider the following non-realistic example:

例如,考虑以下不现实的例子:

struct {
    std::pair<int, int> pos;
} player;

std::pair<const int, const int> treasure{1, 2}; // position of treasure
player.pos = treasure; // ok

Now what if, as you say, this conversion were for some reason not allowed. What would the programmer have to do?

如果,如你所说,由于某种原因,这种转换是不允许的。程序员要做什么?

player.pos.first = treasure.first;
player.pos.second = treasure.second;

If this would also be disallowed, then the case with the zeroes above would also not be allowed, which doesn't really make sense, because you are copying zero, so it shouldn't matter if you can modify it or not, because that is a totally different operation.

如果这也是不允许的,那么上面的0也不允许,这其实没有意义,因为你复制了0,所以你是否可以修改它不重要,因为这是一个完全不同的操作。

If this is allowed, then why would player.pos = treasure; be disallowed, because the only thing that it does is copying? Like above, it shouldn't matter whether you can change the elements of treasure, because you are only copying them.

如果这是允许的,那么为什么玩家会。pos =财富;被禁止,因为它唯一做的事情就是复制?就像上面所说的,你是否可以改变宝物的元素并不重要,因为你只是在复制它们。

This is also why you should use auto&& or const auto& for ranged loops (and maybe even in general?) because it can avoid a copy if you're not careful.

这也是为什么您应该使用auto&& or const auto& for远程循环(甚至可能是一般的),因为如果您不小心的话,它可以避免复制。

#1


22  

A standards compliant compiler would "see" the for loop as follows:

符合标准的编译器会“看到”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 
}

Which basically boils down your question to why the following code is allowed:

这基本上就把你的问题归结为为什么以下代码是允许的:

std::pair<std::string, int> p = std::pair<const std::string, int>{};

Note that I dropped the const& part of p, because it isn't relevant. The conversion is the same, the only difference is that the temporary is bound to a reference instead of being copied.

注意,我省略了p的const&part,因为它不相关。转换是相同的,唯一的区别是临时变量被绑定到引用而不是被复制。

If you're wondering why OP's snippet doesn't work with a non-const reference, the conversion is the reason why. The result of the conversion is a temporary object, and because any change to the temporary will be useless (its lifetime isn't extended and so it is destroyed right after), so the language disallows it.

如果您想知道为什么OP的代码段不使用非常量引用,那么转换就是原因。转换的结果是一个临时对象,因为对临时对象的任何更改都是无用的(它的生存期没有扩展,所以它在之后被销毁),所以语言不允许它。

This is allowed because std::pair has a constructor that enables this conversion.

这是允许的,因为std::pair有一个支持这种转换的构造函数。

template< class U1, class U2 >
pair( const pair<U1, U2>& p );

In your case, U1 is deduced as const std::string and U2 as int. It doesn't actually matter what cv qualifiers U1 and U2 have, because p's elements get copied.

在你的例子中,U1被推断为const std::string和U2作为int,实际上cv限定符U1和U2是什么并不重要,因为p的元素会被复制。

The benefits are the same as to why this is allowed:

其好处和为什么允许这样做是一样的:

const int zero{};
int zero2 = zero;

For example, consider the following non-realistic example:

例如,考虑以下不现实的例子:

struct {
    std::pair<int, int> pos;
} player;

std::pair<const int, const int> treasure{1, 2}; // position of treasure
player.pos = treasure; // ok

Now what if, as you say, this conversion were for some reason not allowed. What would the programmer have to do?

如果,如你所说,由于某种原因,这种转换是不允许的。程序员要做什么?

player.pos.first = treasure.first;
player.pos.second = treasure.second;

If this would also be disallowed, then the case with the zeroes above would also not be allowed, which doesn't really make sense, because you are copying zero, so it shouldn't matter if you can modify it or not, because that is a totally different operation.

如果这也是不允许的,那么上面的0也不允许,这其实没有意义,因为你复制了0,所以你是否可以修改它不重要,因为这是一个完全不同的操作。

If this is allowed, then why would player.pos = treasure; be disallowed, because the only thing that it does is copying? Like above, it shouldn't matter whether you can change the elements of treasure, because you are only copying them.

如果这是允许的,那么为什么玩家会。pos =财富;被禁止,因为它唯一做的事情就是复制?就像上面所说的,你是否可以改变宝物的元素并不重要,因为你只是在复制它们。

This is also why you should use auto&& or const auto& for ranged loops (and maybe even in general?) because it can avoid a copy if you're not careful.

这也是为什么您应该使用auto&& or const auto& for远程循环(甚至可能是一般的),因为如果您不小心的话,它可以避免复制。