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

[base] Ranges: Improve begin() and end()

This change improves util::ranges::begin() and end() by adding constexpr
support and making them prefer a member function over a free standing
function. This serves to close the gap to std::ranges::begin() and
std::ranges::end() as specified by the C++20 standard.

Furthermore, special constexpr support is added for std::array, which
otherwise only has constexpr support in C++17.

Lastly, it moves the functions from the iterator.h to the ranges.h
header, since in C++20 these functions are present in <ranges>, not
<iterator>.

Bug: 1071094
Change-Id: Id5f3b7ff83b3fb336286f4ed2d4fe8a6cfcf0b9c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2325171Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#793034}
parent f8e9e4de
......@@ -8,6 +8,7 @@ source_set("ranges") {
"functional.h",
"iterator.h",
"ranges.h",
"ranges_internal.h",
]
}
......@@ -16,7 +17,7 @@ source_set("unittests") {
sources = [
"algorithm_unittest.cc",
"functional_unittest.cc",
"iterator_unittest.cc",
"ranges_unittest.cc",
]
deps = [
......
......@@ -13,21 +13,14 @@
#include "base/util/ranges/functional.h"
#include "base/util/ranges/iterator.h"
#include "base/util/ranges/ranges.h"
#include "base/util/ranges/ranges_internal.h"
namespace util {
namespace ranges {
namespace internal {
// Helper to express preferences in an overload set. If more than one overload
// are available for a given set of parameters the overload with the higher
// priority will be chosen.
template <size_t I>
struct priority_tag : priority_tag<I - 1> {};
template <>
struct priority_tag<0> {};
// Returns a transformed version of the unary predicate `pred` applying `proj`
// to its argument before invoking `pred` on it.
// Ensures that the return type of `invoke(pred, ...)` is convertible to bool.
......
......@@ -5,8 +5,8 @@
#ifndef BASE_UTIL_RANGES_ITERATOR_H_
#define BASE_UTIL_RANGES_ITERATOR_H_
#include <iterator>
#include <type_traits>
#include <utility>
#include "base/util/ranges/functional.h"
......@@ -44,57 +44,6 @@ struct projected {
IndirectResultT operator*() const; // not defined
};
namespace ranges {
namespace internal {
using std::begin;
template <typename Range>
constexpr auto Begin(Range&& range)
-> decltype(begin(std::forward<Range>(range))) {
return begin(std::forward<Range>(range));
}
using std::end;
template <typename Range>
constexpr auto End(Range&& range) -> decltype(end(std::forward<Range>(range))) {
return end(std::forward<Range>(range));
}
} // namespace internal
// 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.
//
// The trailing return type and dispatch to the internal implementation is
// necessary to be SFINAE friendly.
//
// Reference: https://wg21.link/range.access.begin
template <typename Range>
constexpr auto begin(Range&& range)
-> decltype(internal::Begin(std::forward<Range>(range))) {
return internal::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.
//
// The trailing return type and dispatch to the internal implementation is
// necessary to be SFINAE friendly.
//
// Reference: - https://wg21.link/range.access.end
template <typename Range>
constexpr auto end(Range&& range)
-> decltype(internal::End(std::forward<Range>(range))) {
return internal::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));
static_assert(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));
static_assert(arr + 1 == ranges::end(arr), "");
EXPECT_EQ(s.v.end(), ranges::end(s));
}
} // namespace util
......@@ -5,14 +5,122 @@
#ifndef BASE_UTIL_RANGES_RANGES_H_
#define BASE_UTIL_RANGES_RANGES_H_
#include <array>
#include <iterator>
#include <type_traits>
#include <utility>
#include "base/util/ranges/iterator.h"
#include "base/util/ranges/ranges_internal.h"
namespace util {
namespace ranges {
namespace internal {
// Overload for C array.
template <typename T, size_t N>
constexpr T* begin(T (&array)[N], priority_tag<2>) {
return array;
}
// Overload for mutable std::array. Required since std::array::begin is not
// constexpr prior to C++17. Needs to dispatch to the const overload since only
// const operator[] is constexpr in C++14.
template <typename T, size_t N>
constexpr T* begin(std::array<T, N>& array, priority_tag<2> tag) {
return const_cast<T*>(begin(const_cast<const std::array<T, N>&>(array), tag));
}
// Overload for const std::array. Required since std::array::begin is not
// constexpr prior to C++17.
template <typename T, size_t N>
constexpr const T* begin(const std::array<T, N>& array, priority_tag<2>) {
return N != 0 ? &array[0] : nullptr;
}
// Generic container overload.
template <typename Range>
constexpr auto begin(Range&& range, priority_tag<1>)
-> decltype(std::forward<Range>(range).begin()) {
return std::forward<Range>(range).begin();
}
// Overload for free begin() function.
template <typename Range>
constexpr auto begin(Range&& range, priority_tag<0>)
-> decltype(begin(std::forward<Range>(range))) {
return begin(std::forward<Range>(range));
}
// Overload for C array.
template <typename T, size_t N>
constexpr T* end(T (&array)[N], priority_tag<2>) {
return array + N;
}
// Overload for mutable std::array. Required since std::array::end is not
// constexpr prior to C++17. Needs to dispatch to the const overload since only
// const operator[] is constexpr in C++14.
template <typename T, size_t N>
constexpr T* end(std::array<T, N>& array, priority_tag<2> tag) {
return const_cast<T*>(end(const_cast<const std::array<T, N>&>(array), tag));
}
// Overload for const std::array. Required since std::array::end is not
// constexpr prior to C++17.
template <typename T, size_t N>
constexpr const T* end(const std::array<T, N>& array, priority_tag<2>) {
return N != 0 ? (&array[0]) + N : nullptr;
}
// Generic container overload.
template <typename Range>
constexpr auto end(Range&& range, priority_tag<1>)
-> decltype(std::forward<Range>(range).end()) {
return std::forward<Range>(range).end();
}
// Overload for free end() function.
template <typename Range>
constexpr auto end(Range&& range, priority_tag<0>)
-> decltype(end(std::forward<Range>(range))) {
return end(std::forward<Range>(range));
}
} // namespace internal
// Simplified implementation of C++20's std::ranges::begin.
// As opposed to std::ranges::begin, this implementation does does not check
// whether begin() returns an iterator and does not inhibit ADL.
//
// The trailing return type and dispatch to the internal implementation is
// necessary to be SFINAE friendly.
//
// Reference: https://wg21.link/range.access.begin
template <typename Range>
constexpr auto begin(Range&& range) noexcept
-> decltype(internal::begin(std::forward<Range>(range),
internal::priority_tag<2>())) {
return internal::begin(std::forward<Range>(range),
internal::priority_tag<2>());
}
// Simplified implementation of C++20's std::ranges::end.
// As opposed to std::ranges::end, this implementation does does not check
// whether end() returns an iterator and does not inhibit ADL.
//
// The trailing return type and dispatch to the internal implementation is
// necessary to be SFINAE friendly.
//
// Reference: - https://wg21.link/range.access.end
template <typename Range>
constexpr auto end(Range&& range) noexcept
-> decltype(internal::end(std::forward<Range>(range),
internal::priority_tag<2>())) {
return internal::end(std::forward<Range>(range), internal::priority_tag<2>());
}
// Implementation of C++20's std::ranges::iterator_t.
//
// Reference: https://wg21.link/ranges.syn#:~:text=iterator_t
......
// 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_RANGES_INTERNAL_H_
#define BASE_UTIL_RANGES_RANGES_INTERNAL_H_
#include <stddef.h>
namespace util {
namespace ranges {
namespace internal {
// Helper to express preferences in an overload set. If more than one overload
// are available for a given set of parameters the overload with the higher
// priority will be chosen.
template <size_t I>
struct priority_tag : priority_tag<I - 1> {};
template <>
struct priority_tag<0> {};
} // namespace internal
} // namespace ranges
} // namespace util
#endif // BASE_UTIL_RANGES_RANGES_INTERNAL_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/ranges.h"
#include <array>
#include <initializer_list>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
namespace util {
namespace {
// Test struct with free function overloads for begin and end. Tests whether the
// free functions are found.
struct S {
std::vector<int> v;
};
auto begin(const S& s) {
return s.v.begin();
}
auto end(const S& s) {
return s.v.end();
}
// Test struct with both member and free function overloads for begin and end.
// Tests whether the member function is preferred.
struct T {
constexpr int begin() const { return 1; }
constexpr int end() const { return 1; }
};
constexpr int begin(const T& t) {
return 2;
}
constexpr int end(const T& t) {
return 2;
}
// constexpr utility to generate a std::array. Ensures that a mutable array can
// be used in a constexpr context.
template <size_t N>
constexpr std::array<int, N> GenerateArray() {
std::array<int, N> arr{};
int i = 0;
for (auto* it = ranges::begin(arr); it != ranges::end(arr); ++it) {
*it = i++;
}
return arr;
}
} // namespace
TEST(RangesTest, BeginPrefersMember) {
constexpr T t;
static_assert(ranges::begin(t) == 1, "");
}
TEST(RangesTest, BeginConstexprContainers) {
int arr[1]{};
static_assert(arr == ranges::begin(arr), "");
static constexpr std::initializer_list<int> il = {1, 2, 3};
static_assert(il.begin() == ranges::begin(il), "");
static constexpr std::array<int, 3> array = {1, 2, 3};
static_assert(&array[0] == ranges::begin(array), "");
}
TEST(RangesTest, BeginRegularContainers) {
std::vector<int> vec;
S s;
EXPECT_EQ(vec.begin(), ranges::begin(vec));
EXPECT_EQ(s.v.begin(), ranges::begin(s));
}
TEST(RangesTest, EndPrefersMember) {
constexpr T t;
static_assert(ranges::end(t) == 1, "");
}
TEST(RangesTest, EndConstexprContainers) {
int arr[1]{};
static_assert(arr + 1 == ranges::end(arr), "");
static constexpr std::initializer_list<int> il = {1, 2, 3};
static_assert(il.end() == ranges::end(il), "");
static constexpr std::array<int, 3> array = {1, 2, 3};
static_assert(&array[0] + 3 == ranges::end(array), "");
}
TEST(RangesTest, EndRegularContainers) {
std::vector<int> vec;
S s;
EXPECT_EQ(vec.end(), ranges::end(vec));
EXPECT_EQ(s.v.end(), ranges::end(s));
}
TEST(RangesTest, BeginEndStdArray) {
static constexpr std::array<int, 0> null_array = GenerateArray<0>();
static_assert(ranges::begin(null_array) == ranges::end(null_array), "");
static constexpr std::array<int, 3> array = GenerateArray<3>();
static_assert(array[0] == 0, "");
static_assert(array[1] == 1, "");
static_assert(array[2] == 2, "");
}
} // 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