1

In the code below I use auto (not auto&, const auto, const auto& or auto&&), but k has a reference type.

Why is it captured by reference (as GCC says) and not by value? Looks counterintuitive.

If it's captured by value (as Clang says), why it is captured as const?

What does the standard say about this?

Is there a way to capture it by non-const value (if I want to make further manipulations with k without modifying the map) or I have to make a copy of k inside the loop?

std::map<int, int> map{{3,7},{1,16}};
for (auto [k, v] : map) {
    //k *= 3; // error: assignment of read-only reference 'k'
    std::printf("k=%i v=%i\n", k, v);
}

GCC produces (https://godbolt.org/z/eq6c6Gxc4):

<source>:9:15: error: assignment of read-only reference 'k'
    9 |             k *= 3; // error: assignment of read-only reference 'k'
      |             ~~^~~~

Clang produces (https://godbolt.org/z/K9bcvjqs3):

<source>:9:15: error: cannot assign to variable 'k' with const-qualified type 'std::tuple_element<0, std::pair<const int, int>>::type' (aka 'const int')
    9 |             k *= 3; // error: assignment of read-only reference 'k'
      |             ~ ^
<source>:8:20: note: variable 'k' declared const here
    8 |         for (auto [k, v] : map) {
      |                    ^
4LegsDrivenCat
  • 1,247
  • 1
  • 15
  • 24
  • 3
    Is clang error more explanatory? https://godbolt.org/z/MTEoo5fx1 - `k` is not a reference, but is `const`, because key type in maps is `const`. – Yksisarvinen Jul 14 '23 at 11:39
  • @Yksisarvinen I've updated my question with exact error messages from GCC and Clang. – 4LegsDrivenCat Jul 14 '23 at 11:43
  • @Yksisarvinen Quite inconvenient. So the only way is to make a copy? – 4LegsDrivenCat Jul 14 '23 at 11:49
  • 1
    `k` is not a reference type. It is captured by value, guaranteed. Use your favorite debugger and see for yourself. The weird trick is that `k` is a `const` value which is why it can't be assigned. In the = operation `k` is an lvalue, a reference to a const, which is what the error message is describing. – Sam Varshavchik Jul 14 '23 at 11:56
  • Thank you. Maybe you know what the standard says about this? – 4LegsDrivenCat Jul 14 '23 at 12:05
  • @SamVarshavchik I've checked with debugger, it's indeed a copy. The problem is that it's const. – 4LegsDrivenCat Jul 14 '23 at 12:16
  • the `value_type` of `std::map` is `std::pair`. You are binding to that `const int` with `k` so `k` is a `const int` – NathanOliver Jul 14 '23 at 12:22
  • @NathanOliver-IsonStrike Still looks like an oversight in the standard. Or bug in compilers if the standard does not require such behavior. I can add constness by const but I cannot remove it. The logical intuitive way would be the following: if auto in this context makes a copy it does not make sense to make it const. If constness is desirable, it can be added by const auto. – 4LegsDrivenCat Jul 14 '23 at 13:13
  • 3
    @4LegsDrivenCat That's not how structured bindings work. When you do `auto [k, v] : map` the compiler creates a variable of type `decltype(map)::value_type` which is `std::pair`. Then `k` and `v` are references to the members of said created variable. This is why the const persists through. You don't make a copy of the key but a copy of the whole `std::pair`, which is not const as `v` can be assigned to. – NathanOliver Jul 14 '23 at 13:20
  • @NathanOliver-IsonStrike Aha, now I understand why I see the error. But it's sad. Looks like an area for improvement in C++26 standard :) – 4LegsDrivenCat Jul 14 '23 at 13:30

1 Answers1

0

If the goal here is to lose the const with auto type deduction, then the simplest solution is to bind by reference and make a copy yourself:

#include <map>
#include <cstdio>

int main()
{
    std::map<int, int> map{{3,7},{1,16}};
    for (auto &[kref, vref]: map)
    {
        auto k=kref, v=vref;

        k *= 3; // no more error
        std::printf("k=%i v=%i\n", k, v);
    }

    return 0;
}

It is a near certainty that today's C++ compilers will optimize away the structured bind, entirely. The end result will be the same as a single copy.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Still looks like an oversight in the standard. Or bug in compilers if the standard does not require such behavior. I can add constness by const but I cannot remove it. The logical intuitive way would be the following: if auto in this context makes a copy it does not make sense to make it const. If constness is desirable, it can be added by const auto. – 4LegsDrivenCat Jul 14 '23 at 13:10
  • @4LegsDrivenCat I'm pretty sure the main reason for const is so that you **can't** modify keys when you're iterating over map - that could result in very strange behaviour. Imagine you have map `{1: 10, 2: 20, 3: 30}` and then you change the first key (`1`) into (`3`) - what you really did was not only change a "pair" `1:10` to `3:10` but you also "deleted" the `3:30` "pair" - or is it supposed to still iterate over it? That's textbook undefined behaviour. – Jan Spurny Jul 14 '23 at 13:46
  • @JanSpurny But when I use auto (not auto&) I get a copy of an element from the map, so I cannot modify the map using k, that's why I was confused by the error. See the comment from NathanOliver, he explains the details. – 4LegsDrivenCat Jul 15 '23 at 00:02