2

Couldn't find a concise answer to this problem.

I have a struct named Course, which consists of three variables. The struct is then inserted into a map, whose key is a place (string). Once the courses are inserted into a map, I want to iterate through the courses and see, if a new course that I am adding is the same apart from the number of enrollments. If it is the same, I want to update the enrollments variable to a new one. However, this is not possible, because I need to use address-of operator (&) in the loop, but once I try to do so a semantic issue is confronted:

error: binding value of type 'const Course' to reference to type 'Course' drops 'const' qualifier stl_set.h:344:7: note: selected 'begin' function with iterator type 'std::set<Course, std::less, >std::allocator >::iterator' (aka '_Rb_tree_const_iterator')

Here is the code:

#include <cstring>
#include <iostream>
#include <string>
#include <map>
#include <set>

using namespace std;

struct Course 
{
    string name;
    string theme;
    int enrollments;
};

bool operator <(const Course& lhs, const Course& rhs)
{
return ((lhs.name <= rhs.name) );
}

int main()
{
Course test_course1 = {"English", "Language", 5};
Course test_course2 = {"Armenian", "Language", 10};
Course test_new_course = {"Armenian", "Language", 15};

set<Course> test_set;
test_set.insert(test_course1);
test_set.insert(test_course2);

map <string, set<Course>> courses;
courses.insert({"New York", test_set});

string location = "New York";

for (Course& course: courses.at(location))
{
    if ((course.theme == test_new_course.theme) && (course.name == test_new_course.name))
    {
        course.enrollments = test_new_course.enrollments;
    }
}

return 0;
}

Apparently this is to do with the operator < but what should I do with it in order to make this work?

I have a bonus question: is there a way to make the operator < tell set to put courses first based on theme (alphabetical order) and then based on course name? I tried to achieve this with a && (lhs.theme < rhs.theme) but nothing good came out of it.

Thanks in advance.

Pepe
  • 21
  • 2
  • 2
    `bool operator <(const Course& lhs, const Course& rhs) { return ((lhs.name <= rhs.name) ); }` -- Fix this first. This is not a strict-weak-ordering. Also, you could easily had made a [mcve] consisting of the `struct`, a `std::set`, and then one or two lines in main() adding an item and attempting to change an item in the set. This has nothing to do with `map`. – PaulMcKenzie Oct 19 '20 at 15:55
  • @PaulMcKenzie Is it because it contains "=" in it? But if I remember correctly, if it is omitted, courses with the same name won't appear in a set at all (even if they have a different theme). Can you help me with this? – Pepe Oct 19 '20 at 15:58
  • the problem isnt where you think it is. The error needs much less code: https://godbolt.org/z/Tqh43z – 463035818_is_not_an_ai Oct 19 '20 at 16:00
  • @Pepe: The whole point of `set` is that it ensures uniqueness relative to the comparator; if neither `a < b` nor `b < a` is true, then `a` and `b` are equivalent, and can't both exist in the same `set`. `std::multiset` allows multiple keys with equivalent values, if that's what you want. Or you should be including the `theme` as a fallback comparison in the `operator<` implementation, which is easy/efficient to do with `std::tie`, e.g. `return std::tie(lhs.name, lhs.theme) < std::tie(rhs.name, rhs.theme);`. Your issue with mutating entries is separate, and addressed in the duplicate. – ShadowRanger Oct 19 '20 at 16:17
  • 1
    *courses with the same name won't appear in a set at all (even if they have a different theme)* -- A `std::set` is only supposed to have unique keys, and you have defined uniqueness to include two names equal to each other. That cannot work as it is a contradiction. Thus the code, even with the fix suggested by @idclev463035818, exhibits undefined behavior. Either use a `std::multiset`, or adjust your `<` to use all of the members in some way (maybe `return std::tie(lhs.name, lhs.theme, lhs.enrollments) < std::tie(rhs.name, rhs.theme, rhs.enrollments);`) – PaulMcKenzie Oct 19 '20 at 16:20
  • 1
    @PaulMcKenzie: I suspect `enrollments` shouldn't be part of the comparison (otherwise two courses with the same name and theme are unique if and only if they have the same number of students enrolled). The duplicate's (usually unsuitable) proposal to make the field `mutable` might actually apply here; the course name and theme should never be changed, but enrollment would be expected to fluctuate. Or more cleanly, `enrollment` shouldn't really be a field, and the `set` should really be a `map` where the enrollment is the `int` value associated with the `Course`. – ShadowRanger Oct 19 '20 at 16:23
  • @ShadowRanger How would I make a fallback comparison for theme? My question is not really answered in a suggested link... – Pepe Oct 19 '20 at 16:26
  • 1
    @Pepe: That's what the `return std::tie(lhs.name, lhs.theme) < std::tie(rhs.name, rhs.theme);` in my last comment covers. `tuple`s compare lexicographically, so it sorts by `name` if they differ, and by `theme` if they don't. `std::tie` makes `tuple`s of existing values *very* cheaply (it actually holds references, so the values aren't copied at all), so when `name` differs, `theme` is never read. Your problem with modifying `enrollments` in your loop is answered in the suggested duplicate (that's why you can't iterate over the `set` with a non-`const` reference to the contents). – ShadowRanger Oct 19 '20 at 16:29
  • @ShadowRanger Okay, thank you very much! – Pepe Oct 19 '20 at 16:30
  • @PaulMcKenzie wasnt a fix, just less code to produce the same error message. Only now I realized that OPs error message is actually a different one. Or maybe you wanted to ping Shadow? ("the fix suggested by @ idclev463035818,...") – 463035818_is_not_an_ai Oct 19 '20 at 16:38

0 Answers0