//===- STLForwardCompatTest.cpp - Unit tests for STLForwardCompat ---------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ADT/STLForwardCompat.h" #include "CountCopyAndMove.h" #include "gtest/gtest.h" #include #include #include #include namespace llvm { namespace { template class STLForwardCompatRemoveCVRefTest : public ::testing::Test {}; using STLForwardCompatRemoveCVRefTestTypes = ::testing::Types< // clang-format off std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair, std::pair // clang-format on >; TYPED_TEST_SUITE(STLForwardCompatRemoveCVRefTest, STLForwardCompatRemoveCVRefTestTypes, ); TYPED_TEST(STLForwardCompatRemoveCVRefTest, RemoveCVRef) { using From = typename TypeParam::first_type; using To = typename TypeParam::second_type; EXPECT_TRUE( (std::is_same::type, To>::value)); } TYPED_TEST(STLForwardCompatRemoveCVRefTest, RemoveCVRefT) { using From = typename TypeParam::first_type; EXPECT_TRUE((std::is_same::type, llvm::remove_cvref_t>::value)); } template class TypeIdentityTest : public ::testing::Test { public: using TypeIdentity = llvm::type_identity; }; struct A { struct B {}; }; using TypeIdentityTestTypes = ::testing::Types; TYPED_TEST_SUITE(TypeIdentityTest, TypeIdentityTestTypes, /*NameGenerator*/); TYPED_TEST(TypeIdentityTest, Identity) { // TestFixture is the instantiated TypeIdentityTest. EXPECT_TRUE( (std::is_same_v)); } TEST(TransformTest, TransformStd) { std::optional A; std::optional B = llvm::transformOptional(A, [&](int N) { return N + 1; }); EXPECT_FALSE(B.has_value()); A = 3; std::optional C = llvm::transformOptional(A, [&](int N) { return N + 1; }); EXPECT_TRUE(C.has_value()); EXPECT_EQ(4, *C); } TEST(TransformTest, MoveTransformStd) { using llvm::CountCopyAndMove; std::optional A; CountCopyAndMove::ResetCounts(); std::optional B = llvm::transformOptional( std::move(A), [&](const CountCopyAndMove &M) { return M.val + 2; }); EXPECT_FALSE(B.has_value()); EXPECT_EQ(0, CountCopyAndMove::TotalCopies()); EXPECT_EQ(0, CountCopyAndMove::MoveConstructions); EXPECT_EQ(0, CountCopyAndMove::MoveAssignments); EXPECT_EQ(0, CountCopyAndMove::Destructions); A = CountCopyAndMove(5); CountCopyAndMove::ResetCounts(); std::optional C = llvm::transformOptional( std::move(A), [&](const CountCopyAndMove &M) { return M.val + 2; }); EXPECT_TRUE(C.has_value()); EXPECT_EQ(7, *C); EXPECT_EQ(0, CountCopyAndMove::TotalCopies()); EXPECT_EQ(0, CountCopyAndMove::MoveConstructions); EXPECT_EQ(0, CountCopyAndMove::MoveAssignments); EXPECT_EQ(0, CountCopyAndMove::Destructions); } TEST(TransformTest, TransformLlvm) { std::optional A; std::optional B = llvm::transformOptional(A, [&](int N) { return N + 1; }); EXPECT_FALSE(B.has_value()); A = 3; std::optional C = llvm::transformOptional(A, [&](int N) { return N + 1; }); EXPECT_TRUE(C.has_value()); EXPECT_EQ(4, *C); } TEST(TransformTest, MoveTransformLlvm) { using llvm::CountCopyAndMove; std::optional A; CountCopyAndMove::ResetCounts(); std::optional B = llvm::transformOptional( std::move(A), [&](const CountCopyAndMove &M) { return M.val + 2; }); EXPECT_FALSE(B.has_value()); EXPECT_EQ(0, CountCopyAndMove::TotalCopies()); EXPECT_EQ(0, CountCopyAndMove::MoveConstructions); EXPECT_EQ(0, CountCopyAndMove::MoveAssignments); EXPECT_EQ(0, CountCopyAndMove::Destructions); A = CountCopyAndMove(5); CountCopyAndMove::ResetCounts(); std::optional C = llvm::transformOptional( std::move(A), [&](const CountCopyAndMove &M) { return M.val + 2; }); EXPECT_TRUE(C.has_value()); EXPECT_EQ(7, *C); EXPECT_EQ(0, CountCopyAndMove::TotalCopies()); EXPECT_EQ(0, CountCopyAndMove::MoveConstructions); EXPECT_EQ(0, CountCopyAndMove::MoveAssignments); EXPECT_EQ(0, CountCopyAndMove::Destructions); } TEST(TransformTest, TransformCategory) { struct StructA { int x; }; struct StructB : StructA { StructB(StructA &&A) : StructA(std::move(A)) {} }; std::optional A{StructA{}}; llvm::transformOptional(A, [](auto &&s) { EXPECT_FALSE(std::is_rvalue_reference_v); return StructB{std::move(s)}; }); llvm::transformOptional(std::move(A), [](auto &&s) { EXPECT_TRUE(std::is_rvalue_reference_v); return StructB{std::move(s)}; }); } // Checks that pointer to member is a valid argument TEST(TransformTest, TransformToMember) { struct StructA { int x = 1; int y = 2; }; std::optional Opt{StructA{}}; auto OptX = transformOptional(Opt, &StructA::x); ASSERT_TRUE(OptX.has_value()); EXPECT_EQ(*OptX, 1); auto OptY = transformOptional(Opt, &StructA::y); ASSERT_TRUE(OptY.has_value()); EXPECT_EQ(*OptY, 2); Opt = std::nullopt; OptX = transformOptional(Opt, &StructA::x); EXPECT_FALSE(OptX.has_value()); } // Checks that pointer to member function is a valid argument TEST(TransformTest, TransformToMemberFunc) { struct Foo { int bar() const { return 42; } }; std::optional Opt{Foo{}}; auto OptRes = transformOptional(Opt, &Foo::bar); ASSERT_TRUE(OptRes.has_value()); EXPECT_EQ(*OptRes, 42); } // Checks that callable is forwarded properly TEST(TransformTest, TransformRvalueRef) { struct Foo { int operator()(int x) && { return 42; } int operator()(int x) & { return 43; } }; std::optional Opt = 1; Foo foo; auto Res = transformOptional(Opt, foo); EXPECT_EQ(Res.value_or(0), 43); Res = transformOptional(Opt, std::move(foo)); EXPECT_EQ(Res.value_or(0), 42); } // Checks constexpr TEST(TransformTest, TransfornConstexpr) { constexpr std::optional Opt1 = 42; auto PlusOne = [](int x) { return x + 1; }; constexpr auto Res1 = transformOptional(Opt1, PlusOne); EXPECT_EQ(Res1.value_or(0), 43); constexpr std::optional Opt2; constexpr auto Res2 = transformOptional(Opt2, PlusOne); EXPECT_FALSE(Res2.has_value()); } TEST(TransformTest, ToUnderlying) { enum E { A1 = 0, B1 = -1 }; static_assert(llvm::to_underlying(A1) == 0); static_assert(llvm::to_underlying(B1) == -1); enum E2 : unsigned char { A2 = 0, B2 }; static_assert( std::is_same_v); static_assert(llvm::to_underlying(A2) == 0); static_assert(llvm::to_underlying(B2) == 1); enum class E3 { A3 = -1, B3 }; static_assert(std::is_same_v); static_assert(llvm::to_underlying(E3::A3) == -1); static_assert(llvm::to_underlying(E3::B3) == 0); } TEST(STLForwardCompatTest, IdentityCxx20) { llvm::identity identity; // Test with an lvalue. int X = 42; int &Y = identity(X); EXPECT_EQ(&X, &Y); // Test with a const lvalue. const int CX = 10; const int &CY = identity(CX); EXPECT_EQ(&CX, &CY); // Test with an rvalue. EXPECT_EQ(identity(123), 123); // Test perfect forwarding. static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); } //===----------------------------------------------------------------------===// // llvm::invoke tests //===----------------------------------------------------------------------===// TEST(STLForwardCompatTest, InvokePerfectForwarding) { auto CheckArgs = [](auto &&A, auto &&B, auto &&C) { static_assert(std::is_same_v); static_assert(std::is_same_v); static_assert(std::is_same_v); return A + B + C; }; int X = 1; const int Y = 2; EXPECT_EQ(llvm::invoke(CheckArgs, X, Y, 3), 6); // Make sure the result produced by std::invoke is identical -- // the only meaningful difference is that std::invoke became // constexpr in C++20. EXPECT_EQ(std::invoke(CheckArgs, X, Y, 3), 6); } namespace { struct InvokeTest { int Value; constexpr int scale(int Factor) const { return Value * Factor; } constexpr int &getRef() { return Value; } }; } // namespace TEST(STLForwardCompatTest, InvokeMemberPointers) { InvokeTest Obj{10}; // Member function pointer. EXPECT_EQ(llvm::invoke(&InvokeTest::scale, Obj, 3), 30); EXPECT_EQ(llvm::invoke(&InvokeTest::scale, &Obj, 3), 30); // Member data pointer. EXPECT_EQ(llvm::invoke(&InvokeTest::Value, Obj), 10); EXPECT_EQ(llvm::invoke(&InvokeTest::Value, &Obj), 10); // Member function returning reference - args are forwarded, not copied. llvm::invoke(&InvokeTest::getRef, Obj) = 20; EXPECT_EQ(Obj.Value, 20); // Lvalue forwarded by reference. llvm::invoke(&InvokeTest::getRef, &Obj) = 30; EXPECT_EQ(Obj.Value, 30); // Pointer also works. } TEST(STLForwardCompatTest, InvokeConstexpr) { // Regular function. static constexpr int A = llvm::invoke([](int X, int Y) { return X + Y; }, 1, 2); static_assert(A == 3); // Member data pointer. static constexpr int B = llvm::invoke(&InvokeTest::Value, InvokeTest{42}); static_assert(B == 42); // Member function pointer. static constexpr int C = llvm::invoke(&InvokeTest::scale, InvokeTest{5}, 3); static_assert(C == 15); } TEST(STLForwardCompatTest, BindFrontReferences) { // All bound arguments are forwarded (for ints, this is a copy) into the // wrapper. Call arguments are forwarded with their original value category. int A = 1; const int B = 2; int C = 3; int D = 4; const int E = 5; int F = 6; auto TestTypes = [](auto &&AArg, auto &&BArg, auto &&CArg, auto &&DArg, auto &&EArg, auto &&FArg) { // Bound args: all stored as values, passed as lvalue refs. EXPECT_EQ(AArg, 1); static_assert(std::is_same_v); EXPECT_EQ(BArg, 2); static_assert(std::is_same_v); // Const decayed away. EXPECT_EQ(CArg, 3); static_assert(std::is_same_v); // Call args: forwarded with original value category. EXPECT_EQ(DArg, 4); static_assert(std::is_same_v); EXPECT_EQ(EArg, 5); static_assert(std::is_same_v); EXPECT_EQ(FArg, 6); static_assert(std::is_same_v); ++AArg; ++DArg; }; llvm::bind_front(TestTypes, A, B, std::move(C))(D, E, std::move(F)); EXPECT_EQ(A, 1); // A was copied, original unchanged. EXPECT_EQ(D, 5); // D was passed by reference and incremented. } TEST(STLForwardCompatTest, BindBackReferences) { // With std::decay_t, all bound arguments are copied into the wrapper. // Call arguments are forwarded with their original value category. int A = 1; const int B = 2; int C = 3; int D = 4; const int E = 5; int F = 6; auto TestTypes = [](auto &&AArg, auto &&BArg, auto &&CArg, auto &&DArg, auto &&EArg, auto &&FArg) { // Call args: forwarded with original value category. EXPECT_EQ(AArg, 1); static_assert(std::is_same_v); EXPECT_EQ(BArg, 2); static_assert(std::is_same_v); EXPECT_EQ(CArg, 3); static_assert(std::is_same_v); // Bound args: all stored as values, passed as lvalue refs. EXPECT_EQ(DArg, 4); static_assert(std::is_same_v); EXPECT_EQ(EArg, 5); static_assert(std::is_same_v); // Const decayed away. EXPECT_EQ(FArg, 6); static_assert(std::is_same_v); ++AArg; ++DArg; }; llvm::bind_back(TestTypes, D, E, std::move(F))(A, B, std::move(C)); EXPECT_EQ(A, 2); // A was passed by reference and incremented. EXPECT_EQ(D, 4); // D was copied, original unchanged. } // Check that bound args are copied once during bind, then passed by reference. TEST(STLForwardCompatTest, BindBoundArgsForwarding) { auto Fn = [](CountCopyAndMove &A) -> int { return A.val; }; CountCopyAndMove::ResetCounts(); CountCopyAndMove Arg(42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); // Creating the wrapper should copy the bound args once. auto Bound = llvm::bind_front(Fn, Arg); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 1); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); // Calling should not copy -- bound args are passed by reference. EXPECT_EQ(Bound(), 42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 1); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); } // Check that call args are forwarded without copies. TEST(STLForwardCompatTest, BindCallArgsForwarding) { auto Fn = [](int, CountCopyAndMove &&A) -> int { return A.val; }; CountCopyAndMove::ResetCounts(); auto Bound = llvm::bind_front(Fn, 1); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); // Call arg should be forwarded as rvalue, no copies. EXPECT_EQ(Bound(CountCopyAndMove(42)), 42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); } // Check that the callable itself is moved, not copied excessively. TEST(STLForwardCompatTest, BindCallableForwarding) { CountCopyAndMove::ResetCounts(); CountCopyAndMove Capture(42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); // Lambda captures by value -- this copy is outside bind's control. auto Fn = [Capture]() { return Capture.val; }; EXPECT_EQ(CountCopyAndMove::TotalCopies(), 1); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 0); // Moving lambda into bind should move, not copy. auto Bound = llvm::bind_front(std::move(Fn)); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 1); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 1); // Calling should not copy the callable. EXPECT_EQ(Bound(), 42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 1); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 1); // The bind object should be copyable. auto BoundCopy = Bound; EXPECT_EQ(CountCopyAndMove::TotalCopies(), 2); // The bind object should be movable. auto BoundMove = std::move(BoundCopy); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 2); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 2); } // Check that moving bound args works correctly. TEST(STLForwardCompatTest, BindMoveBoundArgs) { auto Fn = [](CountCopyAndMove &A) -> int { return A.val; }; CountCopyAndMove::ResetCounts(); CountCopyAndMove Arg(42); // Moving into bind should move, not copy. auto Bound = llvm::bind_front(Fn, std::move(Arg)); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 1); EXPECT_EQ(Bound(), 42); EXPECT_EQ(CountCopyAndMove::TotalCopies(), 0); EXPECT_EQ(CountCopyAndMove::TotalMoves(), 1); } TEST(STLForwardCompatTest, BindFrontMutableStorage) { // With std::decay_t, A is copied into the wrapper. The stored copy can be // mutated across calls, but the original A is unchanged. int A = 1; auto TestMutation = [](int &AArg, int &BArg, auto ExtraCheckFn) { ++AArg; ++BArg; ExtraCheckFn(AArg, BArg); }; auto BoundA = llvm::bind_front(TestMutation, A, 42); BoundA([](int AVal, int BVal) { EXPECT_EQ(AVal, 2); // Stored copy incremented from 1. EXPECT_EQ(BVal, 43); }); EXPECT_EQ(A, 1); // Original unchanged. BoundA([](int AVal, int BVal) { EXPECT_EQ(AVal, 3); // Stored copy incremented again. EXPECT_EQ(BVal, 44); }); EXPECT_EQ(A, 1); // Original still unchanged. } TEST(STLForwardCompatTest, BindBackMutableStorage) { // With std::decay_t, A is copied into the wrapper. The stored copy can be // mutated across calls, but the original A is unchanged. int A = 1; auto TestMutation = [](auto ExtraCheckFn, int &AArg, int &BArg) { ++AArg; ++BArg; ExtraCheckFn(AArg, BArg); }; auto BoundA = llvm::bind_back(TestMutation, A, 42); BoundA([](int AVal, int BVal) { EXPECT_EQ(AVal, 2); // Stored copy incremented from 1. EXPECT_EQ(BVal, 43); }); EXPECT_EQ(A, 1); // Original unchanged. BoundA([](int AVal, int BVal) { EXPECT_EQ(AVal, 3); // Stored copy incremented again. EXPECT_EQ(BVal, 44); }); EXPECT_EQ(A, 1); // Original still unchanged. } // Free function for compile-time bind tests. static int subtract(int A, int B) { return A - B; } TEST(STLForwardCompatTest, BindFrontConstexprCallable) { // Test compile-time callable with llvm::bind_front. auto TimesFive = llvm::bind_front(5); EXPECT_EQ(TimesFive(3), 2); // Test compile-time callable with llvm::bind_back. auto FiveTimesX = llvm::bind_back(5); EXPECT_EQ(FiveTimesX(3), -2); } TEST(STLForwardCompatTest, BindFrontBackNoBoundArgs) { auto Fn1 = llvm::bind_front([](int A, int B) { return A + B; }); EXPECT_EQ(Fn1(3, 4), 7); auto Fn2 = llvm::bind_back([](int A, int B) { return A + B; }); EXPECT_EQ(Fn2(3, 4), 7); } TEST(STLForwardCompatTest, BindFrontBindBackConstexpr) { static constexpr auto Fn1 = llvm::bind_front([](int A, int B) { return A + B; }, 1); static_assert(Fn1(3) == 4); static constexpr auto Fn2 = llvm::bind_back([](int A, int B) { return A + B; }, 1); static_assert(Fn2(3) == 4); } // Test that reference return types are preserved (with `decltype(auto)`). TEST(STLForwardCompatTest, BindPreservesReferenceReturn) { int X = 10; auto GetRef = [&X]() -> int & { return X; }; auto BoundFront = llvm::bind_front(GetRef); static_assert(std::is_same_v); BoundFront() = 20; EXPECT_EQ(X, 20); const auto BoundFrontConst = llvm::bind_front(GetRef); static_assert(std::is_same_v); auto BoundBack = llvm::bind_back(GetRef); static_assert(std::is_same_v); BoundBack() = 30; EXPECT_EQ(X, 30); const auto BoundBackConst = llvm::bind_back(GetRef); static_assert(std::is_same_v); } // Use std::ref/std::cref to bind references (bound args are decay-copied). TEST(STLForwardCompatTest, BindWithReferenceWrapper) { int X = 1; auto Increment = llvm::bind_front([](int &Val) { ++Val; }, std::ref(X)); Increment(); EXPECT_EQ(X, 2); Increment(); EXPECT_EQ(X, 3); } // The callable itself can have mutable state. TEST(STLForwardCompatTest, BindMutableCallable) { auto Counter = llvm::bind_front([N = 0]() mutable { return ++N; }); EXPECT_EQ(Counter(), 1); EXPECT_EQ(Counter(), 2); EXPECT_EQ(Counter(), 3); } namespace { struct MemberTest { int Value; int scale(int Factor) const { return Value * Factor; } }; } // namespace TEST(STLForwardCompatTest, BindMembers) { // Member function pointer support via std::apply (with std::invoke used // internally). MemberTest Obj{10}; auto ScaleObj = llvm::bind_front(&MemberTest::scale, Obj); EXPECT_EQ(ScaleObj(3), 30); auto ScaleBy5 = llvm::bind_back(&MemberTest::scale, 5); EXPECT_EQ(ScaleBy5(Obj), 50); // Member data pointer support via std::apply (with std::invoke used // internally). auto GetValue = llvm::bind_front(&MemberTest::Value); EXPECT_EQ(GetValue(Obj), 10); // Make sure we can use member data pointers for constexpr callables. static constexpr int MemberVal = llvm::bind_front(&MemberTest::Value)(MemberTest{10}); EXPECT_EQ(MemberVal, 10); } TEST(STLForwardCompatTest, BindFrontBindBack) { std::vector V; auto MulAdd = [](int A, int B, int C) { return A * (B + C) == 12; }; auto MulAdd1 = [](const int &A, const int &B, const int &C) { return A * (B + C) == 12; }; auto Mul0 = llvm::bind_back(MulAdd, 4, 2); auto MulL = llvm::bind_front(MulAdd1, 2, 4); auto Mul20 = llvm::bind_back(MulAdd, 4); auto Mul21 = llvm::bind_front(MulAdd1, 2); EXPECT_TRUE(all_of(V, Mul0)); EXPECT_TRUE(all_of(V, MulL)); V.push_back(2); EXPECT_TRUE(all_of(V, Mul0)); EXPECT_TRUE(all_of(V, MulL)); V.push_back(2); V.push_back(2); EXPECT_TRUE(all_of(V, Mul0)); EXPECT_TRUE(all_of(V, MulL)); auto Spec0 = llvm::bind_front(Mul20, 2); auto Spec1 = llvm::bind_back(Mul21, 4); EXPECT_TRUE(all_of(V, Spec0)); EXPECT_TRUE(all_of(V, Spec1)); V.push_back(3); EXPECT_FALSE(all_of(V, Mul0)); EXPECT_FALSE(all_of(V, MulL)); EXPECT_FALSE(all_of(V, Spec0)); EXPECT_FALSE(all_of(V, Spec1)); EXPECT_TRUE(any_of(V, Spec0)); EXPECT_TRUE(any_of(V, Spec1)); } // Compile-time tests for llvm::is_sorted_constexpr static constexpr int CEmptyHelper[]{-1}; static_assert(llvm::is_sorted_constexpr(std::begin(CEmptyHelper), std::begin(CEmptyHelper)), "Empty range should be sorted"); static constexpr int CSingle[]{42}; static_assert(llvm::is_sorted_constexpr(std::begin(CSingle), std::end(CSingle)), "Single element range should be sorted"); static_assert(llvm::is_sorted_constexpr(std::begin(CSingle), std::end(CSingle), std::greater<>()), "Single element range should be sorted with std::greater"); static constexpr int CSorted[]{1, 2, 2, 3, 5}; static_assert(llvm::is_sorted_constexpr(std::begin(CSorted), std::end(CSorted)), "Non-descending order with duplicates should be sorted"); static_assert(llvm::is_sorted_constexpr(std::begin(CSorted), std::end(CSorted), std::less<>()), "Explicit std::less non-descending order should be sorted"); static_assert(!llvm::is_sorted_constexpr(std::begin(CSorted), std::end(CSorted), std::greater<>()), "Non-descending order should not be sorted by std::greater"); static constexpr int CUnsorted[]{1, 3, 2, 4, 5}; static_assert(!llvm::is_sorted_constexpr(std::begin(CUnsorted), std::end(CUnsorted)), "Unsorted range should not be sorted"); static constexpr int CDesc[]{9, 7, 7, 3, 0}; static_assert(llvm::is_sorted_constexpr(std::begin(CDesc), std::end(CDesc), std::greater<>()), "Non-ascending order with std::greater should be sorted"); static_assert(llvm::is_sorted_constexpr(std::rbegin(CDesc), std::rend(CDesc)), "Reverse iterators should be supported."); } // namespace } // namespace llvm