Why does C++ allow the following code to compile?
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):
[...] the key part of a
std::unordered_map
isconst
, so the type ofstd::pair
in the hash table (which is what astd::unordered_map
is) isn’tstd::pair<std::string, int>
, it'sstd::pair <const std::string, int>
. But that's not the type declared for the variablep
in the loop above. As a result, compilers will strive to find a way to convertstd::pair<const std::string, int>
objects (i.e., what’s in the hash table) tostd::pair<std::string, int>
objects (the declared type forp
). They’ll succeed by creating a temporary object of the type thatp
wants to bind to by copying each object inm
, 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 referencep
to each element inm
.
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)?
No. A reference is simply an alias for an existing object. const is enforced by the compiler; it simply checks that you don't attempt to modify the object through the reference r .
I think this means that making a reference a "const" when it is referenced to a non const object does absolutely nothing. We may as well take that const keyword out when defining that reference. Not true. You may not modify the a non- const object through a const reference.
A standards compliant compiler would "see" the for loop as follows:
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.
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.
This is allowed because std::pair
has a constructor that enables this conversion.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With