1

Here is a basic code snippet for which I'm getting an error:

error: passing ‘const std::map<int, bool>’ as ‘this’ argument discards qualifiers [-fpermissive]`

struct Point {
    float x;
    float y;
    int id;
    Point(float x, float y, float id) : x(x), y(y), id(id) {}
};

void removePoints(std::vector<Point> &points_vec) {
    std::map<int, bool> my_map;
    for (const auto& pt : points_vec) {
        if(pt.id < 0) 
            my_map[pt.id] = true;
        else
            my_map[pt.id] = false;
    }

    points_vec.erase(std::remove_if(points_vec.begin(), points_vec.end(), [map_lambda = my_map] (const Point pt) -> bool {
        return map_lambda[pt.id];
    }), points_vec.end());
}

int main(int argc, char const *argv[]) {
    std::vector<Point> points_vec;
    points_vec.push_back(Point(1, 2, 0));
    points_vec.push_back(Point(1, 5, -1));
    points_vec.push_back(Point(3, 3, -1));
    points_vec.push_back(Point(4, 9, 2));
    points_vec.push_back(Point(0, 1, 3));
    points_vec.push_back(Point(-1, 7, -2));

    std::cout << points_vec.size() << std::endl;
    removePoints(points_vec);
    std::cout << points_vec.size() << std::endl;
    
    return 0;
}

Note: I know I can remove points without using std::map, but the above code snippet is just an example of a bigger problem.

I checked some questions on a similar error:

  1. error: passing ‘const std::map<int, int>’ as ‘this’ argument discards qualifiers [-fpermissive]
  2. C++ "error: passing 'const std::map<int, std::basic_string<char> >' as 'this' argument of ..."

But in both of them, it was because of the fact that std::map has been declared as const. On the other hand, the map I'm trying to use/access has not been declared as const, and my error is also related to a lambda. As you can see, I'm creating a copy of the original my_map in the lambda capture list as map_lambda = my_map. So, why am I'm getting this -fpermissive error? Or, when we capture something in a lambda, does it automatically get converted to const?

Detailed error message:

main.cpp: In lambda function:
main.cpp:26:32: error: passing ‘const std::map<int, bool>’ as ‘this’ argument discards qualifiers [-fpermissive]
         return map_lambda[pt.id];
                                ^
In file included from /usr/include/c++/7/map:61:0,
                 from main.cpp:2:
/usr/include/c++/7/bits/stl_map.h:484:7: note:   in call to ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&) [with _Key = int; _Tp = bool; _Compare = std::less<int>; _Alloc = std::allocator<std::pair<const int, bool> >; std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type = bool; std::map<_Key, _Tp, _Compare, _Alloc>::key_type = int]’
       operator[](const key_type& __k)
       ^~~~~~~~

On a side note, I know the operator[] returns the value if the key already exists, otherwise it creates a new key and inserts the value (and returns it). But why does const for std::map::operator[] give a compilation error? Shouldn't it be more of like a runtime error instead?

Milan
  • 1,743
  • 2
  • 13
  • 36

2 Answers2

4

All variables captured by lambda are implicitly const, unless you mark lambda as mutable. There is no const overload for operator[] for map, because it may always change the map (create new key-value pair).

If you are sure that every pt.id exists in the map, change operator [] to at() call:

[map_lambda = my_map] (const Point pt) -> bool {
        return map_lambda.at(pt.id);
}

If you want to create keys in the map if they are not present, change lambda to mutable:

[map_lambda = my_map] (const Point pt) mutable -> bool {
        return map_lambda[pt.id];
}
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • 2
    Note that in `[map_lambda = my_map]`, `map_lambda` will be a *copy* of `my_map`, so `my_map` will not be altered in any way, no matter what happens to `map_lambda`. If you need to update `my_map`, capture it by reference instead, eg `[&map_lambda = my_map]`, or even just `[&my_map]` or `[&]` since it doesn't really need a separate name in this example. – Remy Lebeau Apr 29 '21 at 21:39
  • @RemyLebeau thank you for these tips :) Just to confirm, apart from capturing `my_map` by reference, I also have to change lambda to `mutable`, correct? Because, as @Yksisarvinen mentioned above, without `mutable`, it would like `const` reference, right? Please correct me if I'm wrong. Thank you! – Milan Apr 29 '21 at 21:50
  • Thank you @Yksisarvinen. As you mentioned *"There is no const overload for operator[] for map, because it may always change the map (create new key-value pair)."*. But I'm still a little bit confused: shouldn't that be a runtime error? Thanks again! – Milan Apr 29 '21 at 21:52
  • 1
    @Milan "*I also have to change lambda to `mutable`, correct?*" - yes, you would need `mutable`, otherwise `my_map` will be captured by a `const` reference, thus preventing use of `map::operator[]`. – Remy Lebeau Apr 29 '21 at 21:58
  • 1
    @Milan "*shouldn't that be a runtime error?*" - no. It does not make sense to allow non-const methods to be called on `const` objects. And `operator[]` has to be non-const so it can mutate the `map`'s contents. This is simply part of how const-correctness works in C++ – Remy Lebeau Apr 29 '21 at 21:59
  • Thanks a lot, @RemyLebeau . Just to confirm a few things: so if the object is `const` then it doesn't matter whether it's a const method (i.e. `const` at the end of function declaration) or non-const method, right? And in the above case, `map` object has been implicitly converted to `const`. So, `operator[]` can't do anything and that's how `const-correctness` works in C++... this is like a set of defined rules or like standard, correct? Thanks again :) – Milan Apr 29 '21 at 22:18
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231764/discussion-between-remy-lebeau-and-milan). – Remy Lebeau Apr 29 '21 at 22:20
1

The problem is that the lambda expression used in this statement

 points_vec.erase(std::remove_if(points_vec.begin(), points_vec.end(), [map_lambda = my_map] (const Point pt) -> bool {
        return map_lambda[pt.id];
    }), points_vec.end());

is immutable. So map_lambda is considered as a constant object. But the subscript operator requires that the object would be modifiable. That is you may not use the subscript operator with a constant object of the class template std::map.

You should declare map_lambda as a non-constant reference to my_map.

That is change the lambda like

[&map_lambda = my_map] (const Point pt) -> bool {
        return map_lambda[pt.id];
    }
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Thanks @Vlad from Moscow for your answer... but as @Yksisarvinen pointed out above, unless I use `mutable`, anything captured in lambda would be implicitly converted to `const`, right? So, wouldn't that be a `const` reference? Please correct me if I'm mistaken. Thanks again! – Milan Apr 29 '21 at 21:56
  • @Milan I showed how a non-constant reference to the original map is declared n the lambda. – Vlad from Moscow Apr 29 '21 at 21:59
  • Thank you for your reply but wouldn't `[&map_lambda = my_map]` without `mutable` is a `const` reference? Sorry but now I got a little bit more confused because as @Remy Lebeau pointed out about, I need `mutable` otherwise it will be `const` reference! – Milan Apr 29 '21 at 22:04
  • 1
    @Milan No it will not. be a constant reference – Vlad from Moscow Apr 29 '21 at 22:06
  • Thank you @Vlad, I tried out what you said and it looks like if I use reference in the capture list then I can't modify the captured objects, even without `mutable`. So, just to confirm, `mutable` comes into the picture only when I capture something by value, right? Please correct me if I'm wrong. Thanks again :) – Milan Apr 29 '21 at 22:40
  • @Milan You can modify the captured object. – Vlad from Moscow Apr 29 '21 at 22:43
  • no no, I'm talking about capturing something by value and then modifying (or say, trying to modify) it without using `mutable`... just like my code snippet in the main question. So, I just anted to confirm, `mutable` is useful only while capturing something by value, correct? – Milan Apr 29 '21 at 23:58
  • @Milan In this case you need to use the specifier mutable. – Vlad from Moscow Apr 30 '21 at 08:59