CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
702 lines
23 KiB
C++
702 lines
23 KiB
C++
//===- 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 <iterator>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace llvm {
|
|
namespace {
|
|
|
|
template <typename T>
|
|
class STLForwardCompatRemoveCVRefTest : public ::testing::Test {};
|
|
|
|
using STLForwardCompatRemoveCVRefTestTypes = ::testing::Types<
|
|
// clang-format off
|
|
std::pair<int, int>,
|
|
std::pair<int &, int>,
|
|
std::pair<const int, int>,
|
|
std::pair<volatile int, int>,
|
|
std::pair<const volatile int &, int>,
|
|
std::pair<int *, int *>,
|
|
std::pair<int *const, int *>,
|
|
std::pair<const int *, const int *>,
|
|
std::pair<int *&, int *>
|
|
// 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<typename llvm::remove_cvref<From>::type, To>::value));
|
|
}
|
|
|
|
TYPED_TEST(STLForwardCompatRemoveCVRefTest, RemoveCVRefT) {
|
|
using From = typename TypeParam::first_type;
|
|
EXPECT_TRUE((std::is_same<typename llvm::remove_cvref<From>::type,
|
|
llvm::remove_cvref_t<From>>::value));
|
|
}
|
|
|
|
template <typename T> class TypeIdentityTest : public ::testing::Test {
|
|
public:
|
|
using TypeIdentity = llvm::type_identity<T>;
|
|
};
|
|
|
|
struct A {
|
|
struct B {};
|
|
};
|
|
using TypeIdentityTestTypes =
|
|
::testing::Types<int, volatile int, A, const A::B>;
|
|
|
|
TYPED_TEST_SUITE(TypeIdentityTest, TypeIdentityTestTypes, /*NameGenerator*/);
|
|
|
|
TYPED_TEST(TypeIdentityTest, Identity) {
|
|
// TestFixture is the instantiated TypeIdentityTest.
|
|
EXPECT_TRUE(
|
|
(std::is_same_v<TypeParam, typename TestFixture::TypeIdentity::type>));
|
|
}
|
|
|
|
TEST(TransformTest, TransformStd) {
|
|
std::optional<int> A;
|
|
|
|
std::optional<int> B = llvm::transformOptional(A, [&](int N) { return N + 1; });
|
|
EXPECT_FALSE(B.has_value());
|
|
|
|
A = 3;
|
|
std::optional<int> 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<CountCopyAndMove> A;
|
|
|
|
CountCopyAndMove::ResetCounts();
|
|
std::optional<int> 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<int> 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<int> A;
|
|
|
|
std::optional<int> B =
|
|
llvm::transformOptional(A, [&](int N) { return N + 1; });
|
|
EXPECT_FALSE(B.has_value());
|
|
|
|
A = 3;
|
|
std::optional<int> 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<CountCopyAndMove> A;
|
|
|
|
CountCopyAndMove::ResetCounts();
|
|
std::optional<int> 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<int> 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<StructA> A{StructA{}};
|
|
llvm::transformOptional(A, [](auto &&s) {
|
|
EXPECT_FALSE(std::is_rvalue_reference_v<decltype(s)>);
|
|
return StructB{std::move(s)};
|
|
});
|
|
|
|
llvm::transformOptional(std::move(A), [](auto &&s) {
|
|
EXPECT_TRUE(std::is_rvalue_reference_v<decltype(s)>);
|
|
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<StructA> 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<Foo> 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<int> 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<int> 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<int> 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<unsigned char, decltype(llvm::to_underlying(A2))>);
|
|
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<int, decltype(llvm::to_underlying(E3::A3))>);
|
|
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<int &, decltype(identity(X))>);
|
|
static_assert(std::is_same_v<const int &, decltype(identity(CX))>);
|
|
static_assert(std::is_same_v<int &&, decltype(identity(int(5)))>);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// llvm::invoke tests
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
TEST(STLForwardCompatTest, InvokePerfectForwarding) {
|
|
auto CheckArgs = [](auto &&A, auto &&B, auto &&C) {
|
|
static_assert(std::is_same_v<decltype(A), int &>);
|
|
static_assert(std::is_same_v<decltype(B), const int &>);
|
|
static_assert(std::is_same_v<decltype(C), int &&>);
|
|
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<decltype(AArg), int &>);
|
|
EXPECT_EQ(BArg, 2);
|
|
static_assert(std::is_same_v<decltype(BArg), int &>); // Const decayed away.
|
|
EXPECT_EQ(CArg, 3);
|
|
static_assert(std::is_same_v<decltype(CArg), int &>);
|
|
// Call args: forwarded with original value category.
|
|
EXPECT_EQ(DArg, 4);
|
|
static_assert(std::is_same_v<decltype(DArg), int &>);
|
|
EXPECT_EQ(EArg, 5);
|
|
static_assert(std::is_same_v<decltype(EArg), const int &>);
|
|
EXPECT_EQ(FArg, 6);
|
|
static_assert(std::is_same_v<decltype(FArg), int &&>);
|
|
|
|
++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<decltype(AArg), int &>);
|
|
EXPECT_EQ(BArg, 2);
|
|
static_assert(std::is_same_v<decltype(BArg), const int &>);
|
|
EXPECT_EQ(CArg, 3);
|
|
static_assert(std::is_same_v<decltype(CArg), int &&>);
|
|
// Bound args: all stored as values, passed as lvalue refs.
|
|
EXPECT_EQ(DArg, 4);
|
|
static_assert(std::is_same_v<decltype(DArg), int &>);
|
|
EXPECT_EQ(EArg, 5);
|
|
static_assert(std::is_same_v<decltype(EArg), int &>); // Const decayed away.
|
|
EXPECT_EQ(FArg, 6);
|
|
static_assert(std::is_same_v<decltype(FArg), int &>);
|
|
|
|
++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<subtract>(5);
|
|
EXPECT_EQ(TimesFive(3), 2);
|
|
|
|
// Test compile-time callable with llvm::bind_back.
|
|
auto FiveTimesX = llvm::bind_back<subtract>(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<decltype(BoundFront()), int &>);
|
|
BoundFront() = 20;
|
|
EXPECT_EQ(X, 20);
|
|
|
|
const auto BoundFrontConst = llvm::bind_front(GetRef);
|
|
static_assert(std::is_same_v<decltype(BoundFrontConst()), int &>);
|
|
|
|
auto BoundBack = llvm::bind_back(GetRef);
|
|
static_assert(std::is_same_v<decltype(BoundBack()), int &>);
|
|
BoundBack() = 30;
|
|
EXPECT_EQ(X, 30);
|
|
|
|
const auto BoundBackConst = llvm::bind_back(GetRef);
|
|
static_assert(std::is_same_v<decltype(BoundBackConst()), int &>);
|
|
}
|
|
|
|
// 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<int> 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
|