1

My initial question was: why does in C++ the contains() function miss in Containers?

So I looked for an explanation and I found something interesting about why some other function are not implemented in all the Containers (essentially because of performance issues and convenience).

I know that you can use the find function from algorithm library or you can just write your own function with Iterator, but what I can't understand is why in set, for example, the contains function(where it's called find) is implemented, whereas in vector or queue it is not.

It's pretty clear to me too why Container classes does not share a common interface like Collections do in Java (thanks to this answer) but in this case I can't find the reason why not implement the contains() function in all the Containers classes (or at least in some like vector).

Thank you

Marco Luzzara
  • 5,540
  • 3
  • 16
  • 42
  • 2
    "If you can do it with a free function - do it, otherwise - use a member function." `std::find` works just fine for *all* containers. Some have `.find` members which make use of the internal structure of the container to improve efficiency. – DeiDei Jul 01 '17 at 18:57
  • 1
    Well, C++ is just ugly. Deal with it. – user7860670 Jul 01 '17 at 18:57
  • 2
    @VTT That statement's just controversial at best. Maybe your C++ code is ugly. – DeiDei Jul 01 '17 at 18:58
  • 1
    Why implement a function in every container if you could use one for all outside? –  Jul 01 '17 at 19:14
  • Note `std::set` also has `count`, which is closer in semantics to a `contains`. – aschepler Jul 01 '17 at 19:45
  • @CaptainObvlious I admit that my code of filled with horrible things such as `:` after access specifies or `;` after class definitions, but why aren't you among C++ standard committee members if yours is so gorgeous? – user7860670 Jul 01 '17 at 19:53
  • @VTT Have you ever seen the end of Raiders of the Lost Ark? – Captain Obvlious Jul 01 '17 at 21:49

4 Answers4

4

The reason why std::set implements its own find is that the "generic" std::find does a linear search, while std::set can do a lot better than that by searching its tree representation in O(log2 n ).

std::vector and std::list, on the other hand, cannot be searched faster than in linear time, so they rely on std::find implementation.

Note that you are still allowed to apply std::find to std::set to search it linearly; it just wouldn't be as efficient as using set's own find member function.

std::set<int> s {1, 2, 3, 4, 5, 6};
auto res = std::find(s.begin(), s.end(), 3);
std::cout << *res << std::endl; // prints 3
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Thank you for the answer. One thing again: you've said that `find` function can be used with any Container, and some of them have their specific function because of performance improvement. This is clear to me, what is not completely clear is: why then does not implement the `find` function too inside the vector class for example? I mean, the code could be the same as the algorithm find function, but I would do that (maybe erroneously) just for completeness. Why am I wrong? – Marco Luzzara Jul 01 '17 at 19:14
  • 1
    I found the answer in the comments: Why implement a function in every container if you could use one for all outside? Thanks manni66 – Marco Luzzara Jul 01 '17 at 19:19
  • 1
    @MarcoLuzzara, this is a personal anecdote, but I thought list membership was a good idea in Python back in the day. I did 10k lookups on 20k item+ lists routinely. Encouraging bad design patterns has real consequences, IMO. – Alex Huszagh Jul 01 '17 at 19:25
4

Because it's a bad design pattern. If you're using "find" on a linear container repeatedly, there's a problem with your code. The average and worst case time complexity is still O(n), which means you've made a bad choice.

For example, std::map and std::unordered_map have find member functions which allows O(logn) and O(1) lookups. This is because the container employs efficient item lookup via these methods: that is how the container should be used.

If you've weighed all the options, and decided a linear container is the best model for your data, but do need to find an element in rare occasions, std::find() allows you to do just that. You shouldn't rely on it. I view this as an antipattern in Python and Java, and a benefit in C++.

Just as a personal note, 3 years ago, when I was a beginner coder, I wrote if mydict in list: do_something() a lot. I thought this was a good idea, because Python makes list item membership idiomatic. I didn't know better. This led me to produce awful code, until I learned why linear searches are so inefficient compared to binary searches and hashmap lookups. A programming language or framework should enable good design patterns, and discourage bad ones. Enabling linear searches is a bad design pattern.

Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67
2

Other answers for some reason focus on complexity of generalized and per-container find methods. However they fail to explain the OP's question. The actual reason for a lack of helpful utility methods originates from the way the classes from different files are used in C++. If every container that does not hold any special properties would have contains method executing generic search with linear complexity we would stuck either with situation when each container header also includes <algorithm> or when each container header reimplements it's own find algorithm (which is even worse). And the pretty large document build during compilation of each translation unit after preprocessor includes (that is copy-pastes) every included header would grow even bigger. Compilation would take even more time. And the rather cryptic compilation error messaged may get even longer. That old principle of not making member function when a non-member function can do the job (and to include only stuff you are going to use) exists for a reason.

Note that there was a proposal recently for uniform call syntax that may allow to kinda mix-in utility methods into classes. If it goes live it probably will be possible to write extension functions like this:

template< typename TContainer, typename TItem > bool
contains(TContainer const & container, TItem const & item)
{
     // Some implementation possibly calling container member find
     // or ::std::find if container has none...
}

::std::vector< int > registry;
registry.contains(3); // false
user7860670
  • 35,849
  • 4
  • 58
  • 84
2

There is a good reason that containers do not share a "common (inherited) interface" (like in Java) and it is what makes the C++ generics so powerful. Why write code for every container when you can write it only once for all containers? This is one of the main principles the STL was built on.

If using containers relied on member functions from a common inherited interface you would have to write a find function for every container. That is wasteful and poor engineering. Good design says if you can write code in only one place you should because you only have to remove the bugs from that one place, and fixing a bug in one place fixes the bug everywhere.

So the philosophy behind the STL is to separate the algorithms from the containers so that you only have to write the algorithm once and it works for all containers. Once the algorithm is debugged, it is debugged for all containers.

A fly in that ointment is that some containers can make more efficient decisions due to their internal structure. For those containers a type specific function has been added which will take advantage of that efficiency.

But most functions should be separate from the containers. It is called decoupling and it reduces bugs while promoting code reuse, often much more so than polymorphism which is what libraries like Java containers use (a common inherited interface).

Galik
  • 47,303
  • 4
  • 80
  • 117