Concepts / Standard-Library Search Algorithms

Standard-Library Search Algorithms

The find algorithm returns an iterator to the first matching element, or end() when the value is not in the range.

  • Programming

A Yes-or-No Question About a String

Suppose you need a small yes-or-no function. Given a string and a character, it should report whether that character appears anywhere in the string. You could write a loop yourself that walks through the characters one at a time. The standard library, however, already offers a search algorithm that does the scanning for you. This article starts with a working function that uses that algorithm, so you can see its behavior before we explain the mechanism behind it.

cpp
Notes on the code
  • std::find scans the range from s.begin() up to s.end() looking for c
  • p stores whatever find hands back, which is always an iterator
  • The if statement compares that iterator with s.end() to decide the answer

Read the function once and keep it in mind as you go through the rest of this article. The questions that follow are not about how find scans internally. They are about what find gives back and how that value is tested. Notice that to predict the answer for a given input, you do not need to know the scanning details. You only need to know what the call returns and how the caller tests that value.

Tracing What find Hands Back

The key idea to hold onto is this: the return value of find is always an iterator. It is never a plain character and never a yes-or-no flag. Because the result is an iterator, the caller is responsible for deciding what that iterator means. If find locates a matching element, the iterator points at that element. If it does not, the iterator is the same value that end() produces. Both outcomes come back through the same kind of value, so the caller has to test which one happened.

Position 0cPosition 1aPosition 2tend()past the last characternextnextnext
When searching for the character t, find returns an iterator to position 2, the first position holding t. The end() position sits just past the last character and is never a valid element.

In the diagram, the position holding t is the one the returned iterator refers to. Suppose instead you searched for a character that does not appear anywhere in cat, such as z. The search would walk past every real position, never find a match, and return the value that end() refers to. The returned iterator in that case does not point at any character in the string at all. It is simply the boundary marker that signals the search ran out of range.

Reading the Result Against end()

The test p != s.end() is the entire decision in has_c. The variable p holds whatever find returned. If p is different from s.end(), then find stopped at a real matching position, so the character is present. If p equals s.end(), then find ran out of range without a match, so the character is absent. The comparison never inspects the character itself. It only checks where the iterator points. Because of this, the same test works for any element type that find can search, not only characters.

Call findreturns iterator pp != s.end()?Return truematch foundReturn falserange exhaustedyesno
After find returns, the program compares the result with end(). A difference means a match was located and the function returns true. Equality means the range was exhausted and the function returns false.

The source gives a second way to write the same function. Instead of storing the iterator in a named variable and using an if statement, you can return the comparison directly. The two versions perform the same search and the same comparison, so they return the same result for every input. The direct version is shorter, while the first version makes the intermediate iterator easy to see. Both are correct, and you can choose whichever reads more clearly to you.

cpp
Notes on the code
  • The find call and the comparison with s.end() are written as one expression
  • This version behaves the same as the if-statement version for every input

Scanning Until a Match or the Range Ends

Now consider how find reaches its result. It moves through the range one element at a time, starting at the first position. At each element it asks whether the element matches the value being searched for. If it does, the search stops immediately and returns an iterator to that element. If it does not, the search moves on to the next element. If the search reaches the end of the range without a match, it stops and returns end(). In other words, a match causes an early stop, while a miss causes the search to run all the way to the end.

Compare cnot t, continueCompare anot t, continueCompare tmatch, stopReturn iteratorpoints at position 2nextnextstop
find compares one element at a time. It stops at the first match, so the later positions are never examined when a match is found early.

The range that find searches is described by two iterators: begin() and end(). The range includes the element at begin() but stops before end(). This is why end() is called a sentinel: it marks the position just past the last element rather than an element itself. Because end() is not a real element, it must never be read, and it is the value you compare against to detect a failed search. The position begin() is the first element that find examines, while end() is the boundary where the scan ends if nothing matches.

Mistakes When Interpreting a Search Result

  • Common mistake

    Treating the returned iterator as a character or a boolean

    What it looks like
    if (std::find(s.begin(), s.end(), c)) { ... }
    Why it fails

    find returns an iterator, not a character or a yes-or-no flag. The condition does not describe whether a match exists.

    Do this instead

    Store the result in a variable and compare it with s.end(), as in p != s.end().

  • Common mistake

    Comparing the result with begin() to detect a match

    What it looks like
    if (p != s.begin()) { return true; }
    Why it fails

    A match can occur at begin(), and a miss returns end(), so begin() does not separate the two outcomes.

    Do this instead

    Compare the result with end(). A difference from end() means a real match was located.

  • Common mistake

    Dereferencing the result without checking it first

    What it looks like
    char found = *std::find(s.begin(), s.end(), c);
    Why it fails

    When nothing matches, the result equals end(), which does not refer to a valid element. Reading it is not a valid operation.

    Do this instead

    Check that the result is not equal to end() before reading the element it points to.

  • Common mistake

    Assuming find reports the last match rather than the first. find returns an iterator to the first matching element.

Predict, Then Check

What do you think happens?

Given has_c as written above, what does has_c("cat", 'z') return?

  • true

  • false

Reveal answer
Answer:

false

No position in cat holds z, so find scans every element and returns end(). The comparison p != s.end() is then false, and the function returns false.

EASY

Write a function bool has_char(const std::string& s, char c) that uses std::find with s.begin() and s.end(). Return true when c appears in s and false otherwise. Then trace your function by hand for the inputs cat with the character t, and cat with the character z, identifying what find returns in each case.

Hints
  • Call std::find with s.begin(), s.end(), and c as its three arguments.
  • Compare the returned iterator with s.end() and return the result of that comparison.
  • For the character z, the result should equal s.end(), so the comparison is false.

Key Takeaways

  • The find algorithm returns an iterator to the first matching element when one exists, and returns end() when the value is not in the range.
  • Because the return value is always an iterator, the caller must compare it with end() to decide whether a match was found.
  • The range runs from begin() up to but not including end(), so end() marks the position past the last element and must never be read.
  • A match stops the scan early, while a miss runs the scan through the whole range before returning end().

Related concepts

More concepts starting with S