I'm heavily using std::set<int>
and often I simply need to check if such a set contains a number or not.
I'd find it natural to write:
if (myset.contains(number))
...
But because of the lack of a contains
member, I need to write the cumbersome:
if (myset.find(number) != myset.end())
..
or the not as obvious:
if (myset.count(element) > 0)
..
Is there a reason for this design decision ?
Another reason is that it would give a programmer the false impression that std::set is a set in the math set theory sense. If they implement that, then many other questions would follow: if an std::set has contains() for a value, why doesn't it have it for another set? Where are union(), intersection() and other set operations and predicates?
The answer is, of course, that some of the set operations are already implemented as functions in (std::set_union() etc.) and other are as trivially implemented as contains(). Functions and function objects work better with math abstractions than object members, and they are not limited to the particular container type.
If one need to implement a full math-set functionality, he has not only a choice of underlying container, but also he has a choice of implementation details, e.g., would his theory_union() function work with immutable objects, better suited for functional programming, or would it modify its operands and save memory? Would it be implemented as function object from the start or it'd be better to implement is a C-function, and use std::function<> if needed?
As it is now, std::set is just a container, well-suited for the implementation of set in math sense, but it is nearly as far from being a theoretical set as std::vector from being a theoretical vector.
To be able to write
if (s.contains())
,contains()
has to return abool
(or a type convertible tobool
, which is another story), likebinary_search
does.The fundamental reason behind the design decision not to do it this way is that
contains()
which returns abool
would lose valuable information about where the element is in the collection.find()
preserves and returns that information in the form of an iterator, therefore is a better choice for a generic library like STL. This has always been the guiding principle for Alex Stepanov, as he has often explained (for example, here).As to the
count()
approach in general, although it's often an okay workaround, the problem with it is that it does more work than acontains()
would have to do.That is not to say that a
bool contains()
isn't a very nice-to-have or even necessary. A while ago we had a long discussion about this very same issue in the ISO C++ Standard - Future Proposals group.The true reason for
set
is a mystery for me, but one possible explanation for this same design inmap
could be to prevent people from writing inefficient code by accident:Which would result in two
map
lookups.Instead, you are forced to get an iterator. This gives you a mental hint that you should reuse the iterator:
which consumes only one
map
lookup.When we realize that
set
andmap
are made from the same flesh, we can apply this principle also toset
. That is, if we want to act on an item in theset
only if it is present in theset
, this design can prevent us from writing code as this:Of course all this is a mere speculation.