Files
carbon-lang/common/find.h
T
Dana Jansens 9a6c74f0cd Introduce FindIfOrNull() FindIfOrNone() and Contains() (#5322)
`FindIfOrNull` returns a pointer to the element in the range if it's
found, and nullptr otherwise. `FindIfOrNone` returns a copy of the
element in the range if it's found, and `T::None` (for a range of
elements of type `T`) otherwise. `Contains` returns a bool indicating
whether the element in the range is found.

These functions replace `llvm::find()` and `llvm::find_if()` when you
want a single answer back instead of an iterator. This avoids the need
to check against `end()`, allowing the return condition to be tested as
a standard bool.

We replace uses of `find()` and `find_if()` that did not require an
iterator with these new helpers.

Note that the return type of `FindIfOrNull` is a pointer since we can
not write `optional<T&>`, which must be tested for null. If the null
check is omitted, UB occurs and the resulting code may end up with an
incorrect pointer (https://crbug.com/40153300) into the range (or
elsewhere), rather than a null dereference. And this would be very
confusing to debug. Hopefully debug builds and sanitizers keep this from
being an issue we sink a bunch of time into debugging.
2025-04-18 14:17:48 +00:00

93 lines
3.0 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_COMMON_FIND_H_
#define CARBON_COMMON_FIND_H_
#include <concepts>
#include <type_traits>
#include "llvm/ADT/STLExtras.h"
namespace Carbon {
namespace Internal {
template <typename Range>
using RangePointerType = typename std::iterator_traits<decltype(std::begin(
std::declval<Range>()))>::pointer;
template <typename Range>
using RangeValueType = typename std::iterator_traits<decltype(std::begin(
std::declval<Range>()))>::value_type;
template <typename Range, typename Pred>
concept IsValidFindPredicate =
requires(const RangeValueType<Range>& elem, Pred pred) {
{ pred(elem) } -> std::convertible_to<bool>;
};
template <typename A, typename B>
concept IsComparable = requires(const A& a, const B& b) {
{ a == b } -> std::convertible_to<bool>;
};
template <typename Range>
concept RangeValueHasNoneType = requires {
{ RangeValueType<Range>::None } -> std::convertible_to<RangeValueType<Range>>;
};
} // namespace Internal
// Finds a value in the given `range` by testing the `predicate`. Returns a
// pointer to the value from the range on success, and nullptr if nothing is
// found.
//
// This is similar to `std::find_if()` but returns a pointer to the value
// instead of an iterator that must be tested against `end()`.
template <typename Range, typename Pred>
requires Internal::IsValidFindPredicate<Range, Pred>
constexpr auto FindIfOrNull(Range&& range, Pred predicate)
-> Internal::RangePointerType<Range> {
auto it = llvm::find_if(range, predicate);
if (it != range.end()) {
return std::addressof(*it);
} else {
return nullptr;
}
}
// Finds a value in the given `range` by testing the `predicate` and returns a
// copy of it. If no match is found, returns `T::None` where the input range is
// over values of type `T`.
template <typename Range, typename Pred>
requires Internal::IsValidFindPredicate<Range, Pred> &&
Internal::RangeValueHasNoneType<Range> &&
std::copy_constructible<Internal::RangeValueType<Range>>
constexpr auto FindIfOrNone(Range&& range, Pred predicate)
-> Internal::RangeValueType<Range> {
auto it = llvm::find_if(range, predicate);
if (it != range.end()) {
return *it;
} else {
return Internal::RangeValueType<Range>::None;
}
}
// Finds a value in the given `range` by comparing to `query`. Returns a
// pointer to the value from the range on success, and nullptr if nothing is
// found.
//
// This is similar to `std::find_if()` but returns a pointer to the value
// instead of an iterator that must be tested against `end()`.
template <typename Range, typename Query = Internal::RangeValueType<Range>>
requires Internal::IsComparable<Query, Internal::RangeValueType<Range>>
constexpr auto Contains(Range&& range, const Query& query) -> bool {
return llvm::find(range, query) != range.end();
}
} // namespace Carbon
#endif // CARBON_COMMON_FIND_H_