P4205R1
Range-Based Searchers

Published Proposal,

This version:
https://wg21.link/P4205R1
Author:
Audience:
SG9
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Target:
C++29
Source:
github.com/Mick235711/wg21-papers/blob/main/P4205/P4205R1.bs
Issue Tracking:
GitHub Mick235711/wg21-papers

This proposal introduces std::ranges versions of the Searcher overload of the std::search algorithm, which takes a searcher object instead of an iterator pair and (optionally) a predicate. It was originally introduced for more performant specialized searching.

As customary in Ranges algorithms, this proposal also proposes Range-ified versions of the existing standard searchers, along with a concept std::searchable for better capturing the semantic requirements of standard searchers.

1. Revision History

1.1. R1 (2026-08 mailing)

1.2. R0 (2026-05 pre-Brno mailing)

2. Background

std::search is a very old standard algorithm, dating back to the inclusion of the original STL in 1994. The most basic shape of the algorithm takes two pairs of iterators representing haystack and needle, and returns the beginning position of the first occurrence of the needle sequence in the haystack sequence (or returns the end iterator of haystack if no occurrences are found). Optionally, the algorithm also takes a binary predicate to overwrite the equality comparison operation. The algorithm also has a search_n variant that searches for count copies of the same element instead of a provided needle range.

Several improvements and extensions have been applied to std::search after its inclusion in the C++ standard. In the C++17 cycle, the introduction of parallel algorithms brings ExecutionPolicy overloads to std::search; in the C++20 cycle alone, the merging of Ranges TS ([P0896R4]) into the standard brings with it the std::ranges::search variant, which enables iterator-sentinel and ranges argument passing style instead of the traditional iterator pair style to the algorithm. Meanwhile, [P0202R3] also added the constexpr specifier to both variants of the algorithm, finally leading to its present form.

However, all of these modifications apply uniformly to most algorithms in the <algorithm> header; this proposal instead focuses on one specific enhancement that only applies to std::search: the Searcher overload of the algorithm introduced by [N3905] (originally to Library Fundamentals TS v1, later merged into C++17). The original motivation for the inclusion of searchers was to allow more performant specialized search algorithms that precompute various statistics from the pattern to increase performance, as std::search’s normal overloads in general can only achieve a time complexity of O(NS) using naive methods (where N and S are the length of the haystack and needle sequences, respectively). For instance, the standard boyer_moore_searcher implements the Boyer-Moore String Search Algorithm, which can achieve a worst-case running time of O(N + S) instead (requires some variation on the original 1977 version of the algorithm). In practice, using standard searchers often introduces significant speedup compared to normal std::search invocations, at the cost of a larger memory footprint and longer initialization times on first invocation due to the need for precomputations.

Unfortunately, perhaps due to its late entrance to the standard, the Searcher overload is never included in either Boost.Ranges or range-v3, and as a result, the current Ranges library never included a std::ranges::search overload with Searcher arguments. Given that nearly every other overload in the <algorithm> header has a corresponding ranges version, and works ([P1813R0], [P3732R2]) have been underway to Range-ify the remaining <numeric> algorithms too, it seems increasingly likely that the omission of this overload is an unintentional mistake.

This proposal thus proposes fixing this omission by introducing an overload for std::ranges::search that accepts a Searcher algorithm instead of a second range. Furthermore, the C++23 algorithm std::ranges::contains_subrange introduced by [P2302R4] was specified as a convenience wrapper for std::ranges::search, thus a Searcher overload is introduced for that algorithm too for API consistency.

3. Motivation

Current inconsistency in the std::ranges::search API forces users to exit the Ranges world to use searchers and resort to using traditional STL algorithms instead, which is undesirable.

A before/after comparison of the feature is shown below:

// In C++26
std::string haystack = "a quick brown fox jumps over the lazy dog";
std::string needle = "jump";

std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end()); // OK
std::ranges::search(haystack, needle); // OK, clear improvements to std::search

std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](auto a, auto b) { return tolower(a) == b; }); // OK
std::ranges::search(haystack, needle, [](auto a, auto b) { return tolower(a) == b; }); // OK
std::ranges::search(haystack, needle, {}, [](auto a) { return tolower(a); }); // OK, better

std::search(std::execution::par_unseq, haystack.begin(), haystack.end(), needle.begin(), needle.end()); // OK
std::ranges::search(std::execution::par_unseq, haystack, needle); // OK since C++26

std::boyer_moore_searcher searcher(needle.begin(), needle.end());
std::search(haystack.begin(), haystack.end(), searcher); // OK
std::ranges::search(haystack, searcher); // Error!

// After this proposal:
std::ranges::boyer_moore_searcher searcher(needle);
std::ranges::search(haystack, searcher); // Proposed OK

