//===- IteratorTest.cpp - Unit tests for iterator utilities ---------------===// // // 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/iterator.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/ilist.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include using namespace llvm; using testing::ElementsAre; namespace { template struct Shadow; struct WeirdIter : llvm::iterator_facade_base, Shadow<1>, Shadow<2>, Shadow<3>> {}; struct AdaptedIter : iterator_adaptor_base {}; // Test that iterator_adaptor_base forwards typedefs, if value_type is // unchanged. static_assert(std::is_same_v>, ""); static_assert(std::is_same_v>, ""); static_assert(std::is_same_v>, ""); static_assert(std::is_same_v>, ""); // Ensure that pointe{e,r}_iterator adaptors correctly forward the category of // the underlying iterator. using RandomAccessIter = SmallVectorImpl::iterator; using BidiIter = ilist::iterator; template using pointee_iterator_defaulted = pointee_iterator; template using pointer_iterator_defaulted = pointer_iterator; // Ensures that an iterator and its adaptation have the same iterator_category. template class A, typename It> using IsAdaptedIterCategorySame = std::is_same::iterator_category, typename std::iterator_traits>::iterator_category>; // Check that dereferencing works correctly adapting pointers and proxies. template struct PointerWrapper : public iterator_adaptor_base, T *> { PointerWrapper(T *I) : PointerWrapper::iterator_adaptor_base(I) {} }; struct IntProxy { int &I; IntProxy(int &I) : I(I) {} void operator=(int NewValue) { I = NewValue; } }; struct ConstIntProxy { const int &I; ConstIntProxy(const int &I) : I(I) {} }; template struct PointerProxyWrapper : public iterator_adaptor_base, T *, std::random_access_iterator_tag, T, ptrdiff_t, T *, ProxyT> { PointerProxyWrapper(T *I) : PointerProxyWrapper::iterator_adaptor_base(I) {} }; using IntIterator = PointerWrapper; using ConstIntIterator = PointerWrapper; using IntProxyIterator = PointerProxyWrapper; using ConstIntProxyIterator = PointerProxyWrapper; // There should only be a single (const-qualified) operator*, operator->, and // operator[]. This test confirms that there isn't a non-const overload. Rather // than adding those, users should double-check that T, PointerT, and ReferenceT // have the right constness, and/or make fields mutable. static_assert(&IntIterator::operator* == &IntIterator::operator*, ""); static_assert(&IntIterator::operator-> == &IntIterator::operator->, ""); static_assert(&IntIterator::operator[] == &IntIterator::operator[], ""); template , bool> = false> constexpr bool canAssignFromInt(T &&) { return true; } template , bool> = false> constexpr bool canAssignFromInt(T &&) { return false; } TEST(IteratorAdaptorTest, Dereference) { int Number = 1; // Construct some iterators and check whether they can be assigned to. IntIterator I(&Number); const IntIterator IC(&Number); ConstIntIterator CI(&Number); const ConstIntIterator CIC(&Number); EXPECT_EQ(true, canAssignFromInt(*I)); // int * EXPECT_EQ(true, canAssignFromInt(*IC)); // int *const EXPECT_EQ(false, canAssignFromInt(*CI)); // const int * EXPECT_EQ(false, canAssignFromInt(*CIC)); // const int *const // Prove that dereference and assignment work. EXPECT_EQ(1, *I); EXPECT_EQ(1, *IC); EXPECT_EQ(1, *CI); EXPECT_EQ(1, *CIC); *I = 2; EXPECT_EQ(2, Number); *IC = 3; EXPECT_EQ(3, Number); // Construct some proxy iterators and check whether they can be assigned to. IntProxyIterator P(&Number); const IntProxyIterator PC(&Number); ConstIntProxyIterator CP(&Number); const ConstIntProxyIterator CPC(&Number); EXPECT_EQ(true, canAssignFromInt(*P)); // int * EXPECT_EQ(true, canAssignFromInt(*PC)); // int *const EXPECT_EQ(false, canAssignFromInt(*CP)); // const int * EXPECT_EQ(false, canAssignFromInt(*CPC)); // const int *const // Prove that dereference and assignment work. EXPECT_EQ(3, (*P).I); EXPECT_EQ(3, (*PC).I); EXPECT_EQ(3, (*CP).I); EXPECT_EQ(3, (*CPC).I); *P = 4; EXPECT_EQ(4, Number); *PC = 5; EXPECT_EQ(5, Number); } // pointeE_iterator static_assert(IsAdaptedIterCategorySame::value, ""); static_assert(IsAdaptedIterCategorySame::value, ""); // pointeR_iterator static_assert(IsAdaptedIterCategorySame::value, ""); static_assert(IsAdaptedIterCategorySame::value, ""); TEST(PointeeIteratorTest, Basic) { int arr[4] = {1, 2, 3, 4}; SmallVector V; V.push_back(&arr[0]); V.push_back(&arr[1]); V.push_back(&arr[2]); V.push_back(&arr[3]); typedef pointee_iterator::const_iterator> test_iterator; test_iterator Begin, End; Begin = V.begin(); End = test_iterator(V.end()); test_iterator I = Begin; for (int i = 0; i < 4; ++i) { EXPECT_EQ(*V[i], *I); EXPECT_EQ(I, Begin + i); EXPECT_EQ(I, std::next(Begin, i)); test_iterator J = Begin; J += i; EXPECT_EQ(I, J); EXPECT_EQ(*V[i], Begin[i]); EXPECT_NE(I, End); EXPECT_GT(End, I); EXPECT_LT(I, End); EXPECT_GE(I, Begin); EXPECT_LE(Begin, I); EXPECT_EQ(i, I - Begin); EXPECT_EQ(i, std::distance(Begin, I)); EXPECT_EQ(Begin, I - i); test_iterator K = I++; EXPECT_EQ(K, std::prev(I)); } EXPECT_EQ(End, I); } TEST(PointeeIteratorTest, SmartPointer) { SmallVector, 4> V; V.push_back(std::make_unique(1)); V.push_back(std::make_unique(2)); V.push_back(std::make_unique(3)); V.push_back(std::make_unique(4)); typedef pointee_iterator< SmallVectorImpl>::const_iterator> test_iterator; test_iterator Begin, End; Begin = V.begin(); End = test_iterator(V.end()); test_iterator I = Begin; for (int i = 0; i < 4; ++i) { EXPECT_EQ(*V[i], *I); EXPECT_EQ(I, Begin + i); EXPECT_EQ(I, std::next(Begin, i)); test_iterator J = Begin; J += i; EXPECT_EQ(I, J); EXPECT_EQ(*V[i], Begin[i]); EXPECT_NE(I, End); EXPECT_GT(End, I); EXPECT_LT(I, End); EXPECT_GE(I, Begin); EXPECT_LE(Begin, I); EXPECT_EQ(i, I - Begin); EXPECT_EQ(i, std::distance(Begin, I)); EXPECT_EQ(Begin, I - i); test_iterator K = I++; EXPECT_EQ(K, std::prev(I)); } EXPECT_EQ(End, I); } TEST(PointeeIteratorTest, Range) { int A[] = {1, 2, 3, 4}; SmallVector V{&A[0], &A[1], &A[2], &A[3]}; int I = 0; for (int II : make_pointee_range(V)) EXPECT_EQ(A[I++], II); } TEST(PointeeIteratorTest, PointeeType) { struct S { int X; bool operator==(const S &RHS) const { return X == RHS.X; }; }; S A[] = {S{0}, S{1}}; SmallVector V{&A[0], &A[1]}; pointee_iterator::const_iterator, const S> I = V.begin(); for (int j = 0; j < 2; ++j, ++I) { EXPECT_EQ(*V[j], *I); } } TEST(FilterIteratorTest, Lambda) { auto IsOdd = [](int N) { return N % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; auto Range = make_filter_range(A, IsOdd); SmallVector Actual(Range.begin(), Range.end()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, Enumerate) { auto IsOdd = [](auto N) { return N.value() % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; auto Enumerate = llvm::enumerate(A); SmallVector Actual; for (const auto &IndexedValue : make_filter_range(Enumerate, IsOdd)) Actual.push_back(IndexedValue.value()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, CallableObject) { int Counter = 0; struct Callable { int &Counter; Callable(int &Counter) : Counter(Counter) {} bool operator()(int N) { Counter++; return N % 2 == 1; } }; Callable IsOdd(Counter); int A[] = {0, 1, 2, 3, 4, 5, 6}; auto Range = make_filter_range(A, IsOdd); EXPECT_EQ(2, Counter); SmallVector Actual(Range.begin(), Range.end()); EXPECT_GE(Counter, 7); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, FunctionPointer) { bool (*IsOdd)(int) = [](int N) { return N % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; auto Range = make_filter_range(A, IsOdd); SmallVector Actual(Range.begin(), Range.end()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, Composition) { auto IsOdd = [](int N) { return N % 2 == 1; }; std::unique_ptr A[] = {std::make_unique(0), std::make_unique(1), std::make_unique(2), std::make_unique(3), std::make_unique(4), std::make_unique(5), std::make_unique(6)}; using PointeeIterator = pointee_iterator *>; auto Range = make_filter_range( make_range(PointeeIterator(std::begin(A)), PointeeIterator(std::end(A))), IsOdd); SmallVector Actual(Range.begin(), Range.end()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, InputIterator) { struct InputIterator : iterator_adaptor_base { InputIterator(int *It) : InputIterator::iterator_adaptor_base(It) {} }; auto IsOdd = [](int N) { return N % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; auto Range = make_filter_range( make_range(InputIterator(std::begin(A)), InputIterator(std::end(A))), IsOdd); SmallVector Actual(Range.begin(), Range.end()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual); } TEST(FilterIteratorTest, ReverseFilterRange) { auto IsOdd = [](int N) { return N % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; // Check basic reversal. auto Range = reverse(make_filter_range(A, IsOdd)); SmallVector Actual(Range.begin(), Range.end()); EXPECT_EQ((SmallVector{5, 3, 1}), Actual); // Check that the reverse of the reverse is the original. auto Range2 = reverse(reverse(make_filter_range(A, IsOdd))); SmallVector Actual2(Range2.begin(), Range2.end()); EXPECT_EQ((SmallVector{1, 3, 5}), Actual2); // Check empty ranges. auto Range3 = reverse(make_filter_range(ArrayRef(), IsOdd)); SmallVector Actual3(Range3.begin(), Range3.end()); EXPECT_EQ((SmallVector{}), Actual3); // Check that we don't skip the first element, provided it isn't filtered // away. auto IsEven = [](int N) { return N % 2 == 0; }; auto Range4 = reverse(make_filter_range(A, IsEven)); SmallVector Actual4(Range4.begin(), Range4.end()); EXPECT_EQ((SmallVector{6, 4, 2, 0}), Actual4); } TEST(PointerIterator, Basic) { int A[] = {1, 2, 3, 4}; pointer_iterator Begin(std::begin(A)), End(std::end(A)); EXPECT_EQ(A, *Begin); ++Begin; EXPECT_EQ(A + 1, *Begin); ++Begin; EXPECT_EQ(A + 2, *Begin); ++Begin; EXPECT_EQ(A + 3, *Begin); ++Begin; EXPECT_EQ(Begin, End); } TEST(PointerIterator, Const) { int A[] = {1, 2, 3, 4}; const pointer_iterator Begin(std::begin(A)); EXPECT_EQ(A, *Begin); EXPECT_EQ(A + 1, std::next(*Begin, 1)); EXPECT_EQ(A + 2, std::next(*Begin, 2)); EXPECT_EQ(A + 3, std::next(*Begin, 3)); EXPECT_EQ(A + 4, std::next(*Begin, 4)); } TEST(PointerIterator, Range) { int A[] = {1, 2, 3, 4}; int I = 0; for (int *P : make_pointer_range(A)) EXPECT_EQ(A + I++, P); } namespace rbegin_detail { struct WithFreeRBegin { int data[3] = {42, 43, 44}; }; auto rbegin(const WithFreeRBegin &X) { return std::rbegin(X.data); } auto rend(const WithFreeRBegin &X) { return std::rend(X.data); } } // namespace rbegin_detail TEST(ReverseTest, ADL) { // Check that we can find the rbegin/rend functions via ADL. rbegin_detail::WithFreeRBegin Foo; EXPECT_THAT(reverse(Foo), ElementsAre(44, 43, 42)); } TEST(ZipIteratorTest, Basic) { using namespace std; const SmallVector pi{3, 1, 4, 1, 5, 9}; SmallVector odd{1, 1, 0, 1, 1, 1}; const char message[] = "yynyyy\0"; std::array shortArr = {42, 43}; for (auto tup : zip(pi, odd, message)) { EXPECT_EQ(get<0>(tup) & 0x01, get<1>(tup)); EXPECT_EQ(get<0>(tup) & 0x01 ? 'y' : 'n', get<2>(tup)); } // Note the rvalue. for (auto tup : zip(pi, SmallVector{1, 1, 0, 1, 1})) { EXPECT_EQ(get<0>(tup) & 0x01, get<1>(tup)); } // Iterate until we run out elements in the *shortest* range. for (auto [idx, elem] : enumerate(zip(odd, shortArr))) { EXPECT_LT(idx, static_cast(2)); } for (auto [idx, elem] : enumerate(zip(shortArr, odd))) { EXPECT_LT(idx, static_cast(2)); } } TEST(ZipIteratorTest, ZipEqualBasic) { const SmallVector pi = {3, 1, 4, 1, 5, 8}; const SmallVector vals = {1, 1, 0, 1, 1, 0}; unsigned iters = 0; for (auto [lhs, rhs] : zip_equal(vals, pi)) { EXPECT_EQ(lhs, rhs & 0x01); ++iters; } EXPECT_EQ(iters, 6u); } template constexpr bool IsConstRef = std::is_reference_v && std::is_const_v>; template constexpr bool IsBoolConstRef = std::is_same_v, std::vector::const_reference>; /// Returns a `const` copy of the passed value. The `const` on the returned /// value is intentional here so that `MakeConst` can be used in range-for /// loops. template const T MakeConst(T &&value) { return std::forward(value); } TEST(ZipIteratorTest, ZipEqualConstCorrectness) { const std::vector c_first = {3, 1, 4}; std::vector first = c_first; const SmallVector c_second = {1, 1, 0}; SmallVector second = c_second; for (auto [a, b, c, d] : zip_equal(c_first, first, c_second, second)) { b = 0; d = true; static_assert(IsConstRef); static_assert(!IsConstRef); static_assert(IsConstRef); static_assert(!IsConstRef); } EXPECT_THAT(first, ElementsAre(0, 0, 0)); EXPECT_THAT(second, ElementsAre(true, true, true)); std::vector nemesis = {true, false, true}; const std::vector c_nemesis = nemesis; for (auto &&[a, b, c, d] : zip_equal(first, c_first, nemesis, c_nemesis)) { a = 2; c = true; static_assert(!IsConstRef); static_assert(IsConstRef); static_assert(!IsBoolConstRef); static_assert(IsBoolConstRef); } EXPECT_THAT(first, ElementsAre(2, 2, 2)); EXPECT_THAT(nemesis, ElementsAre(true, true, true)); unsigned iters = 0; for (const auto &[a, b, c, d] : zip_equal(first, c_first, nemesis, c_nemesis)) { static_assert(!IsConstRef); static_assert(IsConstRef); static_assert(!IsBoolConstRef); static_assert(IsBoolConstRef); ++iters; } EXPECT_EQ(iters, 3u); iters = 0; for (const auto &[a, b, c, d] : MakeConst(zip_equal(first, c_first, nemesis, c_nemesis))) { static_assert(!IsConstRef); static_assert(IsConstRef); static_assert(!IsBoolConstRef); static_assert(IsBoolConstRef); ++iters; } EXPECT_EQ(iters, 3u); } TEST(ZipIteratorTest, ZipEqualTemporaries) { unsigned iters = 0; // These temporary ranges get moved into the `tuple<...> storage;` inside // `zippy`. From then on, we can use references obtained from this storage to // access them. This does not rely on any lifetime extensions on the // temporaries passed to `zip_equal`. for (auto [a, b, c] : zip_equal(SmallVector{1, 2, 3}, std::string("abc"), std::vector{true, false, true})) { a = 3; b = 'c'; c = false; static_assert(!IsConstRef); static_assert(!IsConstRef); static_assert(!IsBoolConstRef); ++iters; } EXPECT_EQ(iters, 3u); iters = 0; for (auto [a, b, c] : MakeConst(zip_equal(SmallVector{1, 2, 3}, std::string("abc"), std::vector{true, false, true}))) { static_assert(IsConstRef); static_assert(IsConstRef); static_assert(IsBoolConstRef); ++iters; } EXPECT_EQ(iters, 3u); } #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST // Check that an assertion is triggered when ranges passed to `zip_equal` differ // in length. TEST(ZipIteratorTest, ZipEqualNotEqual) { const SmallVector pi = {3, 1, 4, 1, 5, 8}; const SmallVector vals = {1, 1}; EXPECT_DEATH(zip_equal(pi, vals), "Iteratees do not have equal length"); EXPECT_DEATH(zip_equal(vals, pi), "Iteratees do not have equal length"); EXPECT_DEATH(zip_equal(pi, pi, vals), "Iteratees do not have equal length"); EXPECT_DEATH(zip_equal(vals, vals, pi), "Iteratees do not have equal length"); } #endif TEST(ZipIteratorTest, ZipFirstBasic) { using namespace std; const SmallVector pi{3, 1, 4, 1, 5, 9}; unsigned iters = 0; for (auto tup : zip_first(SmallVector{1, 1, 0, 1}, pi)) { EXPECT_EQ(get<0>(tup), get<1>(tup) & 0x01); iters += 1; } EXPECT_EQ(iters, 4u); } #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST // Make sure that we can detect when the first range is not the shortest. TEST(ZipIteratorTest, ZipFirstNotShortest) { const std::array longer = {}; const std::array shorter = {}; EXPECT_DEATH(zip_first(longer, shorter), "First iteratee is not the shortest"); EXPECT_DEATH(zip_first(longer, shorter, longer), "First iteratee is not the shortest"); EXPECT_DEATH(zip_first(longer, longer, shorter), "First iteratee is not the shortest"); } #endif TEST(ZipIteratorTest, ZipLongestBasic) { using namespace std; const vector pi{3, 1, 4, 1, 5, 9}; const vector e{"2", "7", "1", "8"}; { // Check left range longer than right. const vector, optional>> expected{ make_tuple(3, StringRef("2")), make_tuple(1, StringRef("7")), make_tuple(4, StringRef("1")), make_tuple(1, StringRef("8")), make_tuple(5, std::nullopt), make_tuple(9, std::nullopt)}; size_t iters = 0; for (auto tup : zip_longest(pi, e)) { EXPECT_EQ(tup, expected[iters]); iters += 1; } EXPECT_EQ(iters, expected.size()); } { // Check right range longer than left. const vector, optional>> expected{ make_tuple(StringRef("2"), 3), make_tuple(StringRef("7"), 1), make_tuple(StringRef("1"), 4), make_tuple(StringRef("8"), 1), make_tuple(std::nullopt, 5), make_tuple(std::nullopt, 9)}; size_t iters = 0; for (auto tup : zip_longest(e, pi)) { EXPECT_EQ(tup, expected[iters]); iters += 1; } EXPECT_EQ(iters, expected.size()); } } TEST(ZipIteratorTest, Mutability) { using namespace std; const SmallVector pi{3, 1, 4, 1, 5, 9}; char message[] = "hello zip\0"; for (auto tup : zip(pi, message, message)) { EXPECT_EQ(get<1>(tup), get<2>(tup)); get<2>(tup) = get<0>(tup) & 0x01 ? 'y' : 'n'; } // note the rvalue for (auto tup : zip(message, "yynyyyzip\0")) { EXPECT_EQ(get<0>(tup), get<1>(tup)); } } TEST(ZipIteratorTest, ZipFirstMutability) { using namespace std; vector pi{3, 1, 4, 1, 5, 9}; unsigned iters = 0; for (auto tup : zip_first(SmallVector{1, 1, 0, 1}, pi)) { get<1>(tup) = get<0>(tup); iters += 1; } EXPECT_EQ(iters, 4u); for (auto tup : zip_first(SmallVector{1, 1, 0, 1}, pi)) { EXPECT_EQ(get<0>(tup), get<1>(tup)); } } TEST(ZipIteratorTest, Filter) { using namespace std; vector pi{3, 1, 4, 1, 5, 9}; unsigned iters = 0; // pi is length 6, but the zip RHS is length 7. auto zipped = zip_first(pi, vector{1, 1, 0, 1, 1, 1, 0}); for (auto tup : make_filter_range( zipped, [](decltype(zipped)::value_type t) { return get<1>(t); })) { EXPECT_EQ(get<0>(tup) & 0x01, get<1>(tup)); get<0>(tup) += 1; iters += 1; } // Should have skipped pi[2]. EXPECT_EQ(iters, 5u); // Ensure that in-place mutation works. EXPECT_TRUE(all_of(pi, [](unsigned n) { return (n & 0x01) == 0; })); } TEST(ZipIteratorTest, Reverse) { using namespace std; vector ascending{0, 1, 2, 3, 4, 5}; auto zipped = zip_first(ascending, vector{0, 1, 0, 1, 0, 1}); unsigned last = 6; for (auto tup : reverse(zipped)) { // Check that this is in reverse. EXPECT_LT(get<0>(tup), last); last = get<0>(tup); EXPECT_EQ(get<0>(tup) & 0x01, get<1>(tup)); } auto odds = [](decltype(zipped)::value_type tup) { return get<1>(tup); }; last = 6; for (auto tup : make_filter_range(reverse(zipped), odds)) { EXPECT_LT(get<0>(tup), last); last = get<0>(tup); EXPECT_TRUE(get<0>(tup) & 0x01); get<0>(tup) += 1; } // Ensure that in-place mutation works. EXPECT_TRUE(all_of(ascending, [](unsigned n) { return (n & 0x01) == 0; })); } // Int iterator that keeps track of the number of its copies. struct CountingIntIterator : IntIterator { unsigned *cnt; CountingIntIterator(int *it, unsigned &counter) : IntIterator(it), cnt(&counter) {} CountingIntIterator(const CountingIntIterator &other) : IntIterator(other.I), cnt(other.cnt) { ++(*cnt); } CountingIntIterator &operator=(const CountingIntIterator &other) { this->I = other.I; this->cnt = other.cnt; ++(*cnt); return *this; } }; // Check that the iterators do not get copied with each `zippy` iterator // increment. TEST(ZipIteratorTest, IteratorCopies) { std::vector ints(1000, 42); unsigned total_copy_count = 0; CountingIntIterator begin(ints.data(), total_copy_count); CountingIntIterator end(ints.data() + ints.size(), total_copy_count); size_t iters = 0; auto zippy = zip_equal(ints, llvm::make_range(begin, end)); const unsigned creation_copy_count = total_copy_count; for (auto [a, b] : zippy) { EXPECT_EQ(a, b); ++iters; } EXPECT_EQ(iters, ints.size()); // We expect the number of copies to be much smaller than the number of loop // iterations. unsigned loop_copy_count = total_copy_count - creation_copy_count; EXPECT_LT(loop_copy_count, 10u); } TEST(RangeTest, Distance) { std::vector v1; std::vector v2{1, 2, 3}; EXPECT_EQ(std::distance(v1.begin(), v1.end()), size(v1)); EXPECT_EQ(std::distance(v2.begin(), v2.end()), size(v2)); } TEST(RangeSizeTest, CommonRangeTypes) { SmallVector v1 = {1, 2, 3}; EXPECT_EQ(range_size(v1), 3u); std::map m1 = {{1, 1}, {2, 2}}; EXPECT_EQ(range_size(m1), 2u); auto it_range = llvm::make_range(m1.begin(), m1.end()); EXPECT_EQ(range_size(it_range), 2u); static constexpr int c_arr[5] = {}; static_assert(range_size(c_arr) == 5u); static constexpr std::array cpp_arr = {}; static_assert(range_size(cpp_arr) == 6u); } struct FooWithMemberSize { size_t size() const { return 42; } auto begin() { return Data.begin(); } auto end() { return Data.end(); } std::set Data; }; TEST(RangeSizeTest, MemberSize) { // Make sure that member `.size()` is preferred over the free fuction and // `std::distance`. FooWithMemberSize container; EXPECT_EQ(range_size(container), 42u); } struct FooWithFreeSize { friend size_t size(const FooWithFreeSize &) { return 13; } auto begin() { return Data.begin(); } auto end() { return Data.end(); } std::set Data; }; TEST(RangeSizeTest, FreeSize) { // Make sure that `size(x)` is preferred over `std::distance`. FooWithFreeSize container; EXPECT_EQ(range_size(container), 13u); } struct FooWithDistance { auto begin() { return Data.begin(); } auto end() { return Data.end(); } std::set Data; }; TEST(RangeSizeTest, Distance) { // Make sure that we can fall back to `std::distance` even the iterator is not // random-access. FooWithDistance container; EXPECT_EQ(range_size(container), 0u); container.Data = {1, 2, 3, 4}; EXPECT_EQ(range_size(container), 4u); } } // anonymous namespace