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, ...@@ -177,8 +177,12 @@ constexpr bool all_of(InputIterator first,
InputIterator last, InputIterator last,
Pred pred, Pred pred,
Proj proj = {}) { Proj proj = {}) {
return std::all_of(first, last, for (; first != last; ++first) {
internal::ProjectedUnaryPredicate(pred, proj)); if (!invoke(pred, invoke(proj, *first)))
return false;
}
return true;
} }
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`. // Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
...@@ -219,8 +223,12 @@ constexpr bool any_of(InputIterator first, ...@@ -219,8 +223,12 @@ constexpr bool any_of(InputIterator first,
InputIterator last, InputIterator last,
Pred pred, Pred pred,
Proj proj = {}) { Proj proj = {}) {
return std::any_of(first, last, for (; first != last; ++first) {
internal::ProjectedUnaryPredicate(pred, proj)); if (invoke(pred, invoke(proj, *first)))
return true;
}
return false;
} }
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`. // Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
...@@ -261,8 +269,12 @@ constexpr bool none_of(InputIterator first, ...@@ -261,8 +269,12 @@ constexpr bool none_of(InputIterator first,
InputIterator last, InputIterator last,
Pred pred, Pred pred,
Proj proj = {}) { Proj proj = {}) {
return std::none_of(first, last, for (; first != last; ++first) {
internal::ProjectedUnaryPredicate(pred, proj)); if (invoke(pred, invoke(proj, *first)))
return false;
}
return true;
} }
// Let `E(i)` be `invoke(pred, invoke(proj, *i))`. // Let `E(i)` be `invoke(pred, invoke(proj, *i))`.
......
...@@ -21,7 +21,7 @@ namespace util { ...@@ -21,7 +21,7 @@ namespace util {
namespace { namespace {
struct Int { struct Int {
Int(int value) : value(value) {} constexpr Int(int value) : value(value) {}
int value = 0; int value = 0;
}; };
...@@ -43,7 +43,7 @@ struct MoveOnlyInt { ...@@ -43,7 +43,7 @@ struct MoveOnlyInt {
int value = 0; int value = 0;
}; };
bool is_even(int i) { constexpr bool is_even(int i) {
return i % 2 == 0; return i % 2 == 0;
} }
...@@ -54,41 +54,53 @@ bool is_odd(int i) { ...@@ -54,41 +54,53 @@ bool is_odd(int i) {
} // namespace } // namespace
TEST(RangesTest, AllOf) { TEST(RangesTest, AllOf) {
auto is_non_zero = [](int i) { return i != 0; }; // Note: Lambdas don't have a constexpr call operator prior to C++17, thus we
int array[] = {0, 1, 2, 3, 4, 5}; // 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)); constexpr int array[] = {0, 1, 2, 3, 4, 5};
EXPECT_FALSE(ranges::all_of(array, is_non_zero));
Int values[] = {{0}, {2}, {4}, {5}}; static_assert(ranges::all_of(array + 1, array + 6, is_non_zero), "");
EXPECT_TRUE(ranges::all_of(values + 1, ranges::end(values), is_non_zero, static_assert(!ranges::all_of(array, is_non_zero), "");
&Int::value));
EXPECT_FALSE(ranges::all_of(values, is_non_zero, &Int::value)); 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) { 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)); static_assert(!ranges::any_of(array + 5, array + 6, is_even), "");
EXPECT_TRUE(ranges::any_of(array, is_even)); static_assert(ranges::any_of(array, is_even), "");
Int values[] = {{0}, {2}, {4}, {5}}; constexpr Int values[] = {{0}, {2}, {4}, {5}};
EXPECT_FALSE( static_assert(
ranges::any_of(values + 3, ranges::end(values), is_even, &Int::value)); !ranges::any_of(values + 3, ranges::end(values), is_even, &Int::value),
EXPECT_TRUE(ranges::any_of(values, is_even, &Int::value)); "");
static_assert(ranges::any_of(values, is_even, &Int::value), "");
} }
TEST(RangesTest, NoneOf) { TEST(RangesTest, NoneOf) {
auto is_zero = [](int i) { return i == 0; }; // Note: Lambdas don't have a constexpr call operator prior to C++17, thus we
int array[] = {0, 1, 2, 3, 4, 5}; // are providing our own anonyomous struct here.
constexpr struct {
EXPECT_TRUE(ranges::none_of(array + 1, array + 6, is_zero)); constexpr bool operator()(int i) { return i == 0; }
EXPECT_FALSE(ranges::none_of(array, is_zero)); } is_zero;
constexpr int array[] = {0, 1, 2, 3, 4, 5};
Int values[] = {{0}, {2}, {4}, {5}};
EXPECT_TRUE( static_assert(ranges::none_of(array + 1, array + 6, is_zero), "");
ranges::none_of(values + 1, ranges::end(values), is_zero, &Int::value)); static_assert(!ranges::none_of(array, is_zero), "");
EXPECT_FALSE(ranges::none_of(values, is_zero, &Int::value));
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) { TEST(RangesTest, ForEach) {
......
...@@ -11,45 +11,141 @@ ...@@ -11,45 +11,141 @@
namespace util { namespace util {
// Implementation of C++20's std::identity. namespace internal {
//
// 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; // 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 template <typename DecayedF>
// referenced in original std::invoke proposal. 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 // Reference: https://wg21.link/func.require#1.1
// constexpr version can be added in the future, but it won't be as concise, template <typename F,
// since std::mem_fn is not constexpr prior to C++20. 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: // Reference: https://wg21.link/func.require#1.2
// - https://wg21.link/n4169#implementability template <typename F,
// - https://en.cppreference.com/w/cpp/utility/functional/invoke typename T1,
// - https://wg21.link/func.invoke
template <typename Functor,
typename... Args, typename... Args,
std::enable_if_t< EnableIf<IsMemFunPtr<F> && IsRefWrapper<T1>> = true>
std::is_member_pointer<std::decay_t<Functor>>::value>* = nullptr> constexpr decltype(auto) invoke(F&& f, T1&& t1, Args&&... args) {
decltype(auto) invoke(Functor&& f, Args&&... args) { return (t1.get().*f)(std::forward<Args>(args)...);
return std::mem_fn(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, typename... Args,
std::enable_if_t< EnableIf<IsMemFunPtr<F> && !IsMemPtrToBaseOf<F, T1> &&
!std::is_member_pointer<std::decay_t<Functor>>::value>* = nullptr> !IsRefWrapper<T1>> = true>
decltype(auto) invoke(Functor&& f, Args&&... args) { constexpr decltype(auto) invoke(F&& f, T1&& t1, Args&&... args) {
return std::forward<Functor>(f)(std::forward<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. // Implementation of C++17's std::invoke_result_t.
...@@ -63,6 +159,20 @@ decltype(auto) invoke(Functor&& f, Args&&... args) { ...@@ -63,6 +159,20 @@ decltype(auto) invoke(Functor&& f, Args&&... args) {
template <typename Functor, typename... Args> template <typename Functor, typename... Args>
using invoke_result_t = std::result_of_t<Functor && (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 // Simplified implementations of C++20's std::ranges comparison function
// objects. As opposed to the std::ranges implementation, these versions do not // objects. As opposed to the std::ranges implementation, these versions do not
// constrain the passed-in types. // constrain the passed-in types.
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "base/util/ranges/functional.h" #include "base/util/ranges/functional.h"
#include <functional>
#include <vector> #include <vector>
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -22,17 +23,23 @@ TEST(RangesTest, Identity) { ...@@ -22,17 +23,23 @@ TEST(RangesTest, Identity) {
TEST(RangesTest, Invoke) { TEST(RangesTest, Invoke) {
struct S { struct S {
int data_member = 123; int i;
constexpr int add(int x) const { return i + x; }
int member_function() { return 42; }
}; };
S s; constexpr S s = {1};
EXPECT_EQ(123, invoke(&S::data_member, s));
EXPECT_EQ(42, invoke(&S::member_function, s)); // 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; }; static_assert(invoke(std::plus<>(), 1, 2) == 3, "");
EXPECT_EQ(3, invoke(add_functor, 1, 2));
} }
TEST(RangesTest, EqualTo) { TEST(RangesTest, EqualTo) {
......
...@@ -50,13 +50,14 @@ namespace internal { ...@@ -50,13 +50,14 @@ namespace internal {
using std::begin; using std::begin;
template <typename Range> 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)); return begin(std::forward<Range>(range));
} }
using std::end; using std::end;
template <typename Range> 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)); return end(std::forward<Range>(range));
} }
...@@ -72,7 +73,7 @@ auto End(Range&& range) -> decltype(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 // Reference: https://wg21.link/range.access.begin
template <typename Range> template <typename Range>
auto begin(Range&& range) constexpr auto begin(Range&& range)
-> decltype(internal::Begin(std::forward<Range>(range))) { -> decltype(internal::Begin(std::forward<Range>(range))) {
return internal::Begin(std::forward<Range>(range)); return internal::Begin(std::forward<Range>(range));
} }
...@@ -87,7 +88,8 @@ auto begin(Range&& range) ...@@ -87,7 +88,8 @@ auto begin(Range&& range)
// //
// Reference: - https://wg21.link/range.access.end // Reference: - https://wg21.link/range.access.end
template <typename Range> 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)); return internal::End(std::forward<Range>(range));
} }
......
...@@ -32,7 +32,7 @@ TEST(RangesTest, Begin) { ...@@ -32,7 +32,7 @@ TEST(RangesTest, Begin) {
S s; S s;
EXPECT_EQ(vec.begin(), ranges::begin(vec)); 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)); EXPECT_EQ(s.v.begin(), ranges::begin(s));
} }
...@@ -42,7 +42,7 @@ TEST(RangesTest, End) { ...@@ -42,7 +42,7 @@ TEST(RangesTest, End) {
S s; S s;
EXPECT_EQ(vec.end(), ranges::end(vec)); 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)); 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