Since most Ranges algorithms use concepts to constrain their arguments instead of using template parameter names as an implicit named requirement, this proposal also follows this custom and introduces a concept for searchers that constraints their API, instead of relying on named requirements.

4. Design

4.1. What Is Being Proposed?

template<forward_iterator I, sentinel_for<I> S, class Searcher, class Proj = identity>
    requires searchable<Searcher, I, S, Proj>
constexpr auto ranges::search(I first, S last, const Searcher& searcher, Proj proj = {});
template<forward_range R, class Searcher, class Proj = identity>
    requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
constexpr auto ranges::search(R&& r, const Searcher& searcher, Proj proj = {});

template<forward_iterator I, sentinel_for<I> S, class Searcher, class Proj = identity>
    requires searchable<Searcher, I, S, Proj>
constexpr bool ranges::contains_subrange(I first, S last, const Searcher& searcher, Proj proj = {});
template<forward_range R, class Searcher, class Proj = identity>
    requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
constexpr bool ranges::contains_subrange(R&& r, const Searcher& searcher, Proj proj = {});

4.2. Range-Based Searchers

The simplest version of a standard searcher is realized in std::default_searcher, which acts as a thin wrapper around std::search to make it follow the searcher API. The API for std::default_searcher looks like this:

template<class ForwardIterator1, class BinaryPredicate = equal_to<>>
class default_searcher {
public:
    constexpr default_searcher(ForwardIterator1 pat_first, ForwardIterator1 pat_last,
                               BinaryPredicate pred = BinaryPredicate());
    template<class ForwardIterator2>
    constexpr pair<ForwardIterator2, ForwardIterator2>
    operator()(ForwardIterator2 first, ForwardIterator2 last) const;
};

There are several peculiarities in this API that make it unsuitable for the Ranges library, thus prompting this proposal to propose new std::ranges versions of the standard searchers, similarly to the relation between std::ranges::less and std::less<T>. For instance, all standard searchers' operator() returns a pair of iterators after [P0253R1] identified that the end position of the needle inside the haystack is frequently useful for algorithms that are based on searchers. The Ranges library introduced std::ranges::subrange directly for this purpose, and thus, that should be used as the return type instead.

The proposed API for std::ranges::default_searcher looks like this:

template<forward_range V, copy_constructible Pred = ranges::equal_to,
         indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity>
    requires view<V>
class ranges::default_searcher {
public:
    constexpr explicit default_searcher(V base, Pred pred = {}, Proj proj = {});

    constexpr V base() const & requires copy_constructible<V>;
    constexpr V base() &&;

    constexpr const Pred& pred() const;
    constexpr const Proj& proj() const;

    template<forward_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
        requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>
    constexpr subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const;
};

template<forward_range R, copy_constructible Pred = ranges::equal_to,
         indirectly_regular_unary_invocable<iterator_t<R>> Proj = identity>
default_searcher(R&&, Pred = {}, Proj = {}) -> default_searcher<views::all_t<R>, Pred, Proj>;

Essentially, the API for std::ranges::default_searcher is a Range-ified version of std::default_searcher, with the intention that ranges::search(r, ranges::default_searcher(r2, pred, proj2), proj) (which calls searcher(ranges::begin(r), ranges::end(r), proj)) is always equivalent to ranges::search(r, r2, pred, proj, proj2). It is for this reason that the concepts on operator() have its arguments flipped.

The return type of operator() has been changed to subrange to represent the found range. This method will return {last, last} if no occurrences of needle is found, {first, first} if needle is empty, and the original (empty) range if the haystack is empty. This modification is consistent with the normal overload of std::ranges::search.

The API for std::ranges::boyer_moore_[horspool_]searcher adds a Hash type argument as the last template argument (that defaults to std::hash<projected_value_t<iterator_t<V>, Proj>>), and adds a Hash hf = {} argument to the constructor. Apart from these changes, their API is identical to the default searchers (except requiring random-access iterators and ranges instead of forward ones). The Hash argument is changed from the second argument to the last because of its default argument dependency on the projection. It is expected that most users will utilize CTAD to construct these searchers (i.e. boyer_moore_searcher{needle}), so the positions of template argument are not that important anyways.

One thing to be noted here is that the existing std:: searchers have a lifetime dependency on the underlying ranges; they will store the iterators from the provided range, and thus is only valid for use while the provided range is alive (or if it is a borrowed range). The proposed std::ranges:: searchers utilize the existing Ranges mechanism of wrapping the given range in views::all_t to eliminate this hazard.

4.3. Building A Searchers Concept

The concept std::searchable itself does not fully capture the API proposed for the Range versions of searchers; this is because the proposed concept is iterator-based, so the Range constructor is not a required part of the searchers interface to reduce requirements on user-provided searcher types. The proposed specification for the std::searchable concept is as follows:

