24

Some of the STL containers such as std::list and std::vector don't have find() method as a member function. Why is that? I know that there is the alternative of using std::find from <algorithm> but still this use isn't 100% natural.

SylvainD
  • 1,743
  • 1
  • 11
  • 27
Hanna Khalil
  • 975
  • 1
  • 10
  • 28
  • 1
    Probably because searching an element in sequential containres all has some algorithm, so its made common, playing around with iterators – P0W Sep 02 '14 at 07:06
  • @POW that wouldn't prevent the standard to implement `find()`, would it? I mean it could just be a call to `std::find()`... – Theolodis Sep 02 '14 at 07:08
  • 6
    You might want to read this [article by Steve Myers about encapsulation](http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197) and this [GotW by Herb Sutter about `std::string`](http://www.gotw.ca/gotw/084.htm). Basically, the more of a class's functionality you can implement outside of its direct interface, the better. – Angew is no longer proud of SO Sep 02 '14 at 07:11
  • 4
    @Theolodis But there is no need to do that, or the advantages do not outweigh the disadvantages of bloating the interface. It would have been better if `std::find` could have been specialized to do the right thing for fast-look-up containers. – juanchopanza Sep 02 '14 at 07:12
  • 9
    You've got it backwards. The versions from `` should be your defaults. There are special member functions only when there are special requirements. – BoBTFish Sep 02 '14 at 07:13
  • @Theolodis Sorry, typo its _same_ not "some" [here](http://stackoverflow.com/questions/25617610/some-containers-in-stl-dont-have-find-function#comment40020915_25617610) – P0W Sep 02 '14 at 07:15

7 Answers7

42

The general design principle is to use std::find where possible, and implement find member functions when it is more efficient.

The containers that do have a find member are containers which have a more efficient element look-up mechanism then the linear search performed in std::find. For example, binary search trees such as std::set and std::map, or hash tables such as their unordered counterparts.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
13

find, lower_bound and upper_bound member functions are only provided when more efficient than using the non-member equivalents, or when the non-members couldn't operate given the container's public API

Note in particular that std::string has a find function which provides std::find()-like linear search facilities for character searches and std::search()-like facilities for sub-string searches: while the non-member versions may have the same big-O efficiency, they may well be less efficient given dedicated machine code instructions are often available for "string" searching. There are also historical, convenience, and ease-of-porting factors.

Quite apart from the question of efficiency, it's noteworthy that some containers:

  • are inherently either sorted (multi-set, map) or unsorted (unordered_map, unordered_set), typically unsorted (e.g. std::string), or easily either (std::vector)

  • publicly support forward iteration and/or random access

  • possibly privately support binary search

  • have such a specialised public API for element access that potential reuse of the algorithm is relatively limited (e.g. unordered_map::bucket / ::begin(n) et al)

It's also of interest that searching in a vector can be done using a great many algorithms:

  • std::find does a brute force linear O(n) search which will "find" lower-index elements first,

  • std::binary_search requires a sorted vector but jumps around to achieve O(log2n) complexity.

  • other options like extrapolation search and hashing might be applicable

How would you pick which to implement and add as members? Seems a bit arbitrary. Still, the choice of which to use can be important performance-wise: for a million elements, find averages half-a-million element comparisons before a match and the full million whenever the element's not there, while binary_search typically takes ~20 comparisons either way.

The containers with find don't typically provide such flexibility, and the find and/or lower_bound/upper_bound they provide can be seen as replacements for the non-member equivalents, and likely the only reasonable way to search the containers.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
9

Because there's the std::find function from algorithm that applies for them.

Generally, containers like std::vector and std::list have linear search time complexity. As such attaching to them a member find function is a redundancy because there's already std::find. For other containers (e.g., std::set or std::map etc.) there's a better way (i.e., faster than linear complexity) to implement searching. As such the implementers implemented these faster searching algorithm as member functions.

101010
  • 41,839
  • 11
  • 94
  • 168
  • 1
    OP knows this _"I know that there is the alternative of using std::find"_ – P0W Sep 02 '14 at 07:04
  • 1
    Even if this is technically true, it does not really answer the question. – MatthiasB Sep 02 '14 at 07:05
  • 11
    @MatthiasB he was using the "foot in the door" reputation gathering technique.. post a one-line answer quickly to get some upvotes and then write out the details later. *Now* it answers the question. – M.M Sep 02 '14 at 07:12
  • Since this answer answers the question, would be nice if down-voters explained the reason of their down-vote (if any...). – 101010 Sep 03 '14 at 16:57
3

Containers which have a search-by-key like feature will have the find method integrated (e.g. map which is internally implemented with a binary tree which can be looked up efficiently).

Others, like the ones you cited, will allow a range search with the std::find but don't have a featured find function because it would have no algorithmic advantage over the std::find (except in sorted/special cases)

Marco A.
  • 43,032
  • 26
  • 132
  • 246
2

Using the same function for various containers makes for a clearer API, you don't have to learn the peculiarities of each of the containers, just how to apply one function that you use for all of them.

It's also for code reusability - you use the algorithm that takes iterators from any of the containers that provide them, so the algorithm doesn't have to rely on the container being a std::vector, std::list etc.

w.b
  • 11,026
  • 5
  • 30
  • 49
2

Such containers as std::vector, std::list, std::forward_list and some others are sequential containers. There is nothing better than sequential search that can be applied to these containers. So there is no need to rewrite the sequential search for each sequential container if it is the same for all these containers.

The exception is class std::basic_string that initially simulates C-strings that already have special search functions as strchr, strstr and others.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

As mentioned in other comments, the design rationale is that vector::find() can be as efficiently implemented as a non-member function std::find(). The benefits of using the latter is that it decouples the data-structures and the operators acting on the data-structure, which increases maintainability (this is advantageous for the developers of the library).

However, the benefits of the former is that it would make the API between all containers consistent, and make client code less verbose. This would increase learnability and readability (this is advantageous for the users of the library). Also, consistent API would allow to write generic code. Consider this:

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (std::find(c.begin(), c.end(), x) != c.end()) {
        // ...
    }
}

The above is inefficient when Container is a std::map or std::set. To make it efficient, we'd need to do:

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (c.find(x) != c.end()) {
        // ...
    }
}

But then it doesn't compile for std::vector and std::list. This places the burden on users of the library to write their own generic function manually specialized/overloaded for each type they want to support:

template <typename T>
bool contains(const std::vector<T>& c, const T& x) {
    return std::find(c.begin(), c.end(), x) != c.end();
}

template <typename T>
bool contains(const std::set<T>& c, const T& x) {
    return c.find(x) != c.end();
}

template <typename Container, typename T>
void foo(const Container& c, const T& x) {
    if (contains(c, x)) {
        // ...
    }
}

I acknowledge that making these types of design decisions is hard, but my opinion is that designers of the STL made a mistake here. The very tiny maintainability burden seems quite largely worth the better API and consistency for users. In a nutshell, since find must be a member function for some containers (for performance), then find should be a member function for all containers (for consistency). Note that I'm totally okay with other algorithms being non-member functions.

(I mean, come on, a container is by definition something that contains stuff. It should be trivial for users to write a generic and efficient "contains" function. In fact, I'd argue it should be added to the Container concept, but I digress.)

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59