diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index b5b5d5a3..a5655577 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -960,7 +960,10 @@ cc_library( hdrs = ["internal/iterator_traits.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [":config"], + deps = [ + ":config", + "//absl/meta:type_traits", + ], ) cc_test( @@ -971,6 +974,7 @@ cc_test( deps = [ ":config", ":iterator_traits_internal", + ":iterator_traits_test_helper", "@googletest//:gtest", "@googletest//:gtest_main", ], @@ -991,6 +995,14 @@ cc_library( ], ) +cc_library( + name = "iterator_traits_test_helper", + hdrs = ["internal/iterator_traits_test_helper.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [":config"], +) + cc_test( name = "tracing_internal_weak_test", srcs = ["internal/tracing_weak_test.cc"], diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 43c93168..03215aec 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -845,6 +845,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::config + absl::type_traits PUBLIC ) @@ -858,5 +859,19 @@ absl_cc_test( DEPS absl::config absl::iterator_traits_internal + absl::iterator_traits_test_helper_internal GTest::gtest_main ) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + iterator_traits_test_helper_internal + HDRS + "internal/iterator_traits_test_helper.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + PUBLIC +) diff --git a/absl/base/internal/iterator_traits.h b/absl/base/internal/iterator_traits.h index eb3e8f05..472c4368 100644 --- a/absl/base/internal/iterator_traits.h +++ b/absl/base/internal/iterator_traits.h @@ -25,14 +25,40 @@ #include #include "absl/base/config.h" +#include "absl/meta/type_traits.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { +template +struct IteratorCategory {}; + +template +struct IteratorCategory< + Iterator, + absl::void_t::iterator_category>> { + using type = typename std::iterator_traits::iterator_category; +}; + +template +struct IteratorConceptImpl : IteratorCategory {}; + +template +struct IteratorConceptImpl< + Iterator, + absl::void_t::iterator_concept>> { + using type = typename std::iterator_traits::iterator_concept; +}; + +// The newer `std::iterator_traits::iterator_concept` if available, +// else `std::iterator_traits::iterator_category`. +template +using IteratorConcept = typename IteratorConceptImpl::type; + template -using IsAtLeastIterator = std::is_convertible< - typename std::iterator_traits::iterator_category, IteratorTag>; +using IsAtLeastIterator = + std::is_convertible, IteratorTag>; template using IsAtLeastForwardIterator = diff --git a/absl/base/internal/iterator_traits_test.cc b/absl/base/internal/iterator_traits_test.cc index 0aa9f0bb..cb2044fe 100644 --- a/absl/base/internal/iterator_traits_test.cc +++ b/absl/base/internal/iterator_traits_test.cc @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/base/internal/iterator_traits_test_helper.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -67,6 +68,15 @@ TEST(IsAtLeastIteratorTest, IsAtLeastIterator) { std::istream_iterator>())); EXPECT_FALSE((IsAtLeastIterator>())); + + EXPECT_TRUE((IsAtLeastIterator>())); + EXPECT_TRUE((IsAtLeastIterator>())); + EXPECT_FALSE((IsAtLeastIterator>())); + EXPECT_FALSE((IsAtLeastIterator>())); } } // namespace diff --git a/absl/base/internal/iterator_traits_test_helper.h b/absl/base/internal/iterator_traits_test_helper.h new file mode 100644 index 00000000..707612d6 --- /dev/null +++ b/absl/base/internal/iterator_traits_test_helper.h @@ -0,0 +1,97 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_BASE_INTERNAL_ITERATOR_TRAITS_TEST_HELPER_H_ +#define ABSL_BASE_INTERNAL_ITERATOR_TRAITS_TEST_HELPER_H_ + +#include +#include + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +// This would be a forward_iterator in C++20, but it's only an input iterator +// before that, since it has a non-reference `reference`. +template +class Cpp20ForwardZipIterator { + using IteratorReference = typename std::iterator_traits::reference; + + public: + Cpp20ForwardZipIterator() = default; + explicit Cpp20ForwardZipIterator(Iterator left, Iterator right) + : left_(left), right_(right) {} + + Cpp20ForwardZipIterator& operator++() { + ++left_; + ++right_; + return *this; + } + + Cpp20ForwardZipIterator operator++(int) { + Cpp20ForwardZipIterator tmp(*this); + ++*this; + return *this; + } + + std::pair operator*() const { + return {*left_, *right_}; + } + + // C++17 input iterators require `operator->`, but this isn't possible to + // implement. C++20 dropped the requirement. + + friend bool operator==(const Cpp20ForwardZipIterator& lhs, + const Cpp20ForwardZipIterator& rhs) { + return lhs.left_ == rhs.left_ && lhs.right_ == rhs.right_; + } + + friend bool operator!=(const Cpp20ForwardZipIterator& lhs, + const Cpp20ForwardZipIterator& rhs) { + return !(lhs == rhs); + } + + private: + Iterator left_{}; + Iterator right_{}; +}; + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +template +struct std::iterator_traits< + absl::base_internal::Cpp20ForwardZipIterator> { + private: + using IteratorReference = typename std::iterator_traits::reference; + + public: + using iterator_category = std::input_iterator_tag; + using iterator_concept = std::forward_iterator_tag; + using value_type = std::pair; + using difference_type = + typename std::iterator_traits::difference_type; + using reference = value_type; + using pointer = void; +}; + +#if defined(__cpp_lib_concepts) +static_assert( + std::forward_iterator>); +#endif // defined(__cpp_lib_concepts) + +#endif // ABSL_BASE_INTERNAL_ITERATOR_TRAITS_TEST_HELPER_H_ diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 6b295211..0e30c8ed 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -86,6 +86,7 @@ cc_test( ":test_allocator", "//absl/base:config", "//absl/base:exception_testing", + "//absl/base:iterator_traits_test_helper", "//absl/hash:hash_testing", "//absl/memory", "@googletest//:gtest", @@ -173,6 +174,7 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/base:exception_testing", + "//absl/base:iterator_traits_test_helper", "//absl/hash:hash_testing", "//absl/log:check", "//absl/memory", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index c3f57493..fedce24c 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -132,6 +132,7 @@ absl_cc_library( absl::core_headers absl::dynamic_annotations absl::iterator_traits_internal + absl::iterator_traits_test_helper_internal absl::throw_delegate absl::memory PUBLIC diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc index 331d7026..6175a7cc 100644 --- a/absl/container/fixed_array_test.cc +++ b/absl/container/fixed_array_test.cc @@ -17,18 +17,21 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/base/internal/exception_testing.h" +#include "absl/base/internal/iterator_traits_test_helper.h" #include "absl/base/options.h" #include "absl/container/internal/test_allocator.h" #include "absl/hash/hash_testing.h" @@ -409,6 +412,20 @@ TEST(IteratorConstructorTest, FromBidirectionalIteratorRange) { EXPECT_THAT(fixed, testing::ElementsAreArray(kInput)); } +TEST(IteratorConstructorTest, FromCpp20ForwardIteratorRange) { + std::forward_list const kUnzippedInput = {2, 3, 5, 7, 11, 13, 17}; + absl::base_internal::Cpp20ForwardZipIterator< + std::forward_list::const_iterator> const + begin(std::begin(kUnzippedInput), std::begin(kUnzippedInput)); + absl::base_internal:: + Cpp20ForwardZipIterator::const_iterator> const end( + std::end(kUnzippedInput), std::end(kUnzippedInput)); + + std::forward_list> const items(begin, end); + absl::FixedArray> const fixed(begin, end); + EXPECT_THAT(fixed, testing::ElementsAreArray(items)); +} + TEST(InitListConstructorTest, InitListConstruction) { absl::FixedArray fixed = {1, 2, 3}; EXPECT_THAT(fixed, testing::ElementsAreArray({1, 2, 3})); diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index 54cd3105..ff0e77b5 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -31,6 +31,7 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/internal/exception_testing.h" +#include "absl/base/internal/iterator_traits_test_helper.h" #include "absl/base/macros.h" #include "absl/base/options.h" #include "absl/container/internal/test_allocator.h" @@ -649,6 +650,33 @@ TEST(IntVec, Insert) { } } +TEST(IntPairVec, Insert) { + for (size_t len = 0; len < 20; len++) { + for (ptrdiff_t pos = 0; pos <= static_cast(len); pos++) { + // Iterator range (C++20 forward iterator) + const std::forward_list unzipped_input = {9999, 8888, 7777}; + const absl::base_internal::Cpp20ForwardZipIterator< + std::forward_list::const_iterator> + begin(unzipped_input.cbegin(), unzipped_input.cbegin()); + const absl::base_internal::Cpp20ForwardZipIterator< + std::forward_list::const_iterator> + end(unzipped_input.cend(), unzipped_input.cend()); + + std::vector> std_v; + absl::InlinedVector, 8> v; + for (size_t i = 0; i < len; ++i) { + std_v.emplace_back(i, i); + v.emplace_back(i, i); + } + + std_v.insert(std_v.begin() + pos, begin, end); + auto it = v.insert(v.cbegin() + pos, begin, end); + EXPECT_THAT(v, ElementsAreArray(std_v)); + EXPECT_EQ(it, v.cbegin() + pos); + } + } +} + TEST(RefCountedVec, InsertConstructorDestructor) { // Make sure the proper construction/destruction happen during insert // operations.