template<class Searcher, class I, class S, class P = identity>
concept searchable =
    movable<Searcher> &&
    invocable<const Searcher&, I, S, P> &&
    ranges::forward_range<invoke_result_t<const Searcher&, I, S, P>>

It is worth noting that C++17 searchers are required to be copyable ([func.search.general]/2); due to the usage of views::all_t, sometimes the new range-based searchers need to store an owning_view, which makes them move only. Therefore, the new concept only requires movable instead. The existing std::search algorithm is specifically constraint to not rely on searchers being copyable ([alg.search]/12), so this relaxing should not cause issues.

Note that we deliberately use invocable here since most implementations of non-trivial searchers are stateful.

4.4. constexpr And Return Type

It is worth noting that boyer_moore_[horspool_]searcher are not marked as constexpr currently, while default_searcher on the other hand did support constexpr. On the other hand, the existing std::search overload for searchers is marked as constexpr, making it incompatible with any non-trivial default searchers in compile-time invocation.

After investigating the current implementation of searchers among libstdc++, libc++ and MSVC STL, it seems that there is no inherent difficulty in making standard non-trivial searchers (boyer_moore_[horspool_]searcher) constexpr-compatible, as most implementation only relies on a constexpr-enabled hash map that is already provided by [P3372R3] in C++26. However, given that <functional> as a whole still lacked constexpr specifications, the author feels that constexpr-ifying the searchers is best left to a future proposal that tackles constexpr for the entire header instead. The added overloads on ranges::search and ranges::contains_subrange is marked constexpr in this proposal, as they can still work with third-party constexpr-enabled searchers. ranges::default_searcher is marked constexpr following the existing practice of std::default_searcher.

The return type for the new overloads of std::ranges::search is specified as auto (i.e., copy the return type of the searcher’s operator()). It is possible to require convertible to subrange<I> here too, but the author thinks that this is an unnecessary limitation. The searcher’s operator() return type is constrained to be a forward range by the concept searchable.

4.5. Interopability With Existing Algorithms

During SG9 review in Brno (2026-06), the author is asked to investigate whether the new range-based searchers can be used with the existing std::search algorithm, and whether the existing searchers can be used with std::ranges::search.

For the former question, the current answer is no. Although as discussed above the std::search algorithm is currently required to not depend on searchers being copyable, the specification unfortunately requires returning searcher(first, last).first, which essentially limits the return type of searchers' operator() to std::pair-like structures. The newly purposed range-based searchers' operator() returns a subrange, which does not have first and second members.

However, the author would like to purpose an additional change to std::search that can bridge this gap: make the algorithm return std::get<0>(searcher(first, last)) instead. std::pair and most third-party pair-like structures should support the tuple protocol, and thus such a change will not be breaking for any standard searchers and most third-party searchers; furthermore, subrange also implements the tuple protocol, and thus this change will make std::search usable with the new range-based searchers (and return the begin iterator as usual). Of course, this will be a breaking change for searchers that have different behaviour for .first and std::get<0>, or pair-like return types that does not implement the tuple protocol, but the author estimate such searchers to be rare. This change is therefore presented as an optional additional change, with its wording section easily discarded if SG9/LEWG deemed the breakage to be too large.

For the latter question, the answer is also no, mainly because searchable concept currently requires the return type of searchers' operator() to be a forward range, while traditional searchers return a pair of iterators. Although iterator pairs are commonly used to denote a range, they are not accepted by the range concepts. It is possible to drop the return type requirement from searchable (or require forward_iterator<tuple_element<0, invoke_result>> instead), and the author is happy to oblige if SG9/LEWG wants this change. It is worth noting that even with such a change this use case will not be fully supported as traditional searchers may not support iterator and sentinel with different types.

Finally, another question asked during the review is whether user needs to write two version of searchers from now on to make it usable for both std::search and std::ranges::search. If one of the above changes (or both) is adopted, then the answer is no, as the user can simply provide a subrange operator()(I, S, Proj = {}) or pair<I, I> operator()(I, S, Proj = {}) overload that will be compatible with both. Even if both changes are rejected, the user can simply overload on two and three arguments as std::ranges::search always pass in a projection, so the author think that the risk of duplicating efforts are low.

4.6. Header Choice

The existing standard searchers live in <functional>, and all overloads of the std::search algorithm live in <algorithm>.

This proposal proposes that the new overloads of std::ranges::search and std::ranges::contains_subrange both still go into <algorithm>, which is consistent with other overloads of the two algorithms. The std::ranges versions of the three existing standard searchers should also still go into <functional>, following the precedent of ranges::less. The concept std::searchable will go into <iterator>, similar to other indirectly_foo and sortable concepts.

