-2

I am having some trouble understanding how the std::set initialization works. I have the following code in a function:

std::map<int, int> my_map  = {
    {16, 24},
    {19, 29},
    {15, 23},
    {14, 22},
    {13, 21},   
    {17, 28},
};

typedef std::function<bool(std::pair<int, int>, std::pair<int, int>)> comparefunction;


comparefunction compare = 
    [](std::pair<int, int> a, std::pair<int, int> b){
        if(lessthan(a,b))
            std::cout << "a" << std::endl;
        else
            std::cout << "b" << std::endl;


        return true;
    };

std::set<std::pair<int, int>, comparefunction>
    values(my_map.begin(), my_map.end(), compare);

When calling this function it prints "b" a few times, how come?

Edit: I realized I used the range constructor, but how does it "automatically" call the lambda function using elements in the map? I can't seem to find this in the documentation. Printing contents of a and b shows that they're always the same, why is this?

  • Did you try looking at the documentation for the possible constructors of [`std::set`](http://en.cppreference.com/w/cpp/container/set/set)? – Algirdas Preidžius Jun 02 '18 at 18:46
  • 3
    I'm not sure I quite grasp the nature of your confusion. You are using [constructor (2) here](http://en.cppreference.com/w/cpp/container/set/set) – Igor Tandetnik Jun 02 '18 at 18:46
  • Thank you, I realized it used the range constructor, but I don't see how the iterators to the map automatically work with the lambda function. What's the "magic" behind it when I initialize the set? – idontknowwhatusernameicanhave Jun 02 '18 at 18:53
  • 1
    `std::set` keeps its elements in sorted order. In order to do that, it needs to compare them. Hence calls to the comparison function. Why else do you believe you are providing one? Wouldn't it be kind of pointless, if it's never called? – Igor Tandetnik Jun 02 '18 at 18:56
  • Yes of course, but what I am trying to say is how does this work? Does it take the interval, create pairs of all elements in the map, and then send them to the function? The lambda takes two pairs as parameters, can I control which two pairs are sent and compared, or are all possible combinations of pairs sent? – idontknowwhatusernameicanhave Jun 02 '18 at 19:05
  • Elements are inserted into the set one by one, just as if you called `insert` several times. For each new element, the set needs to find a place for it among elements already inserted, by comparing the former with some of the latter (not with all - it only makes `O(log N)` comparisons, by using binary search or similar strategy). No, you cannot control which elements will be compared - you need to be able to compare any two. – Igor Tandetnik Jun 02 '18 at 19:12
  • What is `lessthan`? – aschepler Jun 02 '18 at 19:20
  • 1
    Your comparison function always returns `true`, which means the `set` will get confused trying to use the comparator. You should return the result of `lessthan` (assuming that's a valid comparison function, like `a < b` or `std::less`), and for better debugging you might want to also print out the actual values of the pairs instead of just `"a"` and `"b"`. – Daniel H Jun 02 '18 at 19:25

2 Answers2

0

I think what's confusing you is how map iterators behave. Iterating over a map (from my_map.begin() until my_map.end()) means going over pairs, whose type is std::pair<K, V> (for key type K and mapped value type V); in your case it's std::pair<int, int>. So actually, a map is very much like a set of these pairs.

Now, the constructor for a set that you're using, like @AlgirdasPreidžius AND @IgorTandetnik suggest, is a constructor which inserts all of the elements in some range (= from start iterator to end iterator) into the set. So you get a set of pairs.

Finally, the lambda comes in for possibly changing the rule for which elements are the same.

Does it make sense now?

PS 1: It possible you might prefer unordered_set and unordered_map; give it some though to see what better fits your needs.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
0

You are initializing a set with some values. These values happen to come from your map, but sourcing them is not important. What is important is that the set's constructor will be given the following values with which to work (these are actually std::pairs, but the brace notation is convenient):

{13,21}  {14,22}  {15,23}  {16,24}  {17,28}  {19,29}

[At this point, someone might notice that, instead of the order in which the map was initialized, I put these in order by their first coordinate (the key of the map). This is because the map is ordered, so begin() to end() traverses the map from the lowest key to the highest.]

So what happens? The set's constructor creates a set, then adds these elements to it. The first element ({13,21}) goes in no problem; it's easy to add an element to an empty set.

The second element, though, needs to be placed in the set at a location that maintains order. That is, the constructor needs to know if the new element ({14, 22}) compares less than the existing element ({13,21}). How does it find out? It calls the function you gave it: compare({14, 22}, {13,21}). This returns true, so the new element goes before the old in the set.
Note: the order of the parameters is not fixed; since the output was "b", I'm guessing this is the order that was used.

Adding the third element ({15,23}) is similar. The constructor needs to know where it goes in the set, so it starts with one of the existing elements (implementor's choice as to which we start with) and calls your function with that element and the new one. Your function returns true, so the new element will be placed before the existing element. Depending on which element was chosen, another call to compare might be needed to determine that the new element goes first in the set.
Note: if the implementor had chosen the other order of parameters, the new element would go last in the set; presumably you would have seen "a"s instead of "b"s had this happened.

And so on for the remaining three elements.

Want to confuse the constructor? Have your lambda return false instead of true. This will convince the constructor that all of your pairs are equivalent, meaning that after construction with six elements, the set will contain just one ({13,21}). In this case, changing "set" to "multiset" would allow the other elements to be added.

Actually, the constructor could potentially be confused by the function always returning true. Better to have it return whether or not a is considered less than b.

JaMiT
  • 14,422
  • 4
  • 15
  • 31