blob: 3415d60ca666de474d23be6c7c1a2b5d76f15869 [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 <lib/stdcompat/functional.h>
#include <lib/stdcompat/tuple.h>
#include "gtest.h"
namespace {
constexpr auto noop = [] {};
constexpr auto take_all = [](auto&&...) {};
constexpr auto lambda_add_one = [](int i) { return i + 1; };
int func_add_one(int i) { return i + 1; }
struct member_pointers {
virtual int pmf_add_one(int i) { return i + 1; }
int (*pmd_add_one)(int) = func_add_one;
};
struct liar : member_pointers {
int pmf_add_one(int i) override { return i + 2; }
};
TEST(InvokeTest, Basic) {
static_assert(std::is_same_v<decltype(cpp20::invoke(noop)), void> == true, "");
static_assert(cpp20::invoke([] { return 123; }) == 123, "");
static_assert(cpp20::invoke(lambda_add_one, 1) == 2, "");
}
TEST(InvokeTest, ForwardingReferences) {
int i = 1;
// Wrap in lambda so we can decltype() (also no explicit template parameter).
constexpr auto forward = [](auto&& t) -> decltype(auto) { return std::forward<decltype(t)>(t); };
static_assert(std::is_same_v<decltype(cpp20::invoke(forward, 1)), int&&> == true, "");
static_assert(std::is_same_v<decltype(cpp20::invoke(forward, i)), int&> == true, "");
static_assert(std::is_same_v<decltype(cpp20::invoke(forward, std::move(i))), int&&> == true, "");
}
TEST(InvokeTest, PointersToMember) {
member_pointers mp;
liar lp;
EXPECT_EQ(cpp20::invoke(&member_pointers::pmf_add_one, mp, 1), 2);
EXPECT_EQ(cpp20::invoke(&member_pointers::pmf_add_one, lp, 1), 3);
// Invoke PMD then invoke the result (it's a function pointer to func_add_one)
EXPECT_EQ(cpp20::invoke(cpp20::invoke(&member_pointers::pmd_add_one, lp), 1), 2);
static_assert(std::is_same_v<decltype(cpp20::invoke(take_all)), void> == true, "");
static_assert(
std::is_same_v<decltype(cpp20::invoke(take_all, nullptr, mp, std::tuple())), void> == true,
"");
}
TEST(InvokeTest, GenericCallables) {
// std::make_tuple itself is a template so we can't use it directly but we can wrap it.
constexpr auto make_tuple = [](auto&&... ts) {
return std::make_tuple(std::forward<decltype(ts)>(ts)...);
};
EXPECT_EQ(cpp20::invoke(make_tuple, 1, std::string("asdf"), std::tuple<>()),
std::make_tuple(1, std::string("asdf"), std::tuple<>()));
}
TEST(InvokeTest, SpecialCases) {
liar lp;
// Special handling of std::reference_wrapper per [func.require] ¶ 2 and 5
EXPECT_EQ(cpp20::invoke(&member_pointers::pmf_add_one, std::ref(lp), 2), 4);
EXPECT_EQ(cpp20::invoke(&member_pointers::pmd_add_one, std::ref(lp))(2), 3);
// Handling of dereferenceable entities per [func.require] ¶ 4 and 6
EXPECT_EQ(cpp20::invoke(&member_pointers::pmf_add_one, &lp, 2), 4);
EXPECT_EQ(cpp20::invoke(&member_pointers::pmd_add_one, &lp)(2), 3);
EXPECT_EQ(cpp20::invoke(&member_pointers::pmf_add_one, std::optional<liar>(liar()), 2), 4);
EXPECT_EQ(cpp20::invoke(&member_pointers::pmd_add_one, std::optional<liar>(liar()))(2), 3);
}
template <typename T>
constexpr auto reduce(T&& only) {
return std::forward<T>(only);
}
template <typename First, typename Second, typename... Args>
constexpr auto reduce(First&& first, Second&& second, Args&&... args) {
return std::forward<First>(first) +
reduce(std::forward<Second>(second), std::forward<Args>(args)...);
}
constexpr auto call_reduce = [](auto&&... args) {
return reduce(std::forward<decltype(args)>(args)...);
};
template <size_t N, typename... Args, std::enable_if_t<(N == 1), bool> = true>
constexpr auto reduce_bound(Args&&... args) {
return reduce(std::forward<Args>(args)...);
}
template <size_t N, typename... Args, std::enable_if_t<(N > 1), bool> = true>
constexpr auto reduce_bound(Args&&... args) {
return [call_with_args =
cpp20::bind_front(call_reduce, std::forward<Args>(args)...)](auto&&... next) {
return reduce_bound<N - 1>(std::move(call_with_args)(std::forward<decltype(next)>(next)...));
};
}
TEST(BindFrontTest, Currying) {
static_assert(reduce(1, 2, 3, 4, 5) == 15, "");
static_assert(cpp20::bind_front(call_reduce)(1, 2, 3, 4, 5) == 15, "");
static_assert(cpp20::bind_front(call_reduce, 1)(2, 3, 4, 5) == 15, "");
static_assert(cpp20::bind_front(call_reduce, 1, 2)(3, 4, 5) == 15, "");
static_assert(cpp20::bind_front(call_reduce, 1, 2, 3)(4, 5) == 15, "");
static_assert(cpp20::bind_front(call_reduce, 1, 2, 3, 4)(5) == 15, "");
static_assert(cpp20::bind_front(call_reduce, 1, 2, 3, 4, 5)() == 15, "");
static_assert(reduce_bound<1>(1, 2, 3, 4, 5) == 15, "");
static_assert(reduce_bound<2>(1)(2, 3, 4, 5) == 15, "");
static_assert(reduce_bound<2>(1, 2)(3, 4, 5) == 15, "");
static_assert(reduce_bound<2>(1, 2, 3)(4, 5) == 15, "");
static_assert(reduce_bound<2>(1, 2, 3, 4)(5) == 15, "");
static_assert(reduce_bound<3>(1)(2)(3, 4, 5) == 15, "");
static_assert(reduce_bound<3>(1)(2, 3)(4, 5) == 15, "");
static_assert(reduce_bound<3>(1)(2, 3, 4)(5) == 15, "");
static_assert(reduce_bound<3>(1, 2)(3)(4, 5) == 15, "");
static_assert(reduce_bound<3>(1, 2)(3, 4)(5) == 15, "");
static_assert(reduce_bound<3>(1, 2, 3)(4)(5) == 15, "");
static_assert(reduce_bound<4>(1)(2)(3)(4, 5) == 15, "");
static_assert(reduce_bound<4>(1)(2)(3, 4)(5) == 15, "");
static_assert(reduce_bound<4>(1)(2, 3)(4)(5) == 15, "");
static_assert(reduce_bound<4>(1, 2)(3)(4)(5) == 15, "");
static_assert(reduce_bound<5>(1)(2)(3)(4)(5) == 15, "");
// And these extra ones where we don't even give it a number (they mess up our perfect grid and
// multiply the number of cases by a lot, so I won't include any more)
static_assert(reduce_bound<2>()(1, 2, 3, 4, 5) == 15, "");
static_assert(reduce_bound<2>(1, 2, 3, 4, 5)() == 15, "");
}
TEST(BindFrontTest, BindCopyable) {
constexpr auto one_plus_one = cpp20::bind_front(lambda_add_one, 1);
static_assert(one_plus_one() == 2, "");
constexpr auto one_plus_two = cpp20::bind_front(func_add_one, 2);
EXPECT_EQ(one_plus_two(), 3);
const std::string empty;
auto echo_string = cpp20::bind_front(call_reduce, empty);
auto test_if_copyable = echo_string;
EXPECT_EQ(echo_string("asdf"), "asdf");
EXPECT_EQ(test_if_copyable("jkl"), "jkl");
constexpr char dot = '.';
const std::string space = " ";
const std::string words[][4] = {
{"The", "quick", "brown", "fox"}, {"jumped", "over"}, {"the", "lazy", "dog"}};
EXPECT_EQ(reduce_bound<3>(words[0][0], space, words[0][1], space, words[0][2], space, words[0][3],
space)(words[1][0], space, words[1][1], space)(
words[2][0], space, words[2][1], space, words[2][2], dot),
"The quick brown fox jumped over the lazy dog.");
}
TEST(BindFrontTest, BindMoveOnly) {
auto ptr = std::make_unique<int>(3);
constexpr auto deref = [](auto&& ptr) { return *ptr; };
auto call_with_ptr = cpp20::bind_front(deref, std::move(ptr));
auto test_if_movable = std::move(call_with_ptr);
static_assert(cpp17::is_copy_constructible_v<decltype(ptr)> == false, "");
static_assert(cpp17::is_copy_constructible_v<decltype(call_with_ptr)> == false, "");
static_assert(cpp17::is_copy_constructible_v<decltype(test_if_movable)> == false, "");
EXPECT_EQ(test_if_movable(), 3);
}
TEST(BindFrontTest, MemberPointers) {
liar lp;
auto liar_pmf = cpp20::bind_front(&member_pointers::pmf_add_one, lp, 1);
auto liar_pmd = cpp20::bind_front(&member_pointers::pmd_add_one, lp);
EXPECT_EQ(liar_pmf(), 3);
EXPECT_EQ(liar_pmd()(1), 2);
}
} // namespace