4.7. Feature-Test Macro Changes

Currently, both boyer_moore_searcher and boyer_moore_horspool_searcher use the same feature-test macro __cpp_lib_boyer_moore_searcher, and default_searcher does not have a corresponding macro (its constexpr-edness is tracked by __cpp_lib_constexpr_functional). Also, std::ranges::search does not have its own feature-test macro, and the author thinks that bumping __cpp_lib_ranges for this seems a bit too huge.

This proposal ultimately decided to propose the following changes to feature-test macros:

Note that std::search also does not have its own FTM; if it is decided to pursue the additional interopability changes, the author do not think another separate FTM is needed; the change can be tracked by __cpp_lib_ranges_search.

Of course, the author is happy to split the FTMs further (such as separate FTMs for each Range searcher) if LEWG requests.

4.8. Freestanding

After the adoption of [P2976R1] in C++26, the majority of <algorithm> (including all overloads of std::search and std::ranges::search) are marked as freestanding. This proposal does not change anything in that regard, so the newly proposed overloads are also available in freestanding.

The searchers have a somewhat different status. std::boyer_moore_[horspool_]searcher is deliberately left out of freestanding by [P1642R11] as their common implementations rely on dynamic allocation, so their range versions are also not marked freestanding. std::ranges::default_searcher is marked freestanding as it is just a thin wrapper around std::ranges::search anyway. The concept std::searchable is marked freestanding to maintain consistency with other parts of <iterator>.

All feature-test macros added by this paper are marked freestanding, and all modified FTMs are already freestanding after the adoption of [P2198R7] and [LWG4286]. No existing features are moved to freestanding, so the freestanding-related FTMs are not changed.

4.9. Alternatives Considered

A possible less invasive change is to reuse the existing searchers, but only update std::ranges::search with the new overloads. In that path, the searchable concept will probably accept only an iterator type and test for forward_iterator_pair<invoke_result_t<const Searcher&, I, I>>.

However, such a change will not support range-based construction, not support projections on needles, and also not adapt to the new C++20 range/iterator model. The author thinks that requiring the user to write ranges::search(haystack, boyer_moore_searcher(needle.begin(), needle.end())) is strictly worse than introducing Range-ified versions of the searchers.

It has also been considered briefly to add a default argument to the Searcher argument of the algorithms; however, using default_searcher here is meaningless, and other searchers all have different tradeoffs, so it seems unwise for the standard to specify one as the default choice.

4.10. Future Extensions

4.10.1. Parallel Overloads

After C++26 adopted [P3179R9], most of the algorithms in the std::ranges namespace gained a parallel overload that accepts an execution policy as the first argument. Since searchers are born to increase the performance of searching, it seems natural to also add parallel overload to them to further increase efficiency.

However, most existing specialized algorithms are designed for sequential usage. Paralleled versions of the Boyer-Moore algorithm are certainly possible, but seem to require non-trivial handling of edge cases, such as when the pattern crosses the slicing boundaries. Given that C++17’s parallel STL library deliberately did not add an execution policy overload to the searcher version of std::search, this proposal also does not add an execution policy overload to std::ranges::search or std::ranges::contains_subrange.

A future parallel extension probably requires some kind of coordination between the algorithm and the searcher, so both the constructor and operator() of the searcher probably should accept an execution policy argument (which also requires a new version of the concept std::searchable). However, such extensions are outside the scope of this proposal.

4.10.2. Searcher Overloads In More Algorithms

Currently, searchers are only accepted and used in std::search. This proposal does a slight extension in this regard to make std::ranges::contains_subrange accept searchers too.

In the future, it can be envisioned that more algorithms can take advantage of searchers. For instance, search_n may also accept a searcher that is constructed from count and value, and find family may also benefit from more performant algorithms that search for single elements. However, such extensions are outside the scope of this proposal.

4.10.3. More Searchers

Finally, there are various attempts to add more searchers to the standard library, providing users with searchers that satisfy more tradeoff decisions. Popular choices include the famous Knuth-Morris-Pratt String-Searching Algorithm and Crochemore-Perrin Search Algorithm ([P0638R0]). Similar to Range algorithms, future extensions to searchers probably should only add types to std::ranges (in particular crochemore_perrin_searcher from [P0638R0] can use std::ranges::less instead). Though the author will not oppose adding std:: versions too.

5. Implementation Experience

The author implemented this proposal in beman.range_searcher as part of the Beman Project. The implementation basically copies libc++'s searcher implementation and modify them to accommodate the new API. All modifications are straightforward, and no significant obstacles are observed.

6. Questions To Resolve

6.1. Questions for SG9

6.2. Questions for LEWG

7. Wording

The wording below is based on [N5054].

7.1. 17.3.2 Header <version> synopsis [version.syn]

