tags:

views:

438

answers:

4

I find the update operation on set tedious since there's no such an API on cplusplus. So what I currently do is sth like this:

//find element in set by iterator
Element copy = *iterator;
... // update member value on copy, varies
Set.erase(iterator);
Set.insert(copy);

Basically the iterator return by Set is a const_iterator and you can't change its value directly.

Is there a better way to do this? Or maybe I should override set by creating my own (which I don't know exactly how it works..)

Thanks!

+13  A: 

set returns const iterators (the standard says set::iterator is const, and that set::const_iterator and set::iterator may in fact be the same type - see 23.2.4/6 in n3000.pdf) because it is an ordered container. If it returned a regular iterator, you'd be allowed to change the items value out from under the container, potentially altering the ordering.

Your solution is the idiomatic way to alter items in a set.

Terry Mahaffey
Members of (non-const) `std::set` does not return `const_iterator` and you *can* modify its elements if you're careful. Why is this (incorrect) answer getting so many upvotes? What am I missing?
avakar
My answer is correct - yours is wrong. I updated my post with references to the standard.
Terry Mahaffey
Terry, thank you for discussion. I've rechecked: the defect report had indeed been submitted in 1998, but was not incorporated into C++03. It will be into C++0x. So while your answer is not correct as far as the current letter of the standard is concerned, it is correct as far as the intent goes. +1.
avakar
Thanks for your comment and your debate with Avakar (Or Avatar?). They helped alot.
Figo
+2  A: 

You may want to use an std::map instead. Use the portion of Element that affects the ordering the key, and put all of Element as the value. There will be some minor data duplication, but you will have easier (and possibly faster) updates.

Ben Craig
+4  A: 

Update: Although the following is true as of now, the behavior is considered a defect and will be changed in the upcoming version of the standard. How very sad.


There are several points that make your question rather confusing.

  1. Functions can return values, classes can't. std::set is a class, and therefore cannot return anything.
  2. If you can call s.erase(iter), then iter is not a const_iterator. erase requires a non-const iterator.
  3. All member functions of std::set that return an iterator return a non-const iterator as long as the set is non-const as well.

You are allowed to change the value of an element of a set as long as the update doesn't change the order of elements. The following code compiles and works just fine.

#include <set>

int main()
{
    std::set<int> s;
    s.insert(10);
    s.insert(20);

    std::set<int>::iterator iter = s.find(20);

    // OK
    *iter = 30;

    // error, the following changes the order of elements
    // *iter = 0;
}

If your update changes the order of elements, then you have to erase and reinsert.

avakar
That code does not compile, at least in VC10 - for the reasons I outlined in my post. This may have changed recently (been a "bug fix" to the standard), I thought it was in C++03, but I could be wrong.
Terry Mahaffey
Well, I'm looking at 23.1.2[lib.associative.reqmts] of C++03, table 69 and it says: "a.find(k): iterator; const_iterator for constant a".
avakar
And I just checked the draft for C++0x and there is no change (which is unsurprising and it would break a lot of code).
avakar
I don't have the C++03 pdf handy (it costs money). I thought this was fixed in C++03, but it could have been a post 03 fix. In n3000.pdf it's 23.2.4/6 which explains that for associative containers, iterator is a const iterator (and can be the same type as const_iterator). What compiler are you using? This behavior was implemented in VC9 as well (which was tracking C++03), which is why I thought the behavior was a C++03 bug fix.
Terry Mahaffey
Don't look at the table, look at the paragraph explain the requirements of "iterator" on an associative container. An iterator can be "const" without actually being named "const_iterator".
Terry Mahaffey
@Terry, right, although that's a change from C++03, where 23.1.2/6 (the equivalent of 23.3.4/6 in C++0x) only says "iterator of an associative container is of the bidirectional iterator category." I've built the snippet with msvc9 without problems, so it does indeed track C++03.
avakar
It is in the Standard library defect reports: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#103. It doesn't compile with GCC, which makes a mention of DR 103 and typedefs both iterator and const_iterator to be the same type. - One proposed workaround for OP's problems, BTW, are const_cast and mutable members.
UncleBens
+1 for the interesting discussion in comments
just somebody
@Avakar - did you notice that for maps, C++03 specifies the value type is `pair<const Key, T>`. Based on that, being allowed to change the set key seems like an oversight that the C++0x committee fixed.
R Samuel Klatchko
@Avakar - what happens in MSVC 9 when you do `*iter = 0;`? Does the operation throw? Does it allows the operation but cause problems in further uses of the container?
R Samuel Klatchko
@UncleBens, yes, I've found the DR too. I'm a little surprised it didn't make it into C++03 but was incorporated into the current draft. It is a rather severe change from my perspective as it will break otherwise correct code.
avakar
@R Samuel Klatchko, it will compile and run fine, but will most likely break somewhere down the line.
avakar
A: 

There are 2 ways to do this, in the easy case:

  • You can use mutable on the variable that are not part of the key
  • You can split your class in a Key Value pair (and use a std::map)

Now, the question is for the tricky case: what happens when the update actually modifies the key part of the object ? Your approach works, though I admit it's tedious.

Matthieu M.