4

Why do the sets (std::set and std::multiset) in the C++ standard library provide non const_iterator methods (set::begin and multiset::begin)?

Access to the keys through iterators is always const. It does not matter if the set itself is const or not. Why introduce those extra overloads?

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 3
    @TemaniAfif How is this a duplicate of that question? – Curious Sep 27 '17 at 21:16
  • For completeness and generality, to comply with [Container](http://en.cppreference.com/w/cpp/concept/Container) concept. – yuri kilochek Sep 27 '17 at 21:17
  • @yurikilochek how is it not general enough to only provide const methods for things that return const references to contained values? Can you give an example of general code that will fail if those non const iterators methods are not present? The only thing I can think of is something that has partial specializations that detect the presence of a non const begin() and end() method. – Curious Sep 27 '17 at 21:18
  • @yurikilochek the container concept does not mention that a class must have non const qualified begin and end methods, it only says that a container must have them. Which the sets do. cbegin() and cend() are a different story and not entirely relevant here – Curious Sep 27 '17 at 21:22
  • Before C++11, modifying operations which accepted iterators from the set (such as `erase` and the hint version of `insert`) required non-const iterators. So that could be one reason. – Benjamin Lindley Sep 27 '17 at 21:26
  • @BenjaminLindley I think that can be solved by keeping the alias `const_iterator` right? – Curious Sep 27 '17 at 21:26
  • @Curious, per [container.requirements.general/4](http://eel.is/c++draft/container.requirements#tab:containers.container.requirements) `a.begin()` must return `const_iterator` for const `a`, and `iterator` otherwise. – yuri kilochek Sep 27 '17 at 21:29
  • @Curious: It could, yes. I suspect they simply hadn't thought of that at the time of the original standard, just like they hadn't thought of the fact that it shouldn't be necessary to pass non-const iterators, even though the function is modifying the set (because the set itself is non-const). – Benjamin Lindley Sep 27 '17 at 21:29
  • @yurikilochek that just says `iterator; const_­iterator for constant a`, so that will not matter if `iterator` and `const_iterator` are the same type. C++ does not have strong aliases – Curious Sep 27 '17 at 21:31
  • @Curious if they were the same type, user code wouldn't be able to overload on them. – yuri kilochek Sep 27 '17 at 21:38
  • @yurikilochek and user code shouldn't overload on them right? The standard never specifies if those iterator classes are actually distinct types or whether they are aliased to the same underlying implementation. In fact libstdc++ does exactly this https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_set.h#L138-L139 – Curious Sep 27 '17 at 21:41
  • I believe but am not 100% sure that early versions of `set` would allow you to modify the object at an iterator. Of course modifying the object in a way that reordered it would result in undefined behavior, but there are many cases where part of the object is a key and the rest could be safely modified. I honestly don't know why that's not allowed. – Mark Ransom Sep 27 '17 at 21:49
  • [`std::set::erase`](http://eel.is/c++draft/set#overview-2) is overloaded on them. Can we apply as-is rule here? – yuri kilochek Sep 27 '17 at 21:52
  • @yurikilochek not sure why the standard specifies those as two distinct functions. libstdc++ again only has one of them https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_set.h#L627 I think it might be there in the standard as a guideline for implementations in which they are two distinct types. – Curious Sep 27 '17 at 22:01

2 Answers2

2

Note that in [member.functions], it's expressed that:

For a non-virtual member function described in the C++ standard library, an implementation may declare a different set of member function signatures, provided that any call to the member function that would select an overload from the set of declarations described in this document behaves as if that overload were selected.

The standard defines behavior for each container in [container.requirements.general] which require that:

  • a.begin() yields an iterator (or const_iterator for constant a)
  • a.end() yields an iterator (or const_iterator for constant a)
  • ...

The requirement for the implementations are that those types (C::iterator and C::const_iterator) exist and that those functions yield those types. The requirement is not specifically that those four overloads must exist (nor that the two iterator types must be different). The requirement is just the behavior.

The following would be a perfectly conforming implementation for set:

template <class Key, class Compare = std::less<Key>, class Allocator = std::allocator<Key>>
class set {
public:
    struct iterator { ... };
    using const_iterator = iterator;

    iterator begin() const { ... };
    iterator end() const { ... };

    // other stuff
};    

With this implementation, all the requirements are satisfied. So it's sufficient. Indeed, this is how libstdc++ implements it - the two iterators are the same type and there is just one begin() and just one end().

Why introduce those extra overloads?

There aren't extra overloads. Or at least, there don't have to be. It's up to to the implementer.

Barry
  • 286,269
  • 29
  • 621
  • 977
-1

While I can't say for certain, consistency (see comments by @yurikilochek) seems reasonable to me. "Can you give an example of general code that will fail if those non const iterators methods are not present?" The following code does not compile with libstdc++ (but does compile under Visual Studio) because set does not have the non-const methods. I acknowledge this is not code someone would write in the course of a day that is going well.

#include <iostream>
#include <vector>
#include <set>

template <class C> //C is some container
void f(C& c)
{
    typedef typename C::iterator iter;
    typedef iter (C::*fn)(); // note non-const method

    fn begin = &C::begin; // will fail with set if begin() is const only
    fn end = &C::end;

    for (iter i = (c.*begin)(), e = (c.*end)(); i != e; ++i)
        std::cout << *i << ' ';
    std::cout << '\n';
}

int
main()
{
    std::vector<int> v;
    std::set<int> s;

    f(v);
    f(s);

    return 0;
}
Bowie Owens
  • 2,798
  • 23
  • 20