In this clause’s synopsis, make the following modification, substituting 20XXYYL by the date of adoption. All inserted new macro definition should be in places that respects the current alphabetical order of the synopsis.

#define __cpp_lib_ranges_contains 20XXYYL202207L // freestanding, also in <algorithm>
// [...]
#define __cpp_lib_ranges_search   20XXYYL // freestanding, also in <algorithm>
#define __cpp_lib_ranges_searcher 20XXYYL // freestanding, also in <functional>

7.2. 22.10.2 Header <functional> synopsis [functional.syn]

Modify the synopsis as follows:

// [...]
namespace std {
  // [...]
  // [func.search], searchers
  // [...]
  template<class RandomAccessIterator,
           class Hash = hash<typename iterator_traits<RandomAccessIterator>::value_type>,
           class BinaryPredicate = equal_to<>>
    class boyer_moore_horspool_searcher;

  namespace ranges {
    template <forward_range V,
              copy_constructible Pred = ranges::equal_to,
              indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity>
      requires view<V>
    class default_searcher;  // freestanding

    template <random_access_range V,
              copy_constructible Pred = ranges::equal_to,
              indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity,
              class Hash = hash<projected_value_t<iterator_t<V>, Proj>>>
      requires view<V> && semiregular<range_value_t<V>>
    class boyer_moore_searcher;

    template <random_access_range V,
              copy_constructible Pred = ranges::equal_to,
              indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity,
              class Hash = hash<projected_value_t<iterator_t<V>, Proj>>>
      requires view<V> && semiregular<range_value_t<V>>
    class boyer_moore_horspool_searcher;
  }
  
  // [...]
}

7.3. 22.10.18.1 General [func.search.general]

Modify as follows:

Subclause [func.search] provides function object types ([function.objects]) for operations that search for a sequence [pat_first, pat_last) in another sequence [first,last) that is provided to the object’s function call operator. The first sequence (the pattern to be searched for) is provided to the object’s constructor, and the second (the sequence to be searched) is provided to the function call operator.

Each specialization of a class template specified in [func.search] shall meet the Cpp17CopyConstructible and Cpp17CopyAssignable Cpp17MoveConstructible and Cpp17MoveAssignable requirements. In addition, each specialization of class template std::default_searcher, std::boyer_moore_searcher, and std::boyer_moore_horspool_searcher shall meet the Cpp17CopyConstructible and Cpp17CopyAssignable requirements. Template parameters named

of templates specified in [func.search] shall meet the same requirements and semantics as specified in [algorithms.general]. Template parameters named Hash shall meet the Cpp17Hash requirements ([tab:cpp17.hash]).

The Boyer-Moore searcher implements the Boyer-Moore search algorithm. The Boyer-Moore-Horspool searcher implements the Boyer-Moore-Horspool search algorithm. In general, the Boyer-Moore searcher will use more memory and give better runtime performance than Boyer-Moore-Horspool.

Note: Editor’s Note: Insert the following subclause at the end of 22.10.18 Searchers [func.search]

7.4. 22.10.18.5 Range-based searchers [func.range.search]

7.4.1. 22.10.18.5.1 Class template default_searcher [func.range.search.default]

namespace std::ranges {
  template <forward_range V,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity>
    requires view<V>
  class default_searcher {
  public:
    constexpr explicit default_searcher(V base, Pred pred = {}, Proj proj = {});

    constexpr V base() const & requires copy_constructible<V> { return base_; }
    constexpr V base() && { return std::move(base_); }

    constexpr const Pred& pred() const;
    constexpr const Proj& proj() const;

    template<forward_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
      constexpr subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
        requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;

  private:
    V                 base_; // exposition only
    movable-box<Pred> pred_; // exposition only
    movable-box<Proj> proj_; // exposition only
  }

  template <forward_range R,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<R>> Proj = identity>
    default_searcher(R&&, Pred = {}, Proj = {})
      -> default_searcher<views::all_t<R>, Pred, Proj>
}
constexpr explicit default_searcher(V base, Pred pred = {}, Proj proj = {});
  1. Effects: Constructs a default_searcher object, initializing base_ with move(base), pred_ with move(pred), and proj_ with move(proj).

constexpr const Pred& pred() const;
  1. Effects: Equivalent to: return *pred_;

constexpr const Proj& proj() const;
  1. Effects: Equivalent to: return *proj_;

template<forward_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
  constexpr subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
    requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;
  1. Effects: Equivalent to: return ranges::search(first, last, ranges::begin(base_), ranges::end(base_), *pred_, move(proj2), *proj_);

7.4.2. 22.10.18.5.2 Class template boyer_moore_searcher [func.range.search.bm]

