Commit c569f97b authored by Jan Wilken Dörrie's avatar Jan Wilken Dörrie Committed by Commit Bot

[base] Add first ranges overloads for STL algorithms

This change adds implementations of the range-based non-modifying
sequence operations to //base/util/ranges. Furthermore, it adds
implementations of required helpers such as std::invoke, std::identity,
std::ranges::begin, std::ranges::end, std::ranges::equal_to and
std::ranges::less.

Future CLs will add overloads for the remaining algorithms.

Reference:
https://eel.is/c++draft/alg.nonmodifying

Bug: 1071094
Change-Id: I0cab296f3937de205583408638e9be2f79473768
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1944358Reviewed-by: default avatarAlbert J. Wong <ajwong@chromium.org>
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#772376}
parent 71aab5cb
...@@ -7,6 +7,7 @@ import("//testing/test.gni") ...@@ -7,6 +7,7 @@ import("//testing/test.gni")
test("base_util_unittests") { test("base_util_unittests") {
deps = [ deps = [
"memory_pressure:unittests", "memory_pressure:unittests",
"ranges:unittests",
"timer:unittests", "timer:unittests",
"type_safety:tests", "type_safety:tests",
"values:unittests", "values:unittests",
......
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("ranges") {
sources = [
"algorithm.h",
"functional.h",
"iterator.h",
]
}
source_set("unittests") {
testonly = true
sources = [
"algorithm_unittest.cc",
"functional_unittest.cc",
"iterator_unittest.cc",
]
deps = [
":ranges",
"//testing/gmock",
"//testing/gtest",
]
}
jdoerrie@chromium.org
pkasting@chromium.org
# `util::ranges`
This directory aims to implement a C++14 version of the new `std::ranges`
algorithms that were introduced in C++20. These implementations are added to the
`::util::ranges` namespace, and callers can access them by including
[`base/util/algorithms.h`](https://source.chromium.org/chromium/chromium/src/+/master:base/util/ranges/algorithms.h).
## Similarities with C++20:
### Automatically deducing `begin()` and `end()`
As probably one of the most important changes for readability and usability, all
algorithms in `util::ranges` have overloads for ranges of elements, which allow
callers to no longer specify `begin()` and `end()` iterators themselves.
Before:
```c++
bool HasEvens(const std::vector<int>& vec) {
return std::any_of(vec.begin(), vec.end(), [](int i) { return i % 2 == 0; });
}
```
After:
```c++
bool HasEvens(const std::vector<int>& vec) {
return util::ranges::any_of(vec, [](int i) { return i % 2 == 0; });
}
```
Furthermore, these overloads also support binding to temporaries, so that
applying algorithms to return values is easier:
```c++
std::vector<int> GetNums();
```
Before:
```c++
bool HasEvens() {
std::vector<int> nums = GetNums();
return std::any_of(nums.begin(), nums.end(),
[](int i) { return i % 2 == 0; });
}
```
After:
```c++
bool HasEvens() {
return util::ranges::any_of(GetNums(), [](int i) { return i % 2 == 0; });
}
```
### Support for Projections
In addition to supporting automatically deducing the `begin()` and `end()`
iterator for ranges, the `util::ranges::` algorithms also support projections,
that can be applied to arguments prior to passing it to supplied transformations
or predicates. This is especially useful when ordering a collection of classes
by a specific data member of the class. Example:
Before:
```cpp
std::sort(suggestions->begin(), suggestions->end(),
[](const autofill::Suggestion& a, const autofill::Suggestion& b) {
return a.match < b.match;
});
```
After:
```cpp
util::ranges::sort(*suggestions, /*comp=*/{}, &autofill::Suggestion::match);
```
Anything that is callable can be used as a projection. This includes
`FunctionObjects` like function pointers or functors, but also pointers to
member function and pointers to data members, as shown above. When not specified
a projection defaults to `util::ranges::identity`, which simply perfectly
forwards its argument.
Projections are supported in both range and iterator-pair overloads of the
`util::ranges::` algorithms, for example `util::ranges::all_of` has the
following signatures:
```cpp
template <typename InputIterator, typename Pred, typename Proj = identity>
bool all_of(InputIterator first, InputIterator last, Pred pred, Proj proj = {});
template <typename Range, typename Pred, typename Proj = identity>
bool all_of(Range&& range, Pred pred, Proj proj = {});
```
## Differences from C++20:
To simplify the implementation of the `util::ranges::` algorithms, they dispatch
to the `std::` algorithms found in C++14. This leads to the following list of
differences from C++20. Since most of these differences are differences in the
library and not in the language, they could be addressed in the future by adding
corresponding implementations.
### Lack of Constraints
Due to the lack of support for concepts in the language, the algorithms in
`util::ranges` do not have the constraints that are present on the algorithms in
`std::ranges`. Instead, they support any type, much like C++14's `std::`
algorithms. In the future this might be addressed by adding corresponding
constraints via SFINAE, should the need arise.
### Lack of Range Primitives
Due to C++14's lack of `std::ranges` concepts like sentinels and other range
primitives, algorithms taking a `[first, last)` pair rather than a complete
range, do not support different types for `first` and `last`. Since they rely on
C++14's implementation, the type must be the same. This could be addressed in
the future by implementing support for sentinel types ourselves.
### Lack of `constexpr`
The `util::ranges` algorithms can only be used in a `constexpr` context when
they call underlying `std::` algorithms that are themselves `constexpr`. Before
C++20, only `std::min`, `std::max` and `std::minmax` are annotated
appropriately, so code like `constexpr bool foo = util::ranges::any_of(...);`
will fail because the compiler will not find a `constexpr std::any_of`. This
could be addressed by either upgrading Chromium's STL to C++20, or implementing
`constexpr` versions of some of these algorithms ourselves.
### Lack of post C++14 algorithms
Since all algorithms in `util::ranges` dispatch to their C++14 equivalent,
`std::` algorithms that are not present in C++14 have no implementation in
`util::ranges`. This list of algorithms includes the following:
- [`std::for_each_n`](https://en.cppreference.com/w/cpp/algorithm/for_each_n) (added in C++17)
- [`std::sample`](https://en.cppreference.com/w/cpp/algorithm/sample) (added in C++17)
- [`std::clamp`](https://en.cppreference.com/w/cpp/algorithm/clamp) (added in C++17)
### Return Types
Some of the algorithms in `std::ranges::` have different return types than their
equivalent in `std::`. For example, while `std::for_each` returns the passed-in
`Function`, `std::ranges::for_each` returns a `std::ranges::for_each_result`,
consisting of the `last` iterator and the function.
In the cases where the return type differs, `util::ranges::` algorithms will
continue to return the old return type.
### No blocking of ADL
The algorithms defined in `std::ranges` are not found by ADL, and inhibit ADL
when found by [unqualified name lookup][1]. This is done to be able to enforce
the constraints specified by those algorithms and commonly implemented by using
function objects instead of regular functions. Since we don't support
constrained algorithms yet, we don't implement the blocking of ADL either.
[1]: https://wg21.link/algorithms.requirements#2
This diff is collapsed.
This diff is collapsed.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_UTIL_RANGES_FUNCTIONAL_H_
#define BASE_UTIL_RANGES_FUNCTIONAL_H_
#include <functional>
#include <type_traits>
#include <utility>
namespace util {
// Implementation of C++20's std::identity.
//
// Reference:
// - https://en.cppreference.com/w/cpp/utility/functional/identity
// - https://wg21.link/func.identity
struct identity {
template <typename T>
constexpr T&& operator()(T&& t) const noexcept {
return std::forward<T>(t);
}
using is_transparent = void;
};
// Minimal implementation of C++17's std::invoke. Based on implementation
// referenced in original std::invoke proposal.
//
// Note: Unlike C++20's std::invoke this implementation is not constexpr. A
// constexpr version can be added in the future, but it won't be as concise,
// since std::mem_fn is not constexpr prior to C++20.
//
// References:
// - https://wg21.link/n4169#implementability
// - https://en.cppreference.com/w/cpp/utility/functional/invoke
// - https://wg21.link/func.invoke
template <typename Functor,
typename... Args,
std::enable_if_t<
std::is_member_pointer<std::decay_t<Functor>>::value>* = nullptr>
decltype(auto) invoke(Functor&& f, Args&&... args) {
return std::mem_fn(f)(std::forward<Args>(args)...);
}
template <typename Functor,
typename... Args,
std::enable_if_t<
!std::is_member_pointer<std::decay_t<Functor>>::value>* = nullptr>
decltype(auto) invoke(Functor&& f, Args&&... args) {
return std::forward<Functor>(f)(std::forward<Args>(args)...);
}
// Simplified implementations of C++20's std::ranges comparison function
// objects. As opposed to the std::ranges implementation, these versions do not
// constrain the passed-in types.
//
// Reference: https://wg21.link/range.cmp
namespace ranges {
using equal_to = std::equal_to<>;
using less = std::less<>;
} // namespace ranges
} // namespace util
#endif // BASE_UTIL_RANGES_FUNCTIONAL_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/util/ranges/functional.h"
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
namespace util {
TEST(RangesTest, Identity) {
static constexpr identity id;
std::vector<int> v;
EXPECT_EQ(&v, &id(v));
constexpr int arr = {0};
static_assert(arr == id(arr), "");
}
TEST(RangesTest, Invoke) {
struct S {
int data_member = 123;
int member_function() { return 42; }
};
S s;
EXPECT_EQ(123, invoke(&S::data_member, s));
EXPECT_EQ(42, invoke(&S::member_function, s));
auto add_functor = [](int i, int j) { return i + j; };
EXPECT_EQ(3, invoke(add_functor, 1, 2));
}
TEST(RangesTest, EqualTo) {
ranges::equal_to eq;
EXPECT_TRUE(eq(0, 0));
EXPECT_FALSE(eq(0, 1));
EXPECT_FALSE(eq(1, 0));
}
TEST(RangesTest, Less) {
ranges::less lt;
EXPECT_FALSE(lt(0, 0));
EXPECT_TRUE(lt(0, 1));
EXPECT_FALSE(lt(1, 0));
}
} // namespace util
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_UTIL_RANGES_ITERATOR_H_
#define BASE_UTIL_RANGES_ITERATOR_H_
#include <iterator>
namespace util {
namespace ranges {
// Simplified implementation of C++20's std::ranges::begin.
// As opposed to std::ranges::begin, this implementation does not prefer a
// member begin() over a free standing begin(), does not check whether begin()
// returns an iterator, does not inhibit ADL and is not constexpr.
//
// Reference: https://wg21.link/range.access.begin
template <typename Range>
decltype(auto) begin(Range&& range) {
using std::begin;
return begin(std::forward<Range>(range));
}
// Simplified implementation of C++20's std::ranges::end.
// As opposed to std::ranges::end, this implementation does not prefer a
// member end() over a free standing end(), does not check whether end()
// returns an iterator, does not inhibit ADL and is not constexpr.
//
// Reference: - https://wg21.link/range.access.end
template <typename Range>
decltype(auto) end(Range&& range) {
using std::end;
return end(std::forward<Range>(range));
}
} // namespace ranges
} // namespace util
#endif // BASE_UTIL_RANGES_ITERATOR_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/util/ranges/iterator.h"
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
namespace util {
namespace {
struct S {
std::vector<int> v;
};
auto begin(const S& s) {
return s.v.begin();
}
auto end(const S& s) {
return s.v.end();
}
} // namespace
TEST(RangesTest, Begin) {
std::vector<int> vec;
int arr[1]{};
S s;
EXPECT_EQ(vec.begin(), ranges::begin(vec));
EXPECT_EQ(arr, ranges::begin(arr));
EXPECT_EQ(s.v.begin(), ranges::begin(s));
}
TEST(RangesTest, End) {
std::vector<int> vec;
int arr[1]{};
S s;
EXPECT_EQ(vec.end(), ranges::end(vec));
EXPECT_EQ(arr + 1, ranges::end(arr));
EXPECT_EQ(s.v.end(), ranges::end(s));
}
} // namespace util
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment