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

[base] Ranges: Use constexpr invoke(), begin() and end()

This change modifies the invoke(), begin() and end() function to be
constexpr in //util/ranges. This allows making some of the algorithms
constexpr as well, which is done in this change for all_of, any_of and
none_of.

Bug: 1071094
Change-Id: Ic72a5287dfa4623342e5ef5f592b19040797def7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2316211
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792188}
parent ed149212
......@@ -177,8 +177,12 @@ constexpr bool all_of(InputIterator first,
InputIterator last,
Pred pred,
Proj proj = {}) {
return std::all_of(first, last,
internal::ProjectedUnaryPredicate(pred, proj));
for (; first != last; ++first) {
if (!invoke(pred, invoke(proj, *first)))
return false;
}
return true;
}
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
......@@ -219,8 +223,12 @@ constexpr bool any_of(InputIterator first,
InputIterator last,
Pred pred,
Proj proj = {}) {
return std::any_of(first, last,
internal::ProjectedUnaryPredicate(pred, proj));
for (; first != last; ++first) {
if (invoke(pred, invoke(proj, *first)))
return true;
}
return false;
}
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
......@@ -261,8 +269,12 @@ constexpr bool none_of(InputIterator first,
InputIterator last,
Pred pred,
Proj proj = {}) {
return std::none_of(first, last,
internal::ProjectedUnaryPredicate(pred, proj));
for (; first != last; ++first) {
if (invoke(pred, invoke(proj, *first)))
return false;
}
return true;
}
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
......
......@@ -21,7 +21,7 @@ namespace util {
namespace {
struct Int {
Int(int value) : value(value) {}
constexpr Int(int value) : value(value) {}
int value = 0;
};
......@@ -43,7 +43,7 @@ struct MoveOnlyInt {
int value = 0;
};
bool is_even(int i) {
constexpr bool is_even(int i) {
return i % 2 == 0;
}
......@@ -54,41 +54,53 @@ bool is_odd(int i) {
} // namespace
TEST(RangesTest, AllOf) {
auto is_non_zero = [](int i) { return i != 0; };
int array[] = {0, 1, 2, 3, 4, 5};
// Note: Lambdas don't have a constexpr call operator prior to C++17, thus we
// are providing our own anonyomous struct here.
constexpr struct {
constexpr bool operator()(int i) { return i != 0; }
} is_non_zero;
EXPECT_TRUE(ranges::all_of(array + 1, array + 6, is_non_zero));
EXPECT_FALSE(ranges::all_of(array, is_non_zero));
constexpr int array[] = {0, 1, 2, 3, 4, 5};
Int values[] = {{0}, {2}, {4}, {5}};
EXPECT_TRUE(ranges::all_of(values + 1, ranges::end(values), is_non_zero,
&Int::value));
EXPECT_FALSE(ranges::all_of(values, is_non_zero, &Int::value));
static_assert(ranges::all_of(array + 1, array + 6, is_non_zero), "");
static_assert(!ranges::all_of(array, is_non_zero), "");
constexpr Int values[] = {0, 2, 4, 5};
static_assert(
ranges::all_of(values + 1, ranges::end(values), is_non_zero, &Int::value),
"");
static_assert(!ranges::all_of(values, is_non_zero, &Int::value), "");
}
TEST(RangesTest, AnyOf) {
int array[] = {0, 1, 2, 3, 4, 5};
constexpr int array[] = {0, 1, 2, 3, 4, 5};
EXPECT_FALSE(ranges::any_of(array + 5, array + 6, is_even));
EXPECT_TRUE(ranges::any_of(array, is_even));
static_assert(!ranges::any_of(array + 5, array + 6, is_even), "");
static_assert(ranges::any_of(array, is_even), "");
Int values[] = {{0}, {2}, {4}, {5}};
EXPECT_FALSE(
ranges::any_of(values + 3, ranges::end(values), is_even, &Int::value));
EXPECT_TRUE(ranges::any_of(values, is_even, &Int::value));
constexpr Int values[] = {{0}, {2}, {4}, {5}};
static_assert(
!ranges::any_of(values + 3, ranges::end(values), is_even, &Int::value),
"");
static_assert(ranges::any_of(values, is_even, &Int::value), "");
}
TEST(RangesTest, NoneOf) {
auto is_zero = [](int i) { return i == 0; };
int array[] = {0, 1, 2, 3, 4, 5};
EXPECT_TRUE(ranges::none_of(array + 1, array + 6, is_zero));
EXPECT_FALSE(ranges::none_of(array, is_zero));
Int values[] = {{0}, {2}, {4}, {5}};
EXPECT_TRUE(
ranges::none_of(values + 1, ranges::end(values), is_zero, &Int::value));
EXPECT_FALSE(ranges::none_of(values, is_zero, &Int::value));
// Note: Lambdas don't have a constexpr call operator prior to C++17, thus we
// are providing our own anonyomous struct here.
constexpr struct {
constexpr bool operator()(int i) { return i == 0; }
} is_zero;
constexpr int array[] = {0, 1, 2, 3, 4, 5};
static_assert(ranges::none_of(array + 1, array + 6, is_zero), "");
static_assert(!ranges::none_of(array, is_zero), "");
constexpr Int values[] = {{0}, {2}, {4}, {5}};
static_assert(
ranges::none_of(values + 1, ranges::end(values), is_zero, &Int::value),
"");
static_assert(!ranges::none_of(values, is_zero, &Int::value), "");
}
TEST(RangesTest, ForEach) {
......
......@@ -11,45 +11,141 @@
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);
}
namespace internal {
using is_transparent = void;
// Helper struct and alias to deduce the class type from a member function
// pointer or member object pointer.
template <typename DecayedF>
struct member_pointer_class {};
template <typename ReturnT, typename ClassT>
struct member_pointer_class<ReturnT ClassT::*> {
using type = ClassT;
};
// Minimal implementation of C++17's std::invoke. Based on implementation
// referenced in original std::invoke proposal.
template <typename DecayedF>
using member_pointer_class_t = typename member_pointer_class<DecayedF>::type;
// Utility struct to detect specializations of std::reference_wrapper.
template <typename T>
struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
// Small helpers used below in internal::invoke to make the SFINAE more concise.
template <typename F>
const bool& IsMemFunPtr =
std::is_member_function_pointer<std::decay_t<F>>::value;
template <typename F>
const bool& IsMemObjPtr = std::is_member_object_pointer<std::decay_t<F>>::value;
template <typename F,
typename T,
typename MemPtrClass = member_pointer_class_t<std::decay_t<F>>>
const bool& IsMemPtrToBaseOf =
std::is_base_of<MemPtrClass, std::decay_t<T>>::value;
template <typename T>
const bool& IsRefWrapper = is_reference_wrapper<std::decay_t<T>>::value;
template <bool B>
using EnableIf = std::enable_if_t<B, bool>;
// Invokes a member function pointer on a reference to an object of a suitable
// type. Covers bullet 1 of the INVOKE definition.
//
// 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.
// Reference: https://wg21.link/func.require#1.1
template <typename F,
typename T1,
typename... Args,
EnableIf<IsMemFunPtr<F> && IsMemPtrToBaseOf<F, T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1, Args&&... args) {
return (std::forward<T1>(t1).*f)(std::forward<Args>(args)...);
}
// Invokes a member function pointer on a std::reference_wrapper to an object of
// a suitable type. Covers bullet 2 of the INVOKE definition.
//
// References:
// - https://wg21.link/n4169#implementability
// - https://en.cppreference.com/w/cpp/utility/functional/invoke
// - https://wg21.link/func.invoke
template <typename Functor,
// Reference: https://wg21.link/func.require#1.2
template <typename F,
typename T1,
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)...);
EnableIf<IsMemFunPtr<F> && IsRefWrapper<T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1, Args&&... args) {
return (t1.get().*f)(std::forward<Args>(args)...);
}
template <typename Functor,
// Invokes a member function pointer on a pointer-like type to an object of a
// suitable type. Covers bullet 3 of the INVOKE definition.
//
// Reference: https://wg21.link/func.require#1.3
template <typename F,
typename T1,
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)...);
EnableIf<IsMemFunPtr<F> && !IsMemPtrToBaseOf<F, T1> &&
!IsRefWrapper<T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1, Args&&... args) {
return ((*std::forward<T1>(t1)).*f)(std::forward<Args>(args)...);
}
// Invokes a member object pointer on a reference to an object of a suitable
// type. Covers bullet 4 of the INVOKE definition.
//
// Reference: https://wg21.link/func.require#1.4
template <typename F,
typename T1,
EnableIf<IsMemObjPtr<F> && IsMemPtrToBaseOf<F, T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1) {
return std::forward<T1>(t1).*f;
}
// Invokes a member object pointer on a std::reference_wrapper to an object of
// a suitable type. Covers bullet 5 of the INVOKE definition.
//
// Reference: https://wg21.link/func.require#1.5
template <typename F,
typename T1,
EnableIf<IsMemObjPtr<F> && IsRefWrapper<T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1) {
return t1.get().*f;
}
// Invokes a member object pointer on a pointer-like type to an object of a
// suitable type. Covers bullet 6 of the INVOKE definition.
//
// Reference: https://wg21.link/func.require#1.6
template <typename F,
typename T1,
EnableIf<IsMemObjPtr<F> && !IsMemPtrToBaseOf<F, T1> &&
!IsRefWrapper<T1>> = true>
constexpr decltype(auto) invoke(F&& f, T1&& t1) {
return (*std::forward<T1>(t1)).*f;
}
// Invokes a regular function or function object. Covers bullet 7 of the INVOKE
// definition.
//
// Reference: https://wg21.link/func.require#1.7
template <typename F, typename... Args>
constexpr decltype(auto) invoke(F&& f, Args&&... args) {
return std::forward<F>(f)(std::forward<Args>(args)...);
}
} // namespace internal
// Implementation of C++17's std::invoke. This is not based on implementation
// referenced in original std::invoke proposal, but rather a manual
// implementation, so that it can be constexpr.
//
// References:
// - https://wg21.link/n4169#implementability
// - https://en.cppreference.com/w/cpp/utility/functional/invoke
// - https://wg21.link/func.invoke
template <typename F, typename... Args>
constexpr decltype(auto) invoke(F&& f, Args&&... args) {
return internal::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// Implementation of C++17's std::invoke_result_t.
......@@ -63,6 +159,20 @@ decltype(auto) invoke(Functor&& f, Args&&... args) {
template <typename Functor, typename... Args>
using invoke_result_t = std::result_of_t<Functor && (Args && ...)>;
// 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;
};
// 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.
......
......@@ -4,6 +4,7 @@
#include "base/util/ranges/functional.h"
#include <functional>
#include <vector>
#include "testing/gtest/include/gtest/gtest.h"
......@@ -22,17 +23,23 @@ TEST(RangesTest, Identity) {
TEST(RangesTest, Invoke) {
struct S {
int data_member = 123;
int member_function() { return 42; }
int i;
constexpr int add(int x) const { return i + x; }
};
S s;
EXPECT_EQ(123, invoke(&S::data_member, s));
EXPECT_EQ(42, invoke(&S::member_function, s));
constexpr S s = {1};
// Note: The tests involving a std::reference_wrapper are not static_asserts,
// since std::reference_wrapper is not constexpr prior to C++20.
static_assert(invoke(&S::add, s, 2) == 3, "");
EXPECT_EQ(invoke(&S::add, std::ref(s), 2), 3);
static_assert(invoke(&S::add, &s, 3) == 4, "");
static_assert(invoke(&S::i, s) == 1, "");
EXPECT_EQ(invoke(&S::i, std::ref(s)), 1);
static_assert(invoke(&S::i, &s) == 1, "");
auto add_functor = [](int i, int j) { return i + j; };
EXPECT_EQ(3, invoke(add_functor, 1, 2));
static_assert(invoke(std::plus<>(), 1, 2) == 3, "");
}
TEST(RangesTest, EqualTo) {
......
......@@ -50,13 +50,14 @@ namespace internal {
using std::begin;
template <typename Range>
auto Begin(Range&& range) -> decltype(begin(std::forward<Range>(range))) {
constexpr auto Begin(Range&& range)
-> decltype(begin(std::forward<Range>(range))) {
return begin(std::forward<Range>(range));
}
using std::end;
template <typename Range>
auto End(Range&& range) -> decltype(end(std::forward<Range>(range))) {
constexpr auto End(Range&& range) -> decltype(end(std::forward<Range>(range))) {
return end(std::forward<Range>(range));
}
......@@ -72,7 +73,7 @@ auto End(Range&& range) -> decltype(end(std::forward<Range>(range))) {
//
// Reference: https://wg21.link/range.access.begin
template <typename Range>
auto begin(Range&& range)
constexpr auto begin(Range&& range)
-> decltype(internal::Begin(std::forward<Range>(range))) {
return internal::Begin(std::forward<Range>(range));
}
......@@ -87,7 +88,8 @@ auto begin(Range&& range)
//
// Reference: - https://wg21.link/range.access.end
template <typename Range>
auto end(Range&& range) -> decltype(internal::End(std::forward<Range>(range))) {
constexpr auto end(Range&& range)
-> decltype(internal::End(std::forward<Range>(range))) {
return internal::End(std::forward<Range>(range));
}
......
......@@ -32,7 +32,7 @@ TEST(RangesTest, Begin) {
S s;
EXPECT_EQ(vec.begin(), ranges::begin(vec));
EXPECT_EQ(arr, ranges::begin(arr));
static_assert(arr == ranges::begin(arr), "");
EXPECT_EQ(s.v.begin(), ranges::begin(s));
}
......@@ -42,7 +42,7 @@ TEST(RangesTest, End) {
S s;
EXPECT_EQ(vec.end(), ranges::end(vec));
EXPECT_EQ(arr + 1, ranges::end(arr));
static_assert(arr + 1 == ranges::end(arr), "");
EXPECT_EQ(s.v.end(), ranges::end(s));
}
......
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