namespace std::ranges {
  template <random_access_range V,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity,
            class Hash = hash<projected_value_t<iterator_t<V>, Proj>>>
    requires view<V> && semiregular<range_value_t<R>>
  class boyer_moore_searcher {
  public:
    explicit boyer_moore_searcher(V base, Pred pred = {}, Proj proj = {}, Hash hf = {});

    constexpr V base() const & requires copy_constructible<V> { return base_; }
    constexpr V base() && { return std::move(base_); }

    constexpr const Pred& pred() const;
    constexpr const Proj& proj() const;

    template<random_access_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
      subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
        requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;

  private:
    V                 base_; // exposition only
    movable-box<Pred> pred_; // exposition only
    movable-box<Proj> proj_; // exposition only
    Hash              hash_; // exposition only
  }

  template <random_access_range R,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<R>> Proj = identity,
            class Hash = hash<projected_value_t<iterator_t<R>, Proj>>>
    requires semiregular<range_value_t<R>>
    boyer_moore_searcher(R&&, Pred = {}, Proj = {}, Hash = {})
      -> boyer_moore_searcher<views::all_t<R>, Pred, Proj, Hash>;
}
explicit boyer_moore_searcher(V base, Pred pred = {}, Proj proj = {}, Hash hf = {});
  1. Preconditions: Let T be range_value_t<V>. For any two values A and B of type T, if invoke(pred, invoke(proj, A), invoke(proj, B)) == true, then hf(invoke(proj, A)) == hf(invoke(proj, B)) is true.

  2. Effects: Constructs a boyer_moore_searcher object, initializing base_ with move(base), pred_ with move(pred), proj_ with move(proj), and hash_ with move(hf).

  3. Throws: Any exception thrown by the default constructor, copy constructor, or the copy assignment operator of T, the invoke expression, or the copy constructor or operator() of Hash. May throw bad_alloc if additional memory needed for internal data structures cannot be allocated.

constexpr const Pred& pred() const;
  1. Effects: Equivalent to: return *pred_;

constexpr const Proj& proj() const;
  1. Effects: Equivalent to: return *proj_;

template<random_access_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
  subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
    requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;
  1. Mandates: is_same_v<remove_cvref_t<projected_value_t<iterator_t<V>, Proj>>, remove_cvref_t<projected_value_t<I2, Proj2>>> is true.

  2. Effects: Finds a subsequence of equal values in a sequence.

  3. Returns: subrange(i, j), where i and j is a pair of iterators such that

Returns subrange(first, first) if ranges::empty(base_) is true, otherwise returns subrange(last, last) if no such iterator is found.

  1. Complexity: At most (last - first) * ranges::distance(base_) applications of the predicate, and at most (last - first) * (ranges::distance(base_) + 1) applications of either projection.

7.4.3. 22.10.18.5.3 Class template boyer_moore_horspool_searcher [func.range.search.bmh]

namespace std::ranges {
  template <random_access_range V,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<V>> Proj = identity,
            class Hash = hash<projected_value_t<iterator_t<V>, Proj>>>
    requires view<V> && semiregular<range_value_t<R>>
  class boyer_moore_horspool_searcher {
  public:
    explicit boyer_moore_horspool_searcher(V base, Pred pred = {}, Proj proj = {}, Hash hf = {});

    constexpr V base() const & requires copy_constructible<V> { return base_; }
    constexpr V base() && { return std::move(base_); }

    constexpr const Pred& pred() const;
    constexpr const Proj& proj() const;

    template<random_access_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
      subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
        requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;

  private:
    V                 base_; // exposition only
    movable-box<Pred> pred_; // exposition only
    movable-box<Proj> proj_; // exposition only
    Hash              hash_; // exposition only
  }

  template <random_access_range R,
            copy_constructible Pred = ranges::equal_to,
            indirectly_regular_unary_invocable<iterator_t<R>> Proj = identity,
            class Hash = hash<projected_value_t<iterator_t<R>, Proj>>>
    requires semiregular<range_value_t<R>>
    boyer_moore_horspool_searcher(R&&, Pred = {}, Proj = {}, Hash = {})
      -> boyer_moore_horspool_searcher<views::all_t<R>, Pred, Proj, Hash>;
}
explicit boyer_moore_horspool_searcher(V base, Pred pred = {}, Proj proj = {}, Hash hf = {});
  1. Preconditions: Let T be range_value_t<V>. For any two values A and B of type T, if invoke(pred, invoke(proj, A), invoke(proj, B)) == true, then hf(invoke(proj, A)) == hf(invoke(proj, B)) is true.

  2. Effects: Constructs a boyer_moore_horspool_searcher object, initializing base_ with move(base), pred_ with move(pred), proj_ with move(proj), and hash_ with move(hf).

  3. Throws: Any exception thrown by the default constructor, copy constructor, or the copy assignment operator of T, the invoke expression, or the copy constructor or operator() of Hash. May throw bad_alloc if additional memory needed for internal data structures cannot be allocated.

constexpr const Pred& pred() const;
  1. Effects: Equivalent to: return *pred_;

constexpr const Proj& proj() const;
  1. Effects: Equivalent to: return *proj_;

template<random_access_iterator I2, sentinel_for<I2> S2, class Proj2 = identity>
  subrange<I2> operator()(I2 first, S2 last, Proj2 proj2 = {}) const
    requires indirectly_comparable<I2, iterator_t<V>, Pred, Proj2, Proj>;
  1. Mandates: is_same_v<remove_cvref_t<projected_value_t<iterator_t<V>, Proj>>, remove_cvref_t<projected_value_t<I2, Proj2>>> is true.

  2. Effects: Finds a subsequence of equal values in a sequence.

  3. Returns: subrange(i, j), where i and j is a pair of iterators such that

Returns subrange(first, first) if ranges::empty(base_) is true, otherwise returns subrange(last, last) if no such iterator is found.

  1. Complexity: At most (last - first) * ranges::distance(base_) applications of the predicate, and at most (last - first) * (ranges::distance(base_) + 1) applications of either projection.

7.5. 24.2 Header <iterator> synopsis [iterator.synopsis]

Modify the synopsis as follows:

// [...]
namespace std {
  // [...]
  // [alg.req.ind.cmp], concept indirectly_comparable
  template<class I1, class I2, class R, class P1 = identity, class P2 = identity>
  concept indirectly_comparable = see below; // freestanding

  // [alg.req.searchable], concept searchable
  template<class Searcher, class I, class S, class P = identity>
  concept searchable = see below; // freestanding

  // [alg.req.permutable], concept permutable
  template<class I>
  concept permutable = see below; // freestanding
  // [...]
}

Note: Editor’s Note: Insert the following subclause between 24.3.7.5 Concept indirectly_comparable [alg.req.ind.cmp] and 24.3.7.6 Concept permutable [alg.req.permutable]

7.6. 24.3.7.� Concept searchable [alg.req.searchable]

  1. The searchable concept specifies the requirements of algorithms that interact with searchers (22.10.18 [func.search]) to search for a sequence in another sequence.

template<class Searcher, class I, class S, class P = identity>
  concept searchable =
    movable<Searcher> &&
    invocable<const Searcher&, I, S, P> &&
    ranges::forward_range<invoke_result_t<const Searcher&, I, S, P>>

7.7. 26.4 Header <algorithm> synopsis [algorithm.syn]

Modify the synopsis as follows:

// [...]
namespace std {
  // [...]
  // [alg.contains], contains
  namespace ranges {
    // [...]
    template<execution-policy Ep, sized-random-access-range R1, sized-random-access-range R2,
             class Pred = ranges::equal_to, class Proj1 = identity, class Proj2 = identity>
      requires indirectly_comparable<iterator_t<R1>, iterator_t<R2>, Pred, Proj1, Proj2>
      bool contains_subrange(Ep&& exec, R1&& r1, R2&& r2, Pred pred = {}, Proj1 proj1 = {},
                             Proj2 proj2 = {}); // freestanding-deleted

    template<forward_iterator I, sentinel_for<I> S,
             class Searcher, class Proj = identity>
      requires searchable<Searcher, I, S, Proj>
      constexpr bool contains_subrange(I first, S last,
                                       const Searcher& searcher, Proj proj = {});
    template<forward_range R, class Searcher, class Proj = identity>
      requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
      constexpr bool contains_subrange(R&& r, const Searcher& searcher, Proj proj = {});
  }
  // [...]
  // [alg.search], search
  // [...]
  template<class ForwardIterator, class Searcher>
    constexpr ForwardIterator
      search(ForwardIterator first, ForwardIterator last, const Searcher& searcher);

  namespace ranges {
    template<forward_iterator I, sentinel_for<I> S,
             class Searcher, class Proj = identity>
      requires searchable<Searcher, I, S, Proj>
      constexpr auto search(I first, S last,
                            const Searcher& searcher, Proj proj = {});
    template<forward_range R, class Searcher, class Proj = identity>
      requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
      constexpr auto search(R&& r, const Searcher& searcher, Proj proj = {});
  }
  
  // [...]
}

7.8. 26.6.4 Contains [alg.contains]

Add to the end of this clause:

template<forward_iterator I, sentinel_for<I> S,
         class Searcher, class Proj = identity>
  requires searchable<Searcher, I, S, Proj>
  constexpr bool ranges::contains_subrange(I first, S last,
                                           const Searcher& searcher, Proj proj = {});
  1. Returns:

!ranges::empty(ranges::search(first, last, searcher, move(proj)))
template<forward_range R, class Searcher, class Proj = identity>
  requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
  constexpr bool ranges::contains_subrange(R&& r, const Searcher& searcher, Proj proj = {});
  1. Returns:

!ranges::empty(ranges::search(forward<R>(r), searcher, move(proj)))

7.9. 26.6.15 Search [alg.search]

Add to the end of this clause:

template<forward_iterator I, sentinel_for<I> S,
         class Searcher, class Proj = identity>
  requires searchable<Searcher, I, S, Proj>
  constexpr auto ranges::search(I first, S last,
                                const Searcher& searcher, Proj proj = {});
  1. Effects: Equivalent to: return searcher(first, last, move(proj));

template<forward_range R, class Searcher, class Proj = identity>
  requires searchable<Searcher, iterator_t<R>, sentinel_t<R>, Proj>
  constexpr auto ranges::search(R&& r, const Searcher& searcher, Proj proj = {});
  1. Effects: Equivalent to: return searcher(ranges::begin(r), ranges::end(r), move(proj));

8. Wording for Optional Additional Changes

Note: The wording below is for the standalone additional breaking change to std::search.

8.1. 26.6.15 Search [alg.search]

Modify paragraph 11-12 as indicated:

template<class ForwardIterator, class Searcher>
  constexpr ForwardIterator
    search(ForwardIterator first, ForwardIterator last, const Searcher& searcher);
  1. Effects: Equivalent to: return searcher(first, last).first; return std::get<0>(searcher(first, last));

  2. Remarks: Searcher need not meet the Cpp17CopyConstructible requirements.

9. Poll Results

9.1. SG9 Review, Brno (2026-06)

Poll 1: We want both operator()(begin, end) and operator()(range) on the standardized searchers.

Strongly Favor Favor Neutral Against Strongly Against
2 0 4 1 0

Poll 2: We want only operator()(begin, end) on the standardized searchers.

Strongly Favor Favor Neutral Against Strongly Against
2 2 3 0 0

Poll 3: We want only operator()(range) on the standardized searchers.

Strongly Favor Favor Neutral Against Strongly Against
0 0 2 5 0

References

Normative References

[N3905]
Marshall Clow. Extending std::search to use Additional Searching Algorithms (Version 4). 14 February 2014. URL: https://wg21.link/n3905
[N5054]
Thomas Köppe. Working Draft, Programming Languages — C++. 16 July 2026. URL: https://wg21.link/n5054

Non-Normative References

[LWG4286]
Yihe Li. Some more feature-test macros for fully freestanding features are not marked freestanding. C++26. URL: https://wg21.link/lwg4286
[P0202R3]
Antony Polukhin. Add Constexpr Modifiers to Functions in <algorithm> and <utility> Headers. 9 November 2017. URL: https://wg21.link/p0202r3
[P0253R1]
Marshall Clow. Fixing a design mistake in the searchers interface in Library Fundamentals. 1 March 2016. URL: https://wg21.link/p0253r1
[P0638R0]
Ed Schouten. Crochemore-Perrin search algorithm for std::search. 3 May 2017. URL: https://wg21.link/p0638r0
[P0896R4]
Eric Niebler, Casey Carter, Christopher Di Bella. The One Ranges Proposal. 9 November 2018. URL: https://wg21.link/p0896r4
[P1642R11]
Ben Craig. Freestanding Library: Easy [utilities], [ranges], and [iterators]. 1 July 2022. URL: https://wg21.link/p1642r11
[P1813R0]
Christopher Di Bella. A Concept Design for the Numeric Algorithms. 2 August 2019. URL: https://wg21.link/p1813r0
[P2198R7]
Ben Craig. Freestanding Feature-Test Macros and Implementation-Defined Extensions. 14 December 2022. URL: https://wg21.link/p2198r7
[P2302R4]
Christopher Di Bella. std::ranges::contains. 17 April 2022. URL: https://wg21.link/p2302r4
[P2976R1]
Ben Craig. Freestanding Library: algorithm, numeric, and random. 5 May 2024. URL: https://wg21.link/p2976r1
[P3179R9]
Ruslan Arutyunyan, Alexey Kukanov, Bryce Adelstein Lelbach. C++ parallel range algorithms. 29 May 2025. URL: https://wg21.link/p3179r9
[P3372R3]
Hana Dusíková. constexpr containers and adapters. 11 February 2025. URL: https://wg21.link/p3372r3
[P3732R2]
Ruslan Arutyunyan, Mark Hoemmen, Alexey Kukanov, Bryce Adelstein Lelbach, Abhilash Majumder. Numeric Range Algorithms. 26 March 2026. URL: https://wg21.link/p3732r2