diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 9145d8b..cd633a1 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -95,6 +95,8 @@ "container/internal/raw_hash_set.h" "container/internal/raw_hash_set_resize_impl.h" "container/internal/tracked.h" + "container/linked_hash_map.h" + "container/linked_hash_set.h" "container/node_hash_map.h" "container/node_hash_set.h" "crc/crc32c.cc" @@ -177,6 +179,7 @@ "log/internal/conditions.cc" "log/internal/conditions.h" "log/internal/config.h" + "log/internal/container.h" "log/internal/fnmatch.h" "log/internal/fnmatch.cc" "log/internal/globals.cc" @@ -213,6 +216,7 @@ "log/vlog_is_on.h" "memory/memory.h" "meta/type_traits.h" + "meta/internal/requires.h" "numeric/bits.h" "numeric/int128.cc" "numeric/int128.h" @@ -327,6 +331,9 @@ "strings/internal/cordz_update_tracker.h" "strings/internal/damerau_levenshtein_distance.h" "strings/internal/damerau_levenshtein_distance.cc" + "strings/internal/generic_printer.cc" + "strings/internal/generic_printer.h" + "strings/internal/generic_printer_internal.h" "strings/internal/stl_type_traits.h" "strings/internal/string_constant.h" "strings/internal/stringify_sink.h" @@ -345,8 +352,6 @@ "strings/str_replace.h" "strings/str_split.cc" "strings/str_split.h" - "strings/string_view.cc" - "strings/string_view.h" "strings/strip.h" "strings/substitute.cc" "strings/substitute.h" @@ -446,6 +451,7 @@ "types/variant.h" "utility/utility.h" "debugging/leak_check.cc" + "strings/string_view.h" ) if(MSVC)
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index ade1a01..1486722 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel
@@ -964,6 +964,9 @@ hdrs = ["internal/iterator_traits.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], deps = [ ":config", "//absl/meta:type_traits",
diff --git a/absl/base/config.h b/absl/base/config.h index e4518ca..39d60e3 100644 --- a/absl/base/config.h +++ b/absl/base/config.h
@@ -526,20 +526,11 @@ #define ABSL_USES_STD_ANY 1 #define ABSL_HAVE_STD_OPTIONAL 1 #define ABSL_USES_STD_OPTIONAL 1 +#define ABSL_HAVE_STD_STRING_VIEW 1 +#define ABSL_USES_STD_STRING_VIEW 1 #define ABSL_HAVE_STD_VARIANT 1 #define ABSL_USES_STD_VARIANT 1 -// ABSL_HAVE_STD_STRING_VIEW -// -// Deprecated: always defined to 1. -// std::string_view was added in C++17, which means all versions of C++ -// supported by Abseil have it. -#ifdef ABSL_HAVE_STD_STRING_VIEW -#error "ABSL_HAVE_STD_STRING_VIEW cannot be directly set." -#else -#define ABSL_HAVE_STD_STRING_VIEW 1 -#endif - // ABSL_HAVE_STD_ORDERING // // Checks whether C++20 std::{partial,weak,strong}_ordering are available. @@ -556,20 +547,6 @@ #define ABSL_HAVE_STD_ORDERING 1 #endif -// ABSL_USES_STD_STRING_VIEW -// -// Indicates whether absl::string_view is an alias for std::string_view. -#if !defined(ABSL_OPTION_USE_STD_STRING_VIEW) -#error options.h is misconfigured. -#elif ABSL_OPTION_USE_STD_STRING_VIEW == 0 -#undef ABSL_USES_STD_STRING_VIEW -#elif ABSL_OPTION_USE_STD_STRING_VIEW == 1 || \ - ABSL_OPTION_USE_STD_STRING_VIEW == 2 -#define ABSL_USES_STD_STRING_VIEW 1 -#else -#error options.h is misconfigured. -#endif - // ABSL_USES_STD_ORDERING // // Indicates whether absl::{partial,weak,strong}_ordering are aliases for the
diff --git a/absl/base/internal/iterator_traits.h b/absl/base/internal/iterator_traits.h index 472c436..5fa4df8 100644 --- a/absl/base/internal/iterator_traits.h +++ b/absl/base/internal/iterator_traits.h
@@ -61,6 +61,10 @@ std::is_convertible<IteratorConcept<Iterator>, IteratorTag>; template <typename Iterator> +using IsAtLeastInputIterator = + IsAtLeastIterator<std::input_iterator_tag, Iterator>; + +template <typename Iterator> using IsAtLeastForwardIterator = IsAtLeastIterator<std::forward_iterator_tag, Iterator>;
diff --git a/absl/base/options.h b/absl/base/options.h index 71bafb3..6f48e75 100644 --- a/absl/base/options.h +++ b/absl/base/options.h
@@ -73,32 +73,6 @@ // Type Compatibility Options // ----------------------------------------------------------------------------- -// ABSL_OPTION_USE_STD_STRING_VIEW -// -// This option controls whether absl::string_view is implemented as an alias to -// std::string_view, or as an independent implementation. -// -// A value of 0 means to use Abseil's implementation. This requires only C++11 -// support, and is expected to work on every toolchain we support. -// -// A value of 1 means to use an alias to std::string_view. This requires that -// all code using Abseil is built in C++17 mode or later. -// -// A value of 2 means to detect the C++ version being used to compile Abseil, -// and use an alias only if a working std::string_view is available. This -// option is useful when you are building your program from source. It should -// not be used otherwise -- for example, if you are distributing Abseil in a -// binary package manager -- since in mode 2, absl::string_view will name a -// different type, with a different mangled name and binary layout, depending on -// the compiler flags passed by the end user. For more info, see -// https://abseil.io/about/design/dropin-types. -// -// User code should not inspect this macro. To check in the preprocessor if -// absl::string_view is a typedef of std::string_view, use the feature macro -// ABSL_USES_STD_STRING_VIEW. - -#define ABSL_OPTION_USE_STD_STRING_VIEW 2 - // ABSL_OPTION_USE_STD_ORDERING // // This option controls whether absl::{partial,weak,strong}_ordering are
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index ab6533f..15fb0a5 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel
@@ -308,7 +308,7 @@ cc_test( name = "flat_hash_set_test", srcs = ["flat_hash_set_test.cc"], - copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"], + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_loonix"], deps = [ @@ -388,7 +388,7 @@ cc_test( name = "node_hash_set_test", srcs = ["node_hash_set_test.cc"], - copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"], + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_loonix"], deps = [ @@ -496,6 +496,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash_policy_testing", + "//absl/base:config", "//absl/base:no_destructor", "//absl/memory", "//absl/meta:type_traits", @@ -942,9 +943,13 @@ hdrs = ["internal/unordered_map_constructor_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", + "//absl/base:config", "@googletest//:gtest", ], ) @@ -955,6 +960,9 @@ hdrs = ["internal/unordered_map_lookup_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -968,6 +976,9 @@ hdrs = ["internal/unordered_map_modifiers_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -981,9 +992,13 @@ hdrs = ["internal/unordered_set_constructor_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", + "//absl/base:config", "//absl/meta:type_traits", "@googletest//:gtest", ], @@ -995,6 +1010,9 @@ hdrs = ["internal/unordered_set_members_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ "//absl/meta:type_traits", "@googletest//:gtest", @@ -1007,6 +1025,9 @@ hdrs = ["internal/unordered_map_members_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ "//absl/meta:type_traits", "@googletest//:gtest", @@ -1019,6 +1040,9 @@ hdrs = ["internal/unordered_set_lookup_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -1032,6 +1056,9 @@ hdrs = ["internal/unordered_set_modifiers_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -1200,3 +1227,125 @@ "@google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "heterogeneous_lookup_testing", + testonly = True, + hdrs = ["internal/heterogeneous_lookup_testing.h"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/container:test_instance_tracker", + "@googletest//:gtest", + ], +) + +cc_library( + name = "linked_hash_set", + hdrs = ["linked_hash_set.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":common", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/container:flat_hash_set", + ], +) + +cc_test( + name = "linked_hash_set_test", + srcs = ["linked_hash_set_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":heterogeneous_lookup_testing", + ":linked_hash_set", + "//absl/base:config", + "//absl/container:hash_generator_testing", + "//absl/container:hash_policy_testing", + "//absl/container:test_instance_tracker", + "//absl/container:unordered_set_constructor_test", + "//absl/container:unordered_set_lookup_test", + "//absl/container:unordered_set_members_test", + "//absl/container:unordered_set_modifiers_test", + "//absl/strings:string_view", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_binary( + name = "linked_hash_set_benchmark", + testonly = True, + srcs = ["linked_hash_set_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":linked_hash_set", + "//absl/functional:function_ref", + "//absl/strings:str_format", + "//absl/strings:string_view", + "@google_benchmark//:benchmark_main", + ], +) + +cc_library( + name = "linked_hash_map", + hdrs = ["linked_hash_map.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":common", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:throw_delegate", + "//absl/container:flat_hash_set", + ], +) + +cc_test( + name = "linked_hash_map_test", + srcs = ["linked_hash_map_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":heterogeneous_lookup_testing", + ":linked_hash_map", + "//absl/base:config", + "//absl/base:exception_testing", + "//absl/container:hash_generator_testing", + "//absl/container:hash_policy_testing", + "//absl/container:test_instance_tracker", + "//absl/container:unordered_map_constructor_test", + "//absl/container:unordered_map_lookup_test", + "//absl/container:unordered_map_members_test", + "//absl/container:unordered_map_modifiers_test", + "//absl/strings:string_view", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_binary( + name = "linked_hash_map_benchmark", + testonly = True, + srcs = ["linked_hash_map_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":linked_hash_map", + "//absl/functional:function_ref", + "//absl/strings:str_format", + "//absl/strings:string_view", + "@google_benchmark//:benchmark_main", + ], +)
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index adce9a9..f1ea9e2 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt
@@ -349,7 +349,6 @@ "flat_hash_set_test.cc" COPTS ${ABSL_TEST_COPTS} - "-DUNORDERED_SET_CXX17" DEPS absl::check absl::config @@ -432,7 +431,6 @@ "node_hash_set_test.cc" COPTS ${ABSL_TEST_COPTS} - "-DUNORDERED_SET_CXX17" DEPS absl::hash_generator_testing absl::hash_policy_testing @@ -541,6 +539,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_policy_testing absl::memory absl::meta @@ -950,6 +949,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_generator_testing absl::hash_policy_testing GTest::gmock @@ -1009,6 +1009,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_generator_testing absl::hash_policy_testing GTest::gmock @@ -1104,3 +1105,100 @@ absl::hashtablez_sampler GTest::gmock_main ) + +absl_cc_library( + NAME + heterogeneous_lookup_testing + HDRS + "internal/heterogeneous_lookup_testing.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::test_instance_tracker + GTest::gmock +) + +absl_cc_library( + NAME + linked_hash_set + HDRS + "linked_hash_set.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::container_common + absl::config + absl::core_headers + absl::flat_hash_set +) + +absl_cc_test( + NAME + linked_hash_set_test + SRCS + "linked_hash_set_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::linked_hash_set + absl::config + absl::heterogeneous_lookup_testing + absl::hash_generator_testing + absl::hash_policy_testing + absl::string_view + absl::test_instance_tracker + absl::unordered_set_constructor_test + absl::unordered_set_lookup_test + absl::unordered_set_members_test + absl::unordered_set_modifiers_test + GTest::gmock_main +) + +absl_cc_library( + NAME + linked_hash_map + HDRS + "linked_hash_map.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::container_common + absl::config + absl::core_headers + absl::flat_hash_set + absl::throw_delegate +) + +absl_cc_test( + NAME + linked_hash_map_test + SRCS + "linked_hash_map_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::linked_hash_map + absl::config + absl::exception_testing + absl::heterogeneous_lookup_testing + absl::hash_generator_testing + absl::hash_policy_testing + absl::string_view + absl::test_instance_tracker + absl::unordered_set_constructor_test + absl::unordered_set_lookup_test + absl::unordered_set_members_test + absl::unordered_set_modifiers_test + GTest::gmock_main +)
diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index 131f622..0746f72 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h
@@ -57,18 +57,44 @@ #ifndef ABSL_CONTAINER_BTREE_MAP_H_ #define ABSL_CONTAINER_BTREE_MAP_H_ +#include <functional> +#include <memory> +#include <type_traits> +#include <utility> + #include "absl/base/attributes.h" #include "absl/container/internal/btree.h" // IWYU pragma: export #include "absl/container/internal/btree_container.h" // IWYU pragma: export +#include "absl/container/internal/common.h" +#include "absl/container/internal/container_memory.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { +template <typename Key, typename Data, typename... Params> +struct map_params_impl; + +template <typename Key, typename Data> +struct btree_map_defaults { + using Compare = std::less<Key>; + using Alloc = std::allocator<std::pair<const Key, Data>>; + using TargetNodeSize = std::integral_constant<int, 256>; + using IsMulti = std::false_type; +}; + template <typename Key, typename Data, typename Compare, typename Alloc, int TargetNodeSize, bool IsMulti> -struct map_params; +using map_params = typename ApplyWithoutDefaultSuffix< + map_params_impl, + TypeList<void, void, typename btree_map_defaults<Key, Data>::Compare, + typename btree_map_defaults<Key, Data>::Alloc, + typename btree_map_defaults<Key, Data>::TargetNodeSize, + typename btree_map_defaults<Key, Data>::IsMulti>, + TypeList<Key, Data, Compare, Alloc, + std::integral_constant<int, TargetNodeSize>, + std::integral_constant<bool, IsMulti>>>::type; } // namespace container_internal @@ -855,11 +881,20 @@ // A parameters structure for holding the type parameters for a btree_map. // Compare and Alloc should be nothrow copy-constructible. -template <typename Key, typename Data, typename Compare, typename Alloc, - int TargetNodeSize, bool IsMulti> -struct map_params : common_params<Key, Compare, Alloc, TargetNodeSize, IsMulti, - /*IsMap=*/true, map_slot_policy<Key, Data>> { - using super_type = typename map_params::common_params; +template <typename Key, typename Data, typename... Params> +struct map_params_impl + : common_params< + Key, + GetFromListOr<typename btree_map_defaults<Key, Data>::Compare, 0, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::Alloc, 1, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::TargetNodeSize, + 2, Params...>::value, + GetFromListOr<typename btree_map_defaults<Key, Data>::IsMulti, 3, + Params...>::value, + /*IsMap=*/true, map_slot_policy<Key, Data>> { + using super_type = typename map_params_impl::common_params; using mapped_type = Data; // This type allows us to move keys when it is safe to do so. It is safe // for maps in which value_type and mutable_value_type are layout compatible. @@ -868,6 +903,21 @@ using value_type = typename super_type::value_type; using init_type = typename super_type::init_type; + static_assert( + std::is_same_v< + map_params< + Key, Data, + GetFromListOr<typename btree_map_defaults<Key, Data>::Compare, 0, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::Alloc, 1, + Params...>, + GetFromListOr< + typename btree_map_defaults<Key, Data>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_map_defaults<Key, Data>::IsMulti, 3, + Params...>::value>, + map_params_impl>); + template <typename V> static auto key(const V &value ABSL_ATTRIBUTE_LIFETIME_BOUND) -> decltype((value.first)) {
diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index 44a39cf..991cb89 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h
@@ -56,9 +56,15 @@ #ifndef ABSL_CONTAINER_BTREE_SET_H_ #define ABSL_CONTAINER_BTREE_SET_H_ +#include <functional> +#include <memory> +#include <type_traits> +#include <utility> + #include "absl/base/attributes.h" #include "absl/container/internal/btree.h" // IWYU pragma: export #include "absl/container/internal/btree_container.h" // IWYU pragma: export +#include "absl/container/internal/common.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -68,9 +74,27 @@ template <typename Key> struct set_slot_policy; +template <typename Key, typename...Params> +struct set_params_impl; + +template <typename Key> +struct btree_set_defaults { + using Compare = std::less<Key>; + using Alloc = std::allocator<Key>; + using TargetNodeSize = std::integral_constant<int, 256>; + using IsMulti = std::false_type; +}; + template <typename Key, typename Compare, typename Alloc, int TargetNodeSize, bool IsMulti> -struct set_params; +using set_params = typename ApplyWithoutDefaultSuffix< + set_params_impl, + TypeList<void, typename btree_set_defaults<Key>::Compare, + typename btree_set_defaults<Key>::Alloc, + typename btree_set_defaults<Key>::TargetNodeSize, + typename btree_set_defaults<Key>::IsMulti>, + TypeList<Key, Compare, Alloc, std::integral_constant<int, TargetNodeSize>, + std::integral_constant<bool, IsMulti>>>::type; } // namespace container_internal @@ -803,12 +827,34 @@ // A parameters structure for holding the type parameters for a btree_set. // Compare and Alloc should be nothrow copy-constructible. -template <typename Key, typename Compare, typename Alloc, int TargetNodeSize, - bool IsMulti> -struct set_params : common_params<Key, Compare, Alloc, TargetNodeSize, IsMulti, - /*IsMap=*/false, set_slot_policy<Key>> { +template <typename Key, typename... Params> +struct set_params_impl + : common_params< + Key, + GetFromListOr<typename btree_set_defaults<Key>::Compare, 0, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::Alloc, 1, Params...>, + GetFromListOr<typename btree_set_defaults<Key>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_set_defaults<Key>::IsMulti, 3, + Params...>::value, + /*IsMap=*/false, set_slot_policy<Key>> { using value_type = Key; - using slot_type = typename set_params::common_params::slot_type; + using slot_type = typename set_params_impl::common_params::slot_type; + + static_assert( + std::is_same_v< + set_params< + Key, + GetFromListOr<typename btree_set_defaults<Key>::Compare, 0, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::Alloc, 1, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_set_defaults<Key>::IsMulti, 3, + Params...>::value>, + set_params_impl>); template <typename V> static const V &key(const V &value) {
diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index a6438f5..0cf3ed3 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc
@@ -3544,6 +3544,109 @@ TestBasicFunctionality(set_type()); } +// Alias whose only purpose is to have the same length as set_params for better +// alignment in the test below. +template <typename... T> +using set_p_impl = set_params_impl<T...>; + +TEST(BtreeTest, SetParamsStripsDefaults) { + using K = int; + using DA = btree_set_defaults<int>::Compare; + using DB = btree_set_defaults<int>::Alloc; + using DC = btree_set_defaults<int>::TargetNodeSize; + using DD = btree_set_defaults<int>::IsMulti; + + using XA = std::greater<int>; + struct XB {}; + using XC = std::integral_constant<int, 100>; + using XD = std::true_type; + + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, XD{}>, + set_p_impl<K, XA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, DD{}>, + set_p_impl<K, XA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, XD{}>, + set_p_impl<K, XA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, DD{}>, + set_p_impl<K, XA, XB>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, XD{}>, + set_p_impl<K, XA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, DD{}>, + set_p_impl<K, XA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, XD{}>, + set_p_impl<K, XA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, DD{}>, + set_p_impl<K, XA>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, XD{}>, + set_p_impl<K, DA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, DD{}>, + set_p_impl<K, DA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, XD{}>, + set_p_impl<K, DA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, DD{}>, + set_p_impl<K, DA, XB>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, XD{}>, + set_p_impl<K, DA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, DD{}>, + set_p_impl<K, DA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, XD{}>, + set_p_impl<K, DA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, DD{}>, + set_p_impl<K>>)); +} + +// Alias whose only purpose is to have the same length as map_params for better +// alignment in the test below. +template <typename... T> +using map_p_impl = map_params_impl<T...>; + +TEST(BtreeTest, MapParamsStripsDefaults) { + using K = int; + using V = double; + using DA = btree_map_defaults<int, double>::Compare; + using DB = btree_map_defaults<int, double>::Alloc; + using DC = btree_map_defaults<int, double>::TargetNodeSize; + using DD = btree_map_defaults<int, double>::IsMulti; + + using XA = std::greater<int>; + struct XB {}; + using XC = std::integral_constant<int, 100>; + using XD = std::true_type; + + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, XD{}>, + map_p_impl<K, V, XA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, DD{}>, + map_p_impl<K, V, XA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, XD{}>, + map_p_impl<K, V, XA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, DD{}>, + map_p_impl<K, V, XA, XB>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, XD{}>, + map_p_impl<K, V, XA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, DD{}>, + map_p_impl<K, V, XA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, XD{}>, + map_p_impl<K, V, XA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, DD{}>, + map_p_impl<K, V, XA>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, XD{}>, + map_p_impl<K, V, DA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, DD{}>, + map_p_impl<K, V, DA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, XD{}>, + map_p_impl<K, V, DA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, DD{}>, + map_p_impl<K, V, DA, XB>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, XD{}>, + map_p_impl<K, V, DA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, DD{}>, + map_p_impl<K, V, DA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, XD{}>, + map_p_impl<K, V, DA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, DD{}>, + map_p_impl<K, V>>)); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END
diff --git a/absl/container/flat_hash_map.h b/absl/container/flat_hash_map.h index 7d6fd21..7ce3353 100644 --- a/absl/container/flat_hash_map.h +++ b/absl/container/flat_hash_map.h
@@ -127,13 +127,17 @@ // if (result != ducks.end()) { // std::cout << "Result: " << result->second << std::endl; // } -template <class K, class V, class Hash = DefaultHashContainerHash<K>, - class Eq = DefaultHashContainerEq<K>, - class Allocator = std::allocator<std::pair<const K, V>>> +template < + class K, class V, + class Hash = + typename container_internal::FlatHashMapPolicy<K, V>::DefaultHash, + class Eq = typename container_internal::FlatHashMapPolicy<K, V>::DefaultEq, + class Allocator = + typename container_internal::FlatHashMapPolicy<K, V>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER flat_hash_map - : public absl::container_internal::raw_hash_map< + : public absl::container_internal::InstantiateRawHashMap< absl::container_internal::FlatHashMapPolicy<K, V>, Hash, Eq, - Allocator> { + Allocator>::type { using Base = typename flat_hash_map::raw_hash_map; public: @@ -637,6 +641,10 @@ using mapped_type = V; using init_type = std::pair</*non const*/ key_type, mapped_type>; + using DefaultHash = DefaultHashContainerHash<K>; + using DefaultEq = DefaultHashContainerEq<K>; + using DefaultAlloc = std::allocator<std::pair<const K, V>>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { slot_policy::construct(alloc, slot, std::forward<Args>(args)...);
diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index e1d9382..73f28c7 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc
@@ -39,8 +39,7 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; + using ::testing::_; using ::testing::IsEmpty; using ::testing::Pair;
diff --git a/absl/container/flat_hash_set.h b/absl/container/flat_hash_set.h index 95e4533..a469fa0 100644 --- a/absl/container/flat_hash_set.h +++ b/absl/container/flat_hash_set.h
@@ -124,12 +124,16 @@ // if (ducks.contains("dewey")) { // std::cout << "We found dewey!" << std::endl; // } -template <class T, class Hash = DefaultHashContainerHash<T>, - class Eq = DefaultHashContainerEq<T>, - class Allocator = std::allocator<T>> +template < + class T, + class Hash = typename container_internal::FlatHashSetPolicy<T>::DefaultHash, + class Eq = typename container_internal::FlatHashSetPolicy<T>::DefaultEq, + class Allocator = + typename container_internal::FlatHashSetPolicy<T>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER flat_hash_set - : public absl::container_internal::raw_hash_set< - absl::container_internal::FlatHashSetPolicy<T>, Hash, Eq, Allocator> { + : public absl::container_internal::InstantiateRawHashSet< + absl::container_internal::FlatHashSetPolicy<T>, Hash, Eq, + Allocator>::type { using Base = typename flat_hash_set::raw_hash_set; public: @@ -535,6 +539,10 @@ using init_type = T; using constant_iterators = std::true_type; + using DefaultHash = DefaultHashContainerHash<T>; + using DefaultEq = DefaultHashContainerEq<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { absl::allocator_traits<Allocator>::construct(*alloc, slot,
diff --git a/absl/container/flat_hash_set_test.cc b/absl/container/flat_hash_set_test.cc index ca069b4..9b6a6d1 100644 --- a/absl/container/flat_hash_set_test.cc +++ b/absl/container/flat_hash_set_test.cc
@@ -43,8 +43,6 @@ namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; using ::testing::IsEmpty; using ::testing::Pointee; using ::testing::UnorderedElementsAre;
diff --git a/absl/container/internal/common.h b/absl/container/internal/common.h index 5ef6c56..3e263a3 100644 --- a/absl/container/internal/common.h +++ b/absl/container/internal/common.h
@@ -15,7 +15,10 @@ #ifndef ABSL_CONTAINER_INTERNAL_COMMON_H_ #define ABSL_CONTAINER_INTERNAL_COMMON_H_ +#include <algorithm> #include <cassert> +#include <cstddef> +#include <tuple> #include <type_traits> #include "absl/meta/type_traits.h" @@ -243,6 +246,54 @@ NodeType node; }; +// Utilities to strip redundant template parameters from the underlying +// implementation types. +// We use a variadic pack (ie Params...) to specify required prefix of types for +// non-default types, and then we use GetFromListOr to select the provided types +// or the default ones otherwise. +// +// These default types do not contribute information for debugging and just +// bloat the binary. +// Removing the redundant tail types reduces mangled names and stringified +// function names like __PRETTY_FUNCTION__. +// +// How to use: +// 1. Define a template with `typename ...Params` +// 2. Instantiate it via `ApplyWithoutDefaultSuffix<>` to only pass the minimal +// set of types. +// 3. Inside the template use `GetFromListOr` to map back from the existing +// `Params` list to the actual types, filling the gaps when types are +// missing. + +template <typename Or, size_t N, typename... Params> +using GetFromListOr = std::tuple_element_t<(std::min)(N, sizeof...(Params)), + std::tuple<Params..., Or>>; + +template <typename... T> +struct TypeList { + template <template <typename...> class Template> + using Apply = Template<T...>; +}; + +// Evaluate to `Template<TPrefix...>` where the last type in the list (if any) +// is different from the corresponding one in the default list. +// Eg +// ApplyWithoutDefaultSuffix<Template, TypeList<a, b, c>, TypeList<a, X, c>> +// evaluates to +// Template<a, X> +template <template <typename...> class Template, typename D, typename T, + typename L = TypeList<>, typename = void> +struct ApplyWithoutDefaultSuffix { + using type = typename L::template Apply<Template>; +}; +template <template <typename...> class Template, typename D, typename... Ds, + typename T, typename... Ts, typename... L> +struct ApplyWithoutDefaultSuffix< + Template, TypeList<D, Ds...>, TypeList<T, Ts...>, TypeList<L...>, + std::enable_if_t<!std::is_same_v<TypeList<D, Ds...>, TypeList<T, Ts...>>>> + : ApplyWithoutDefaultSuffix<Template, TypeList<Ds...>, TypeList<Ts...>, + TypeList<L..., T>> {}; + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hash_generator_testing.cc b/absl/container/internal/hash_generator_testing.cc index be20e21..4ae58da 100644 --- a/absl/container/internal/hash_generator_testing.cc +++ b/absl/container/internal/hash_generator_testing.cc
@@ -26,7 +26,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { -namespace hash_internal { std::string Generator<std::string>::operator()() const { absl::InsecureBitGen gen; @@ -50,7 +49,6 @@ return res; } -} // namespace hash_internal } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hash_generator_testing.h b/absl/container/internal/hash_generator_testing.h index 14c878e..4c5d87b 100644 --- a/absl/container/internal/hash_generator_testing.h +++ b/absl/container/internal/hash_generator_testing.h
@@ -31,6 +31,7 @@ #include <utility> #include <vector> +#include "absl/base/config.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/memory/memory.h" #include "absl/meta/type_traits.h" @@ -40,7 +41,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { -namespace hash_internal { namespace generator_internal { template <class Container, class = void> @@ -165,7 +165,6 @@ } }; -} // namespace hash_internal } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hash_policy_testing.h b/absl/container/internal/hash_policy_testing.h index e9f5757..86ea96a 100644 --- a/absl/container/internal/hash_policy_testing.h +++ b/absl/container/internal/hash_policy_testing.h
@@ -170,18 +170,4 @@ ABSL_NAMESPACE_END } // namespace absl -// ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS is false for glibcxx versions -// where the unordered containers are missing certain constructors that -// take allocator arguments. This test is defined ad-hoc for the platforms -// we care about (notably Crosstool 17) because libstdcxx's useless -// versioning scheme precludes a more principled solution. -// From GCC-4.9 Changelog: (src: https://gcc.gnu.org/gcc-4.9/changes.html) -// "the unordered associative containers in <unordered_map> and <unordered_set> -// meet the allocator-aware container requirements;" -#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140425 -#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 0 -#else -#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 1 -#endif - #endif // ABSL_CONTAINER_INTERNAL_HASH_POLICY_TESTING_H_
diff --git a/absl/container/internal/heterogeneous_lookup_testing.h b/absl/container/internal/heterogeneous_lookup_testing.h new file mode 100644 index 0000000..b8cfae3 --- /dev/null +++ b/absl/container/internal/heterogeneous_lookup_testing.h
@@ -0,0 +1,80 @@ +// 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_CONTAINER_INTERNAL_HETEROGENEOUS_LOOKUP_TESTING_H_ +#define ABSL_CONTAINER_INTERNAL_HETEROGENEOUS_LOOKUP_TESTING_H_ + +#include <cstddef> +#include <ostream> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/container/internal/test_instance_tracker.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { + +// An expensive class that is convertible to CheapType to demonstrate +// heterogeneous lookups. +class ExpensiveType : public absl::test_internal::CopyableMovableInstance { + public: + explicit ExpensiveType(int value) + : absl::test_internal::CopyableMovableInstance(value) {} + + friend std::ostream& operator<<(std::ostream& os, const ExpensiveType& a) { + return os << a.value(); + } +}; + +class CheapType { + public: + explicit CheapType(const int value) : value_(value) {} + + explicit operator ExpensiveType() const { return ExpensiveType(value_); } + + int value() const { return value_; } + + private: + int value_; +}; + +struct HeterogeneousHash { + using is_transparent = void; + size_t operator()(const ExpensiveType& a) const { return a.value(); } + size_t operator()(const CheapType& a) const { return a.value(); } +}; + +struct HeterogeneousEqual { + using is_transparent = void; + bool operator()(const ExpensiveType& a, const ExpensiveType& b) const { + return a.value() == b.value(); + } + bool operator()(const ExpensiveType& a, const CheapType& b) const { + return a.value() == b.value(); + } + bool operator()(const CheapType& a, const ExpensiveType& b) const { + return a.value() == b.value(); + } +}; + +MATCHER_P(HasExpensiveValue, n, "") { + return ::testing::ExplainMatchResult(n, arg.value(), result_listener); +} + +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_INTERNAL_HETEROGENEOUS_LOOKUP_TESTING_H_
diff --git a/absl/container/internal/raw_hash_map.h b/absl/container/internal/raw_hash_map.h index 85adf87..9338f16 100644 --- a/absl/container/internal/raw_hash_map.h +++ b/absl/container/internal/raw_hash_map.h
@@ -31,8 +31,20 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { -template <class Policy, class Hash, class Eq, class Alloc> -class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { +template <class Policy, class... Params> +class raw_hash_map; + +template <typename Policy, typename Hash, typename Eq, typename Alloc> +struct InstantiateRawHashMap { + using type = typename ApplyWithoutDefaultSuffix< + raw_hash_map, + TypeList<int, typename Policy::DefaultHash, typename Policy::DefaultEq, + typename Policy::DefaultAlloc>, + TypeList<Policy, Hash, Eq, Alloc>>::type; +}; + +template <class Policy, class... Params> +class raw_hash_map : public raw_hash_set<Policy, Params...> { // P is Policy. It's passed as a template argument to support maps that have // incomplete types as values, as in unordered_map<K, IncompleteType>. // MappedReference<> may be a non-reference type. @@ -45,6 +57,10 @@ using MappedConstReference = decltype(P::value( std::addressof(std::declval<typename raw_hash_map::const_reference>()))); + using Hash = typename raw_hash_map::raw_hash_set::hasher; + using Eq = typename raw_hash_map::raw_hash_set::key_equal; + using Alloc = typename raw_hash_map::raw_hash_set::allocator_type; + template <class K> using key_arg = typename KeyArg<IsTransparent<Eq>::value && IsTransparent<Hash>::value>:: @@ -327,6 +343,13 @@ } private: + static_assert( + std::is_same_v< + typename InstantiateRawHashMap<Policy, Hash, Eq, Alloc>::type, + raw_hash_map>, + "Redundant template parameters were passed. Use InstantiateRawHashMap<> " + "instead"); + template <class K, class V> std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 1d209e0..d307e2d 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -1138,7 +1138,7 @@ HeapOrSoo heap_or_soo_; }; -template <class Policy, class Hash, class Eq, class Alloc> +template <class Policy, class... Params> class raw_hash_set; // Returns the next valid capacity after `n`. @@ -1760,32 +1760,62 @@ Group::NonIterableBitMaskType mask_empty, FindInfo target_group, absl::FunctionRef<size_t(size_t)> recompute_hash); +template <typename Policy, typename Hash, typename Eq, typename Alloc> +struct InstantiateRawHashSet { + using type = typename ApplyWithoutDefaultSuffix< + raw_hash_set, + TypeList<void, typename Policy::DefaultHash, typename Policy::DefaultEq, + typename Policy::DefaultAlloc>, + TypeList<Policy, Hash, Eq, Alloc>>::type; +}; + // A SwissTable. // // Policy: a policy defines how to perform different operations on // the slots of the hashtable (see hash_policy_traits.h for the full interface // of policy). // +// Params...: a variadic list of parameters that allows us to omit default +// types. This reduces the mangled name of the class and the size of +// debug strings like __PRETTY_FUNCTION__. Default types do not give +// any new information. +// // Hash: a (possibly polymorphic) functor that hashes keys of the hashtable. The // functor should accept a key and return size_t as hash. For best performance // it is important that the hash function provides high entropy across all bits // of the hash. +// This is the first element in `Params...` if it exists, or Policy::DefaultHash +// otherwise. // // Eq: a (possibly polymorphic) functor that compares two keys for equality. It // should accept two (of possibly different type) keys and return a bool: true // if they are equal, false if they are not. If two keys compare equal, then // their hash values as defined by Hash MUST be equal. +// This is the second element in `Params...` if it exists, or Policy::DefaultEq +// otherwise. // // Allocator: an Allocator // [https://en.cppreference.com/w/cpp/named_req/Allocator] with which // the storage of the hashtable will be allocated and the elements will be // constructed and destroyed. -template <class Policy, class Hash, class Eq, class Alloc> +// This is the third element in `Params...` if it exists, or +// Policy::DefaultAlloc otherwise. +template <class Policy, class... Params> class raw_hash_set { using PolicyTraits = hash_policy_traits<Policy>; + using Hash = GetFromListOr<typename Policy::DefaultHash, 0, Params...>; + using Eq = GetFromListOr<typename Policy::DefaultEq, 1, Params...>; + using Alloc = GetFromListOr<typename Policy::DefaultAlloc, 2, Params...>; using KeyArgImpl = KeyArg<IsTransparent<Eq>::value && IsTransparent<Hash>::value>; + static_assert( + std::is_same_v< + typename InstantiateRawHashSet<Policy, Hash, Eq, Alloc>::type, + raw_hash_set>, + "Redundant template parameters were passed. Use InstantiateRawHashSet<> " + "instead"); + public: using init_type = typename PolicyTraits::init_type; using key_type = typename PolicyTraits::key_type; @@ -2640,8 +2670,11 @@ // Moves elements from `src` into `this`. // If the element already exists in `this`, it is left unmodified in `src`. - template <typename H, typename E> - void merge(raw_hash_set<Policy, H, E, Alloc>& src) { // NOLINT + template < + typename... Params2, + typename = std::enable_if_t<std::is_same_v< + Alloc, typename raw_hash_set<Policy, Params2...>::allocator_type>>> + void merge(raw_hash_set<Policy, Params2...>& src) { // NOLINT AssertNotDebugCapacity(); src.AssertNotDebugCapacity(); assert(this != &src); @@ -2665,8 +2698,11 @@ } } - template <typename H, typename E> - void merge(raw_hash_set<Policy, H, E, Alloc>&& src) { + template < + typename... Params2, + typename = std::enable_if_t<std::is_same_v< + Alloc, typename raw_hash_set<Policy, Params2...>::allocator_type>>> + void merge(raw_hash_set<Policy, Params2...>&& src) { // NOLINT merge(src); } @@ -3622,19 +3658,19 @@ }; // Erases all elements that satisfy the predicate `pred` from the container `c`. -template <typename P, typename H, typename E, typename A, typename Predicate> -typename raw_hash_set<P, H, E, A>::size_type EraseIf( - Predicate& pred, raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Predicate> +typename raw_hash_set<P, Params...>::size_type EraseIf( + Predicate& pred, raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::EraseIf(pred, c); } // Calls `cb` for all elements in the container `c`. -template <typename P, typename H, typename E, typename A, typename Callback> -void ForEach(Callback& cb, raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Callback> +void ForEach(Callback& cb, raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::ForEach(cb, c); } -template <typename P, typename H, typename E, typename A, typename Callback> -void ForEach(Callback& cb, const raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Callback> +void ForEach(Callback& cb, const raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::ForEach(cb, c); }
diff --git a/absl/container/internal/raw_hash_set_allocator_test.cc b/absl/container/internal/raw_hash_set_allocator_test.cc index b268d9e..c4bff60 100644 --- a/absl/container/internal/raw_hash_set_allocator_test.cc +++ b/absl/container/internal/raw_hash_set_allocator_test.cc
@@ -142,6 +142,10 @@ using init_type = Tracked<int32_t>; using key_type = int32_t; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args&&... args) {
diff --git a/absl/container/internal/raw_hash_set_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc index f3e32fd..589f4f5 100644 --- a/absl/container/internal/raw_hash_set_benchmark.cc +++ b/absl/container/internal/raw_hash_set_benchmark.cc
@@ -51,6 +51,10 @@ using key_type = int64_t; using init_type = int64_t; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + static void construct(void*, int64_t* slot, int64_t v) { *slot = v; } static void destroy(void*, int64_t*) {} static void transfer(void*, int64_t* new_slot, int64_t* old_slot) { @@ -97,6 +101,10 @@ using key_type = std::string; using init_type = std::pair<std::string, std::string>; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args... args) { std::allocator_traits<allocator_type>::construct(
diff --git a/absl/container/internal/raw_hash_set_probe_benchmark.cc b/absl/container/internal/raw_hash_set_probe_benchmark.cc index 2712160..a09f7d9 100644 --- a/absl/container/internal/raw_hash_set_probe_benchmark.cc +++ b/absl/container/internal/raw_hash_set_probe_benchmark.cc
@@ -58,6 +58,10 @@ using key_type = T; using init_type = T; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class Arg> static void construct(allocator_type* alloc, slot_type* slot, const Arg& arg) {
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index e8de41a..0b5ad8f 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc
@@ -382,6 +382,10 @@ using key_type = T; using init_type = T; + using DefaultHash = hash_default_hash<T>; + using DefaultEq = std::equal_to<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { absl::allocator_traits<Allocator>::construct(*alloc, slot, @@ -529,6 +533,10 @@ using key_type = std::string; using init_type = std::pair<std::string, std::string>; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args... args) { std::allocator_traits<allocator_type>::construct( @@ -581,9 +589,9 @@ template <typename T, bool kTransferable = false, bool kSoo = false, class Alloc = std::allocator<T>> -struct ValueTable - : raw_hash_set<ValuePolicy<T, kTransferable, kSoo>, hash_default_hash<T>, - std::equal_to<T>, Alloc> { +struct ValueTable : InstantiateRawHashSet<ValuePolicy<T, kTransferable, kSoo>, + hash_default_hash<T>, + std::equal_to<T>, Alloc>::type { using Base = typename ValueTable::raw_hash_set; using Base::Base; }; @@ -782,6 +790,34 @@ std::equal_to<absl::string_view>, std::allocator<int>>)); } +TEST(InstantiateRawHashSetTest, VerifyTypes) { + using P = ValuePolicy<int>; + using DA = typename P::DefaultHash; + using DB = typename P::DefaultEq; + using DC = typename P::DefaultAlloc; + + struct A {}; + struct B {}; + struct C {}; + + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, B, C>::type, + raw_hash_set<P, A, B, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, B, DC>::type, + raw_hash_set<P, A, B>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, DB, C>::type, + raw_hash_set<P, A, DB, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, DB, DC>::type, + raw_hash_set<P, A>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, B, C>::type, + raw_hash_set<P, DA, B, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, B, DC>::type, + raw_hash_set<P, DA, B>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, DB, C>::type, + raw_hash_set<P, DA, DB, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, DB, DC>::type, + raw_hash_set<P>>)); +} + template <class TableType> class SooTest : public testing::Test {}; @@ -1144,6 +1180,10 @@ using key_type = DecomposeType; using init_type = DecomposeType; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <typename T> static void construct(void*, DecomposeType* slot, T&& v) { ::new (slot) DecomposeType(std::forward<T>(v)); @@ -2430,8 +2470,9 @@ } }; - struct Table : raw_hash_set<ValuePolicy<Value>, H, std::equal_to<Value>, - std::allocator<Value>> { + struct Table + : InstantiateRawHashSet<ValuePolicy<Value>, H, std::equal_to<Value>, + std::allocator<Value>>::type { using Base = typename Table::raw_hash_set; using Base::Base; };
diff --git a/absl/container/internal/unordered_map_constructor_test.h b/absl/container/internal/unordered_map_constructor_test.h index 7e84dc2..1076aea 100644 --- a/absl/container/internal/unordered_map_constructor_test.h +++ b/absl/container/internal/unordered_map_constructor_test.h
@@ -21,6 +21,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/hash_policy_testing.h" @@ -85,28 +86,8 @@ EXPECT_GE(m.bucket_count(), 123); } -template <typename T> -struct is_std_unordered_map : std::false_type {}; - -template <typename... T> -struct is_std_unordered_map<std::unordered_map<T...>> : std::true_type {}; - -#if defined(UNORDERED_MAP_CXX14) || defined(UNORDERED_MAP_CXX17) -using has_cxx14_std_apis = std::true_type; -#else -using has_cxx14_std_apis = std::false_type; -#endif - -template <typename T> -using expect_cxx14_apis = - absl::disjunction<absl::negation<is_std_unordered_map<T>>, - has_cxx14_std_apis>; - template <typename TypeParam> -void BucketCountAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountAllocTest(std::true_type) { +void BucketCountAllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(123, alloc); @@ -117,14 +98,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountAlloc) { - BucketCountAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountAllocTest<TypeParam>(); } template <typename TypeParam> -void BucketCountHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountHashAllocTest(std::true_type) { +void BucketCountHashAllocTest() { using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; @@ -138,25 +116,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountHashAlloc) { - BucketCountHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountHashAllocTest<TypeParam>(); } -#if ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS -using has_alloc_std_constructors = std::true_type; -#else -using has_alloc_std_constructors = std::false_type; -#endif - -template <typename T> -using expect_alloc_constructors = - absl::disjunction<absl::negation<is_std_unordered_map<T>>, - has_alloc_std_constructors>; - template <typename TypeParam> -void AllocTest(std::false_type) {} - -template <typename TypeParam> -void AllocTest(std::true_type) { +void AllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(alloc); @@ -165,12 +129,10 @@ EXPECT_THAT(m, ::testing::UnorderedElementsAre()); } -TYPED_TEST_P(ConstructorTest, Alloc) { - AllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); -} +TYPED_TEST_P(ConstructorTest, Alloc) { AllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -178,8 +140,7 @@ E equal; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, hasher, equal, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.key_eq(), equal); @@ -189,16 +150,12 @@ } template <typename TypeParam> -void InputIteratorBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, alloc); EXPECT_EQ(m.get_allocator(), alloc); EXPECT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); @@ -206,22 +163,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketAlloc) { - InputIteratorBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.get_allocator(), alloc); @@ -230,18 +183,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashAlloc) { - InputIteratorBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam n(m); @@ -252,18 +205,15 @@ } template <typename TypeParam> -void CopyConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void CopyConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void CopyConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam n(m, A(11)); @@ -274,20 +224,20 @@ } TYPED_TEST_P(ConstructorTest, CopyConstructorAlloc) { - CopyConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + CopyConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on copy constructors. TYPED_TEST_P(ConstructorTest, MoveConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam t(m); @@ -299,18 +249,15 @@ } template <typename TypeParam> -void MoveConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void MoveConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void MoveConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam t(m); @@ -322,14 +269,14 @@ } TYPED_TEST_P(ConstructorTest, MoveConstructorAlloc) { - MoveConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + MoveConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on move constructors. TYPED_TEST_P(ConstructorTest, InitializerListBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; @@ -346,13 +293,10 @@ } template <typename TypeParam> -void InitializerListBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; A alloc(0); TypeParam m(values, 123, alloc); @@ -362,20 +306,17 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketAlloc) { - InitializerListBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values, 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); @@ -385,18 +326,18 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketHashAlloc) { - InitializerListBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, Assignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam n; n = m; @@ -409,14 +350,14 @@ // (it depends on traits). TYPED_TEST_P(ConstructorTest, MoveAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam t(m); TypeParam n; @@ -427,8 +368,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerList) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -436,8 +377,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam n({gen()}); n = m; @@ -445,8 +386,8 @@ } TYPED_TEST_P(ConstructorTest, MoveAssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam t(m); TypeParam n({gen()}); @@ -455,8 +396,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerListOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -464,8 +405,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOnSelf) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values); m = *&m; // Avoid -Wself-assign
diff --git a/absl/container/internal/unordered_map_lookup_test.h b/absl/container/internal/unordered_map_lookup_test.h index 3713cd9..ba037c0 100644 --- a/absl/container/internal/unordered_map_lookup_test.h +++ b/absl/container/internal/unordered_map_lookup_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(LookupTest); TYPED_TEST_P(LookupTest, At) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); for (const auto& p : values) { const auto& val = m.at(p.first); @@ -42,11 +41,10 @@ } TYPED_TEST_P(LookupTest, OperatorBracket) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) { auto& val = m[p.first]; @@ -58,10 +56,9 @@ } TYPED_TEST_P(LookupTest, Count) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) EXPECT_EQ(0, m.count(p.first)) << ::testing::PrintToString(p.first); @@ -72,10 +69,9 @@ TYPED_TEST_P(LookupTest, Find) { using std::get; - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) EXPECT_TRUE(m.end() == m.find(p.first)) @@ -90,10 +86,9 @@ TYPED_TEST_P(LookupTest, EqualRange) { using std::get; - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) { auto r = m.equal_range(p.first);
diff --git a/absl/container/internal/unordered_map_members_test.h b/absl/container/internal/unordered_map_members_test.h index 7d48cdb..e9f4979 100644 --- a/absl/container/internal/unordered_map_members_test.h +++ b/absl/container/internal/unordered_map_members_test.h
@@ -36,10 +36,10 @@ EXPECT_TRUE((std::is_same<std::pair<const typename TypeParam::key_type, typename TypeParam::mapped_type>, typename TypeParam::value_type>())); - EXPECT_TRUE((absl::conjunction< - absl::negation<std::is_signed<typename TypeParam::size_type>>, + EXPECT_TRUE((std::conjunction< + std::negation<std::is_signed<typename TypeParam::size_type>>, std::is_integral<typename TypeParam::size_type>>())); - EXPECT_TRUE((absl::conjunction< + EXPECT_TRUE((std::conjunction< std::is_signed<typename TypeParam::difference_type>, std::is_integral<typename TypeParam::difference_type>>())); EXPECT_TRUE((std::is_convertible<
diff --git a/absl/container/internal/unordered_map_modifiers_test.h b/absl/container/internal/unordered_map_modifiers_test.h index 4d9ab30..a0b01a0 100644 --- a/absl/container/internal/unordered_map_modifiers_test.h +++ b/absl/container/internal/unordered_map_modifiers_test.h
@@ -32,10 +32,9 @@ TYPED_TEST_SUITE_P(ModifiersTest); TYPED_TEST_P(ModifiersTest, Clear) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); m.clear(); @@ -44,63 +43,61 @@ } TYPED_TEST_P(ModifiersTest, Insert) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto p = m.insert(val); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.insert(val2); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); } TYPED_TEST_P(ModifiersTest, InsertHint) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto it = m.insert(m.end(), val); EXPECT_TRUE(it != m.end()); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.insert(it, val2); EXPECT_TRUE(it != m.end()); EXPECT_EQ(val, *it); } TYPED_TEST_P(ModifiersTest, InsertRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; m.insert(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); } TYPED_TEST_P(ModifiersTest, InsertWithinCapacity) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; m.reserve(10); const size_t original_capacity = m.bucket_count(); m.insert(val); EXPECT_EQ(m.bucket_count(), original_capacity); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; m.insert(val2); EXPECT_EQ(m.bucket_count(), original_capacity); } TYPED_TEST_P(ModifiersTest, InsertRangeWithinCapacity) { #if !defined(__GLIBCXX__) - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> base_values; - std::generate_n(std::back_inserter(base_values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(base_values), 10, Generator<T>()); std::vector<T> values; while (values.size() != 100) { std::copy_n(base_values.begin(), 10, std::back_inserter(values)); @@ -114,106 +111,98 @@ } TYPED_TEST_P(ModifiersTest, InsertOrAssign) { -#ifdef UNORDERED_MAP_CXX17 using std::get; using K = typename TypeParam::key_type; using V = typename TypeParam::mapped_type; - K k = hash_internal::Generator<K>()(); - V val = hash_internal::Generator<V>()(); + K k = Generator<K>()(); + V val = Generator<V>()(); TypeParam m; auto p = m.insert_or_assign(k, val); EXPECT_TRUE(p.second); EXPECT_EQ(k, get<0>(*p.first)); EXPECT_EQ(val, get<1>(*p.first)); - V val2 = hash_internal::Generator<V>()(); + V val2 = Generator<V>()(); p = m.insert_or_assign(k, val2); EXPECT_FALSE(p.second); EXPECT_EQ(k, get<0>(*p.first)); EXPECT_EQ(val2, get<1>(*p.first)); -#endif } TYPED_TEST_P(ModifiersTest, InsertOrAssignHint) { -#ifdef UNORDERED_MAP_CXX17 using std::get; using K = typename TypeParam::key_type; using V = typename TypeParam::mapped_type; - K k = hash_internal::Generator<K>()(); - V val = hash_internal::Generator<V>()(); + K k = Generator<K>()(); + V val = Generator<V>()(); TypeParam m; auto it = m.insert_or_assign(m.end(), k, val); EXPECT_TRUE(it != m.end()); EXPECT_EQ(k, get<0>(*it)); EXPECT_EQ(val, get<1>(*it)); - V val2 = hash_internal::Generator<V>()(); + V val2 = Generator<V>()(); it = m.insert_or_assign(it, k, val2); EXPECT_EQ(k, get<0>(*it)); EXPECT_EQ(val2, get<1>(*it)); -#endif } TYPED_TEST_P(ModifiersTest, Emplace) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto p = m.emplace(val); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.emplace(val2); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); } TYPED_TEST_P(ModifiersTest, EmplaceHint) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto it = m.emplace_hint(m.end(), val); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.emplace_hint(it, val2); EXPECT_EQ(val, *it); } TYPED_TEST_P(ModifiersTest, TryEmplace) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto p = m.try_emplace(val.first, val.second); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.try_emplace(val2.first, val2.second); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); -#endif } TYPED_TEST_P(ModifiersTest, TryEmplaceHint) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto it = m.try_emplace(m.end(), val.first, val.second); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.try_emplace(it, val2.first, val2.second); EXPECT_EQ(val, *it); -#endif } template <class V> @@ -236,11 +225,10 @@ }; TYPED_TEST_P(ModifiersTest, Erase) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using std::get; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); auto& first = *m.begin(); @@ -255,10 +243,9 @@ } TYPED_TEST_P(ModifiersTest, EraseRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); auto it = m.erase(m.begin(), m.end()); @@ -267,10 +254,9 @@ } TYPED_TEST_P(ModifiersTest, EraseKey) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); EXPECT_EQ(1, m.erase(values[0].first)); @@ -280,11 +266,11 @@ } TYPED_TEST_P(ModifiersTest, Swap) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> v1; std::vector<T> v2; - std::generate_n(std::back_inserter(v1), 5, hash_internal::Generator<T>()); - std::generate_n(std::back_inserter(v2), 5, hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(v1), 5, Generator<T>()); + std::generate_n(std::back_inserter(v2), 5, Generator<T>()); TypeParam m1(v1.begin(), v1.end()); TypeParam m2(v2.begin(), v2.end()); EXPECT_THAT(items(m1), ::testing::UnorderedElementsAreArray(v1)); @@ -327,20 +313,18 @@ // Test that we do not move from rvalue arguments if an insertion does not // happen. TYPED_TEST_P(UniquePtrModifiersTest, TryEmplace) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto p = m.try_emplace(val.first, std::move(val.second)); EXPECT_TRUE(p.second); // A moved from std::unique_ptr is guaranteed to be nullptr. EXPECT_EQ(val.second, nullptr); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.try_emplace(val2.first, std::move(val2.second)); EXPECT_FALSE(p.second); EXPECT_NE(val2.second, nullptr); -#endif } REGISTER_TYPED_TEST_SUITE_P(UniquePtrModifiersTest, TryEmplace);
diff --git a/absl/container/internal/unordered_set_constructor_test.h b/absl/container/internal/unordered_set_constructor_test.h index af1116e..7038a0c 100644 --- a/absl/container/internal/unordered_set_constructor_test.h +++ b/absl/container/internal/unordered_set_constructor_test.h
@@ -21,6 +21,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/meta/type_traits.h" @@ -94,28 +95,8 @@ EXPECT_GE(cm.bucket_count(), 123); } -template <typename T> -struct is_std_unordered_set : std::false_type {}; - -template <typename... T> -struct is_std_unordered_set<std::unordered_set<T...>> : std::true_type {}; - -#if defined(UNORDERED_SET_CXX14) || defined(UNORDERED_SET_CXX17) -using has_cxx14_std_apis = std::true_type; -#else -using has_cxx14_std_apis = std::false_type; -#endif - -template <typename T> -using expect_cxx14_apis = - absl::disjunction<absl::negation<is_std_unordered_set<T>>, - has_cxx14_std_apis>; - template <typename TypeParam> -void BucketCountAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountAllocTest(std::true_type) { +void BucketCountAllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(123, alloc); @@ -126,14 +107,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountAlloc) { - BucketCountAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountAllocTest<TypeParam>(); } template <typename TypeParam> -void BucketCountHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountHashAllocTest(std::true_type) { +void BucketCountHashAllocTest() { using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; @@ -147,25 +125,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountHashAlloc) { - BucketCountHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountHashAllocTest<TypeParam>(); } -#if ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS -using has_alloc_std_constructors = std::true_type; -#else -using has_alloc_std_constructors = std::false_type; -#endif - -template <typename T> -using expect_alloc_constructors = - absl::disjunction<absl::negation<is_std_unordered_set<T>>, - has_alloc_std_constructors>; - template <typename TypeParam> -void AllocTest(std::false_type) {} - -template <typename TypeParam> -void AllocTest(std::true_type) { +void AllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(alloc); @@ -174,12 +138,10 @@ EXPECT_THAT(keys(m), ::testing::UnorderedElementsAre()); } -TYPED_TEST_P(ConstructorTest, Alloc) { - AllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); -} +TYPED_TEST_P(ConstructorTest, Alloc) { AllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -187,8 +149,7 @@ E equal; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, hasher, equal, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.key_eq(), equal); @@ -198,16 +159,12 @@ } template <typename TypeParam> -void InputIteratorBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, alloc); EXPECT_EQ(m.get_allocator(), alloc); EXPECT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); @@ -215,22 +172,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketAlloc) { - InputIteratorBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.get_allocator(), alloc); @@ -239,11 +192,11 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashAlloc) { - InputIteratorBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -251,7 +204,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam n(m); EXPECT_EQ(m.hash_function(), n.hash_function()); EXPECT_EQ(m.key_eq(), n.key_eq()); @@ -261,11 +214,8 @@ } template <typename TypeParam> -void CopyConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void CopyConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void CopyConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -273,7 +223,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam n(m, A(11)); EXPECT_EQ(m.hash_function(), n.hash_function()); EXPECT_EQ(m.key_eq(), n.key_eq()); @@ -282,13 +232,13 @@ } TYPED_TEST_P(ConstructorTest, CopyConstructorAlloc) { - CopyConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + CopyConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on copy constructors. TYPED_TEST_P(ConstructorTest, MoveConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -296,7 +246,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam t(m); TypeParam n(std::move(t)); EXPECT_EQ(m.hash_function(), n.hash_function()); @@ -306,11 +256,8 @@ } template <typename TypeParam> -void MoveConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void MoveConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void MoveConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -318,7 +265,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam t(m); TypeParam n(std::move(t), A(1)); EXPECT_EQ(m.hash_function(), n.hash_function()); @@ -328,14 +275,14 @@ } TYPED_TEST_P(ConstructorTest, MoveConstructorAlloc) { - MoveConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + MoveConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on move constructors. TYPED_TEST_P(ConstructorTest, InitializerListBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; @@ -352,13 +299,10 @@ } template <typename TypeParam> -void InitializerListBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; - hash_internal::Generator<T> gen; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; A alloc(0); TypeParam m(values, 123, alloc); @@ -368,20 +312,17 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketAlloc) { - InitializerListBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values, 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); @@ -391,18 +332,18 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketHashAlloc) { - InitializerListBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam n; n = m; @@ -415,14 +356,14 @@ // (it depends on traits). TYPED_TEST_P(ConstructorTest, MoveAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam t(m); TypeParam n; @@ -433,8 +374,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerList) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -442,8 +383,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam n({gen()}); n = m; @@ -451,8 +392,8 @@ } TYPED_TEST_P(ConstructorTest, MoveAssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam t(m); TypeParam n({gen()}); @@ -461,8 +402,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerListOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -470,8 +411,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOnSelf) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values); m = *&m; // Avoid -Wself-assign.
diff --git a/absl/container/internal/unordered_set_lookup_test.h b/absl/container/internal/unordered_set_lookup_test.h index b35f766..dc63118 100644 --- a/absl/container/internal/unordered_set_lookup_test.h +++ b/absl/container/internal/unordered_set_lookup_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(LookupTest); TYPED_TEST_P(LookupTest, Count) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) EXPECT_EQ(0, m.count(v)) << ::testing::PrintToString(v); @@ -43,10 +42,9 @@ } TYPED_TEST_P(LookupTest, Find) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) EXPECT_TRUE(m.end() == m.find(v)) << ::testing::PrintToString(v); @@ -65,10 +63,9 @@ } TYPED_TEST_P(LookupTest, EqualRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) { auto r = m.equal_range(v);
diff --git a/absl/container/internal/unordered_set_members_test.h b/absl/container/internal/unordered_set_members_test.h index 4c5e104..b416fef 100644 --- a/absl/container/internal/unordered_set_members_test.h +++ b/absl/container/internal/unordered_set_members_test.h
@@ -35,10 +35,10 @@ TYPED_TEST_P(MembersTest, Typedefs) { EXPECT_TRUE((std::is_same<typename TypeParam::key_type, typename TypeParam::value_type>())); - EXPECT_TRUE((absl::conjunction< - absl::negation<std::is_signed<typename TypeParam::size_type>>, + EXPECT_TRUE((std::conjunction< + std::negation<std::is_signed<typename TypeParam::size_type>>, std::is_integral<typename TypeParam::size_type>>())); - EXPECT_TRUE((absl::conjunction< + EXPECT_TRUE((std::conjunction< std::is_signed<typename TypeParam::difference_type>, std::is_integral<typename TypeParam::difference_type>>())); EXPECT_TRUE((std::is_convertible<
diff --git a/absl/container/internal/unordered_set_modifiers_test.h b/absl/container/internal/unordered_set_modifiers_test.h index d8864bb..b647642 100644 --- a/absl/container/internal/unordered_set_modifiers_test.h +++ b/absl/container/internal/unordered_set_modifiers_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(ModifiersTest); TYPED_TEST_P(ModifiersTest, Clear) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); m.clear(); @@ -42,8 +41,8 @@ } TYPED_TEST_P(ModifiersTest, Insert) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; auto p = m.insert(val); EXPECT_TRUE(p.second); @@ -53,8 +52,8 @@ } TYPED_TEST_P(ModifiersTest, InsertHint) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; auto it = m.insert(m.end(), val); EXPECT_TRUE(it != m.end()); @@ -65,18 +64,17 @@ } TYPED_TEST_P(ModifiersTest, InsertRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; m.insert(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); } TYPED_TEST_P(ModifiersTest, InsertWithinCapacity) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; m.reserve(10); const size_t original_capacity = m.bucket_count(); @@ -88,10 +86,9 @@ TYPED_TEST_P(ModifiersTest, InsertRangeWithinCapacity) { #if !defined(__GLIBCXX__) - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> base_values; - std::generate_n(std::back_inserter(base_values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(base_values), 10, Generator<T>()); std::vector<T> values; while (values.size() != 100) { values.insert(values.end(), base_values.begin(), base_values.end()); @@ -105,8 +102,8 @@ } TYPED_TEST_P(ModifiersTest, Emplace) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. @@ -119,8 +116,8 @@ } TYPED_TEST_P(ModifiersTest, EmplaceHint) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. @@ -150,10 +147,9 @@ }; TYPED_TEST_P(ModifiersTest, Erase) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); std::vector<T> values2; @@ -167,10 +163,9 @@ } TYPED_TEST_P(ModifiersTest, EraseRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); auto it = m.erase(m.begin(), m.end()); @@ -179,10 +174,9 @@ } TYPED_TEST_P(ModifiersTest, EraseKey) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); EXPECT_EQ(1, m.erase(values[0])); @@ -192,11 +186,11 @@ } TYPED_TEST_P(ModifiersTest, Swap) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> v1; std::vector<T> v2; - std::generate_n(std::back_inserter(v1), 5, hash_internal::Generator<T>()); - std::generate_n(std::back_inserter(v2), 5, hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(v1), 5, Generator<T>()); + std::generate_n(std::back_inserter(v2), 5, Generator<T>()); TypeParam m1(v1.begin(), v1.end()); TypeParam m2(v2.begin(), v2.end()); EXPECT_THAT(keys(m1), ::testing::UnorderedElementsAreArray(v1));
diff --git a/absl/container/linked_hash_map.h b/absl/container/linked_hash_map.h new file mode 100644 index 0000000..cec0ada --- /dev/null +++ b/absl/container/linked_hash_map.h
@@ -0,0 +1,660 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// File: linked_hash_map.h +// ----------------------------------------------------------------------------- +// +// This is a simple insertion-ordered map. It provides O(1) amortized +// insertions and lookups, as well as iteration over the map in the insertion +// order. +// +// This class is thread-compatible. +// +// Iterators point into the list and should be stable in the face of +// mutations, except for an iterator pointing to an element that was just +// deleted. +// +// This class supports heterogeneous lookups. + +#ifndef ABSL_CONTAINER_LINKED_HASH_MAP_H_ +#define ABSL_CONTAINER_LINKED_HASH_MAP_H_ + +#include <cassert> +#include <cstddef> +#include <initializer_list> +#include <list> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/optimization.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/internal/common.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template <typename Key, typename Value, + typename KeyHash = typename absl::flat_hash_set<Key>::hasher, + typename KeyEq = + typename absl::flat_hash_set<Key, KeyHash>::key_equal, + typename Alloc = std::allocator<std::pair<const Key, Value>>> +class linked_hash_map { + using KeyArgImpl = absl::container_internal::KeyArg< + absl::container_internal::IsTransparent<KeyEq>::value && + absl::container_internal::IsTransparent<KeyHash>::value>; + + public: + using key_type = Key; + using mapped_type = Value; + using hasher = KeyHash; + using key_equal = KeyEq; + using value_type = std::pair<const key_type, mapped_type>; + using allocator_type = Alloc; + using difference_type = ptrdiff_t; + + private: + template <class K> + using key_arg = typename KeyArgImpl::template type<K, key_type>; + + using ListType = std::list<value_type, Alloc>; + + template <class Fn> + class Wrapped { + template <typename K> + static const K& ToKey(const K& k) { + return k; + } + static const key_type& ToKey(typename ListType::const_iterator it) { + return it->first; + } + static const key_type& ToKey(typename ListType::iterator it) { + return it->first; + } + + Fn fn_; + + friend linked_hash_map; + + public: + using is_transparent = void; + + Wrapped() = default; + explicit Wrapped(Fn fn) : fn_(std::move(fn)) {} + + template <class... Args> + auto operator()(Args&&... args) const + -> decltype(this->fn_(ToKey(args)...)) { + return fn_(ToKey(args)...); + } + }; + using SetType = + absl::flat_hash_set<typename ListType::iterator, Wrapped<hasher>, + Wrapped<key_equal>, Alloc>; + + class NodeHandle { + public: + using key_type = linked_hash_map::key_type; + using mapped_type = linked_hash_map::mapped_type; + using allocator_type = linked_hash_map::allocator_type; + + constexpr NodeHandle() noexcept = default; + NodeHandle(NodeHandle&& nh) noexcept = default; + ~NodeHandle() = default; + NodeHandle& operator=(NodeHandle&& node) noexcept = default; + bool empty() const noexcept { return list_.empty(); } + explicit operator bool() const noexcept { return !empty(); } + allocator_type get_allocator() const { return list_.get_allocator(); } + const key_type& key() const { return list_.front().first; } + mapped_type& mapped() { return list_.front().second; } + void swap(NodeHandle& nh) noexcept { list_.swap(nh.list_); } + + private: + friend linked_hash_map; + + explicit NodeHandle(ListType list) : list_(std::move(list)) {} + ListType list_; + }; + + template <class Iterator, class NodeType> + struct InsertReturnType { + Iterator position; + bool inserted; + NodeType node; + }; + + public: + using iterator = typename ListType::iterator; + using const_iterator = typename ListType::const_iterator; + using reverse_iterator = typename ListType::reverse_iterator; + using const_reverse_iterator = typename ListType::const_reverse_iterator; + using reference = typename ListType::reference; + using const_reference = typename ListType::const_reference; + using size_type = typename ListType::size_type; + using pointer = typename std::allocator_traits<allocator_type>::pointer; + using const_pointer = + typename std::allocator_traits<allocator_type>::const_pointer; + using node_type = NodeHandle; + using insert_return_type = InsertReturnType<iterator, node_type>; + + linked_hash_map() {} + + explicit linked_hash_map(size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : set_(bucket_count, Wrapped<hasher>(hash), Wrapped<key_equal>(eq), + alloc), + list_(alloc) {} + + linked_hash_map(size_t bucket_count, const hasher& hash, + const allocator_type& alloc) + : linked_hash_map(bucket_count, hash, key_equal(), alloc) {} + + linked_hash_map(size_t bucket_count, const allocator_type& alloc) + : linked_hash_map(bucket_count, hasher(), key_equal(), alloc) {} + + explicit linked_hash_map(const allocator_type& alloc) + : linked_hash_map(0, hasher(), key_equal(), alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_map(bucket_count, hash, eq, alloc) { + insert(first, last); + } + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_map(first, last, bucket_count, hash, key_equal(), alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_map(first, last, bucket_count, hasher(), key_equal(), + alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, const allocator_type& alloc) + : linked_hash_map(first, last, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, + size_t bucket_count = 0, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_map(init.begin(), init.end(), bucket_count, hash, eq, + alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_map(init, bucket_count, hash, key_equal(), alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_map(init, bucket_count, hasher(), key_equal(), alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, + const allocator_type& alloc) + : linked_hash_map(init, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_map(const linked_hash_map& other) + : linked_hash_map(other.bucket_count(), other.hash_function(), + other.key_eq(), other.get_allocator()) { + CopyFrom(other); + } + + linked_hash_map(const linked_hash_map& other, const allocator_type& alloc) + : linked_hash_map(other.bucket_count(), other.hash_function(), + other.key_eq(), alloc) { + CopyFrom(other); + } + + linked_hash_map(linked_hash_map&& other) noexcept + : set_(std::move(other.set_)), list_(std::move(other.list_)) { + // Since the list and set must agree for other to end up "valid", + // explicitly clear them. + other.set_.clear(); + other.list_.clear(); + } + + linked_hash_map(linked_hash_map&& other, const allocator_type& alloc) + : linked_hash_map(0, other.hash_function(), other.key_eq(), alloc) { + if (get_allocator() == other.get_allocator()) { + *this = std::move(other); + } else { + CopyFrom(std::move(other)); + } + } + + linked_hash_map& operator=(const linked_hash_map& other) { + if (this == &other) return *this; + // Make a new set, with other's hash/eq/alloc. + set_ = SetType(other.bucket_count(), other.set_.hash_function(), + other.set_.key_eq(), other.get_allocator()); + // Copy the list, with other's allocator. + list_ = ListType(other.get_allocator()); + CopyFrom(other); + return *this; + } + + linked_hash_map& operator=(linked_hash_map&& other) noexcept { + // underlying containers will handle progagate_on_container_move details + set_ = std::move(other.set_); + list_ = std::move(other.list_); + other.set_.clear(); + other.list_.clear(); + return *this; + } + + linked_hash_map& operator=(std::initializer_list<value_type> values) { + clear(); + insert(values.begin(), values.end()); + return *this; + } + + // Derive size_ from set_, as list::size might be O(N). + size_type size() const { return set_.size(); } + size_type max_size() const noexcept { return ~size_type{}; } + bool empty() const { return set_.empty(); } + + // Iteration is list-like, in insertion order. + // These are all forwarded. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + const_iterator cbegin() const { return list_.cbegin(); } + const_iterator cend() const { return list_.cend(); } + reverse_iterator rbegin() { return list_.rbegin(); } + reverse_iterator rend() { return list_.rend(); } + const_reverse_iterator rbegin() const { return list_.rbegin(); } + const_reverse_iterator rend() const { return list_.rend(); } + const_reverse_iterator crbegin() const { return list_.crbegin(); } + const_reverse_iterator crend() const { return list_.crend(); } + reference front() { return list_.front(); } + reference back() { return list_.back(); } + const_reference front() const { return list_.front(); } + const_reference back() const { return list_.back(); } + + void pop_front() { erase(begin()); } + void pop_back() { erase(std::prev(end())); } + + ABSL_ATTRIBUTE_REINITIALIZES void clear() { + set_.clear(); + list_.clear(); + } + + void reserve(size_t n) { set_.reserve(n); } + size_t capacity() const { return set_.capacity(); } + size_t bucket_count() const { return set_.bucket_count(); } + float load_factor() const { return set_.load_factor(); } + + hasher hash_function() const { return set_.hash_function().fn_; } + key_equal key_eq() const { return set_.key_eq().fn_; } + allocator_type get_allocator() const { return list_.get_allocator(); } + + template <class K = key_type> + size_type erase(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return 0; + auto list_it = *found; + // Erase set entry first since it refers to the list element. + set_.erase(found); + list_.erase(list_it); + return 1; + } + + iterator erase(const_iterator position) { + auto found = set_.find(position); + assert(*found == position); + set_.erase(found); + return list_.erase(position); + } + + iterator erase(iterator position) { + return erase(static_cast<const_iterator>(position)); + } + + iterator erase(iterator first, iterator last) { + while (first != last) first = erase(first); + return first; + } + + iterator erase(const_iterator first, const_iterator last) { + while (first != last) first = erase(first); + if (first == end()) return end(); + return *set_.find(first); + } + + template <class K = key_type> + iterator find(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <class K = key_type> + const_iterator find(const key_arg<K>& key) const { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <class K = key_type> + size_type count(const key_arg<K>& key) const { + return contains(key) ? 1 : 0; + } + template <class K = key_type> + bool contains(const key_arg<K>& key) const { + return set_.contains(key); + } + + template <class K = key_type> + mapped_type& at(const key_arg<K>& key) { + auto it = find(key); + if (ABSL_PREDICT_FALSE(it == end())) { + absl::base_internal::ThrowStdOutOfRange("absl::linked_hash_map::at"); + } + return it->second; + } + + template <class K = key_type> + const mapped_type& at(const key_arg<K>& key) const { + return const_cast<linked_hash_map*>(this)->at(key); + } + + template <class K = key_type> + std::pair<iterator, iterator> equal_range(const key_arg<K>& key) { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <class K = key_type> + std::pair<const_iterator, const_iterator> equal_range( + const key_arg<K>& key) const { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <class K = key_type> + mapped_type& operator[](const key_arg<K>& key) { + return LazyEmplaceInternal(key).first->second; + } + + template <class K = key_type, K* = nullptr> + mapped_type& operator[](key_arg<K>&& key) { + return LazyEmplaceInternal(std::forward<key_arg<K>>(key)).first->second; + } + + std::pair<iterator, bool> insert(const value_type& v) { + return InsertInternal(v); + } + std::pair<iterator, bool> insert(value_type&& v) { + return InsertInternal(std::move(v)); + } + + iterator insert(const_iterator, const value_type& v) { + return insert(v).first; + } + iterator insert(const_iterator, value_type&& v) { + return insert(std::move(v)).first; + } + + void insert(std::initializer_list<value_type> ilist) { + insert(ilist.begin(), ilist.end()); + } + + template <class InputIt> + void insert(InputIt first, InputIt last) { + for (; first != last; ++first) insert(*first); + } + + insert_return_type insert(node_type&& node) { + if (node.empty()) return {end(), false, node_type()}; + if (auto [set_itr, inserted] = set_.emplace(node.list_.begin()); inserted) { + list_.splice(list_.end(), node.list_); + return {*set_itr, true, node_type()}; + } else { + return {*set_itr, false, std::move(node)}; + } + } + + iterator insert(const_iterator, node_type&& node) { + return insert(std::move(node)).first; + } + + // The last two template parameters ensure that both arguments are rvalues + // (lvalue arguments are handled by the overloads below). This is necessary + // for supporting bitfield arguments. + // + // union { int n : 1; }; + // linked_hash_map<int, int> m; + // m.insert_or_assign(n, n); + template <class K = key_type, class V = mapped_type, K* = nullptr, + V* = nullptr> + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) { + return InsertOrAssignInternal(std::forward<key_arg<K>>(k), + std::forward<V>(v)); + } + + template <class K = key_type, class V = mapped_type, K* = nullptr> + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) { + return InsertOrAssignInternal(std::forward<key_arg<K>>(k), v); + } + + template <class K = key_type, class V = mapped_type, V* = nullptr> + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) { + return InsertOrAssignInternal(k, std::forward<V>(v)); + } + + template <class K = key_type, class V = mapped_type> + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v) { + return InsertOrAssignInternal(k, v); + } + + template <class K = key_type, class V = mapped_type, K* = nullptr, + V* = nullptr> + iterator insert_or_assign(const_iterator, key_arg<K>&& k, V&& v) { + return insert_or_assign(std::forward<key_arg<K>>(k), std::forward<V>(v)) + .first; + } + + template <class K = key_type, class V = mapped_type, K* = nullptr> + iterator insert_or_assign(const_iterator, key_arg<K>&& k, const V& v) { + return insert_or_assign(std::forward<key_arg<K>>(k), v).first; + } + + template <class K = key_type, class V = mapped_type, V* = nullptr> + iterator insert_or_assign(const_iterator, const key_arg<K>& k, V&& v) { + return insert_or_assign(k, std::forward<V>(v)).first; + } + + template <class K = key_type, class V = mapped_type> + iterator insert_or_assign(const_iterator, const key_arg<K>& k, const V& v) { + return insert_or_assign(k, v).first; + } + + template <typename... Args> + std::pair<iterator, bool> emplace(Args&&... args) { + ListType node_donor; + auto list_iter = + node_donor.emplace(node_donor.end(), std::forward<Args>(args)...); + auto ins = set_.insert(list_iter); + if (!ins.second) return {*ins.first, false}; + list_.splice(list_.end(), node_donor, list_iter); + return {list_iter, true}; + } + + template <class K = key_type, class... Args, K* = nullptr> + iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) { + return try_emplace(std::forward<key_arg<K>>(k), std::forward<Args>(args)...) + .first; + } + + template <typename... Args> + iterator emplace_hint(const_iterator, Args&&... args) { + return emplace(std::forward<Args>(args)...).first; + } + + template <class K = key_type, typename... Args, K* = nullptr> + std::pair<iterator, bool> try_emplace(key_arg<K>&& key, Args&&... args) { + return LazyEmplaceInternal(std::forward<key_arg<K>>(key), + std::forward<Args>(args)...); + } + + template <typename H, typename E> + void merge(linked_hash_map<Key, Value, H, E, Alloc>& src) { + auto itr = src.list_.begin(); + while (itr != src.list_.end()) { + if (contains(itr->first)) { + ++itr; + } else { + insert(src.extract(itr++)); + } + } + } + + template <typename H, typename E> + void merge(linked_hash_map<Key, Value, H, E, Alloc>&& src) { + merge(src); + } + + node_type extract(const_iterator position) { + set_.erase(position->first); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, position); + return node_type(std::move(extracted_node_list)); + } + + template <class K = key_type, + std::enable_if_t<!std::is_same_v<K, iterator>, int> = 0> + node_type extract(const key_arg<K>& key) { + auto node = set_.extract(key); + if (node.empty()) return node_type(); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, node.value()); + return node_type(std::move(extracted_node_list)); + } + + template <typename H, typename E> + void splice(const_iterator, linked_hash_map<Key, Value, H, E, Alloc>& list, + const_iterator it) { + if (&list == this) { + list_.splice(list_.end(), list.list_, it); + } else { + insert(list.extract(it)); + } + } + + template <class K = key_type, typename... Args> + std::pair<iterator, bool> try_emplace(const key_arg<K>& key, Args&&... args) { + return LazyEmplaceInternal(key, std::forward<Args>(args)...); + } + + template <class K = key_type, typename... Args> + iterator try_emplace(const_iterator, const key_arg<K>& key, Args&&... args) { + return LazyEmplaceInternal(key, std::forward<Args>(args)...).first; + } + + void swap(linked_hash_map& other) noexcept { + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + } + + friend bool operator==(const linked_hash_map& a, const linked_hash_map& b) { + if (a.size() != b.size()) return false; + const linked_hash_map* outer = &a; + const linked_hash_map* inner = &b; + if (outer->capacity() > inner->capacity()) std::swap(outer, inner); + for (const value_type& elem : *outer) { + auto it = inner->find(elem.first); + if (it == inner->end()) return false; + if (it->second != elem.second) return false; + } + + return true; + } + + friend bool operator!=(const linked_hash_map& a, const linked_hash_map& b) { + return !(a == b); + } + + void rehash(size_t n) { set_.rehash(n); } + + private: + template <typename Other> + void CopyFrom(Other&& other) { + for (auto& elem : other.list_) { + set_.insert(list_.insert(list_.end(), std::move(elem))); + } + assert(set_.size() == list_.size()); + } + + template <typename U> + std::pair<iterator, bool> InsertInternal(U&& pair) { // NOLINT(build/c++11) + bool constructed = false; + auto set_iter = set_.lazy_emplace(pair.first, [&](const auto& ctor) { + constructed = true; + ctor(list_.emplace(list_.end(), std::forward<U>(pair))); + }); + return {*set_iter, constructed}; + } + + template <class K, class V> + std::pair<iterator, bool> InsertOrAssignInternal(K&& k, V&& v) { + auto [it, inserted] = + LazyEmplaceInternal(std::forward<K>(k), std::forward<V>(v)); + if (!inserted) it->second = std::forward<V>(v); + return {it, inserted}; + } + + template <typename K, typename... Args> + std::pair<iterator, bool> LazyEmplaceInternal(K&& key, Args&&... args) { + bool constructed = false; + auto set_iter = set_.lazy_emplace( + key, [this, &constructed, &key, &args...](const auto& ctor) { + auto list_iter = + list_.emplace(list_.end(), std::piecewise_construct, + std::forward_as_tuple(std::forward<K>(key)), + std::forward_as_tuple(std::forward<Args>(args)...)); + constructed = true; + ctor(list_iter); + }); + return {*set_iter, constructed}; + } + + // The set component, used for speedy lookups. + SetType set_; + + // The list component, used for maintaining insertion order. + ListType list_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_LINKED_HASH_MAP_H_
diff --git a/absl/container/linked_hash_map_benchmark.cc b/absl/container/linked_hash_map_benchmark.cc new file mode 100644 index 0000000..5a0db38 --- /dev/null +++ b/absl/container/linked_hash_map_benchmark.cc
@@ -0,0 +1,140 @@ +// 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. + +#include <algorithm> +#include <cstddef> +#include <string> +#include <vector> + +#include "absl/container/linked_hash_map.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +struct BenchmarkData { + std::vector<std::string> sample; + size_t str_bytes = 0; + explicit BenchmarkData(int size, + absl::FunctionRef<std::string(int)> factory) { + sample.reserve(size); + for (int i = 0; i < size; ++i) { + sample.push_back(factory(i)); + str_bytes += sample.back().size(); + } + } +}; + +void BenchmarkTryEmplaceStrings(benchmark::State& state, + absl::FunctionRef<std::string(int)> factory) { + BenchmarkData data(state.range(0), factory); + + // Make a batch around 1Mi bytes. + const size_t batch_size = + std::max(size_t{1}, size_t{1000000} / data.str_bytes); + std::vector<absl::linked_hash_map<std::string, int>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : data.sample) { + benchmark::DoNotOptimize(set.try_emplace(str)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * data.str_bytes); +} + +void BenchmarkInsertOrAssignStrings( + benchmark::State& state, absl::FunctionRef<std::string(int)> factory) { + BenchmarkData data(state.range(0), factory); + + // Make a batch around 1Mi bytes. + const size_t batch_size = std::max(size_t{1}, 1000000 / data.str_bytes); + std::vector<absl::linked_hash_map<std::string, int>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : data.sample) { + benchmark::DoNotOptimize(set.insert_or_assign(str, 0)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * data.str_bytes); +} + +constexpr absl::string_view kFormatShort = "%10d"; +constexpr absl::string_view kFormatLong = + "a longer string that exceeds the SSO %10d"; + +void BM_TryEmplaceShortStrings_Hit(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_TryEmplaceShortStrings_Hit)->Range(1, 1 << 16); + +void BM_TryEmplaceLongStrings_Hit(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_TryEmplaceLongStrings_Hit)->Range(1, 1 << 16); + +void BM_TryEmplaceShortStrings_Miss(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_TryEmplaceShortStrings_Miss)->Range(1, 1 << 16); + +void BM_TryEmplaceLongStrings_Miss(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_TryEmplaceLongStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertOrAssignShortStrings_Hit(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_InsertOrAssignShortStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertOrAssignLongStrings_Hit(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_InsertOrAssignLongStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertOrAssignShortStrings_Miss(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_InsertOrAssignShortStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertOrAssignLongStrings_Miss(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_InsertOrAssignLongStrings_Miss)->Range(1, 1 << 16); + +} // namespace
diff --git a/absl/container/linked_hash_map_test.cc b/absl/container/linked_hash_map_test.cc new file mode 100644 index 0000000..54d42fe --- /dev/null +++ b/absl/container/linked_hash_map_test.cc
@@ -0,0 +1,964 @@ +// 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. + +#include "absl/container/linked_hash_map.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/internal/exception_testing.h" +#include "absl/container/internal/hash_generator_testing.h" +#include "absl/container/internal/hash_policy_testing.h" +#include "absl/container/internal/heterogeneous_lookup_testing.h" +#include "absl/container/internal/test_instance_tracker.h" +#include "absl/container/internal/unordered_map_constructor_test.h" +#include "absl/container/internal/unordered_map_lookup_test.h" +#include "absl/container/internal/unordered_map_members_test.h" +#include "absl/container/internal/unordered_map_modifiers_test.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::ElementsAre; +using ::testing::Pair; +using ::testing::Pointee; + +template <class K, class V> +using Map = linked_hash_map<K, V, StatefulTestingHash, StatefulTestingEqual, + Alloc<std::pair<const K, V>>>; + +static_assert(!std::is_standard_layout<NonStandardLayout>(), ""); + +using MapTypes = + ::testing::Types<Map<int, int>, Map<std::string, int>, + Map<Enum, std::string>, Map<EnumClass, int>, + Map<int, NonStandardLayout>, Map<NonStandardLayout, int>>; + +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, ConstructorTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, LookupTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, MembersTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, ModifiersTest, MapTypes); + +// Tests that the range constructor works. +TEST(LinkedHashMapTest, RangeConstruct) { + const std::pair<int, int> items[] = {{1, 2}, {2, 3}, {3, 4}}; + EXPECT_THAT((linked_hash_map<int, int>(std::begin(items), std::end(items))), + ElementsAre(Pair(1, 2), Pair(2, 3), Pair(3, 4))); +} + +// Tests that copying works. +TEST(LinkedHashMapTest, Copy) { + linked_hash_map<int, int> m{{2, 12}, {3, 13}}; + auto copy = m; + + EXPECT_TRUE(copy.contains(2)); + auto found = copy.find(2); + ASSERT_TRUE(found != copy.end()); + for (auto iter = copy.begin(); iter != copy.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Copied map's find method returned an invalid iterator."; +} + +// Tests that assignment works. +TEST(LinkedHashMapTest, Assign) { + linked_hash_map<int, int> m{{2, 12}, {3, 13}}; + linked_hash_map<int, int> n{{4, 14}}; + + n = m; + EXPECT_TRUE(n.contains(2)); + auto found = n.find(2); + ASSERT_TRUE(found != n.end()); + for (auto iter = n.begin(); iter != n.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Assigned map's find method returned an invalid iterator."; +} + +// Tests that move constructor works. +TEST(LinkedHashMapTest, Move) { + // Use unique_ptr as an example of a non-copyable type. + linked_hash_map<int, std::unique_ptr<int>> m; + m[2] = std::make_unique<int>(12); + m[3] = std::make_unique<int>(13); + linked_hash_map<int, std::unique_ptr<int>> n = std::move(m); + EXPECT_THAT(n, ElementsAre(Pair(2, Pointee(12)), Pair(3, Pointee(13)))); +} + +TEST(LinkedHashMapTest, CanInsertMoveOnly) { + linked_hash_map<int, std::unique_ptr<int>> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) + m.insert({kv.k, std::make_unique<int>(int{kv.v})}); + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, *found->second); +} + +TEST(LinkedHashMapTest, CanEmplaceMoveOnly) { + linked_hash_map<int, std::unique_ptr<int>> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) { + m.emplace(std::piecewise_construct, std::make_tuple(kv.k), + std::make_tuple(new int{kv.v})); + } + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, *found->second); +} + +struct NoCopy { + explicit NoCopy(int x) : x(x) {} + NoCopy(const NoCopy&) = delete; + NoCopy& operator=(const NoCopy&) = delete; + NoCopy(NoCopy&&) = delete; + NoCopy& operator=(NoCopy&&) = delete; + int x; +}; + +TEST(LinkedHashMapTest, CanEmplaceNoMoveNoCopy) { + linked_hash_map<int, NoCopy> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) { + m.emplace(std::piecewise_construct, std::make_tuple(kv.k), + std::make_tuple(kv.v)); + } + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, found->second.x); +} + +TEST(LinkedHashMapTest, ConstKeys) { + linked_hash_map<int, int> m; + m.insert(std::make_pair(1, 2)); + // Test that keys are const in iteration. + std::pair<const int, int>& p = *m.begin(); + EXPECT_EQ(1, p.first); +} + +// Tests that iteration from begin() to end() works +TEST(LinkedHashMapTest, Iteration) { + linked_hash_map<int, int> m; + EXPECT_TRUE(m.begin() == m.end()); + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + linked_hash_map<int, int>::iterator i = m.begin(); + ASSERT_TRUE(m.begin() == i); + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(2, i->first); + EXPECT_EQ(12, i->second); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(1, i->first); + EXPECT_EQ(11, i->second); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(3, i->first); + EXPECT_EQ(13, i->second); + + ++i; // Should be the end of the line. + ASSERT_TRUE(m.end() == i); +} + +// Tests that reverse iteration from rbegin() to rend() works +TEST(LinkedHashMapTest, ReverseIteration) { + linked_hash_map<int, int> m; + EXPECT_TRUE(m.rbegin() == m.rend()); + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + linked_hash_map<int, int>::reverse_iterator i = m.rbegin(); + ASSERT_TRUE(m.rbegin() == i); + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(3, i->first); + EXPECT_EQ(13, i->second); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(1, i->first); + EXPECT_EQ(11, i->second); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(2, i->first); + EXPECT_EQ(12, i->second); + + ++i; // Should be the end of the line. + ASSERT_TRUE(m.rend() == i); +} + +// Tests that clear() works +TEST(LinkedHashMapTest, Clear) { + linked_hash_map<int, int> m; + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + ASSERT_EQ(3, m.size()); + + m.clear(); + + EXPECT_EQ(0, m.size()); + + m.clear(); // Make sure we can call it on an empty map. + + EXPECT_EQ(0, m.size()); +} + +// Tests that size() works. +TEST(LinkedHashMapTest, Size) { + linked_hash_map<int, int> m; + EXPECT_EQ(0, m.size()); + m.insert(std::make_pair(2, 12)); + EXPECT_EQ(1, m.size()); + m.insert(std::make_pair(1, 11)); + EXPECT_EQ(2, m.size()); + m.insert(std::make_pair(3, 13)); + EXPECT_EQ(3, m.size()); + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests empty() +TEST(LinkedHashMapTest, Empty) { + linked_hash_map<int, int> m; + ASSERT_TRUE(m.empty()); + m.insert(std::make_pair(2, 12)); + ASSERT_FALSE(m.empty()); + m.clear(); + ASSERT_TRUE(m.empty()); +} + +TEST(LinkedHashMapTest, Erase) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(std::make_pair(2, 12)); + ASSERT_EQ(1, m.size()); + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(0, m.size()); + + EXPECT_EQ(0, m.erase(2)); // Make sure nothing bad happens if we repeat. + EXPECT_EQ(0, m.size()); +} + +TEST(LinkedHashMapTest, Erase2) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + m.insert(std::make_pair(4, 14)); + ASSERT_EQ(4, m.size()); + + // Erase middle two + EXPECT_EQ(1, m.erase(1)); + EXPECT_EQ(1, m.erase(3)); + + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_map<int, int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(12, it->second); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(2, m.size()); + + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(1, m.erase(4)); + ASSERT_EQ(0, m.size()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(0, m.size()); +} + +// Test that erase(iter,iter) and erase(iter) compile and work. +TEST(LinkedHashMapTest, Erase3) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(3, 13)); + m.insert(std::make_pair(4, 14)); + + // Erase middle two + linked_hash_map<int, int>::iterator it2 = m.find(2); + linked_hash_map<int, int>::iterator it4 = m.find(4); + EXPECT_EQ(m.erase(it2, it4), m.find(4)); + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_map<int, int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(11, it->second); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); + + // Erase first one using an iterator. + EXPECT_EQ(m.erase(m.begin()), m.find(4)); + + // Only the last element should be left. + it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); +} + +// Test all types of insertion +TEST(LinkedHashMapTest, Insertion) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + std::pair<linked_hash_map<int, int>::iterator, bool> result; + + result = m.insert(std::make_pair(2, 12)); + ASSERT_EQ(1, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(2, result.first->first); + EXPECT_EQ(12, result.first->second); + + result = m.insert(std::make_pair(1, 11)); + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(1, result.first->first); + EXPECT_EQ(11, result.first->second); + + result = m.insert(std::make_pair(3, 13)); + linked_hash_map<int, int>::iterator result_iterator = result.first; + ASSERT_EQ(3, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(3, result.first->first); + EXPECT_EQ(13, result.first->second); + + result = m.insert(std::make_pair(3, 13)); + EXPECT_EQ(3, m.size()); + EXPECT_FALSE(result.second) << "No insertion should have occurred."; + EXPECT_TRUE(result_iterator == result.first) + << "Duplicate insertion should have given us the original iterator."; + + std::vector<std::pair<int, int>> v = {{3, 13}, {4, 14}, {5, 15}}; + m.insert(v.begin(), v.end()); + // Expect 4 and 5 inserted, 3 not inserted. + EXPECT_EQ(5, m.size()); + EXPECT_EQ(14, m.at(4)); + EXPECT_EQ(15, m.at(5)); +} + +static std::pair<const int, int> Pair(int i, int j) { return {i, j}; } + +// Test front accessors. +TEST(LinkedHashMapTest, Front) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(Pair(2, 12), m.front()); + m.pop_front(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(Pair(1, 11), m.front()); + m.pop_front(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(Pair(3, 13), m.front()); + m.pop_front(); + EXPECT_TRUE(m.empty()); +} + +// Test back accessors. +TEST(LinkedHashMapTest, Back) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(Pair(3, 13), m.back()); + m.pop_back(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(Pair(1, 11), m.back()); + m.pop_back(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(Pair(2, 12), m.back()); + m.pop_back(); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashMapTest, Find) { + linked_hash_map<int, int> m; + + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in an empty map."; + + m.insert(std::make_pair(2, 12)); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find an element that doesn't exist in the map."; + + std::pair<linked_hash_map<int, int>::iterator, bool> result = + m.insert(std::make_pair(1, 11)); + ASSERT_TRUE(result.second); + ASSERT_TRUE(m.end() != result.first); + EXPECT_TRUE(result.first == m.find(1)) + << "We should have found an element we know exists in the map."; + EXPECT_EQ(11, result.first->second); + + // Check that a follow-up insertion doesn't affect our original + m.insert(std::make_pair(3, 13)); + linked_hash_map<int, int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(11, it->second); + + m.clear(); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in a map that we've cleared."; +} + +TEST(LinkedHashMapTest, Contains) { + linked_hash_map<int, int> m; + + EXPECT_FALSE(m.contains(1)) << "An empty map shouldn't contain anything."; + + m.insert(std::make_pair(2, 12)); + EXPECT_FALSE(m.contains(1)) + << "The map shouldn't contain an element that doesn't exist."; + + m.insert(std::make_pair(1, 11)); + EXPECT_TRUE(m.contains(1)) + << "The map should contain an element that we know exists."; + + m.clear(); + EXPECT_FALSE(m.contains(1)) + << "A map that we've cleared shouldn't contain anything."; +} + +TEST(LinkedHashMapTest, At) { + linked_hash_map<int, int> m = {{1, 2}}; + EXPECT_EQ(2, m.at(1)); + ABSL_BASE_INTERNAL_EXPECT_FAIL(m.at(3), std::out_of_range, + "linked_hash_map::at"); + + const linked_hash_map<int, int> cm = {{1, 2}}; + EXPECT_EQ(2, cm.at(1)); + ABSL_BASE_INTERNAL_EXPECT_FAIL(cm.at(3), std::out_of_range, + "linked_hash_map::at"); +} + +TEST(LinkedHashMapTest, Swap) { + linked_hash_map<int, int> m1; + linked_hash_map<int, int> m2; + m1.insert(std::make_pair(1, 1)); + m1.insert(std::make_pair(2, 2)); + m2.insert(std::make_pair(3, 3)); + ASSERT_EQ(2, m1.size()); + ASSERT_EQ(1, m2.size()); + m1.swap(m2); + ASSERT_EQ(1, m1.size()); + ASSERT_EQ(2, m2.size()); +} + +TEST(LinkedHashMapTest, InitializerList) { + linked_hash_map<int, int> m{{1, 2}, {3, 4}}; + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(1)); + linked_hash_map<int, int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(2, it->second); + EXPECT_TRUE(m.contains(3)); + it = m.find(3); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(4, it->second); +} + +TEST(LinkedHashMapTest, CustomHashAndEquality) { + struct CustomIntHash { + size_t operator()(int x) const { return x; } + }; + struct CustomIntEq { + bool operator()(int x, int y) const { return x == y; } + }; + linked_hash_map<int, int, CustomIntHash, CustomIntEq> m; + m.insert(std::make_pair(1, 1)); + EXPECT_TRUE(m.contains(1)); + EXPECT_EQ(1, m[1]); +} + +TEST(LinkedHashMapTest, EqualRange) { + linked_hash_map<int, int> m{{3, 11}, {1, 13}}; + const auto& const_m = m; + + EXPECT_THAT(m.equal_range(2), testing::Pair(m.end(), m.end())); + EXPECT_THAT(const_m.equal_range(2), + testing::Pair(const_m.end(), const_m.end())); + + EXPECT_THAT(m.equal_range(1), testing::Pair(m.find(1), ++m.find(1))); + EXPECT_THAT(const_m.equal_range(1), + testing::Pair(const_m.find(1), ++const_m.find(1))); +} + +TEST(LinkedHashMapTest, ReserveWorks) { + linked_hash_map<int, int> m; + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.reserve(10); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.emplace(1, 1); + m.emplace(2, 2); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(2, m.size()); + EXPECT_LT(0.0, m.load_factor()); +} + +// We require key types to have hash and equals. +class CopyableMovableType + : public absl::test_internal::CopyableMovableInstance { + public: + using CopyableMovableInstance::CopyableMovableInstance; + + bool operator==(const CopyableMovableType& o) const { + return value() == o.value(); + } + + template <typename H> + friend H AbslHashValue(H h, const CopyableMovableType& c) { + return H::combine(std::move(h), c.value()); + } +}; + +TEST(LinkedHashMapTest, RValueOperatorAt) { + absl::test_internal::InstanceTracker tracker; + + linked_hash_map<CopyableMovableType, int> map; + map[CopyableMovableType(1)] = 1; + EXPECT_EQ(tracker.copies(), 0); + CopyableMovableType c(2); + map[std::move(c)] = 2; + EXPECT_EQ(tracker.copies(), 0); + EXPECT_THAT(map, ElementsAre(Pair(CopyableMovableType(1), 1), + Pair(CopyableMovableType(2), 2))); +} + +TEST(LinkedHashMapTest, HeterogeneousTests) { + absl::test_internal::InstanceTracker tracker; + + using MapType = linked_hash_map<ExpensiveType, int, HeterogeneousHash, + HeterogeneousEqual>; + MapType map; + ExpensiveType one(1); + map[one] = 1; + // Two instances: 'one' var and an instance in the map. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[one] = 5; + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[CheapType(1)] = 10; + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[CheapType(2)] = 20; + // Construction since key==2 doesn't exist in the map. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20))); + + // find + auto itr = map.find(CheapType(1)); + tracker.ResetCopiesMovesSwaps(); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(10, itr->second); + // contains + EXPECT_TRUE(map.contains(CheapType(2))); + // count + EXPECT_EQ(1, map.count(CheapType(2))); + // equal_range + auto eq_itr_pair = map.equal_range(CheapType(2)); + ASSERT_NE(eq_itr_pair.first, map.end()); + EXPECT_EQ(20, eq_itr_pair.first->second); + // No construction for find, contains, count or equal_range. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + // insert + auto three = MapType::value_type(CheapType(3), 30); + tracker.ResetCopiesMovesSwaps(); + map.insert(three); + // Two instances: 'three' var and an instance in the map. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map.insert(three); + // No additional construction since key==3 exists. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30))); + + // Test std::move() using operator[] and insert(). + ExpensiveType four(4); + tracker.ResetCopiesMovesSwaps(); + map[std::move(four)] = 40; + // Two constructions (regular and move). + EXPECT_EQ(7, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(1, tracker.moves()); + + auto five = MapType::value_type(CheapType(5), 50); + tracker.ResetCopiesMovesSwaps(); + map.insert(std::move(five)); + // Two constructions (regular and move). + EXPECT_EQ(9, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30), + Pair(HasExpensiveValue(4), 40), + Pair(HasExpensiveValue(5), 50))); + + // Insert using std::pair. + tracker.ResetCopiesMovesSwaps(); + map.insert(std::make_pair(ExpensiveType(6), 60)); + EXPECT_EQ(10, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(2, tracker.moves()); + + // Heterogeneous erase. + map.erase(CheapType(1)); + // No construction and instance reduced by one. + tracker.ResetCopiesMovesSwaps(); + EXPECT_EQ(9, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30), + Pair(HasExpensiveValue(4), 40), + Pair(HasExpensiveValue(5), 50), + Pair(HasExpensiveValue(6), 60))); +} + +TEST(LinkedHashMapTest, HeterogeneousStringViewLookup) { + linked_hash_map<std::string, int> map; + map["foo"] = 1; + map["bar"] = 2; + map["blah"] = 3; + + { + absl::string_view lookup("foo"); + auto itr = map.find(lookup); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(1, itr->second); + } + + // Not found. + { + absl::string_view lookup("foobar"); + EXPECT_EQ(map.end(), map.find(lookup)); + } + + { + absl::string_view lookup("blah"); + auto itr = map.find(lookup); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(3, itr->second); + } +} + +TEST(LinkedHashMapTest, UniversalReferenceWorks) { + linked_hash_map<std::string, int> map; + std::string s = "very very very very very long string"; + map[s] = 1; + EXPECT_EQ(s, "very very very very very long string"); +} + +TEST(LinkedHashMap, ExtractInsert) { + linked_hash_map<int, int> m = {{1, 7}, {2, 9}}; + auto node = m.extract(1); + EXPECT_TRUE(node); + EXPECT_EQ(node.key(), 1); + EXPECT_EQ(node.mapped(), 7); + EXPECT_THAT(m, ElementsAre(Pair(2, 9))); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.contains(2)); + + node.mapped() = 17; + m.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_THAT(m, ElementsAre(Pair(2, 9), Pair(1, 17))); + EXPECT_TRUE(m.contains(2)); + EXPECT_TRUE(m.contains(1)); + + node = m.extract(m.find(1)); + EXPECT_TRUE(node); + EXPECT_EQ(node.key(), 1); + EXPECT_EQ(node.mapped(), 17); + EXPECT_THAT(m, ElementsAre(Pair(2, 9))); +} + +TEST(LinkedHashMap, Merge) { + linked_hash_map<int, int> m = {{1, 7}, {3, 6}}; + linked_hash_map<int, int> src = {{1, 10}, {2, 9}, {4, 16}}; + + m.merge(src); + + EXPECT_THAT(m, ElementsAre(Pair(1, 7), Pair(3, 6), Pair(2, 9), Pair(4, 16))); + EXPECT_THAT(src, ElementsAre(Pair(1, 10))); + for (int i : {2, 9, 4}) { + EXPECT_FALSE(src.contains(i)); + } +} + +TEST(LinkedHashMap, Splice) { + linked_hash_map<int, int> m = {{1, 7}, {3, 6}}; + linked_hash_map<int, int> src = {{1, 10}, {2, 9}, {4, 16}}; + m.splice(m.end(), m, m.begin()); + + EXPECT_THAT(m, ElementsAre(Pair(3, 6), Pair(1, 7))); + + m.splice(m.end(), src, ++src.begin()); + EXPECT_THAT(m, ElementsAre(Pair(3, 6), Pair(1, 7), Pair(2, 9))); + EXPECT_THAT(src, ElementsAre(Pair(1, 10), Pair(4, 16))); +} + +TEST(LinkedHashMap, EraseRange) { + linked_hash_map<int, int> map = {{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, + {6, 36}, {7, 49}, {8, 64}, {9, 81}}; + auto start = map.find(3); + auto end = map.find(8); + auto itr = map.erase(start, end); + ASSERT_NE(itr, map.end()); + EXPECT_THAT(*itr, Pair(8, 64)); + EXPECT_THAT(map, + ElementsAre(Pair(1, 1), Pair(2, 4), Pair(8, 64), Pair(9, 81))); + for (int i : {1, 2, 8, 9}) { + EXPECT_TRUE(map.contains(i)); + } + for (int i : {3, 4, 5, 6, 7}) { + EXPECT_FALSE(map.contains(i)); + } +} + +TEST(LinkedHashMap, InsertInitializerList) { + linked_hash_map<int, int> m; + m.insert({{1, 7}, {2, 9}, {3, 29}}); + EXPECT_THAT(m, ElementsAre(Pair(1, 7), Pair(2, 9), Pair(3, 29))); +} + +TEST(LinkedHashMap, InsertOrAssign) { + linked_hash_map<int, int> m; + { + auto [itr, elem_inserted] = m.insert_or_assign(10, 20); + EXPECT_THAT(*itr, Pair(10, 20)); + EXPECT_EQ(elem_inserted, true); + } + { + auto [itr, elem_inserted] = m.insert_or_assign(10, 30); + EXPECT_THAT(*itr, Pair(10, 30)); + EXPECT_EQ(elem_inserted, false); + } +} + +TEST(LinkedHashMap, TryEmplace) { + linked_hash_map<int, std::pair<int, std::string>> m; + { + auto [itr, elem_inserted] = m.try_emplace(10, 20, "string"); + EXPECT_THAT(*itr, Pair(10, Pair(20, "string"))); + EXPECT_EQ(elem_inserted, true); + } + { + absl::test_internal::InstanceTracker tracker; + std::string s = "some string"; + auto [itr, elem_inserted] = m.try_emplace(10, 2, std::move(s)); + EXPECT_THAT(*itr, Pair(10, Pair(20, "string"))); + EXPECT_EQ(elem_inserted, false); + EXPECT_THAT(tracker.moves(), 0); + } +} + +TEST(LinkedHashMapTest, TryEmplace) { + linked_hash_map<int, std::tuple<int, std::string, std::unique_ptr<float>>> + map; + auto result = map.try_emplace(3, 2, "foo", new float(1.0)); + EXPECT_TRUE(result.second); + EXPECT_EQ(std::get<0>(result.first->second), 2); + EXPECT_EQ(std::get<1>(result.first->second), "foo"); + EXPECT_EQ(*std::get<2>(result.first->second), 1.0); + + // Ptr should not be moved. + auto ptr = std::make_unique<float>(3.0); + auto result2 = map.try_emplace(3, 22, "foo-bar", std::move(ptr)); + EXPECT_FALSE(result2.second); + EXPECT_EQ(std::get<0>(result.first->second), 2); + EXPECT_EQ(std::get<1>(result.first->second), "foo"); + EXPECT_EQ(*std::get<2>(result.first->second), 1.0); + EXPECT_NE(ptr.get(), nullptr); +} + +struct CountedHash { + explicit CountedHash(int* count) : count(count) {} + size_t operator()(int value) const { + ++(*count); + return value; + } + int* count = nullptr; +}; + +// Makes a map too big for small object optimization. Counts the number of +// hashes in `count`, but leaves `count` set to 0. +linked_hash_map<int, std::string, CountedHash> MakeNonSmallMap(int* count) { + const int kFirstKey = -1000; + linked_hash_map<int, std::string, CountedHash> m(0, CountedHash(count)); + for (int i = kFirstKey; i < kFirstKey + 100; ++i) { + m[i] = "foo"; + } + *count = 0; + return m; +} + +constexpr bool BuildHasDebugModeRehashes() { +#if !defined(NDEBUG) || defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) + return true; +#else + return false; +#endif +} + +TEST(LinkedHashMapTest, HashCountInOptBuilds) { + if (BuildHasDebugModeRehashes()) { + GTEST_SKIP() << "Only run under NDEBUG: `assert` statements and sanitizer " + "rehashing may cause redundant hashing."; + } + + using Map = linked_hash_map<int, std::string, CountedHash>; + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert({1, "foo"}); + EXPECT_EQ(count, 1); + m.erase(1); + EXPECT_EQ(count, 2); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m[2] = "bar"; + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert({3, "baz"}); + EXPECT_EQ(count, 1); + auto node = m.extract(3); + EXPECT_EQ(count, 2); + m.insert(std::move(node)); + EXPECT_EQ(count, 3); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert_or_assign(4, "qux"); + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.emplace(5, "vog"); + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.try_emplace(6, 'x', 42); + EXPECT_EQ(count, 1); + } + { + int src_count = 0, dst_count = 0; + Map src = MakeNonSmallMap(&src_count); + Map dst = MakeNonSmallMap(&dst_count); + src[7] = "siete"; + dst.merge(src); + EXPECT_LE(src_count, 200); + EXPECT_LE(dst_count, 200); + } +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/linked_hash_set.h b/absl/container/linked_hash_set.h new file mode 100644 index 0000000..f874cd1 --- /dev/null +++ b/absl/container/linked_hash_set.h
@@ -0,0 +1,524 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// File: linked_hash_set.h +// ----------------------------------------------------------------------------- +// +// This is a simple insertion-ordered set. It provides O(1) amortized +// insertions and lookups, as well as iteration over the set in the insertion +// order. +// +// This class is thread-compatible. +// +// Iterators point into the list and should be stable in the face of +// mutations, except for an iterator pointing to an element that was just +// deleted. +// +// This class supports heterogeneous lookups. + +#ifndef ABSL_CONTAINER_LINKED_HASH_SET_H_ +#define ABSL_CONTAINER_LINKED_HASH_SET_H_ + +#include <cassert> +#include <cstddef> +#include <initializer_list> +#include <list> +#include <memory> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/internal/common.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template < + typename Key, typename KeyHash = typename absl::flat_hash_set<Key>::hasher, + typename KeyEq = typename absl::flat_hash_set<Key, KeyHash>::key_equal, + typename Alloc = std::allocator<Key>> +class linked_hash_set { + using KeyArgImpl = absl::container_internal::KeyArg< + absl::container_internal::IsTransparent<KeyEq>::value && + absl::container_internal::IsTransparent<KeyHash>::value>; + + public: + using key_type = Key; + using hasher = KeyHash; + using key_equal = KeyEq; + using value_type = key_type; + using allocator_type = Alloc; + using difference_type = ptrdiff_t; + + private: + template <class K> + using key_arg = typename KeyArgImpl::template type<K, key_type>; + + using ListType = std::list<key_type, Alloc>; + + template <class Fn> + class Wrapped { + template <typename K> + static const K& ToKey(const K& k) { + return k; + } + static const key_type& ToKey(typename ListType::const_iterator it) { + return *it; + } + static const key_type& ToKey(typename ListType::iterator it) { return *it; } + + Fn fn_; + + friend linked_hash_set; + + public: + using is_transparent = void; + + Wrapped() = default; + explicit Wrapped(Fn fn) : fn_(std::move(fn)) {} + + template <class... Args> + auto operator()(Args&&... args) const + -> decltype(this->fn_(ToKey(args)...)) { + return fn_(ToKey(args)...); + } + }; + using SetType = + absl::flat_hash_set<typename ListType::iterator, Wrapped<hasher>, + Wrapped<key_equal>, Alloc>; + + class NodeHandle { + public: + using allocator_type = linked_hash_set::allocator_type; + using value_type = linked_hash_set::value_type; + + constexpr NodeHandle() noexcept = default; + NodeHandle(NodeHandle&& nh) noexcept = default; + ~NodeHandle() = default; + NodeHandle& operator=(NodeHandle&& node) noexcept = default; + bool empty() const noexcept { return list_.empty(); } + explicit operator bool() const noexcept { return !empty(); } + allocator_type get_allocator() const { return list_.get_allocator(); } + value_type& value() { return list_.front(); } + void swap(NodeHandle& nh) noexcept { list_.swap(nh.list_); } + + private: + friend linked_hash_set; + + explicit NodeHandle(ListType list) : list_(std::move(list)) {} + ListType list_; + }; + + template <class Iterator, class NodeType> + struct InsertReturnType { + Iterator position; + bool inserted; + NodeType node; + }; + + public: + using iterator = typename ListType::const_iterator; + using const_iterator = typename ListType::const_iterator; + using reverse_iterator = typename ListType::const_reverse_iterator; + using const_reverse_iterator = typename ListType::const_reverse_iterator; + using reference = typename ListType::reference; + using const_reference = typename ListType::const_reference; + using pointer = typename std::allocator_traits<allocator_type>::pointer; + using const_pointer = + typename std::allocator_traits<allocator_type>::const_pointer; + using size_type = typename ListType::size_type; + using node_type = NodeHandle; + using insert_return_type = InsertReturnType<iterator, node_type>; + + linked_hash_set() {} + + explicit linked_hash_set(size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : set_(bucket_count, Wrapped<hasher>(hash), Wrapped<key_equal>(eq), + alloc), + list_(alloc) {} + + linked_hash_set(size_t bucket_count, const hasher& hash, + const allocator_type& alloc) + : linked_hash_set(bucket_count, hash, key_equal(), alloc) {} + + linked_hash_set(size_t bucket_count, const allocator_type& alloc) + : linked_hash_set(bucket_count, hasher(), key_equal(), alloc) {} + + explicit linked_hash_set(const allocator_type& alloc) + : linked_hash_set(0, hasher(), key_equal(), alloc) {} + + template <class InputIt> + linked_hash_set(InputIt first, InputIt last, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_set(bucket_count, hash, eq, alloc) { + insert(first, last); + } + + template <class InputIter> + linked_hash_set(InputIter first, InputIter last, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_set(first, last, bucket_count, hash, key_equal(), alloc) {} + + template <class InputIter> + linked_hash_set(InputIter first, InputIter last, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_set(first, last, bucket_count, hasher(), key_equal(), + alloc) {} + + template <class InputIt> + linked_hash_set(InputIt first, InputIt last, const allocator_type& alloc) + : linked_hash_set(first, last, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_set(init.begin(), init.end(), bucket_count, hash, eq, + alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_set(init, bucket_count, hasher(), key_equal(), alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_set(init, bucket_count, hash, key_equal(), alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, + const allocator_type& alloc) + : linked_hash_set(init, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_set(const linked_hash_set& other) + : linked_hash_set(other.bucket_count(), other.hash_function(), + other.key_eq(), other.get_allocator()) { + CopyFrom(other); + } + + linked_hash_set(const linked_hash_set& other, const allocator_type& alloc) + : linked_hash_set(other.bucket_count(), other.hash_function(), + other.key_eq(), alloc) { + CopyFrom(other); + } + + linked_hash_set(linked_hash_set&& other) noexcept + : set_(std::move(other.set_)), list_(std::move(other.list_)) { + // Since the list and set must agree for other to end up "valid", + // explicitly clear them. + other.set_.clear(); + other.list_.clear(); + } + + linked_hash_set(linked_hash_set&& other, const allocator_type& alloc) + : linked_hash_set(0, other.hash_function(), other.key_eq(), alloc) { + if (get_allocator() == other.get_allocator()) { + *this = std::move(other); + } else { + CopyFrom(std::move(other)); + } + } + + linked_hash_set& operator=(const linked_hash_set& other) { + if (this == &other) return *this; + // Make a new set, with other's hash/eq/alloc. + set_ = SetType(other.bucket_count(), other.set_.hash_function(), + other.set_.key_eq(), other.get_allocator()); + // Copy the list, with other's allocator. + list_ = ListType(other.get_allocator()); + CopyFrom(other); + return *this; + } + + linked_hash_set& operator=(linked_hash_set&& other) noexcept { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + other.set_.clear(); + other.list_.clear(); + return *this; + } + + linked_hash_set& operator=(std::initializer_list<key_type> values) { + clear(); + insert(values.begin(), values.end()); + return *this; + } + + // Derive size from set_, as list::size might be O(N). + size_type size() const { return set_.size(); } + size_type max_size() const noexcept { return ~size_type{}; } + bool empty() const { return set_.empty(); } + + // Iteration is list-like, in insertion order. + // These are all forwarded. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + const_iterator cbegin() const { return list_.cbegin(); } + const_iterator cend() const { return list_.cend(); } + reverse_iterator rbegin() { return list_.rbegin(); } + reverse_iterator rend() { return list_.rend(); } + const_reverse_iterator rbegin() const { return list_.rbegin(); } + const_reverse_iterator rend() const { return list_.rend(); } + const_reverse_iterator crbegin() const { return list_.crbegin(); } + const_reverse_iterator crend() const { return list_.crend(); } + reference front() { return list_.front(); } + reference back() { return list_.back(); } + const_reference front() const { return list_.front(); } + const_reference back() const { return list_.back(); } + + void pop_front() { erase(begin()); } + void pop_back() { erase(std::prev(end())); } + + ABSL_ATTRIBUTE_REINITIALIZES void clear() { + set_.clear(); + list_.clear(); + } + + void reserve(size_t n) { set_.reserve(n); } + size_t bucket_count() const { return set_.bucket_count(); } + size_t capacity() const { return set_.capacity(); } + float load_factor() const { return set_.load_factor(); } + + hasher hash_function() const { return set_.hash_function().fn_; } + key_equal key_eq() const { return set_.key_eq().fn_; } + allocator_type get_allocator() const { return list_.get_allocator(); } + + template <typename K = key_type> + size_type erase(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return 0; + auto list_it = *found; + // Erase set entry first since it refers to the list element. + set_.erase(found); + list_.erase(list_it); + return 1; + } + + iterator erase(const_iterator position) { + auto found = set_.find(position); + assert(*found == position); + set_.erase(found); + return list_.erase(position); + } + + iterator erase(const_iterator first, const_iterator last) { + while (first != last) first = erase(first); + return first; + } + + template <typename K = key_type> + iterator find(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <typename K = key_type> + const_iterator find(const key_arg<K>& key) const { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <typename K = key_type> + size_t count(const key_arg<K>& key) const { + return contains(key) ? 1 : 0; + } + template <typename K = key_type> + bool contains(const key_arg<K>& key) const { + return set_.contains(key); + } + + template <typename K = key_type> + std::pair<iterator, iterator> equal_range(const key_arg<K>& key) { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <typename K = key_type> + std::pair<const_iterator, const_iterator> equal_range( + const key_arg<K>& key) const { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <typename K = key_type> + std::pair<iterator, bool> insert(const key_arg<K>& k) { + return InsertInternal(list_.end(), k); + } + template <typename K = key_type, K* = nullptr> + std::pair<iterator, bool> insert(key_arg<K>&& k) { + return InsertInternal(list_.end(), std::move(k)); + } + + template <typename K = key_type, + std::enable_if_t< + !std::is_convertible_v<const key_arg<K>&, const_iterator> && + !std::is_convertible_v<const key_arg<K>&, iterator>, + int> = 0> + iterator insert(const_iterator hint, const key_arg<K>& k) { + return InsertInternal(hint, k).first; + } + template < + typename K = key_type, K* = nullptr, + std::enable_if_t<!std::is_convertible_v<key_arg<K>&&, const_iterator> && + !std::is_convertible_v<key_arg<K>&&, iterator>, + int> = 0> + iterator insert(const_iterator hint, key_arg<K>&& k) { + return InsertInternal(hint, std::move(k)).first; + } + + void insert(std::initializer_list<key_type> ilist) { + insert(ilist.begin(), ilist.end()); + } + + template <class InputIt> + void insert(InputIt first, InputIt last) { + for (; first != last; ++first) insert(*first); + } + + insert_return_type insert(node_type&& node) { + if (node.empty()) return {end(), false, node_type()}; + if (auto [set_itr, inserted] = set_.emplace(node.list_.begin()); inserted) { + list_.splice(list_.end(), node.list_); + return {*set_itr, true, node_type()}; + } else { + return {*set_itr, false, std::move(node)}; + } + } + + iterator insert(const_iterator, node_type&& node) { + return insert(std::move(node)).first; + } + + template <typename... Args> + std::pair<iterator, bool> emplace(Args&&... args) { + return EmplaceInternal(list_.end(), std::forward<Args>(args)...); + } + + template <typename... Args> + iterator emplace_hint(const_iterator hint, Args&&... args) { + return EmplaceInternal(hint, std::forward<Args>(args)...).first; + } + + template <typename H, typename E> + void merge(linked_hash_set<Key, H, E, Alloc>& src) { + auto itr = src.list_.begin(); + while (itr != src.list_.end()) { + if (contains(*itr)) { + ++itr; + } else { + insert(src.extract(itr++)); + } + } + } + + template <typename H, typename E> + void merge(linked_hash_set<Key, H, E, Alloc>&& src) { + merge(src); + } + + node_type extract(const_iterator position) { + set_.erase(position); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, position); + return node_type(std::move(extracted_node_list)); + } + + template <class K = key_type, typename std::enable_if_t< + !std::is_same<K, iterator>::value, int> = 0> + node_type extract(const key_arg<K>& key) { + auto node = set_.extract(key); + if (node.empty()) return node_type(); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, node.value()); + return node_type(std::move(extracted_node_list)); + } + + void swap(linked_hash_set& other) noexcept { + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + } + + friend bool operator==(const linked_hash_set& a, const linked_hash_set& b) { + if (a.size() != b.size()) return false; + const linked_hash_set* outer = &a; + const linked_hash_set* inner = &b; + if (outer->capacity() > inner->capacity()) std::swap(outer, inner); + for (const value_type& elem : *outer) + if (!inner->contains(elem)) return false; + return true; + } + + friend bool operator!=(const linked_hash_set& a, const linked_hash_set& b) { + return !(a == b); + } + + void rehash(size_t n) { set_.rehash(n); } + + private: + template <typename Other> + void CopyFrom(Other&& other) { + for (auto& elem : other.list_) { + set_.insert(list_.insert(list_.end(), std::move(elem))); + } + assert(set_.size() == list_.size()); + } + + template <typename... Args> + std::pair<iterator, bool> EmplaceInternal(const_iterator hint, + Args&&... args) { + ListType node_donor; + auto list_iter = + node_donor.emplace(node_donor.end(), std::forward<Args>(args)...); + auto ins = set_.insert(list_iter); + if (!ins.second) return {*ins.first, false}; + list_.splice(hint, node_donor, list_iter); + return {list_iter, true}; + } + + template <typename U> + std::pair<iterator, bool> InsertInternal(const_iterator hint, + U&& key) { // NOLINT(build/c++11) + bool constructed = false; + auto set_iter = set_.lazy_emplace(key, [&](const auto& ctor) { + constructed = true; + ctor(list_.emplace(hint, std::forward<U>(key))); + }); + return {*set_iter, constructed}; + } + + // The set component, used for speedy lookups. + SetType set_; + + // The list component, used for maintaining insertion order. + ListType list_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_LINKED_HASH_SET_H_
diff --git a/absl/container/linked_hash_set_benchmark.cc b/absl/container/linked_hash_set_benchmark.cc new file mode 100644 index 0000000..e790e7d --- /dev/null +++ b/absl/container/linked_hash_set_benchmark.cc
@@ -0,0 +1,84 @@ +// 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. + +#include <algorithm> +#include <cstddef> +#include <string> +#include <vector> + +#include "absl/container/linked_hash_set.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +void BenchmarkInsertStrings(benchmark::State& state, + absl::FunctionRef<std::string(int)> factory) { + std::vector<std::string> sample; + size_t str_bytes = 0; + for (int i = 0; i < state.range(0); ++i) { + sample.push_back(factory(i)); + str_bytes += sample.back().size(); + } + + // Make a batch around 1Mi bytes. + const size_t batch_size = std::max(size_t{1}, size_t{1000000} / str_bytes); + std::vector<absl::linked_hash_set<std::string>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : sample) { + benchmark::DoNotOptimize(set.insert(str)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * str_bytes); +} + +constexpr absl::string_view kFormatShort = "%10d"; +constexpr absl::string_view kFormatLong = + "a longer string that exceeds the SSO %10d"; + +void BM_InsertShortStrings_Hit(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_InsertShortStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertLongStrings_Hit(benchmark::State& state) { + BenchmarkInsertStrings(state, + [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_InsertLongStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertShortStrings_Miss(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_InsertShortStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertLongStrings_Miss(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_InsertLongStrings_Miss)->Range(1, 1 << 16); + +} // namespace
diff --git a/absl/container/linked_hash_set_test.cc b/absl/container/linked_hash_set_test.cc new file mode 100644 index 0000000..b641b97 --- /dev/null +++ b/absl/container/linked_hash_set_test.cc
@@ -0,0 +1,917 @@ +// 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. + +#include "absl/container/linked_hash_set.h" + +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/container/internal/hash_generator_testing.h" +#include "absl/container/internal/hash_policy_testing.h" +#include "absl/container/internal/heterogeneous_lookup_testing.h" +#include "absl/container/internal/test_instance_tracker.h" +#include "absl/container/internal/unordered_set_constructor_test.h" +#include "absl/container/internal/unordered_set_lookup_test.h" +#include "absl/container/internal/unordered_set_members_test.h" +#include "absl/container/internal/unordered_set_modifiers_test.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Pointee; + +template <class T> +using Set = + linked_hash_set<T, StatefulTestingHash, StatefulTestingEqual, Alloc<T>>; + +using SetTypes = + ::testing::Types<Set<int>, Set<std::string>, Set<Enum>, Set<EnumClass>>; + +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, ConstructorTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, LookupTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, MembersTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, ModifiersTest, SetTypes); + +// Tests that the range constructor works. +TEST(LinkedHashSetTest, RangeConstruct) { + const auto items = {1, 2, 3}; + EXPECT_THAT(linked_hash_set<int>(items.begin(), items.end()), + ElementsAre(1, 2, 3)); +} + +// Tests that copying works. +TEST(LinkedHashSetTest, Copy) { + linked_hash_set<int> m{4, 8, 15, 16, 23, 42}; + auto copy = m; + + auto found = copy.find(8); + ASSERT_TRUE(found != copy.end()); + for (auto iter = copy.begin(); iter != copy.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Copied set's find method returned an invalid iterator."; +} + +// Tests that assignment works. +TEST(LinkedHashSetTest, Assign) { + linked_hash_set<int> m{2, 3}; + linked_hash_set<int> n{4}; + + n = m; + EXPECT_TRUE(n.contains(2)); + auto found = n.find(2); + ASSERT_TRUE(found != n.end()); + for (auto iter = n.begin(); iter != n.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Assigned set's find method returned an invalid iterator."; +} + +// Tests that move constructor works. +TEST(LinkedHashSetTest, Move) { + // Use unique_ptr as an example of a non-copyable type. + linked_hash_set<std::unique_ptr<int>> m; + m.insert(std::make_unique<int>(2)); + m.insert(std::make_unique<int>(3)); + linked_hash_set<std::unique_ptr<int>> n = std::move(m); + EXPECT_THAT(n, ElementsAre(Pointee(2), Pointee(3))); +} + +struct IntUniquePtrHash { + size_t operator()(const std::unique_ptr<int>& p) const { + return static_cast<size_t>(*p); + } +}; + +struct IntUniquePtrEq { + size_t operator()(const std::unique_ptr<int>& a, + const std::unique_ptr<int>& b) const { + return *a == *b; + } +}; + +// Pretty artificial for a set, but unique_ptr is a convenient move-only type. +TEST(LinkedHashSetTest, CanInsertMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (int x : data) s.insert(std::make_unique<int>(x)); + EXPECT_EQ(s.size(), data.size()); + for (const std::unique_ptr<int>& elt : s) { + EXPECT_TRUE(s.contains(elt)); + EXPECT_TRUE(s.find(elt) != s.end()); + } +} + +TEST(LinkedHashSetTest, CanMoveMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (int x : data) s.insert(std::make_unique<int>(x)); + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> ss = + std::move(s); + EXPECT_EQ(ss.size(), data.size()); +} + +TEST(LinkedHashSetTest, CanEmplaceMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (const int x : data) { + s.emplace(new int{x}); + } + EXPECT_EQ(s.size(), data.size()); + for (const std::unique_ptr<int>& elt : s) { + EXPECT_TRUE(s.contains(elt)); + EXPECT_TRUE(s.find(elt) != s.end()); + } +} + +TEST(LinkedHashSetTest, CanInsertTransparent) { + linked_hash_set<std::string> s; + s.insert(absl::string_view("foo")); + s.insert(absl::string_view("bar")); + s.insert(absl::string_view("foo")); + EXPECT_THAT(s, ElementsAre("foo", "bar")); +} + +// Tests that iteration from begin() to end() works +TEST(LinkedHashSetTest, Iteration) { + linked_hash_set<int> m; + EXPECT_TRUE(m.begin() == m.end()); + + m.insert(2); + m.insert(1); + m.insert(3); + + linked_hash_set<int>::iterator i = m.begin(); + ASSERT_TRUE(m.begin() == i); + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(2, *i); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(1, *i); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(3, *i); + + ++i; + ASSERT_TRUE(m.end() == i); +} + +// Tests that reverse iteration from rbegin() to rend() works +TEST(LinkedHashSetTest, ReverseIteration) { + linked_hash_set<int> m; + EXPECT_TRUE(m.rbegin() == m.rend()); + + m.insert(2); + m.insert(1); + m.insert(3); + + linked_hash_set<int>::reverse_iterator i = m.rbegin(); + ASSERT_TRUE(m.rbegin() == i); + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(3, *i); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(1, *i); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(2, *i); + + ++i; + ASSERT_TRUE(m.rend() == i); +} + +// Tests that clear() works +TEST(LinkedHashSetTest, Clear) { + linked_hash_set<int> m{2, 1, 3}; + ASSERT_EQ(3, m.size()); + + m.clear(); + EXPECT_EQ(0, m.size()); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.find(1) == m.end()); + + // Make sure we can call it on an empty set. + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests that size() works. +TEST(LinkedHashSetTest, Size) { + linked_hash_set<int> m; + EXPECT_EQ(0, m.size()); + m.insert(2); + EXPECT_EQ(1, m.size()); + m.insert(11); + EXPECT_EQ(2, m.size()); + m.insert(0); + EXPECT_EQ(3, m.size()); + m.insert(0); + EXPECT_EQ(3, m.size()); + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests empty() +TEST(LinkedHashSetTest, Empty) { + linked_hash_set<int> m; + ASSERT_TRUE(m.empty()); + m.insert(2); + ASSERT_FALSE(m.empty()); + m.clear(); + ASSERT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Erase) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(2); + ASSERT_EQ(1, m.size()); + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); + + EXPECT_EQ(0, m.erase(2)); // Make sure nothing bad happens if we repeat. + EXPECT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Erase2) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(2); + m.insert(1); + m.insert(3); + m.insert(4); + ASSERT_EQ(4, m.size()); + + // Erase middle two + EXPECT_EQ(1, m.erase(1)); + EXPECT_EQ(1, m.erase(3)); + + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_set<int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(2, *it); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(2, m.size()); + + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(1, m.erase(4)); + ASSERT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); +} + +// Test that erase(iter,iter) and erase(iter) compile and work. +TEST(LinkedHashSetTest, Erase3) { + linked_hash_set<int> m; + + m.insert(1); + m.insert(2); + m.insert(3); + m.insert(4); + + // Erase middle two + linked_hash_set<int>::iterator it2 = m.find(2); + linked_hash_set<int>::iterator it4 = m.find(4); + EXPECT_EQ(m.erase(it2, it4), m.find(4)); + EXPECT_FALSE(m.contains(2)); + EXPECT_TRUE(m.find(2) == m.end()); + EXPECT_FALSE(m.contains(3)); + EXPECT_TRUE(m.find(3) == m.end()); + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_set<int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(1, *it); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); + + // Erase first one using an iterator. + EXPECT_EQ(m.erase(m.begin()), m.find(4)); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.find(1) == m.end()); + + // Only the last element should be left. + EXPECT_TRUE(m.contains(4)); + it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); +} + +// Test all types of insertion +TEST(LinkedHashSetTest, Insertion) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + std::pair<linked_hash_set<int>::iterator, bool> result; + + result = m.insert(2); + ASSERT_EQ(1, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(2, *result.first); + EXPECT_TRUE(m.contains(2)); + EXPECT_TRUE(m.find(2) != m.end()); + + result = m.insert(1); + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(1, *result.first); + EXPECT_TRUE(m.contains(1)); + EXPECT_TRUE(m.find(1) != m.end()); + + result = m.insert(3); + linked_hash_set<int>::iterator result_iterator = result.first; + ASSERT_EQ(3, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(3, *result.first); + EXPECT_TRUE(m.contains(3)); + EXPECT_TRUE(m.find(3) != m.end()); + + result = m.insert(3); + EXPECT_EQ(3, m.size()); + EXPECT_FALSE(result.second) << "No insertion should have occurred."; + EXPECT_TRUE(result_iterator == result.first) + << "Duplicate insertion should have given us the original iterator."; + EXPECT_TRUE(m.contains(3)); + EXPECT_TRUE(m.find(3) != m.end()); + + std::vector<int> v = {3, 4, 5}; + m.insert(v.begin(), v.end()); + // Expect 4 and 5 inserted, 3 not inserted. + EXPECT_EQ(5, m.size()); + EXPECT_TRUE(m.contains(4)); + EXPECT_NE(m.find(4), m.end()); + EXPECT_TRUE(m.contains(5)); + EXPECT_NE(m.find(5), m.end()); +} + +TEST(LinkedHashSetTest, HintedInsertionMoveable) { + linked_hash_set<int> m = {1, 3}; + m.insert(m.find(3), 2); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedInsertionReference) { + linked_hash_set<int> m = {1, 3}; + const int val = 2; + m.insert(m.find(3), val); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedEmplaceMoveable) { + linked_hash_set<int> m = {1, 3}; + m.emplace_hint(m.find(3), 2); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedEmplaceReference) { + linked_hash_set<int> m = {1, 3}; + const int val = 2; + m.emplace_hint(m.find(3), val); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +// Test front accessors. +TEST(LinkedHashSetTest, Front) { + linked_hash_set<int> m; + + m.insert(222); + m.insert(111); + m.insert(333); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(222, m.front()); + m.pop_front(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(111, m.front()); + m.pop_front(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(333, m.front()); + m.pop_front(); + EXPECT_TRUE(m.empty()); +} + +// Test back accessors. +TEST(LinkedHashSetTest, Back) { + linked_hash_set<int> m; + + m.insert(222); + m.insert(111); + m.insert(333); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(333, m.back()); + m.pop_back(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(111, m.back()); + m.pop_back(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(222, m.back()); + m.pop_back(); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Find) { + linked_hash_set<int> m; + + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in an empty set."; + + m.insert(2); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find an element that doesn't exist in the set."; + + std::pair<linked_hash_set<int>::iterator, bool> result = m.insert(1); + ASSERT_TRUE(result.second); + ASSERT_TRUE(m.end() != result.first); + EXPECT_TRUE(result.first == m.find(1)) + << "We should have found an element we know exists in the set."; + EXPECT_EQ(1, *result.first); + + // Check that a follow-up insertion doesn't affect our original + m.insert(3); + linked_hash_set<int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(1, *it); + + m.clear(); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in a set that we've cleared."; +} + +TEST(LinkedHashSetTest, Contains) { + linked_hash_set<int> m; + + EXPECT_FALSE(m.contains(1)) << "The empty set shouldn't contain anything."; + + m.insert(2); + EXPECT_FALSE(m.contains(1)) + << "contains() should not return true for an element that doesn't exist " + << "in the set."; + + m.insert(1); + EXPECT_TRUE(m.contains(1)) + << "contains() should return true for an element we know exists in the " + << "set."; + + m.clear(); + EXPECT_FALSE(m.contains(1)) + << "A set that we've cleared shouldn't contain anything."; +} + +TEST(LinkedHashSetTest, Swap) { + linked_hash_set<int> m1; + linked_hash_set<int> m2; + m1.insert(1); + m1.insert(2); + m2.insert(3); + ASSERT_EQ(2, m1.size()); + ASSERT_EQ(1, m2.size()); + m1.swap(m2); + ASSERT_EQ(1, m1.size()); + ASSERT_EQ(2, m2.size()); +} + +TEST(LinkedHashSetTest, InitializerList) { + linked_hash_set<int> m{1, 3}; + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(1)); + linked_hash_set<int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(1, *it); + it = m.find(3); + EXPECT_TRUE(m.contains(3)); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(3, *it); +} + +TEST(LinkedHashSetTest, CustomHashAndEquality) { + struct CustomIntHash { + size_t operator()(int x) const { return 0; } + }; + struct CustomIntEq { + bool operator()(int x, int y) const { return abs(x) == abs(y); } + }; + linked_hash_set<int, CustomIntHash, CustomIntEq> m; + m.insert(1); + EXPECT_EQ(1, m.size()); + m.insert(2); + EXPECT_EQ(2, m.size()); + EXPECT_FALSE(m.insert(-2).second); + EXPECT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(-1)); + EXPECT_TRUE(m.find(-1) != m.end()); +} + +TEST(LinkedHashSetTest, EqualRange) { + linked_hash_set<int> m{3, 1}; + const auto& const_m = m; + + EXPECT_THAT(m.equal_range(2), testing::Pair(m.end(), m.end())); + EXPECT_THAT(const_m.equal_range(2), + testing::Pair(const_m.end(), const_m.end())); + + EXPECT_THAT(m.equal_range(1), testing::Pair(m.find(1), ++m.find(1))); + EXPECT_THAT(const_m.equal_range(1), + testing::Pair(const_m.find(1), ++const_m.find(1))); +} + +TEST(LinkedHashSetTest, ReserveWorks) { + linked_hash_set<int> m; + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.reserve(10); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.insert(1); + m.insert(2); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(2, m.size()); + EXPECT_LT(0.0, m.load_factor()); +} + +TEST(LinkedHashSetTest, HeterogeneousTests) { + absl::test_internal::InstanceTracker tracker; + + linked_hash_set<ExpensiveType, HeterogeneousHash, HeterogeneousEqual> set; + ExpensiveType one(1); + tracker.ResetCopiesMovesSwaps(); + set.insert(one); + // Two instances: 'one' var and an instance in the set. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.insert(one); + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(CheapType(1)); + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(CheapType(2)); + // Construction since key==2 doesn't exist in the set. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2))); + + // find + tracker.ResetCopiesMovesSwaps(); + auto itr = set.find(CheapType(1)); + ASSERT_NE(itr, set.end()); + EXPECT_EQ(1, itr->value()); + // contains + EXPECT_TRUE(set.contains(CheapType(2))); + // count + EXPECT_EQ(1, set.count(CheapType(2))); + // equal_range + auto eq_itr_pair = set.equal_range(CheapType(2)); + ASSERT_NE(eq_itr_pair.first, set.end()); + EXPECT_EQ(2, eq_itr_pair.first->value()); + // No construction for find, contains, count or equal_range. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + // emplace + tracker.ResetCopiesMovesSwaps(); + set.emplace(3); + // Just one construction. + EXPECT_EQ(4, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(3); + // No additional construction since key==3 exists. + EXPECT_EQ(4, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2), + HasExpensiveValue(3))); + + // Test std::move() using insert(). + ExpensiveType four(4); + tracker.ResetCopiesMovesSwaps(); + set.insert(std::move(four)); + // Two constructions (regular and move). + EXPECT_EQ(6, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(1, tracker.moves()); + + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2), + HasExpensiveValue(3), HasExpensiveValue(4))); + + tracker.ResetCopiesMovesSwaps(); + set.erase(CheapType(1)); + // No construction and instance reduced by one. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(2), HasExpensiveValue(3), + HasExpensiveValue(4))); +} + +TEST(LinkedHashSetTest, HeterogeneousStringViewLookup) { + linked_hash_set<std::string> set; + set.insert("foo"); + set.insert("bar"); + set.insert("blah"); + + { + absl::string_view lookup("foo"); + auto itr = set.find(lookup); + ASSERT_NE(itr, set.end()); + EXPECT_EQ("foo", *itr); + } + + // Not found. + { + absl::string_view lookup("foobar"); + EXPECT_EQ(set.end(), set.find(lookup)); + } + + { + absl::string_view lookup("blah"); + auto itr = set.find(lookup); + ASSERT_NE(itr, set.end()); + EXPECT_EQ("blah", *itr); + } +} + +TEST(LinkedHashSetTest, EmplaceString) { + std::vector<std::string> v = {"a", "b"}; + linked_hash_set<absl::string_view> hs(v.begin(), v.end()); + EXPECT_THAT(hs, ElementsAreArray(v)); +} + +TEST(LinkedHashSetTest, BitfieldArgument) { + union { + int n : 1; + }; + n = 0; + linked_hash_set<int> s = {n}; + s.insert(n); + s.insert(s.end(), n); + s.insert({n}); + s.erase(n); + s.count(n); + s.find(n); + s.contains(n); + s.equal_range(n); +} + +TEST(LinkedHashSetTest, MergeExtractInsert) { + struct Hash { + size_t operator()(const std::unique_ptr<int>& p) const { return *p; } + }; + struct Eq { + bool operator()(const std::unique_ptr<int>& a, + const std::unique_ptr<int>& b) const { + return *a == *b; + } + }; + linked_hash_set<std::unique_ptr<int>, Hash, Eq> set1, set2; + set1.insert(std::make_unique<int>(7)); + set1.insert(std::make_unique<int>(17)); + + set2.insert(std::make_unique<int>(7)); + set2.insert(std::make_unique<int>(19)); + + EXPECT_THAT(set1, ElementsAre(Pointee(7), Pointee(17))); + EXPECT_THAT(set2, ElementsAre(Pointee(7), Pointee(19))); + + set1.merge(set2); + + EXPECT_THAT(set1, ElementsAre(Pointee(7), Pointee(17), Pointee(19))); + EXPECT_THAT(set2, ElementsAre(Pointee(7))); + + auto node = set1.extract(std::make_unique<int>(7)); + EXPECT_TRUE(node); + EXPECT_THAT(node.value(), Pointee(7)); + EXPECT_THAT(set1, ElementsAre(Pointee(17), Pointee(19))); + + auto insert_result = set2.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_FALSE(insert_result.inserted); + EXPECT_TRUE(insert_result.node); + EXPECT_THAT(insert_result.node.value(), Pointee(7)); + EXPECT_EQ(**insert_result.position, 7); + EXPECT_NE(insert_result.position->get(), insert_result.node.value().get()); + EXPECT_THAT(set2, ElementsAre(Pointee(7))); + + node = set1.extract(std::make_unique<int>(17)); + EXPECT_TRUE(node); + EXPECT_THAT(node.value(), Pointee(17)); + EXPECT_THAT(set1, ElementsAre(Pointee(19))); + + node.value() = std::make_unique<int>(23); + + insert_result = set2.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_TRUE(insert_result.inserted); + EXPECT_FALSE(insert_result.node); + EXPECT_EQ(**insert_result.position, 23); + EXPECT_THAT(set2, ElementsAre(Pointee(7), Pointee(23))); +} + +TEST(LinkedHashSet, ExtractInsert) { + linked_hash_set<int> s = {1, 7, 2, 9}; + auto node = s.extract(1); + EXPECT_TRUE(node); + EXPECT_EQ(node.value(), 1); + EXPECT_THAT(s, ElementsAre(7, 2, 9)); + EXPECT_FALSE(s.contains(1)); + + node.value() = 17; + s.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_THAT(s, ElementsAre(7, 2, 9, 17)); + EXPECT_TRUE(s.contains(17)); + + node = s.extract(s.find(9)); + EXPECT_TRUE(node); + EXPECT_EQ(node.value(), 9); + EXPECT_THAT(s, ElementsAre(7, 2, 17)); + EXPECT_FALSE(s.contains(9)); +} + +TEST(LinkedHashSet, Merge) { + linked_hash_set<int> m = {1, 7, 3, 6, 10}; + linked_hash_set<int> src = {1, 2, 9, 10, 4, 16}; + + m.merge(src); + + EXPECT_THAT(m, ElementsAre(1, 7, 3, 6, 10, 2, 9, 4, 16)); + for (int i : {1, 7, 3, 6, 10, 2, 9, 4, 16}) { + EXPECT_TRUE(m.contains(i)); + } + EXPECT_THAT(src, ElementsAre(1, 10)); + for (int i : {1, 10}) { + EXPECT_TRUE(src.contains(i)); + } + for (int i : {2, 9, 4, 16}) { + EXPECT_FALSE(src.contains(i)); + } +} + +TEST(LinkedHashSet, EraseRange) { + linked_hash_set<int> set = {1, 2, 3, 4, 5, 25, 36, 7, 8, 9, 81}; + auto start = set.find(3); + auto end = set.find(8); + auto itr = set.erase(start, end); + ASSERT_NE(itr, set.end()); + EXPECT_THAT(*itr, 8); + EXPECT_THAT(set, ElementsAre(1, 2, 8, 9, 81)); + for (int i : {1, 2, 8, 9, 81}) { + EXPECT_TRUE(set.contains(i)); + } + for (int i : {3, 4, 5, 25, 36, 7}) { + EXPECT_FALSE(set.contains(i)); + } +} + +TEST(LinkedHashSet, InsertInitializerList) { + linked_hash_set<int> set; + set.insert({1, 7, 2, 9, 3, 29}); + EXPECT_THAT(set, ElementsAre(1, 7, 2, 9, 3, 29)); + for (int i : {1, 7, 2, 9, 3, 29}) { + EXPECT_TRUE(set.contains(i)); + } +} + +struct CountedHash { + explicit CountedHash(int* count) : count(count) {} + size_t operator()(int value) const { + ++(*count); + return value; + } + int* count = nullptr; +}; + +// Makes a set too big for small object optimization. Counts the number of +// hashes in `count`, but leaves `count` set to 0. +linked_hash_set<int, CountedHash> MakeNonSmallSet(int* count) { + const int kFirstKey = -1000; + linked_hash_set<int, CountedHash> s(0, CountedHash(count)); + for (int i = kFirstKey; i < kFirstKey + 100; ++i) { + s.insert(i); + } + *count = 0; + return s; +} + +constexpr bool BuildHasDebugModeRehashes() { +#if !defined(NDEBUG) || defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) + return true; +#else + return false; +#endif +} + +TEST(LinkedHashSetTest, HashCountInOptBuilds) { + if (BuildHasDebugModeRehashes()) { + GTEST_SKIP() << "Only run under NDEBUG: `assert` statements and sanitizer " + "rehashing may cause redundant hashing."; + } + + using Set = linked_hash_set<int, CountedHash>; + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.insert(1); + EXPECT_EQ(count, 1); + s.erase(1); + EXPECT_EQ(count, 2); + } + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.insert(3); + EXPECT_EQ(count, 1); + auto node = s.extract(3); + EXPECT_EQ(count, 2); + s.insert(std::move(node)); + EXPECT_EQ(count, 3); + } + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.emplace(5); + EXPECT_EQ(count, 1); + } + { + int src_count = 0, dst_count = 0; + Set src = MakeNonSmallSet(&src_count); + Set dst = MakeNonSmallSet(&dst_count); + src.insert(7); + dst.merge(src); + EXPECT_LE(src_count, 200); + EXPECT_LE(dst_count, 200); + } +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/node_hash_map.h b/absl/container/node_hash_map.h index b24db36..580a044 100644 --- a/absl/container/node_hash_map.h +++ b/absl/container/node_hash_map.h
@@ -122,13 +122,18 @@ // if (result != ducks.end()) { // std::cout << "Result: " << result->second << std::endl; // } -template <class Key, class Value, class Hash = DefaultHashContainerHash<Key>, - class Eq = DefaultHashContainerEq<Key>, - class Alloc = std::allocator<std::pair<const Key, Value>>> +template < + class Key, class Value, + class Hash = + typename container_internal::NodeHashMapPolicy<Key, Value>::DefaultHash, + class Eq = + typename container_internal::NodeHashMapPolicy<Key, Value>::DefaultEq, + class Alloc = typename container_internal::NodeHashMapPolicy< + Key, Value>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER node_hash_map - : public absl::container_internal::raw_hash_map< + : public absl::container_internal::InstantiateRawHashMap< absl::container_internal::NodeHashMapPolicy<Key, Value>, Hash, Eq, - Alloc> { + Alloc>::type { using Base = typename node_hash_map::raw_hash_map; public: @@ -629,6 +634,10 @@ using mapped_type = Value; using init_type = std::pair</*non const*/ key_type, mapped_type>; + using DefaultHash = DefaultHashContainerHash<Key>; + using DefaultEq = DefaultHashContainerEq<Key>; + using DefaultAlloc = std::allocator<std::pair<const Key, Value>>; + template <class Allocator, class... Args> static value_type* new_element(Allocator* alloc, Args&&... args) { using PairAlloc = typename absl::allocator_traits<
diff --git a/absl/container/node_hash_set.h b/absl/container/node_hash_set.h index 508248c..f69c6ab 100644 --- a/absl/container/node_hash_set.h +++ b/absl/container/node_hash_set.h
@@ -118,11 +118,16 @@ // if (ducks.contains("dewey")) { // std::cout << "We found dewey!" << std::endl; // } -template <class T, class Hash = DefaultHashContainerHash<T>, - class Eq = DefaultHashContainerEq<T>, class Alloc = std::allocator<T>> +template < + class T, + class Hash = typename container_internal::NodeHashSetPolicy<T>::DefaultHash, + class Eq = typename container_internal::NodeHashSetPolicy<T>::DefaultEq, + class Alloc = + typename container_internal::NodeHashSetPolicy<T>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER node_hash_set - : public absl::container_internal::raw_hash_set< - absl::container_internal::NodeHashSetPolicy<T>, Hash, Eq, Alloc> { + : public absl::container_internal::InstantiateRawHashSet< + absl::container_internal::NodeHashSetPolicy<T>, Hash, Eq, + Alloc>::type { using Base = typename node_hash_set::raw_hash_set; public: @@ -529,6 +534,10 @@ using init_type = T; using constant_iterators = std::true_type; + using DefaultHash = DefaultHashContainerHash<T>; + using DefaultEq = DefaultHashContainerEq<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static T* new_element(Allocator* alloc, Args&&... args) { using ValueAlloc =
diff --git a/absl/container/node_hash_set_test.cc b/absl/container/node_hash_set_test.cc index e616ac1..e1f5bd9 100644 --- a/absl/container/node_hash_set_test.cc +++ b/absl/container/node_hash_set_test.cc
@@ -35,8 +35,7 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; + using ::testing::IsEmpty; using ::testing::Pointee; using ::testing::UnorderedElementsAre;
diff --git a/absl/debugging/stacktrace.cc b/absl/debugging/stacktrace.cc index aee065d..1a770af 100644 --- a/absl/debugging/stacktrace.cc +++ b/absl/debugging/stacktrace.cc
@@ -53,17 +53,17 @@ #if defined(ABSL_STACKTRACE_INL_HEADER) #include ABSL_STACKTRACE_INL_HEADER #else -# error Cannot calculate stack trace: will need to write for your environment +#error Cannot calculate stack trace: will need to write for your environment -# include "absl/debugging/internal/stacktrace_aarch64-inl.inc" -# include "absl/debugging/internal/stacktrace_arm-inl.inc" -# include "absl/debugging/internal/stacktrace_emscripten-inl.inc" -# include "absl/debugging/internal/stacktrace_generic-inl.inc" -# include "absl/debugging/internal/stacktrace_powerpc-inl.inc" -# include "absl/debugging/internal/stacktrace_riscv-inl.inc" -# include "absl/debugging/internal/stacktrace_unimplemented-inl.inc" -# include "absl/debugging/internal/stacktrace_win32-inl.inc" -# include "absl/debugging/internal/stacktrace_x86-inl.inc" +#include "absl/debugging/internal/stacktrace_aarch64-inl.inc" +#include "absl/debugging/internal/stacktrace_arm-inl.inc" +#include "absl/debugging/internal/stacktrace_emscripten-inl.inc" +#include "absl/debugging/internal/stacktrace_generic-inl.inc" +#include "absl/debugging/internal/stacktrace_powerpc-inl.inc" +#include "absl/debugging/internal/stacktrace_riscv-inl.inc" +#include "absl/debugging/internal/stacktrace_unimplemented-inl.inc" +#include "absl/debugging/internal/stacktrace_win32-inl.inc" +#include "absl/debugging/internal/stacktrace_x86-inl.inc" #endif namespace absl {
diff --git a/absl/flags/flag.h b/absl/flags/flag.h index e052d5f..4c328e3 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h
@@ -241,7 +241,8 @@ /* default value argument. That keeps temporaries alive */ \ /* long enough for NonConst to work correctly. */ \ static constexpr absl::string_view Value( \ - absl::string_view absl_flag_help = ABSL_FLAG_IMPL_FLAGHELP(txt)) { \ + absl::string_view absl_flag_help ABSL_ATTRIBUTE_LIFETIME_BOUND = \ + ABSL_FLAG_IMPL_FLAGHELP(txt)) { \ return absl_flag_help; \ } \ static std::string NonConst() { return std::string(Value()); } \
diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index ca4a130..acdc880 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc
@@ -45,22 +45,7 @@ // AbslParseFlag specializations for boolean type. bool AbslParseFlag(absl::string_view text, bool* dst, std::string*) { - const char* kTrue[] = {"1", "t", "true", "y", "yes"}; - const char* kFalse[] = {"0", "f", "false", "n", "no"}; - static_assert(sizeof(kTrue) == sizeof(kFalse), "true_false_equal"); - - text = absl::StripAsciiWhitespace(text); - - for (size_t i = 0; i < ABSL_ARRAYSIZE(kTrue); ++i) { - if (absl::EqualsIgnoreCase(text, kTrue[i])) { - *dst = true; - return true; - } else if (absl::EqualsIgnoreCase(text, kFalse[i])) { - *dst = false; - return true; - } - } - return false; // didn't match a legal input + return SimpleAtob(absl::StripAsciiWhitespace(text), dst); } // --------------------------------------------------------------------
diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index df2a179..4961930 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc
@@ -206,8 +206,10 @@ std::string line; bool success = true; + int line_number = 0; while (std::getline(flag_file, line)) { + line_number++; absl::string_view stripped = absl::StripLeadingAsciiWhitespace(line); if (stripped.empty() || stripped[0] == '#') { @@ -229,8 +231,8 @@ } flags_internal::ReportUsageError( - absl::StrCat("Unexpected line in the flagfile ", flag_file_name, ": ", - line), + absl::StrCat("Unexpected line ", line_number, " in the flagfile ", + flag_file_name), true); success = false;
diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index 9997069..08eb81a 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc
@@ -827,7 +827,7 @@ flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args5), - "Unexpected line in the flagfile .*: \\*bin\\*"); + "Unexpected line 2 in the flagfile .*"); } // --------------------------------------------------------------------
diff --git a/absl/hash/hash_benchmark.cc b/absl/hash/hash_benchmark.cc index 73b037d..b39ef75 100644 --- a/absl/hash/hash_benchmark.cc +++ b/absl/hash/hash_benchmark.cc
@@ -338,6 +338,16 @@ }(); } // namespace +struct PodPairInt64 { + int64_t a; + int64_t b; + + template <typename H> + friend H AbslHashValue(H h, const PodPairInt64& p) { + return H::combine(std::move(h), p.a, p.b); + } +}; + template <class T> struct PodRand { static_assert(std::is_pod<T>::value, ""); @@ -378,6 +388,7 @@ MAKE_LATENCY_BENCHMARK(AbslHash, Int32, PodRand<int32_t>) MAKE_LATENCY_BENCHMARK(AbslHash, Int64, PodRand<int64_t>) +MAKE_LATENCY_BENCHMARK(AbslHash, PairInt64, PodRand<PodPairInt64>) MAKE_LATENCY_BENCHMARK(AbslHash, String3, StringRand<3>) MAKE_LATENCY_BENCHMARK(AbslHash, String5, StringRand<5>) MAKE_LATENCY_BENCHMARK(AbslHash, String9, StringRand<9>)
diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc index 500d1e0..89e0470 100644 --- a/absl/hash/hash_test.cc +++ b/absl/hash/hash_test.cc
@@ -1284,7 +1284,7 @@ constexpr char kMinChar = 0; constexpr char kMaxChar = 64; // These sizes cover the different hashing cases. - for (size_t size : {8u, 16u, 32u, 64u}) { + for (size_t size : {8u, 16u, 32u, 64u, 128u}) { for (size_t b = 0; b < size - 1; ++b) { absl::flat_hash_set<std::string> set; std::string s(size, '\0');
diff --git a/absl/hash/internal/hash.cc b/absl/hash/internal/hash.cc index 6b02f19..23caae9 100644 --- a/absl/hash/internal/hash.cc +++ b/absl/hash/internal/hash.cc
@@ -26,7 +26,6 @@ #include "absl/base/prefetch.h" #include "absl/hash/internal/city.h" - #ifdef ABSL_AES_INTERNAL_HAVE_X86_SIMD #error ABSL_AES_INTERNAL_HAVE_X86_SIMD cannot be directly set #elif defined(__SSE4_2__) && defined(__AES__) @@ -46,18 +45,20 @@ namespace { -uint64_t Mix32Bytes(const uint8_t* ptr, uint64_t current_state) { - uint64_t a = absl::base_internal::UnalignedLoad64(ptr); - uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); - uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); - uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); - - uint64_t cs0 = Mix(a ^ kStaticRandomData[1], b ^ current_state); - uint64_t cs1 = Mix(c ^ kStaticRandomData[2], d ^ current_state); - return cs0 ^ cs1; +void PrefetchFutureDataToLocalCache(const uint8_t* ptr) { + PrefetchToLocalCache(ptr + 5 * ABSL_CACHELINE_SIZE); } #ifdef ABSL_AES_INTERNAL_HAVE_X86_SIMD +uint64_t Mix4x16Vectors(__m128i a, __m128i b, __m128i c, __m128i d) { + // res128 = encrypt(a + c, d) + decrypt(b - d, a) + auto res128 = _mm_add_epi64(_mm_aesenc_si128(_mm_add_epi64(a, c), d), + _mm_aesdec_si128(_mm_sub_epi64(b, d), a)); + auto x64 = static_cast<uint64_t>(_mm_cvtsi128_si64(res128)); + auto y64 = static_cast<uint64_t>(_mm_extract_epi64(res128, 1)); + return x64 ^ y64; +} + uint64_t LowLevelHash33To64(uint64_t seed, const uint8_t* ptr, size_t len) { assert(len > 32); assert(len <= 64); @@ -84,13 +85,82 @@ // We perform another round of encryption to mix bits between two halves of // the input. - auto res128 = _mm_add_epi64(_mm_aesenc_si128(_mm_add_epi64(na, nc), nd), - _mm_aesdec_si128(_mm_sub_epi64(nb, nd), na)); - auto x64 = static_cast<uint64_t>(_mm_cvtsi128_si64(res128)); - auto y64 = static_cast<uint64_t>(_mm_extract_epi64(res128, 1)); - return x64 ^ y64; + return Mix4x16Vectors(na, nb, nc, nd); +} + +[[maybe_unused]] ABSL_ATTRIBUTE_NOINLINE uint64_t +LowLevelHashLenGt64(uint64_t seed, const void* data, size_t len) { + assert(len > 64); + const uint8_t* ptr = static_cast<const uint8_t*>(data); + const uint8_t* last_32_ptr = ptr + len - 32; + + // If we have more than 64 bytes, we're going to handle chunks of 64 + // bytes at a time. We're going to build up four separate hash states + // which we will then hash together. This avoids short dependency chains. + __m128i state0 = + _mm_set_epi64x(static_cast<int64_t>(seed), static_cast<int64_t>(len)); + __m128i state1 = state0; + __m128i state2 = state1; + __m128i state3 = state2; + + // Mixing two 128-bit vectors at a time with corresponding states. + // All variables are mixed slightly differently to avoid hash collision + // due to trivial byte rotation. + // We combine state and data with _mm_add_epi64/_mm_sub_epi64 before applying + // AES encryption to make hash function dependent on the order of the blocks. + // See comments in LowLevelHash33To64 for more considerations. + auto mix_ab = [&state0, + &state1](const uint8_t* p) ABSL_ATTRIBUTE_ALWAYS_INLINE { + // i128 a = *p; + // i128 b = *(p + 16); + // state0 = decrypt(state0 + a, state0); + // state1 = decrypt(state1 - b, state1); + auto a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p)); + auto b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16)); + state0 = _mm_aesdec_si128(_mm_add_epi64(state0, a), state0); + state1 = _mm_aesdec_si128(_mm_sub_epi64(state1, b), state1); + }; + auto mix_cd = [&state2, + &state3](const uint8_t* p) ABSL_ATTRIBUTE_ALWAYS_INLINE { + // i128 c = *p; + // i128 d = *(p + 16); + // state2 = encrypt(state2 + c, state2); + // state3 = encrypt(state3 - d, state3); + auto c = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p)); + auto d = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16)); + state2 = _mm_aesenc_si128(_mm_add_epi64(state2, c), state2); + state3 = _mm_aesenc_si128(_mm_sub_epi64(state3, d), state3); + }; + + do { + PrefetchFutureDataToLocalCache(ptr); + mix_ab(ptr); + mix_cd(ptr + 32); + + ptr += 64; + len -= 64; + } while (len > 64); + + // We now have a data `ptr` with at most 64 bytes. + if (len > 32) { + mix_ab(ptr); + } + mix_cd(last_32_ptr); + + return Mix4x16Vectors(state0, state1, state2, state3); } #else +uint64_t Mix32Bytes(const uint8_t* ptr, uint64_t current_state) { + uint64_t a = absl::base_internal::UnalignedLoad64(ptr); + uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); + uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); + uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); + + uint64_t cs0 = Mix(a ^ kStaticRandomData[1], b ^ current_state); + uint64_t cs1 = Mix(c ^ kStaticRandomData[2], d ^ current_state); + return cs0 ^ cs1; +} + uint64_t LowLevelHash33To64(uint64_t seed, const uint8_t* ptr, size_t len) { assert(len > 32); assert(len <= 64); @@ -98,7 +168,6 @@ const uint8_t* last_32_ptr = ptr + len - 32; return Mix32Bytes(last_32_ptr, Mix32Bytes(ptr, current_state)); } -#endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD [[maybe_unused]] ABSL_ATTRIBUTE_NOINLINE uint64_t LowLevelHashLenGt64(uint64_t seed, const void* data, size_t len) { @@ -114,7 +183,7 @@ uint64_t duplicated_state2 = current_state; do { - PrefetchToLocalCache(ptr + 5 * ABSL_CACHELINE_SIZE); + PrefetchFutureDataToLocalCache(ptr); uint64_t a = absl::base_internal::UnalignedLoad64(ptr); uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); @@ -148,6 +217,7 @@ // safely read from `ptr + len - 32`. return Mix32Bytes(last_32_ptr, current_state); } +#endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD [[maybe_unused]] uint64_t LowLevelHashLenGt32(uint64_t seed, const void* data, size_t len) {
diff --git a/absl/hash/internal/low_level_hash_test.cc b/absl/hash/internal/low_level_hash_test.cc index cdd279b..d054337 100644 --- a/absl/hash/internal/low_level_hash_test.cc +++ b/absl/hash/internal/low_level_hash_test.cc
@@ -380,28 +380,28 @@ 0xe4c78173c7ea537b, 0x0bbdc2bcabdb50b1, 0xd9aa134df2d87623, 0x6c4907c9477a9409, 0xc3e418a5dbda52e5, 0x4d24f3e9d0dda93a, 0xcdb565a363dbe45f, 0xa95f228c8ee57478, 0x6b8f00bab5130227, - 0x2d05a0f44818b67a, 0xa64b55b071afbbea, 0xa205bfe6c724ce4d, - 0x69dd26ca8ac21744, 0xef80e2ff2f6a9bc0, 0xde266c0baa202c20, - 0xfa3463080ac74c50, 0x379d968a40125c2b, 0x4cbbd0a7b3c7d648, - 0xc92afd93f4c665d2, 0x6e28f5adb7ae38dc, 0x7c689c9c237be35e, - 0xaea41b29bd9d0f73, 0x832cef631d77e59f, 0x70cac8e87bc37dd3, - 0x8e8c98bbde68e764, 0xd6117aeb3ddedded, 0xd796ab808e766240, - 0x8953d0ea1a7d9814, 0xa212eba4281b391c, 0x21a555a8939ce597, - 0x809d31660f6d81a8, 0x2356524b20ab400f, 0x5bc611e1e49d0478, - 0xba9c065e2f385ce2, 0xb0a0fd12f4e83899, 0x14d076a35b1ff2ca, - 0x8acd0bb8cf9a93c0, 0xe62e8ec094039ee4, 0x38a536a7072bdc61, - 0xca256297602524f8, 0xfc62ebfb3530caeb, 0x8d8b0c05520569f6, - 0xbbaca65cf154c59d, 0x3739b5ada7e338d3, 0xdb9ea31f47365340, - 0x410b5c9c1da56755, 0x7e0abc03dbd10283, 0x136f87be70ed442e, - 0x6b727d4feddbe1e9, 0x074ebb21183b01df, 0x3fe92185b1985484, - 0xc5d8efd3c68305ca, 0xd9bada21b17e272e, 0x64d73133e1360f83, - 0xeb8563aa993e21f9, 0xe5e8da50cceab28f, 0x7a6f92eb3223d2f3, - 0xbdaf98370ea9b31b, 0x1682a84457f077bc, 0x4abd2d33b6e3be37, - 0xb35bc81a7c9d4c04, 0x3e5bde3fb7cfe63d, 0xff3abe6e2ffec974, - 0xb8116dd26cf6feec, 0x7a77a6e4ed0cf081, 0xb71eec2d5a184316, - 0x6fa932f77b4da817, 0x795f79b33909b2c4, 0x1b8755ef6b5eb34e, - 0x2255b72d7d6b2d79, 0xf2bdafafa90bd50a, 0x442a578f02cb1fc8, - 0xc25aefe55ecf83db, 0x3114c056f9c5a676, + 0x2d05a0f44818b67a, 0xd6bf7d990b5f44cb, 0xa3608bdb4712861a, + 0xf20c33e5e355330b, 0xbc86e1b13130180d, 0x0848221b397b839a, + 0x17cc0acf44a7e210, 0xc18c6dc584fe0f62, 0x896c7858a59f991d, + 0xeab1e6d7d2856ed7, 0x7e4b2d99c23edc51, 0x9aeeeb7fa46e7cf0, + 0x161b9f2e3611790f, 0x5f82aae18d971b36, 0x8d0dd9965881e162, + 0x56700ea26285895a, 0xcd919c86c29a053e, 0x3e5d589282d9a722, + 0x92caee9f48a66604, 0x7e1a2fd9b06f14b0, 0xce1d5293f95b0178, + 0x8101361290e70a11, 0x570e3e9c9eafc1c6, 0x77b6241926a7a568, + 0x313e5cb34f346699, 0xab8ebeab0514b82b, 0x6e0a43763a310408, + 0x761b76ec22b2e440, 0x4238c84a9ec00528, 0xb9ea1f6d4d5552af, + 0xd21f8f110b9dc060, 0xb3d3842b69ac3689, 0xd0a88aa1dcf59869, + 0xf3f69f637b123403, 0xf5f34b1068cac7da, 0xe69a08d604774abf, + 0x57648d3a73332437, 0x9762947f5013d00d, 0x35c5d734a0015922, + 0xbee2fe5a104ce209, 0xedb060efa6efca34, 0x5ccf0f4786d97bc2, + 0x1ef8ed72e80d7bef, 0x58522deb49c5e30f, 0xde97cd2a6f8bd13b, + 0x3fae37c6f9855d09, 0xea99ae786feca261, 0x8c6d1d46670b0943, + 0x84658b2a232c7bfb, 0x7058b7a7968de394, 0x0d44fba68e25aa8f, + 0xc7f687020f8eb00b, 0xbf9671e1196153d6, 0x1009be891b7f83e7, + 0x4f9457fb4aa12865, 0x30a49d9563643b32, 0x0302e2c5b46d5a3a, + 0x77553f42fb0bfbf7, 0x26b95e89f0077110, 0x76ce68ebe01191ba, + 0x724110fb509e4376, 0xebe74b016b5cfb88, 0x3b0fe11dcf175fc9, + 0x20b737b9c0490538, 0x0db21c429b45fd17, }; #else constexpr uint64_t kGolden[kNumGoldenOutputs] = {
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 51f399e..9d097ab 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt
@@ -1204,3 +1204,36 @@ absl::log_internal_fnmatch GTest::gmock_main ) + +absl_cc_library( + NAME + log_internal_container + HDRS + "internal/container.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::requires_internal + absl::strings +) + +absl_cc_test( + NAME + internal_container_test + SRCS + "internal/container_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_container + absl::span + absl::strings + absl::str_format + GTest::gmock_main +)
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index bb20a95..32ae277 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel
@@ -548,3 +548,34 @@ "@google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "container", + hdrs = ["container.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + "//absl/meta:requires", + "//absl/strings", + ], +) + +cc_test( + name = "container_test", + srcs = ["container_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":container", + "//absl/base:config", + "//absl/strings", + "//absl/strings:str_format", + "//absl/types:span", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/log/internal/container.h b/absl/log/internal/container.h new file mode 100644 index 0000000..1144652 --- /dev/null +++ b/absl/log/internal/container.h
@@ -0,0 +1,312 @@ +// 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. +// +// The typical use looks like this: +// +// LOG(INFO) << LogContainer(container); +// +// By default, LogContainer() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +// +// Policies can be specified: +// +// LOG(INFO) << LogContainer(container, LogMultiline()); +// +// The above example will print the container using newlines between elements, +// enclosed in [] braces. +// +// See below for further details on policies. + +#ifndef ABSL_LOG_INTERNAL_CONTAINER_H_ +#define ABSL_LOG_INTERNAL_CONTAINER_H_ + +#include <cstdint> +#include <limits> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/meta/internal/requires.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Several policy classes below determine how LogRangeToStream will +// format a range of items. A Policy class should have these methods: +// +// Called to print an individual container element. +// void Log(ostream &out, const ElementT &element) const; +// +// Called before printing the set of elements: +// void LogOpening(ostream &out) const; +// +// Called after printing the set of elements: +// void LogClosing(ostream &out) const; +// +// Called before printing the first element: +// void LogFirstSeparator(ostream &out) const; +// +// Called before printing the remaining elements: +// void LogSeparator(ostream &out) const; +// +// Returns the maximum number of elements to print: +// int64 MaxElements() const; +// +// Called to print an indication that MaximumElements() was reached: +// void LogEllipsis(ostream &out) const; + +namespace internal { + +struct LogBase { + template <typename ElementT> + void Log(std::ostream &out, const ElementT &element) const { // NOLINT + // Fallback to `AbslStringify` if the type does not have `operator<<`. + if constexpr (meta_internal::Requires<std::ostream, ElementT>( + [](auto&& x, auto&& y) -> decltype(x << y) {})) { + out << element; + } else { + out << absl::StrCat(element); + } + } + void LogEllipsis(std::ostream &out) const { // NOLINT + out << "..."; + } +}; + +struct LogShortBase : public LogBase { + void LogOpening(std::ostream &out) const { out << "["; } // NOLINT + void LogClosing(std::ostream &out) const { out << "]"; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << ""; } // NOLINT + void LogSeparator(std::ostream &out) const { out << ", "; } // NOLINT +}; + +struct LogMultilineBase : public LogBase { + void LogOpening(std::ostream &out) const { out << "["; } // NOLINT + void LogClosing(std::ostream &out) const { out << "\n]"; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << "\n"; } // NOLINT + void LogSeparator(std::ostream &out) const { out << "\n"; } // NOLINT +}; + +struct LogLegacyBase : public LogBase { + void LogOpening(std::ostream &out) const { out << ""; } // NOLINT + void LogClosing(std::ostream &out) const { out << ""; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << ""; } // NOLINT + void LogSeparator(std::ostream &out) const { out << " "; } // NOLINT +}; + +} // namespace internal + +// LogShort uses [] braces and separates items with comma-spaces. For +// example "[1, 2, 3]". +struct LogShort : public internal::LogShortBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// LogShortUpToN(max_elements) formats the same as LogShort but prints no more +// than the max_elements elements. +class LogShortUpToN : public internal::LogShortBase { + public: + explicit LogShortUpToN(int64_t max_elements) : max_elements_(max_elements) {} + int64_t MaxElements() const { return max_elements_; } + + private: + int64_t max_elements_; +}; + +// LogShortUpTo100 formats the same as LogShort but prints no more +// than 100 elements. +struct LogShortUpTo100 : public LogShortUpToN { + LogShortUpTo100() : LogShortUpToN(100) {} +}; + +// LogMultiline uses [] braces and separates items with +// newlines. For example "[ +// 1 +// 2 +// 3 +// ]". +struct LogMultiline : public internal::LogMultilineBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// LogMultilineUpToN(max_elements) formats the same as LogMultiline but +// prints no more than max_elements elements. +class LogMultilineUpToN : public internal::LogMultilineBase { + public: + explicit LogMultilineUpToN(int64_t max_elements) + : max_elements_(max_elements) {} + int64_t MaxElements() const { return max_elements_; } + + private: + int64_t max_elements_; +}; + +// LogMultilineUpTo100 formats the same as LogMultiline but +// prints no more than 100 elements. +struct LogMultilineUpTo100 : public LogMultilineUpToN { + LogMultilineUpTo100() : LogMultilineUpToN(100) {} +}; + +// The legacy behavior of LogSequence() does not use braces and +// separates items with spaces. For example "1 2 3". +struct LogLegacyUpTo100 : public internal::LogLegacyBase { + int64_t MaxElements() const { return 100; } +}; +struct LogLegacy : public internal::LogLegacyBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// The default policy for new code. +typedef LogShortUpTo100 LogDefault; + +// LogRangeToStream should be used to define operator<< for +// STL and STL-like containers. For example, see stl_logging.h. +template <typename IteratorT, typename PolicyT> +inline void LogRangeToStream(std::ostream &out, // NOLINT + IteratorT begin, IteratorT end, + const PolicyT &policy) { + policy.LogOpening(out); + for (int64_t i = 0; begin != end && i < policy.MaxElements(); ++i, ++begin) { + if (i == 0) { + policy.LogFirstSeparator(out); + } else { + policy.LogSeparator(out); + } + policy.Log(out, *begin); + } + if (begin != end) { + policy.LogSeparator(out); + policy.LogEllipsis(out); + } + policy.LogClosing(out); +} + +namespace detail { + +// RangeLogger is a helper class for LogRange and LogContainer; do not use it +// directly. This object captures iterators into the argument of the LogRange +// and LogContainer functions, so its lifetime should be confined to a single +// logging statement. Objects of this type should not be assigned to local +// variables. +template <typename IteratorT, typename PolicyT> +class RangeLogger { + public: + RangeLogger(const IteratorT &begin, const IteratorT &end, + const PolicyT &policy) + : begin_(begin), end_(end), policy_(policy) {} + + friend std::ostream &operator<<(std::ostream &out, const RangeLogger &range) { + LogRangeToStream<IteratorT, PolicyT>(out, range.begin_, range.end_, + range.policy_); + return out; + } + + // operator<< above is generally recommended. However, some situations may + // require a string, so a convenience str() method is provided as well. + std::string str() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } + + private: + IteratorT begin_; + IteratorT end_; + PolicyT policy_; +}; + +template <typename E> +class EnumLogger { + public: + explicit EnumLogger(E e) : e_(e) {} + + friend std::ostream &operator<<(std::ostream &out, const EnumLogger &v) { + using I = typename std::underlying_type<E>::type; + return out << static_cast<I>(v.e_); + } + + private: + E e_; +}; + +} // namespace detail + +// Log a range using "policy". For example: +// +// LOG(INFO) << LogRange(start_pos, end_pos, LogMultiline()); +// +// The above example will print the range using newlines between +// elements, enclosed in [] braces. +template <typename IteratorT, typename PolicyT> +detail::RangeLogger<IteratorT, PolicyT> LogRange(const IteratorT &begin, + const IteratorT &end, + const PolicyT &policy) { + return detail::RangeLogger<IteratorT, PolicyT>(begin, end, policy); +} + +// Log a range. For example: +// +// LOG(INFO) << LogRange(start_pos, end_pos); +// +// By default, Range() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +template <typename IteratorT> +detail::RangeLogger<IteratorT, LogDefault> LogRange(const IteratorT &begin, + const IteratorT &end) { + return LogRange(begin, end, LogDefault()); +} + +// Log a container using "policy". For example: +// +// LOG(INFO) << LogContainer(container, LogMultiline()); +// +// The above example will print the container using newlines between +// elements, enclosed in [] braces. +template <typename ContainerT, typename PolicyT> +auto LogContainer(const ContainerT& container, const PolicyT& policy) + -> decltype(LogRange(container.begin(), container.end(), policy)) { + return LogRange(container.begin(), container.end(), policy); +} + +// Log a container. For example: +// +// LOG(INFO) << LogContainer(container); +// +// By default, Container() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +template <typename ContainerT> +auto LogContainer(const ContainerT& container) + -> decltype(LogContainer(container, LogDefault())) { + return LogContainer(container, LogDefault()); +} + +// Log a (possibly scoped) enum. For example: +// +// enum class Color { kRed, kGreen, kBlue }; +// LOG(INFO) << LogEnum(kRed); +template <typename E> +detail::EnumLogger<E> LogEnum(E e) { + static_assert(std::is_enum<E>::value, "must be an enum"); + return detail::EnumLogger<E>(e); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONTAINER_H_
diff --git a/absl/log/internal/container_test.cc b/absl/log/internal/container_test.cc new file mode 100644 index 0000000..0a5a058 --- /dev/null +++ b/absl/log/internal/container_test.cc
@@ -0,0 +1,254 @@ +// 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. + +#include "absl/log/internal/container.h" + +#include <cstdint> +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +class ContainerLoggingTest : public ::testing::Test { + protected: + ContainerLoggingTest() : stream_(new std::stringstream) {} + std::ostream& stream() { return *stream_; } + std::string logged() { + std::string r = stream_->str(); + stream_ = std::make_unique<std::stringstream>(); + return r; + } + + private: + std::unique_ptr<std::stringstream> stream_; +}; + +TEST_F(ContainerLoggingTest, ShortRange) { + std::vector<std::string> words = {"hi", "hello"}; + LogRangeToStream(stream(), words.begin(), words.end(), LogMultiline()); + EXPECT_EQ("[\nhi\nhello\n]", logged()); +} + +TEST_F(ContainerLoggingTest, LegacyRange) { + std::vector<int> lengths = {1, 2}; + LogRangeToStream(stream(), lengths.begin(), lengths.end(), + LogLegacyUpTo100()); + EXPECT_EQ("1 2", logged()); +} + +TEST_F(ContainerLoggingTest, ToString) { + std::vector<int> lengths = {1, 2, 3, 4, 5}; + EXPECT_EQ(LogContainer(lengths).str(), "[1, 2, 3, 4, 5]"); +} + +class UserDefFriend { + public: + explicit UserDefFriend(int i) : i_(i) {} + + private: + friend std::ostream& operator<<(std::ostream& str, const UserDefFriend& i) { + return str << i.i_; + } + int i_; +}; + +TEST_F(ContainerLoggingTest, RangeOfUserDefined) { + std::vector<UserDefFriend> ints = {UserDefFriend(1), UserDefFriend(2), + UserDefFriend(3)}; + LogRangeToStream(stream(), ints.begin(), ints.begin() + 1, LogDefault()); + LogRangeToStream(stream(), ints.begin() + 1, ints.begin() + 2, + LogMultiline()); + LogRangeToStream(stream(), ints.begin() + 2, ints.begin() + 3, LogDefault()); + LogRangeToStream(stream(), ints.begin(), ints.begin(), LogMultiline()); + + EXPECT_EQ("[1][\n2\n][3][\n]", logged()); +} + +TEST_F(ContainerLoggingTest, FullContainer) { + std::vector<int> ints; + std::vector<int> ints100; + std::vector<int> ints123; + int64_t max_elements = 123; + std::string expected1; + std::string expected2; + std::string expected3; + std::string expected4; + std::string expected5; + std::string expected6; + std::string expected7; + std::string expected8; + std::string expected9; + for (int i = 0; i < 1000; ++i) { + ints.push_back(i); + if (i < 100) { + ints100.push_back(i); + } + if (i < max_elements) { + ints123.push_back(i); + } + } + expected1 = "[\n" + absl::StrJoin(ints, "\n") + "\n]"; + expected2 = "[" + absl::StrJoin(ints, ", ") + "]"; + expected3 = "[\n" + absl::StrJoin(ints100, "\n") + "\n...\n]"; + expected4 = "[" + absl::StrJoin(ints100, ", ") + ", ...]"; + expected5 = absl::StrJoin(ints100, " ") + " ..."; + expected6 = "[\n" + absl::StrJoin(ints, "\n") + "\n]"; + expected7 = "[\n" + absl::StrJoin(ints123, "\n") + "\n...\n]"; + expected8 = "[" + absl::StrJoin(ints, ", ") + "]"; + expected9 = "[" + absl::StrJoin(ints123, ", ") + ", ...]"; + + LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultiline()); + EXPECT_EQ(expected1, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogShort()); + EXPECT_EQ(expected2, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultilineUpTo100()); + EXPECT_EQ(expected3, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogShortUpTo100()); + EXPECT_EQ(expected4, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogLegacyUpTo100()); + EXPECT_EQ(expected5, logged()); + + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogMultilineUpToN(ints.size())); + EXPECT_EQ(expected6, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogMultilineUpToN(max_elements)); + EXPECT_EQ(expected7, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogShortUpToN(ints.size())); + EXPECT_EQ(expected8, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogShortUpToN(max_elements)); + EXPECT_EQ(expected9, logged()); +} + +TEST_F(ContainerLoggingTest, LogContainer) { + std::set<int> ints = {1, 2, 3}; + stream() << LogContainer(ints, LogMultiline()); + EXPECT_EQ("[\n1\n2\n3\n]", logged()); + + stream() << LogContainer(ints); + EXPECT_EQ("[1, 2, 3]", logged()); + + stream() << LogContainer(std::vector<int>(ints.begin(), ints.end()), + LogLegacyUpTo100()); + EXPECT_EQ("1 2 3", logged()); +} + +TEST_F(ContainerLoggingTest, LogMutableSpan) { + std::vector<int> ints = {1, 2, 3}; + absl::Span<int> int_span(ints); + stream() << LogContainer(int_span); + EXPECT_EQ("[1, 2, 3]", logged()); +} + +TEST_F(ContainerLoggingTest, LogRange) { + std::set<int> ints = {1, 2, 3}; + stream() << LogRange(ints.begin(), ints.end(), LogMultiline()); + EXPECT_EQ("[\n1\n2\n3\n]", logged()); + + stream() << LogRange(ints.begin(), ints.end()); + EXPECT_EQ("[1, 2, 3]", logged()); +} + +// Some class with a custom Stringify +class C { + public: + explicit C(int x) : x_(x) {} + + private: + // This is intentionally made private for the purposes of the test; + //` AbslStringify` isn't meant to be called directly, and instead invoked + // via `StrCat` and friends. + template <typename Sink> + friend void AbslStringify(Sink& sink, const C& p) { + absl::Format(&sink, "C(%d)", p.x_); + } + + int x_; +}; + +TEST_F(ContainerLoggingTest, LogContainerWithCustomStringify) { + std::vector<C> c = {C(1), C(2), C(3)}; + stream() << LogContainer(c); + EXPECT_EQ("[C(1), C(2), C(3)]", logged()); +} + +class LogEnumTest : public ContainerLoggingTest { + protected: + enum Unscoped { kUnscoped0, kUnscoped1, kUnscoped2 }; + + enum StreamableUnscoped { + kStreamableUnscoped0, + kStreamableUnscoped1, + kStreamableUnscoped2 + }; + + enum class Scoped { k0, k1, k2 }; + + enum class StreamableScoped { k0, k1, k2 }; + + friend std::ostream& operator<<(std::ostream& os, StreamableUnscoped v) { + return os << LogEnum(v); + } + + friend std::ostream& operator<<(std::ostream& os, StreamableScoped v) { + return os << LogEnum(v); + } +}; + +TEST_F(LogEnumTest, Unscoped) { + stream() << LogEnum(kUnscoped0) << "," << LogEnum(kUnscoped1) << "," + << LogEnum(kUnscoped2); + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, StreamableUnscoped) { + stream() << kStreamableUnscoped0 << "," << kStreamableUnscoped1 << "," + << kStreamableUnscoped2; + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, Scoped) { + stream() << LogEnum(Scoped::k0) << "," << LogEnum(Scoped::k1) << "," + << LogEnum(Scoped::k2); + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, StreamableScoped) { + // Test using LogEnum to implement an operator<<. + stream() << StreamableScoped::k0 << "," << StreamableScoped::k1 << "," + << StreamableScoped::k2; + EXPECT_EQ("0,1,2", logged()); +} + +} // namespace +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel index f6efa42..26468c6 100644 --- a/absl/meta/BUILD.bazel +++ b/absl/meta/BUILD.bazel
@@ -35,6 +35,57 @@ licenses(["notice"]) cc_library( + name = "constexpr_testing", + testonly = 1, + hdrs = ["internal/constexpr_testing.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "constexpr_testing_test", + srcs = ["internal/constexpr_testing_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":constexpr_testing", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "requires", + hdrs = ["internal/requires.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "requires_test", + srcs = ["internal/requires_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":requires", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( name = "type_traits", hdrs = ["type_traits.h"], copts = ABSL_DEFAULT_COPTS,
diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt index d509114..c98c360 100644 --- a/absl/meta/CMakeLists.txt +++ b/absl/meta/CMakeLists.txt
@@ -16,6 +16,52 @@ absl_cc_library( NAME + constexpr_testing_internal + HDRS + "internal/constexpr_testing.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +absl_cc_test( + NAME + constexpr_testing_test + SRCS + "internal/constexpr_testing_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::constexpr_testing_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + requires_internal + HDRS + "internal/requires.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +absl_cc_test( + NAME + requires_test + SRCS + "internal/requires_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::requires_internal + GTest::gmock_main +) + +absl_cc_library( + NAME type_traits HDRS "type_traits.h"
diff --git a/absl/meta/internal/constexpr_testing.h b/absl/meta/internal/constexpr_testing.h new file mode 100644 index 0000000..eddf64b --- /dev/null +++ b/absl/meta/internal/constexpr_testing.h
@@ -0,0 +1,73 @@ +// 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_META_INTERNAL_CONSTEXPR_TESTING_H_ +#define ABSL_META_INTERNAL_CONSTEXPR_TESTING_H_ + +#include <type_traits> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace meta_internal { + +// HasConstexprEvaluation([] { ... }) will evaluate to `true` if the +// lambda can be evaluated in a constant expression and `false` +// otherwise. +// The return type of the lambda is not relevant, as long as the whole +// evaluation works in a constant expression. +template <typename F> +constexpr bool HasConstexprEvaluation(F f); + +/// Implementation details below /// + +namespace internal_constexpr_evaluation { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wuninitialized" +#endif +// This will give a constexpr instance of `F`. +// This works for captureless lambdas because they have no state and the copy +// constructor does not look at the input reference. +template <typename F> +constexpr F default_instance = default_instance<F>; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template <typename F> +constexpr std::integral_constant<bool, (default_instance<F>(), true)> Tester( + int) { + return {}; +} + +template <typename S> +constexpr std::false_type Tester(char) { + return {}; +} + +} // namespace internal_constexpr_evaluation + +template <typename F> +constexpr bool HasConstexprEvaluation(F) { + return internal_constexpr_evaluation::Tester<F>(0); +} + +} // namespace meta_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_META_INTERNAL_CONSTEXPR_TESTING_H_
diff --git a/absl/meta/internal/constexpr_testing_test.cc b/absl/meta/internal/constexpr_testing_test.cc new file mode 100644 index 0000000..50c8c53 --- /dev/null +++ b/absl/meta/internal/constexpr_testing_test.cc
@@ -0,0 +1,40 @@ +// 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. + +#include "absl/meta/internal/constexpr_testing.h" + +#include <map> +#include <string_view> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +TEST(ConstexprTesting, Basic) { + using absl::meta_internal::HasConstexprEvaluation; + + EXPECT_TRUE(HasConstexprEvaluation([] {})); + static constexpr int const_global = 7; + EXPECT_TRUE(HasConstexprEvaluation([] { return const_global; })); + EXPECT_TRUE(HasConstexprEvaluation([] { return 0; })); + EXPECT_TRUE(HasConstexprEvaluation([] { return std::string_view{}; })); + + static int nonconst_global; + EXPECT_FALSE(HasConstexprEvaluation([] { return nonconst_global; })); + EXPECT_FALSE(HasConstexprEvaluation([] { std::abort(); })); + EXPECT_FALSE(HasConstexprEvaluation([] { return std::map<int, int>(); })); +} + +} // namespace
diff --git a/absl/meta/internal/requires.h b/absl/meta/internal/requires.h new file mode 100644 index 0000000..2166e99 --- /dev/null +++ b/absl/meta/internal/requires.h
@@ -0,0 +1,67 @@ +// Copyright 2017 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_META_INTERNAL_REQUIRES_H_ +#define ABSL_META_INTERNAL_REQUIRES_H_ + +#include <type_traits> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace meta_internal { + +// C++17 port of the C++20 `requires` expressions. +// It allows easy inline test of properties of types in template code. +// https://en.cppreference.com/w/cpp/language/constraints#Requires_expressions +// +// Example usage: +// +// if constexpr (Requires<T>([](auto&& x) -> decltype(x.foo()) {})) { +// // T has foo() +// return t.foo(); +// } else if constexpr (Requires<T>([](auto&& x) -> decltype(Bar(x)) {})) { +// // Can call Bar with T +// return Bar(t); +// } else if constexpr (Requires<T, U>( +// // Can test expression with multiple inputs +// [](auto&& x, auto&& y) -> decltype(x + y) {})) { +// return t + t2; +// } +// +// The `Requires` function takes a list of types and a generic lambda where all +// arguments are of type `auto&&`. The lambda is never actually invoked and the +// body must be empty. +// When used this way, `Requires` returns whether the expression inside +// `decltype` is well-formed, when the lambda parameters have the types that +// are specified by the corresponding template arguments. +// +// NOTE: C++17 does not allow lambdas in template parameters, which means that +// code like the following is _not_ valid in C++17: +// +// template <typename T, +// typename = std::enable_if_t<gtl::Requires<T>( +// [] (auto&& v) -> decltype(<expr>) {})>> +// +template <typename... T, typename F> +constexpr bool Requires(F) { + return std::is_invocable_v<F, T...>; +} + +} // namespace meta_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_META_INTERNAL_REQUIRES_H_
diff --git a/absl/meta/internal/requires_test.cc b/absl/meta/internal/requires_test.cc new file mode 100644 index 0000000..046d8f3 --- /dev/null +++ b/absl/meta/internal/requires_test.cc
@@ -0,0 +1,66 @@ +// Copyright 2017 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. + +#include "absl/meta/internal/requires.h" + +#include <string> +#include <vector> + +#include "gtest/gtest.h" + +namespace { + +TEST(RequiresTest, SimpleLambdasWork) { + static_assert(absl::meta_internal::Requires([] {})); + static_assert(absl::meta_internal::Requires<int>([](auto&&) {})); + static_assert( + absl::meta_internal::Requires<int, char>([](auto&&, auto&&) {})); +} + +template <typename T> +inline constexpr bool has_cstr = + absl::meta_internal::Requires<T>([](auto&& x) -> decltype(x.c_str()) {}); + +template <typename T, typename U> +inline constexpr bool have_plus = absl::meta_internal::Requires<T, U>( + [](auto&& x, auto&& y) -> decltype(x + y) {}); + +TEST(RequiresTest, CanTestProperties) { + static_assert(has_cstr<std::string>); + static_assert(!has_cstr<std::vector<int>>); + + static_assert(have_plus<int, double>); + static_assert(have_plus<std::string, std::string>); + static_assert(!have_plus<std::string, double>); +} + +TEST(RequiresTest, WorksWithUnmovableTypes) { + struct S { + S(const S&) = delete; + int foo() { return 0; } + }; + static_assert( + absl::meta_internal::Requires<S>([](auto&& x) -> decltype(x.foo()) {})); + static_assert( + !absl::meta_internal::Requires<S>([](auto&& x) -> decltype(x.bar()) {})); +} + +TEST(RequiresTest, WorksWithArrays) { + static_assert( + absl::meta_internal::Requires<int[2]>([](auto&& x) -> decltype(x[1]) {})); + static_assert( + !absl::meta_internal::Requires<int[2]>([](auto&& x) -> decltype(-x) {})); +} + +} // namespace
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 9dde5e5..a1e5021 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -36,16 +36,13 @@ cc_library( name = "string_view", - srcs = ["string_view.cc"], hdrs = ["string_view.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:nullability", - "//absl/base:throw_delegate", ], ) @@ -410,23 +407,6 @@ ], ) -cc_binary( - name = "string_view_benchmark", - testonly = True, - srcs = ["string_view_benchmark.cc"], - copts = ABSL_TEST_COPTS, - tags = ["benchmark"], - visibility = ["//visibility:private"], - deps = [ - ":string_view", - ":strings", - "//absl/base:core_headers", - "//absl/base:raw_logging_internal", - "//absl/random", - "@google_benchmark//:benchmark_main", - ], -) - cc_test( name = "string_view_test", size = "small", @@ -1571,3 +1551,43 @@ "@googletest//:gtest_main", ], ) + +cc_library( + name = "generic_printer", + srcs = [ + "internal/generic_printer.cc", + "internal/generic_printer_internal.h", + ], + hdrs = ["internal/generic_printer.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":str_format", + ":strings", + "//absl/base:config", + "//absl/log/internal:container", + "//absl/meta:requires", + ], +) + +cc_test( + name = "generic_printer_test", + srcs = ["internal/generic_printer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":generic_printer", + ":strings", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/log", + "//absl/status", + "//absl/status:statusor", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 2b97fb5..a03943d 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt
@@ -19,16 +19,12 @@ string_view HDRS "string_view.h" - SRCS - "string_view.cc" COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::base absl::config absl::core_headers absl::nullability - absl::throw_delegate PUBLIC ) @@ -1260,3 +1256,40 @@ absl::strings GTest::gmock_main ) + +absl_cc_library( + NAME + generic_printer_internal + SRCS + "internal/generic_printer.cc" + "internal/generic_printer_internal.h" + HDRS + "internal/generic_printer.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::strings + absl::str_format + absl::log_internal_container + absl::requires_internal +) + +absl_cc_test( + NAME + generic_printer_test + SRCS + "internal/generic_printer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::config + absl::flat_hash_map + absl::generic_printer_internal + absl::log + absl::status + absl::statusor + absl::strings + GTest::gmock_main +)
diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 89aa603..2f8cbc1 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc
@@ -32,8 +32,8 @@ #include "absl/base/nullability.h" #include "absl/strings/ascii.h" #include "absl/strings/charset.h" +#include "absl/strings/internal/append_and_overwrite.h" #include "absl/strings/internal/escaping.h" -#include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/utf8.h" #include "absl/strings/numbers.h" #include "absl/strings/resize_and_overwrite.h" @@ -446,22 +446,22 @@ // We keep 3 slop bytes so that we can call `little_endian::Store32` // invariably regardless of the length of the escaped character. - constexpr size_t slop_bytes = 3; + constexpr size_t kSlopBytes = 3; size_t cur_dest_len = dest->size(); - size_t new_dest_len = cur_dest_len + escaped_len + slop_bytes; - ABSL_INTERNAL_CHECK(new_dest_len > cur_dest_len, "std::string size overflow"); - strings_internal::AppendUninitializedTraits<std::string>::Append( - dest, escaped_len + slop_bytes); - char* append_ptr = &(*dest)[cur_dest_len]; - - for (char c : src) { - unsigned char uc = static_cast<unsigned char>(c); - size_t char_len = kCEscapedLen[uc]; - uint32_t little_endian_uint32 = kCEscapedLittleEndianUint32Array[uc]; - little_endian::Store32(append_ptr, little_endian_uint32); - append_ptr += char_len; - } - dest->resize(new_dest_len - slop_bytes); + size_t append_buf_len = cur_dest_len + escaped_len + kSlopBytes; + ABSL_INTERNAL_CHECK(append_buf_len > cur_dest_len, + "std::string size overflow"); + strings_internal::StringAppendAndOverwrite( + *dest, append_buf_len, [src, escaped_len](char* append_ptr, size_t) { + for (char c : src) { + unsigned char uc = static_cast<unsigned char>(c); + size_t char_len = kCEscapedLen[uc]; + uint32_t little_endian_uint32 = kCEscapedLittleEndianUint32Array[uc]; + little_endian::Store32(append_ptr, little_endian_uint32); + append_ptr += char_len; + } + return escaped_len; + }); } // Reverses the mapping in Base64EscapeInternal; see that method's
diff --git a/absl/strings/internal/generic_printer.cc b/absl/strings/internal/generic_printer.cc new file mode 100644 index 0000000..16ca228 --- /dev/null +++ b/absl/strings/internal/generic_printer.cc
@@ -0,0 +1,107 @@ +// 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. + +#include "absl/strings/internal/generic_printer.h" + +#include <cstddef> +#include <cstdlib> +#include <ostream> +#include <string> + +#include "absl/base/config.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_generic_printer { + +// Out-of-line helper for PrintAsStringWithEscaping. +std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v) { + return os << "\"" << absl::CHexEscape(v) << "\""; +} + +// Retuns a string representation of 'v', shortened if possible. +template <class T, class F> +std::string TryShorten(T v, F strtox) { + std::string printed = + absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 / 2, v); + T parsed = strtox(printed.data()); + if (parsed != v) { + printed = + absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 + 1, v); + } + return printed; +} + +// Out-of-line helpers for floating point values. These don't necessarily +// ensure that values are precise, but rather that they are wide enough to +// represent distinct values. go/c++17std/numeric.limits.members.html +std::ostream& PrintPreciseFP(std::ostream& os, float v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtof(buf, &unused); + }) << "f"; +} +std::ostream& PrintPreciseFP(std::ostream& os, double v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtod(buf, &unused); + }); +} +std::ostream& PrintPreciseFP(std::ostream& os, long double v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtold(buf, &unused); + }) << "L"; +} + +// Prints a nibble of 'v' in hexadecimal. +inline char hexnib(int v) { + return static_cast<char>((v < 10 ? '0' : ('a' - 10)) + v); +} + +template <typename T> +static std::ostream& PrintCharImpl(std::ostream& os, T v) { + // Specialization for chars: print as 'c' if printable, otherwise + // hex-escaped. + return (absl::ascii_isprint(static_cast<unsigned char>(v)) + ? (os << (v == '\'' ? "'\\" : "'") << v) + : (os << "'\\x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf))) + << "' (0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf) << " " + << static_cast<int>(v) << ")"; +} + +std::ostream& PrintChar(std::ostream& os, char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintChar(std::ostream& os, signed char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintChar(std::ostream& os, unsigned char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintByte(std::ostream& os, std::byte b) { + auto v = std::to_integer<int>(b); + os << "0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf); + return os; +} + +} // namespace internal_generic_printer +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/strings/internal/generic_printer.h b/absl/strings/internal/generic_printer.h new file mode 100644 index 0000000..ed60155 --- /dev/null +++ b/absl/strings/internal/generic_printer.h
@@ -0,0 +1,115 @@ +// 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_STRINGS_INTERNAL_GENERIC_PRINTER_H_ +#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_ + +#include "absl/strings/internal/generic_printer_internal.h" // IWYU pragma: export + +// Helpers for printing objects in generic code. +// +// These functions help with streaming in generic code. It may be desirable, for +// example, to log values from generic functions; however, operator<< may not be +// overloaded for all types. +// +// The helpers in this library use, in order of precedence: +// +// 1. std::string and std::string_view are quoted and escaped. (The specific +// format is not guaranteed to be stable.) +// 2. A defined AbslStringify() method. +// 3. absl::log_internal::LogContainer, if the object is a STL container. +// 4. For std::tuple, std::pair, and std::optional, recursively calls +// GenericPrint for each element. +// 5. Floating point values are printed with enough precision for round-trip. +// 6. char values are quoted, with non-printable values escaped, and the +// char's numeric value is included. +// 7. A defined operator<< overload (which should be found by ADL). +// 8. A defined DebugString() const method. +// 9. A fallback value with basic information otherwise. +// +// Note that the fallback value means that if no formatting conversion is +// defined, you will not see a compile-time error. This also means that +// GenericPrint() can safely be used in generic template contexts, and can +// format any types needed (even though the output will vary). +// +// Example usage: +// +// // All values after GenericPrint() are formatted: +// LOG(INFO) << GenericPrint() +// << int_var // <- printed normally +// << float_var // <- sufficient precision for round-trip +// << " unchanged literal text "; +// +// // Just one value is formatted: +// LOG(INFO) << GenericPrint(string("this is quoted and escaped\t\n")) +// << GenericPrint("but not this, "); +// << string("and not this."); +// +// To make a type loggable with GenericPrint, prefer defining operator<< as a +// friend function. For example: +// +// class TypeToLog { +// public: +// string ToString() const; // Many types already implement this. +// // Define out-of-line if it is complex. +// friend std::ostream& operator<<(std::ostream& os, const TypeToLog& v) { +// return os << v.ToString(); // OK to define in-line instead, if simple. +// } +// }; +// +// (Defining operator<< as an inline friend free function allows it to be found +// by Argument-Dependent Lookup, or ADL, which is the mechanism typically used +// for operator overload resolution. An inline friend function is the tightest +// scope possible for overloading the left-hand side of an operator.) + +#include <ostream> +#include <utility> + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +// Helper for logging values in generic code. +// +// Example usage: +// +// template <typename T> +// void GenericFunction() { +// T v1, v2; +// VLOG(1) << GenericPrint() << v1 << v2; // GenericPrint everything +// VLOG(1) << GenericPrint(v1) << v2; // GenericPrint just v1 +// } +// +inline constexpr internal_generic_printer::GenericPrintAdapterFactory + GenericPrint{}; + +// Generic printer type: this class can be used, for example, as a template +// argument to allow users to provide alternative printing strategies. +// +// For example, to allow callers to provide a custom strategy: +// +// template <typename T, typename PrinterT = GenericPrinter<T>> +// void GenericFunction() { +// T value; +// VLOG(1) << PrinterT{value}; +// } +// +template <typename T> +using GenericPrinter = internal_generic_printer::GenericPrinter<T>; + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
diff --git a/absl/strings/internal/generic_printer_internal.h b/absl/strings/internal/generic_printer_internal.h new file mode 100644 index 0000000..1a2d0bd --- /dev/null +++ b/absl/strings/internal/generic_printer_internal.h
@@ -0,0 +1,423 @@ +// 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_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_ +#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <ostream> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <variant> +#include <vector> + +#include "absl/base/config.h" +#include "absl/log/internal/container.h" +#include "absl/meta/internal/requires.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +// NOTE: we do not want to expand the dependencies of this library. All +// compatibility detection must be done in a generic way, without having to +// include the headers of other libraries. + +// Internal implementation details: see generic_printer.h. +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_generic_printer { + +template <typename T> +std::ostream& GenericPrintImpl(std::ostream& os, const T& v); + +// Scope blocker: we must always use ADL in our predicates below. +struct Anonymous; +std::ostream& operator<<(const Anonymous&, const Anonymous&) = delete; + +// Logging policy for LogContainer. Our SFINAE overload will not fail +// if the contained type cannot be printed, so make sure to circle back to +// GenericPrinter. +struct ContainerLogPolicy { + void LogOpening(std::ostream& os) const { os << "["; } + void LogClosing(std::ostream& os) const { os << "]"; } + void LogFirstSeparator(std::ostream& os) const { os << ""; } + void LogSeparator(std::ostream& os) const { os << ", "; } + int64_t MaxElements() const { return 15; } + template <typename T> + void Log(std::ostream& os, const T& element) const { + internal_generic_printer::GenericPrintImpl(os, element); + } + void LogEllipsis(std::ostream& os) const { os << "..."; } +}; + +// Out-of-line helper for PrintAsStringWithEscaping. +std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v); + +// Trait to recognize a string, possibly with a custom allocator. +template <class T> +inline constexpr bool is_any_string = false; +template <class A> +inline constexpr bool + is_any_string<std::basic_string<char, std::char_traits<char>, A>> = true; + +// Trait to recognize a supported pointer. Below are documented reasons why +// raw pointers and std::shared_ptr are not currently supported. +// See also http://b/239459272#comment9 and the discussion in the comment +// threads of cl/485200996. +// +// The tl;dr: +// 1. Pointers that logically represent an object (or nullptr) are safe to print +// out. +// 2. Pointers that may represent some other concept (delineating memory bounds, +// overridden managed-memory deleters to mimic RAII, ...) may be unsafe to +// print out. +// +// raw pointers: +// - A pointer one-past the last element of an array +// - Non-null but non-dereferenceable: https://gcc.godbolt.org/z/sqsqGvKbP +// +// `std::shared_ptr`: +// - "Aliasing" / similar to raw pointers: https://gcc.godbolt.org/z/YbWPzvhae +template <class T, class = void> +inline constexpr bool is_supported_ptr = false; +// `std::unique_ptr` has the same theoretical risks as raw pointers, but those +// risks are far less likely (typically requiring a custom deleter), and the +// benefits of supporting unique_ptr outweigh the costs. +// - Note: `std::unique_ptr<T[]>` cannot safely be and is not dereferenced. +template <class A, class... Deleter> +inline constexpr bool is_supported_ptr<std::unique_ptr<A, Deleter...>> = true; +// `ArenaSafeUniquePtr` is at least as safe as `std::unique_ptr`. +template <class T> +inline constexpr bool is_supported_ptr< + T, + // Check for `ArenaSafeUniquePtr` without having to include its header here. + // This does match any type named `ArenaSafeUniquePtr`, regardless of the + // namespace it is defined in, but it's pretty plausible that any such type + // would be a fork. + decltype(T().~ArenaSafeUniquePtr())> = true; + +// Specialization for floats: print floating point types using their +// max_digits10 precision. This ensures each distinct underlying values +// can be represented uniquely, even though it's not (strictly speaking) +// the most precise representation. +std::ostream& PrintPreciseFP(std::ostream& os, float v); +std::ostream& PrintPreciseFP(std::ostream& os, double v); +std::ostream& PrintPreciseFP(std::ostream& os, long double v); + +std::ostream& PrintChar(std::ostream& os, char c); +std::ostream& PrintChar(std::ostream& os, signed char c); +std::ostream& PrintChar(std::ostream& os, unsigned char c); + +std::ostream& PrintByte(std::ostream& os, std::byte b); + +template <class... Ts> +std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& tuple) { + absl::string_view sep = ""; + const auto print_one = [&](const auto& v) { + os << sep; + (GenericPrintImpl)(os, v); + sep = ", "; + }; + os << "<"; + std::apply([&](const auto&... v) { (print_one(v), ...); }, tuple); + os << ">"; + return os; +} + +template <typename T, typename U> +std::ostream& PrintPair(std::ostream& os, const std::pair<T, U>& p) { + os << "<"; + (GenericPrintImpl)(os, p.first); + os << ", "; + (GenericPrintImpl)(os, p.second); + os << ">"; + return os; +} + +template <typename T> +std::ostream& PrintOptionalLike(std::ostream& os, const T& v) { + if (v.has_value()) { + os << "<"; + (GenericPrintImpl)(os, *v); + os << ">"; + } else { + (GenericPrintImpl)(os, std::nullopt); + } + return os; +} + +template <typename... Ts> +std::ostream& PrintVariant(std::ostream& os, const std::variant<Ts...>& v) { + os << "("; + os << "'(index = " << v.index() << ")' "; + + // NOTE(derekbailey): This may throw a std::bad_variant_access if the variant + // is "valueless", which only occurs if exceptions are thrown. This is + // non-relevant when not using exceptions, but it is worth mentioning if that + // invariant is ever changed. + std::visit([&](const auto& arg) { (GenericPrintImpl)(os, arg); }, v); + os << ")"; + return os; +} + +template <typename StatusOrLike> +std::ostream& PrintStatusOrLike(std::ostream& os, const StatusOrLike& v) { + os << "<"; + if (v.ok()) { + os << "OK: "; + (GenericPrintImpl)(os, *v); + } else { + (GenericPrintImpl)(os, v.status()); + } + os << ">"; + return os; +} + +template <typename SmartPointer> +std::ostream& PrintSmartPointerContents(std::ostream& os, + const SmartPointer& v) { + os << "<"; + if (v == nullptr) { + (GenericPrintImpl)(os, nullptr); + } else { + // Cast to void* so that every type (e.g. `char*`) is printed as an address. + os << absl::implicit_cast<const void*>(v.get()) << " pointing to "; + + if constexpr (meta_internal::Requires<SmartPointer>( + [](auto&& p) -> decltype(p[0]) {})) { + // e.g. std::unique_ptr<int[]>, which only has operator[] + os << "an array"; + } else if constexpr (std::is_object_v< + typename SmartPointer::element_type>) { + (GenericPrintImpl)(os, *v); + } else { + // e.g. std::unique_ptr<void, MyCustomDeleter> + os << "a non-object type"; + } + } + os << ">"; + return os; +} + +template <typename T> +std::ostream& GenericPrintImpl(std::ostream& os, const T& v) { + if constexpr (is_any_string<T> || std::is_same_v<T, absl::string_view>) { + // Specialization for strings: prints with plausible quoting and escaping. + return PrintEscapedString(os, v); + } else if constexpr (absl::HasAbslStringify<T>::value) { + // If someone has specified `AbslStringify`, we should prefer that. + return os << absl::StrCat(v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintTuple)(std::declval<std::ostream&>(), + w)) {})) { + // For tuples, use `< elem0, ..., elemN >`. + return (PrintTuple)(os, v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintPair)(std::declval<std::ostream&>(), + w)) {})) { + // For pairs, use `< first, second >`. + return (PrintPair)(os, v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintVariant)(std::declval<std::ostream&>(), + w)) {})) { + // For std::variant, use `std::visit(v)` + return (PrintVariant)(os, v); + } else if constexpr (is_supported_ptr<T>) { + return (PrintSmartPointerContents)(os, v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(w.ok(), w.status(), *w) { + })) { + return (PrintStatusOrLike)(os, v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(w.has_value(), *w) {})) { + return (PrintOptionalLike)(os, v); + } else if constexpr (std::is_same_v<T, std::nullopt_t>) { + // Specialization for nullopt. + return os << "nullopt"; + + } else if constexpr (std::is_same_v<T, std::nullptr_t>) { + // Specialization for nullptr. + return os << "nullptr"; + + } else if constexpr (std::is_floating_point_v<T>) { + // For floating point print with enough precision for a roundtrip. + return PrintPreciseFP(os, v); + + } else if constexpr (std::is_same_v<T, char> || + std::is_same_v<T, signed char> || + std::is_same_v<T, unsigned char>) { + // Chars are printed as the char (if a printable char) and the integral + // representation in hex and decimal. + return PrintChar(os, v); + + } else if constexpr (std::is_same_v<T, std::byte>) { + return PrintByte(os, v); + + } else if constexpr (std::is_same_v<T, bool> || + std::is_same_v<T, + typename std::vector<bool>::reference> || + std::is_same_v< + T, typename std::vector<bool>::const_reference>) { + return os << (v ? "true" : "false"); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(ProtobufInternalGetEnumDescriptor( + w)) {})) { + os << static_cast<std::underlying_type_t<T>>(v); + if (auto* desc = + ProtobufInternalGetEnumDescriptor(T{})->FindValueByNumber(v)) { + os << "(" << desc->name() << ")"; + } + return os; + } else if constexpr (!std::is_enum_v<T> && + meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(absl::StrCat(w)) {})) { + return os << absl::StrCat(v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() + << log_internal::LogContainer(w)) { + })) { + // For containers, use `[ elem0, ..., elemN ]` with a max of 15. + return os << log_internal::LogContainer(v, ContainerLogPolicy()); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() << w) { + })) { + // Streaming + return os << v; + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() + << w.DebugString()) {})) { + // DebugString + return os << v.DebugString(); + + } else if constexpr (std::is_enum_v<T>) { + // In case the underlying type is some kind of char, we have to recurse. + return GenericPrintImpl(os, static_cast<std::underlying_type_t<T>>(v)); + + } else { + // Default: we don't have anything to stream the value. + return os << "[unprintable value of size " << sizeof(T) << " @" << &v + << "]"; + } +} + +// GenericPrinter always has a valid operator<<. It defers to the disjuction +// above. +template <typename T> +class GenericPrinter { + public: + explicit GenericPrinter(const T& value) : value_(value) {} + + private: + friend std::ostream& operator<<(std::ostream& os, const GenericPrinter& gp) { + return internal_generic_printer::GenericPrintImpl(os, gp.value_); + } + const T& value_; +}; + +struct GenericPrintStreamAdapter { + template <class StreamT> + struct Impl { + // Stream operator: this object will adapt to the underlying stream type, + // but only if the Impl is an rvalue. For example, this works: + // std::cout << GenericPrint() << foo; + // but not: + // auto adapter = (std::cout << GenericPrint()); + // adapter << foo; + template <typename T> + Impl&& operator<<(const T& value) && { + os << internal_generic_printer::GenericPrinter<T>(value); + return std::move(*this); + } + + // Inhibit using a stack variable for the adapter: + template <typename T> + Impl& operator<<(const T& value) & = delete; + + // Detects a Flush() method, for LogMessage compatibility. + template <typename T> + class HasFlushMethod { + private: + template <typename C> + static std::true_type Test(decltype(&C::Flush)); + template <typename C> + static std::false_type Test(...); + + public: + static constexpr bool value = decltype(Test<T>(nullptr))::value; + }; + + // LogMessage compatibility requires a Flush() method. + void Flush() { + if constexpr (HasFlushMethod<StreamT>::value) { + os.Flush(); + } + } + + StreamT& os; + }; + + // If Impl is evaluated on the RHS of an 'operator&&', and 'lhs && Impl.os' + // implicitly converts to void, then it's fine for Impl to do so, too. This + // will create precisely as many objects as 'lhs && Impl.os', so we should + // both observe any side effects, and avoid observing multiple side + // effects. (See absl::log_internal::Voidify for an example of why this might + // be useful.) + template <typename LHS, typename RHS> + friend auto operator&&(LHS&& lhs, Impl<RHS>&& rhs) + -> decltype(lhs && rhs.os) { + return lhs && rhs.os; + } + + template <class StreamT> + friend Impl<StreamT> operator<<(StreamT& os, GenericPrintStreamAdapter&&) { + return Impl<StreamT>{os}; + } +}; + +struct GenericPrintAdapterFactory { + internal_generic_printer::GenericPrintStreamAdapter operator()() const { + return internal_generic_printer::GenericPrintStreamAdapter{}; + } + template <typename T> + internal_generic_printer::GenericPrinter<T> operator()(const T& value) const { + return internal_generic_printer::GenericPrinter<T>{value}; + } +}; + +} // namespace internal_generic_printer +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
diff --git a/absl/strings/internal/generic_printer_test.cc b/absl/strings/internal/generic_printer_test.cc new file mode 100644 index 0000000..4093228 --- /dev/null +++ b/absl/strings/internal/generic_printer_test.cc
@@ -0,0 +1,685 @@ +// 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. + +#include "absl/strings/internal/generic_printer.h" + +#include <array> +#include <cstdint> +#include <limits> +#include <map> +#include <memory> +#include <optional> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> +#include <utility> +#include <variant> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/substitute.h" + +namespace generic_logging_test { +struct NotStreamable {}; +} // namespace generic_logging_test + +static std::ostream& operator<<(std::ostream& os, + const generic_logging_test::NotStreamable&) { + return os << "This overload should NOT be found by GenericPrint."; +} + +// Types to test selection logic for streamable and non-streamable types. +namespace generic_logging_test { +struct Streamable { + int x; + friend std::ostream& operator<<(std::ostream& os, const Streamable& l) { + return os << "Streamable{" << l.x << "}"; + } +}; +} // namespace generic_logging_test + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +namespace { + +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::ContainsRegex; +using ::testing::EndsWith; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +struct AbslStringifiable { + template <typename S> + friend void AbslStringify(S& sink, const AbslStringifiable&) { + sink.Append("AbslStringifiable!"); + } +}; + +auto IsUnprintable() { +#ifdef GTEST_USES_SIMPLE_RE + return HasSubstr("unprintable value of size"); +#else + return ContainsRegex( + "\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]"); +#endif +} + +auto HasExactlyNInstancesOf(int n, absl::string_view me) { +#ifdef GTEST_USES_SIMPLE_RE + (void)n; + return HasSubstr(me); +#else + absl::string_view value_m_times = "(.*$0){$1}.*"; + + return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)), + Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1)))); +#endif +} + +template <typename T> +std::string GenericPrintToString(const T& v) { + std::stringstream ss; + ss << GenericPrint(v); + { + std::stringstream ss2; + ss2 << GenericPrint() << v; + EXPECT_EQ(ss.str(), ss2.str()); + } + return ss.str(); +} + +TEST(GenericPrinterTest, Bool) { + EXPECT_EQ("true", GenericPrintToString(true)); + EXPECT_EQ("false", GenericPrintToString(false)); +} + +TEST(GenericPrinterTest, VectorOfBool) { + std::vector<bool> v{true, false, true}; + const auto& cv = v; + EXPECT_EQ("[true, false, true]", GenericPrintToString(v)); + EXPECT_EQ("true", GenericPrintToString(v[0])); + EXPECT_EQ("true", GenericPrintToString(cv[0])); +} + +TEST(GenericPrinterTest, CharLiterals) { + EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)")); +} + +TEST(GenericPrinterTest, Builtin) { + EXPECT_EQ("123", GenericPrintToString(123)); +} + +TEST(GenericPrinterTest, AbslStringifiable) { + EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{})); +} + +TEST(GenericPrinterTest, Nullptr) { + EXPECT_EQ("nullptr", GenericPrintToString(nullptr)); +} + +TEST(GenericPrinterTest, Chars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a')); + EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' ')); + EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~')); + EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\'')); +} + +TEST(GenericPrinterTest, SignedChars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", + GenericPrintToString(static_cast<signed char>('\x0a'))); + EXPECT_EQ(R"(' ' (0x20 32))", + GenericPrintToString(static_cast<signed char>(' '))); + EXPECT_EQ(R"('~' (0x7e 126))", + GenericPrintToString(static_cast<signed char>('~'))); + EXPECT_EQ(R"('\'' (0x27 39))", + GenericPrintToString(static_cast<signed char>('\''))); +} + +TEST(GenericPrinterTest, UnsignedChars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", + GenericPrintToString(static_cast<unsigned char>('\x0a'))); + EXPECT_EQ(R"(' ' (0x20 32))", + GenericPrintToString(static_cast<unsigned char>(' '))); + EXPECT_EQ(R"('~' (0x7e 126))", + GenericPrintToString(static_cast<unsigned char>('~'))); + EXPECT_EQ(R"('\'' (0x27 39))", + GenericPrintToString(static_cast<unsigned char>('\''))); +} + +TEST(GenericPrinterTest, Bytes) { + EXPECT_EQ("0x00", GenericPrintToString(static_cast<std::byte>(0))); + EXPECT_EQ("0x7f", GenericPrintToString(static_cast<std::byte>(0x7F))); + EXPECT_EQ("0xff", GenericPrintToString(static_cast<std::byte>(0xFF))); +} + +TEST(GenericPrinterTest, Strings) { + const std::string expected_quotes = R"("a\"\\b")"; + EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)"))); + const std::string expected_nonprintable = R"("\x00\xcd\n\xab")"; + EXPECT_EQ(expected_nonprintable, + GenericPrintToString(absl::string_view("\0\315\n\xAB", 4))); +} + +TEST(GenericPrinterTest, PreciseFloat) { + // Instead of testing exactly how the values are formatted, just check that + // they are distinct. + + // Ensure concise output for exact values: + EXPECT_EQ("1f", GenericPrintToString(1.f)); + EXPECT_EQ("1.1f", GenericPrintToString(1.1f)); + + // Plausible real-world values: + float f = 10.0000095f; + EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f)); + // Smallest increment for a real-world value: + EXPECT_NE(GenericPrintToString(f), + GenericPrintToString(std::nextafter(f, 11))); + // The two smallest (finite) values possible: + EXPECT_NE(GenericPrintToString(std::numeric_limits<float>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<float>::lowest(), 1))); + // Ensure the value has the correct type suffix: + EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f")); +} + +TEST(GenericPrinterTest, PreciseDouble) { + EXPECT_EQ("1", GenericPrintToString(1.)); + EXPECT_EQ("1.1", GenericPrintToString(1.1)); + double d = 10.000000000000002; + EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004)); + EXPECT_NE(GenericPrintToString(d), + GenericPrintToString(std::nextafter(d, 11))); + EXPECT_NE(GenericPrintToString(std::numeric_limits<double>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<double>::lowest(), 1))); + EXPECT_THAT(GenericPrintToString(0.), EndsWith("0")); +} + +TEST(GenericPrinterTest, PreciseLongDouble) { + EXPECT_EQ("1L", GenericPrintToString(1.L)); + EXPECT_EQ("1.1L", GenericPrintToString(1.1L)); + long double ld = 10.0000000000000000000000000000002; + EXPECT_NE(GenericPrintToString(ld), + GenericPrintToString(10.0000000000000000000000000000004)); + EXPECT_NE(GenericPrintToString(ld), + GenericPrintToString(std::nextafter(ld, 11))); + EXPECT_NE(GenericPrintToString(std::numeric_limits<long double>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<long double>::lowest(), 1))); + EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L")); +} + +TEST(GenericPrinterTest, StreamableLvalue) { + generic_logging_test::Streamable x{234}; + EXPECT_EQ("Streamable{234}", GenericPrintToString(x)); +} + +TEST(GenericPrinterTest, StreamableXvalue) { + EXPECT_EQ("Streamable{345}", + GenericPrintToString(generic_logging_test::Streamable{345})); +} + +TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) { + ::generic_logging_test::NotStreamable x; + std::stringstream ss; + ::operator<<(ss, x); + EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint."); +} + +TEST(GenericPrinterTest, NotStreamableLvalue) { + generic_logging_test::NotStreamable x; + EXPECT_THAT(GenericPrintToString(x), IsUnprintable()); +} + +TEST(GenericPrinterTest, NotStreamableXvalue) { + EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}), + IsUnprintable()); +} + +TEST(GenericPrinterTest, DebugString) { + struct WithDebugString { + std::string val; + std::string DebugString() const { + return absl::StrCat("WithDebugString{", val, "}"); + } + }; + EXPECT_EQ("WithDebugString{foo}", + GenericPrintToString(WithDebugString{"foo"})); +} + +TEST(GenericPrinterTest, Vector) { + std::vector<int> v = {4, 5, 6}; + EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*")); +} + +TEST(GenericPrinterTest, StreamableVector) { + std::vector<generic_logging_test::Streamable> v = {{7}, {8}, {9}}; + EXPECT_THAT(GenericPrintToString(v), + MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*")); +} + +TEST(GenericPrinterTest, Map) { + absl::flat_hash_map< + std::string, absl::flat_hash_map<std::string, std::pair<double, double>>> + v = {{"A", {{"B", {.5, .25}}}}}; + + EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])"); + + std::map<std::string, std::map<std::string, std::pair<double, double>>> v2 = { + {"A", {{"B", {.5, .25}}}}}; + + EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])"); +} + +TEST(GenericPrinterTest, StreamAdapter) { + std::stringstream ss; + static_assert( + std::is_same< + typename std::remove_reference<decltype(ss << GenericPrint())>::type, + internal_generic_printer::GenericPrintStreamAdapter::Impl< + std::stringstream>>::value, + "expected ostream << GenericPrint() to yield adapter impl"); + + ss << GenericPrint() << "again, " << "back-up, " << "cue, " + << "double-u, " << "eye, " + << "four: " << generic_logging_test::NotStreamable{}; + EXPECT_THAT( + ss.str(), + MatchesRegex( + "again, back-up, cue, double-u, eye, four: .unprintable value.*")); +} + +TEST(GenericPrinterTest, NotStreamableVector) { + std::vector<generic_logging_test::NotStreamable> v = {{}, {}, {}}; +#ifdef GTEST_USES_SIMPLE_RE + EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable")); +#else + EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*")); +#endif +} + +struct CustomContainer : public std::array<int, 4> { + template <typename Sink> + friend void AbslStringify(Sink& sink, const CustomContainer& c) { + absl::Format(&sink, "%d %d", c[0], c[1]); + } +}; + +// Checks that AbslStringify (go/totw/215) is respected for container-like +// types. +TEST(GenericPrinterTest, ContainerLikeCustomLogging) { + CustomContainer c = {1, 2, 3, 4}; + EXPECT_EQ(GenericPrintToString(c), "1 2"); +} + +// Test helper: this function demonstrates customizable printing logic: +// 'GenericPrinter<T>' can be nominated as a default template argument. +template <typename T, typename Printer = GenericPrinter<T>> +std::string SpecializablePrint(const T& v) { + std::stringstream ss; + ss << Printer{v}; + return ss.str(); +} + +TEST(GenericPrinterTest, DefaultPrinter) { + EXPECT_EQ("123", SpecializablePrint(123)); +} + +// Example of custom printing logic. This doesn't actually test anything in +// GenericPrinter, but it's a working example of customizing printing logic (as +// opposed to the comments in generic_printer.h). +struct CustomPrinter { + explicit CustomPrinter(int) {} + friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) { + return os << "custom printer"; + } +}; + +TEST(GenericPrinterTest, CustomPrinter) { + EXPECT_EQ("custom printer", (SpecializablePrint<int, CustomPrinter>(123))); +} + +TEST(GenricPrinterTest, Nullopt) { + EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt)); +} + +TEST(GenericPrinterTest, Optional) { + EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>())); + EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>(std::nullopt))); + EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3))); + EXPECT_EQ("<Streamable{3}>", GenericPrintToString(std::make_optional( + generic_logging_test::Streamable{3}))); +} + +TEST(GenericPrinterTest, Tuple) { + EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3))); +} + +TEST(GenericPrinterTest, EmptyTuple) { + EXPECT_EQ("<>", GenericPrintToString(std::make_tuple())); +} + +TEST(GenericPrinterTest, TupleWithStreamableMember) { + EXPECT_EQ("<1, two, Streamable{3}>", + GenericPrintToString(std::make_tuple( + 1, "two", generic_logging_test::Streamable{3}))); +} + +TEST(GenericPrinterTest, Variant) { + EXPECT_EQ(R"(('(index = 0)' "cow"))", + GenericPrintToString(std::variant<std::string, float>("cow"))); + + EXPECT_EQ("('(index = 1)' 1.1f)", + GenericPrintToString(std::variant<std::string, float>(1.1F))); +} + +TEST(GenericPrinterTest, VariantMonostate) { + EXPECT_THAT(GenericPrintToString(std::variant<std::monostate, std::string>()), + IsUnprintable()); +} + +TEST(GenericPrinterTest, VariantNonStreamable) { + EXPECT_EQ(R"(('(index = 0)' "cow"))", + GenericPrintToString( + std::variant<std::string, generic_logging_test::NotStreamable>( + "cow"))); + + EXPECT_THAT( + GenericPrintToString( + std::variant<std::string, generic_logging_test::NotStreamable>( + generic_logging_test::NotStreamable{})), + IsUnprintable()); +} + +TEST(GenericPrinterTest, VariantNestedVariant) { + EXPECT_EQ( + "('(index = 1)' ('(index = 1)' 1.1f))", + GenericPrintToString(std::variant<std::string, std::variant<int, float>>( + std::variant<int, float>(1.1F)))); +} + +TEST(GenericPrinterTest, VariantInPlace) { + EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant<int, int>( + std::in_place_index<0>, 17))); + + EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant<int, int>( + std::in_place_index<1>, 17))); +} + +TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) { + EXPECT_EQ(R"(<OK: "cow">)", + GenericPrintToString(absl::StatusOr<std::string>("cow"))); + + EXPECT_EQ(R"(<OK: 1.1f>)", GenericPrintToString(absl::StatusOr<float>(1.1F))); +} + +TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<float>( + absl::InvalidArgumentError("my error message"))), + AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT"))); + + EXPECT_THAT(GenericPrintToString( + absl::StatusOr<int>(absl::AbortedError("other message"))), + AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); +} + +TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>( + generic_logging_test::NotStreamable{})), + IsUnprintable()); +} + +TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>( + absl::AbortedError("other message"))), + AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); +} + +TEST(GenericPrinterTest, IsSupportedPointer) { + using internal_generic_printer::is_supported_ptr; + + EXPECT_TRUE(is_supported_ptr<std::unique_ptr<std::string>>); + EXPECT_TRUE(is_supported_ptr<std::unique_ptr<int[]>>); + EXPECT_TRUE((is_supported_ptr<std::unique_ptr<void, void (*)(void*)>>)); + + EXPECT_FALSE(is_supported_ptr<int*>); + EXPECT_FALSE(is_supported_ptr<std::shared_ptr<int>>); + EXPECT_FALSE(is_supported_ptr<std::weak_ptr<int>>); +} + +TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) { + std::unique_ptr<std::string> up; + + EXPECT_EQ("<nullptr>", GenericPrintToString(up)); +} + +TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) { + EXPECT_THAT(GenericPrintToString(std::make_unique<int>(5)), + HasSubstr("pointing to 5")); +} + +TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) { + auto i = std::make_unique<int>(5); + auto c = std::make_unique<char>('z'); + char memory[] = "abcdefg"; + auto cp = std::make_unique<char*>(memory); + + EXPECT_THAT(GenericPrintToString(i), + AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>", + reinterpret_cast<intptr_t>(&*i))), + Eq(absl::StrFormat("<%#x pointing to 5>", + reinterpret_cast<intptr_t>(&*i))))); + + EXPECT_THAT( + GenericPrintToString(c), + AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'", + reinterpret_cast<intptr_t>(&*c))), + HasSubstr(absl::StrFormat("<%#x pointing to 'z'", + reinterpret_cast<intptr_t>(&*c))))); + + EXPECT_THAT(GenericPrintToString(cp), + AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>", + reinterpret_cast<intptr_t>(&*cp))), + Eq(absl::StrFormat("<%#x pointing to abcdefg>", + reinterpret_cast<intptr_t>(&*cp))))); +} + +TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) { + auto empty = std::make_unique<int[]>(0); + auto nonempty = std::make_unique<int[]>(5); + nonempty[0] = 12345; + nonempty[4] = 54321; + // NOTE: ArenaSafeUniquePtr is not meant to support array-type template + // parameters, so we skip testing that here. + // http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185. + + EXPECT_THAT( + GenericPrintToString(nonempty), + AllOf(AnyOf(HasSubstr(absl::StrFormat( + "%016X", reinterpret_cast<intptr_t>(nonempty.get()))), + HasSubstr(absl::StrFormat( + "%#x", reinterpret_cast<intptr_t>(nonempty.get())))), + HasSubstr("array"), Not(HasSubstr("to 54321")), + Not(HasSubstr("to 12345")))); + + EXPECT_THAT( + GenericPrintToString(empty), + AllOf(AnyOf(HasSubstr(absl::StrFormat( + "%016X", reinterpret_cast<intptr_t>(empty.get()))), + HasSubstr(absl::StrFormat( + "%#x", reinterpret_cast<intptr_t>(empty.get())))), + HasSubstr("array"))); +} + +TEST(GenericPrinterTest, SmartPointerToNonObjectType) { + auto int_ptr_deleter = [](void* data) { + int* p = static_cast<int*>(data); + delete p; + }; + + std::unique_ptr<void, decltype(int_ptr_deleter)> void_ptr(new int(959), + int_ptr_deleter); + + EXPECT_THAT(GenericPrintToString(void_ptr), + HasSubstr("pointing to a non-object type")); +} + +TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) { + // Delete `p` (if not nullptr) only on the 4th time the deleter is used. + auto four_deleter = [](std::string* p) { + static int counter = 0; + if (p == nullptr) return; // skip calls to moved-from destructors. + if (++counter >= 4) delete p; + }; + + // Have four `unique_ptr`s "manage" the same string-pointer, with only the + // final (4th) call to the deleter deleting the string pointer. + auto* unique_string = new std::string("unique string"); + std::vector<std::unique_ptr<std::string, decltype(four_deleter)>> test_ptrs; + for (int i = 0; i < 4; ++i) { + test_ptrs.emplace_back(unique_string, four_deleter); + } + + EXPECT_THAT(GenericPrintToString(test_ptrs), + HasExactlyNInstancesOf(4, "unique string")); +} + +// Ensure that GenericPrint is robust to recursion when a type's operator<< +// calls into GenericPrint internally. +struct CustomRecursive { + std::unique_ptr<CustomRecursive> next; + int val = 0; + + friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) { + return os << "custom print: next = " << GenericPrintToString(cr.next); + } +}; + +TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) { + auto r1 = std::make_unique<CustomRecursive>(); + r1->val = 1; + auto& r2 = r1->next = std::make_unique<CustomRecursive>(); + r2->val = 2; + r2->next = std::move(r1); + + EXPECT_THAT(GenericPrintToString(*r2), + AllOf(HasExactlyNInstancesOf(2, "custom print"), + HasExactlyNInstancesOf(1, "<recursive>"))); + + r2->next = nullptr; // break the cycle +} +// <end DISABLED_ test section> + +enum CStyleEnum { kValue0, kValue1 }; +TEST(GenericPrinterTest, Enum) { + EXPECT_EQ("1", GenericPrintToString(kValue1)); +} + +enum class CppStyleEnum { kValue0, kValue1, kValue2 }; +TEST(GenericPrinterTest, EnumClass) { + EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2)); +} + +enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' }; +TEST(GenericPrinterTest, CharBasedEnum) { + EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA)); + EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1)); +} + +enum class WideBasedEnum : uint64_t { + kValue = std::numeric_limits<uint64_t>::max() +}; +TEST(GenericPrinterTest, WideBasedEnum) { + EXPECT_EQ(absl::StrCat(std::numeric_limits<uint64_t>::max()), + GenericPrintToString(WideBasedEnum::kValue)); +} + +enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 }; +template <typename Sink> +void AbslStringify(Sink& sink, CStyleEnumWithStringify e) { + switch (e) { + case CStyleEnumWithStringify::kValueA: + sink.Append("A"); + return; + case CStyleEnumWithStringify::kValueB: + sink.Append("B"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CStyleEnumWithStringify) { + EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CStyleEnumWithStringify>(1))); +} + +enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC }; +template <typename Sink> +void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) { + switch (e) { + case CppStyleEnumWithStringify::kValueA: + sink.Append("A"); + return; + case CppStyleEnumWithStringify::kValueB: + sink.Append("B"); + return; + case CppStyleEnumWithStringify::kValueC: + sink.Append("C"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CppStyleEnumWithStringify) { + EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CppStyleEnumWithStringify>(17))); +} + +enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' }; +template <typename Sink> +void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) { + switch (e) { + case CharBasedEnumWithStringify::kValueA: + sink.Append("charA"); + return; + case CharBasedEnumWithStringify::kValueB: + sink.Append("charB"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CharBasedEnumWithStringify) { + EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CharBasedEnumWithStringify>('W'))); +} + +} // namespace +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/strings/internal/resize_uninitialized.h b/absl/strings/internal/resize_uninitialized.h index 49859dc..7e55e5a 100644 --- a/absl/strings/internal/resize_uninitialized.h +++ b/absl/strings/internal/resize_uninitialized.h
@@ -68,18 +68,6 @@ ResizeUninitializedTraits<string_type>::Resize(s, new_size); } -// Used to ensure exponential growth so that the amortized complexity of -// increasing the string size by a small amount is O(1), in contrast to -// O(str->size()) in the case of precise growth. -template <typename string_type> -void STLStringReserveAmortized(string_type* s, size_t new_size) { - const size_t cap = s->capacity(); - if (new_size > cap) { - // Make sure to always grow by at least a factor of 2x. - s->reserve((std::max)(new_size, 2 * cap)); - } -} - // In this type trait, we look for an __append_default_init member function, and // we use it if available, otherwise, we use append. template <typename string_type, typename = void>
diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index 60e43e0..b6a8e42 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc
@@ -242,6 +242,18 @@ return tens; } + +// Encodes v to buffer as 16 digits padded with leading zeros. +// Pre-condition: v must be < 10^16. +inline char* EncodePadded16(uint64_t v, char* absl_nonnull buffer) { + constexpr uint64_t k1e8 = 100000000; + uint32_t hi = static_cast<uint32_t>(v / k1e8); + uint32_t lo = static_cast<uint32_t>(v % k1e8); + little_endian::Store64(buffer, PrepareEightDigits(hi) + kEightZeroBytes); + little_endian::Store64(buffer + 8, PrepareEightDigits(lo) + kEightZeroBytes); + return buffer + 16; +} + inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU32( uint32_t n, char* absl_nonnull out_str) { if (n < 10) { @@ -265,19 +277,19 @@ return out_str + sizeof(bottom); } -inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* EncodeFullU64(uint64_t i, - char* buffer) { +inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU64( + uint64_t i, char* absl_nonnull buffer) { if (i <= std::numeric_limits<uint32_t>::max()) { return EncodeFullU32(static_cast<uint32_t>(i), buffer); } uint32_t mod08; if (i < 1'0000'0000'0000'0000ull) { uint32_t div08 = static_cast<uint32_t>(i / 100'000'000ull); - mod08 = static_cast<uint32_t>(i % 100'000'000ull); + mod08 = static_cast<uint32_t>(i % 100'000'000ull); buffer = EncodeFullU32(div08, buffer); } else { uint64_t div08 = i / 100'000'000ull; - mod08 = static_cast<uint32_t>(i % 100'000'000ull); + mod08 = static_cast<uint32_t>(i % 100'000'000ull); uint32_t div016 = static_cast<uint32_t>(div08 / 100'000'000ull); uint32_t div08mod08 = static_cast<uint32_t>(div08 % 100'000'000ull); uint64_t mid_result = PrepareEightDigits(div08mod08) + kEightZeroBytes; @@ -290,6 +302,30 @@ return buffer + sizeof(mod_result); } +inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU128( + uint128 i, char* absl_nonnull buffer) { + if (absl::Uint128High64(i) == 0) { + return EncodeFullU64(absl::Uint128Low64(i), buffer); + } + // We divide the number into 16-digit chunks because `EncodePadded16` is + // optimized to handle 16 digits at a time (as two 8-digit chunks). + constexpr uint64_t k1e16 = uint64_t{10'000'000'000'000'000}; + uint128 high = i / k1e16; + uint64_t low = absl::Uint128Low64(i % k1e16); + uint64_t mid = absl::Uint128Low64(high % k1e16); + high /= k1e16; + + if (high == 0) { + buffer = EncodeFullU64(mid, buffer); + buffer = EncodePadded16(low, buffer); + } else { + buffer = EncodeFullU64(absl::Uint128Low64(high), buffer); + buffer = EncodePadded16(mid, buffer); + buffer = EncodePadded16(low, buffer); + } + return buffer; +} + } // namespace void numbers_internal::PutTwoDigits(uint32_t i, char* absl_nonnull buf) { @@ -345,6 +381,25 @@ return buffer; } +char* absl_nonnull numbers_internal::FastIntToBuffer( + uint128 i, char* absl_nonnull buffer) { + buffer = EncodeFullU128(i, buffer); + *buffer = '\0'; + return buffer; +} + +char* absl_nonnull numbers_internal::FastIntToBuffer( + int128 i, char* absl_nonnull buffer) { + uint128 u = static_cast<uint128>(i); + if (i < 0) { + *buffer++ = '-'; + u = -u; + } + buffer = EncodeFullU128(u, buffer); + *buffer = '\0'; + return buffer; +} + // Given a 128-bit number expressed as a pair of uint64_t, high half first, // return that number multiplied by the given 32-bit value. If the result is // too large to fit in a 128-bit number, divide it by 2 until it fits.
diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index 9c67974..fa552af 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h
@@ -174,8 +174,9 @@ bool safe_strtou128_base(absl::string_view text, absl::uint128* absl_nonnull value, int base); -static const int kFastToBufferSize = 32; -static const int kSixDigitsToBufferSize = 16; +inline constexpr int kFastToBuffer128Size = 41; +inline constexpr int kFastToBufferSize = 32; +inline constexpr int kSixDigitsToBufferSize = 16; // Helper function for fast formatting of floating-point values. // The result is the same as printf's "%g", a.k.a. "%.6g"; that is, six @@ -188,7 +189,8 @@ // WARNING: These functions may write more characters than necessary, because // they are intended for speed. All functions take an output buffer // as an argument and return a pointer to the last byte they wrote, which is the -// terminating '\0'. At most `kFastToBufferSize` bytes are written. +// terminating '\0'. The maximum size written is `kFastToBufferSize` for 64-bit +// integers or less, and `kFastToBuffer128Size` for 128-bit integers. char* absl_nonnull FastIntToBuffer(int32_t i, char* absl_nonnull buffer) ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); char* absl_nonnull FastIntToBuffer(uint32_t n, char* absl_nonnull out_str) @@ -197,25 +199,36 @@ ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); char* absl_nonnull FastIntToBuffer(uint64_t i, char* absl_nonnull buffer) ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); +char* absl_nonnull FastIntToBuffer(int128 i, char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBuffer128Size); +char* absl_nonnull FastIntToBuffer(uint128 i, char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBuffer128Size); -// For enums and integer types that are not an exact match for the types above, -// use templates to call the appropriate one of the four overloads above. +// For enums and integer types that are up to 128 bits and are not an exact +// match for the types above, use templates to call the appropriate one of the +// four overloads above. template <typename int_type> -char* absl_nonnull FastIntToBuffer(int_type i, char* absl_nonnull buffer) - ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize) { - static_assert(sizeof(i) <= 64 / 8, - "FastIntToBuffer works only with 64-bit-or-less integers."); +char* absl_nonnull FastIntToBuffer(int_type i, + char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE( + buffer, (sizeof(int_type) > 8 ? kFastToBuffer128Size + : kFastToBufferSize)) { // These conditions are constexpr bools to suppress MSVC warning C4127. constexpr bool kIsSigned = is_signed<int_type>(); constexpr bool kUse64Bit = sizeof(i) > 32 / 8; + constexpr bool kUse128Bit = sizeof(i) > 64 / 8; if (kIsSigned) { - if (kUse64Bit) { + if constexpr (kUse128Bit) { + return FastIntToBuffer(static_cast<int128>(i), buffer); + } else if constexpr (kUse64Bit) { return FastIntToBuffer(static_cast<int64_t>(i), buffer); } else { return FastIntToBuffer(static_cast<int32_t>(i), buffer); } } else { - if (kUse64Bit) { + if constexpr (kUse128Bit) { + return FastIntToBuffer(static_cast<uint128>(i), buffer); + } else if constexpr (kUse64Bit) { return FastIntToBuffer(static_cast<uint64_t>(i), buffer); } else { return FastIntToBuffer(static_cast<uint32_t>(i), buffer);
diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index ca13da0..2eaa8c7 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc
@@ -159,6 +159,8 @@ typedef MyInteger<int64_t> MyInt64; typedef MyInteger<uint64_t> MyUInt64; +typedef MyInteger<absl::uint128> MyUInt128; +typedef MyInteger<absl::int128> MyInt128; void CheckInt32(int32_t x) { char buffer[absl::numbers_internal::kFastToBufferSize]; @@ -212,6 +214,32 @@ EXPECT_EQ(expected, std::string(&buffer[1], my_actual)) << " Input " << x; } +void CheckUInt128(absl::uint128 x) { + char buffer[absl::numbers_internal::kFastToBuffer128Size]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string s; + absl::strings_internal::OStringStream strm(&s); + strm << x; + EXPECT_EQ(s, std::string(buffer, actual)) << " Input " << s; + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyUInt128(x), buffer); + EXPECT_EQ(s, std::string(buffer, my_actual)) << " Input " << s; +} + +void CheckInt128(absl::int128 x) { + char buffer[absl::numbers_internal::kFastToBuffer128Size]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string s; + absl::strings_internal::OStringStream strm(&s); + strm << x; + EXPECT_EQ(s, std::string(buffer, actual)) << " Input " << s; + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyInt128(x), buffer); + EXPECT_EQ(s, std::string(buffer, my_actual)) << " Input " << s; +} + void CheckHex64(uint64_t v) { char expected[16 + 1]; std::string actual = absl::StrCat(absl::Hex(v, absl::kZeroPad16)); @@ -251,6 +279,34 @@ CheckUInt64(uint64_t{1000000000000000000}); CheckUInt64(uint64_t{1199999999999999999}); CheckUInt64(std::numeric_limits<uint64_t>::max()); + CheckUInt128(0); + CheckUInt128(1); + CheckUInt128(9); + CheckUInt128(10); + CheckUInt128(99); + CheckUInt128(100); + CheckUInt128(std::numeric_limits<uint64_t>::max()); + CheckUInt128(absl::uint128(std::numeric_limits<uint64_t>::max()) + 1); + CheckUInt128(absl::MakeUint128(1, 0)); + absl::uint128 k1e16 = 10000000000000000ULL; + CheckUInt128(k1e16 - 1); + CheckUInt128(k1e16); + CheckUInt128(k1e16 + 1); + CheckUInt128(k1e16 * k1e16 - 1); + CheckUInt128(k1e16 * k1e16); + CheckUInt128(k1e16 * k1e16 + 1); + CheckUInt128(absl::Uint128Max() - 1); + CheckUInt128(absl::Uint128Max()); + + CheckInt128(0); + CheckInt128(1); + CheckInt128(-1); + CheckInt128(10); + CheckInt128(-10); + CheckInt128(absl::Int128Max()); + CheckInt128(absl::Int128Min()); + CheckInt128(absl::MakeInt128(-1, 1)); + CheckInt128(absl::MakeInt128(-1, std::numeric_limits<uint64_t>::max())); for (int i = 0; i < 10000; i++) { CheckHex64(i);
diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc deleted file mode 100644 index 33bd1bb..0000000 --- a/absl/strings/string_view.cc +++ /dev/null
@@ -1,257 +0,0 @@ -// Copyright 2017 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. - -#include "absl/strings/string_view.h" - -#ifndef ABSL_USES_STD_STRING_VIEW - -#include <algorithm> -#include <climits> -#include <cstring> -#include <ostream> - -#include "absl/base/nullability.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN - -namespace { - -// This is significantly faster for case-sensitive matches with very -// few possible matches. -const char* absl_nullable memmatch(const char* absl_nullable phaystack, - size_t haylen, - const char* absl_nullable pneedle, - size_t neelen) { - if (0 == neelen) { - return phaystack; // even if haylen is 0 - } - if (haylen < neelen) return nullptr; - - const char* match; - const char* hayend = phaystack + haylen - neelen + 1; - // A static cast is used here as memchr returns a const void *, and pointer - // arithmetic is not allowed on pointers to void. - while ( - (match = static_cast<const char*>(memchr( - phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) { - if (memcmp(match, pneedle, neelen) == 0) - return match; - else - phaystack = match + 1; - } - return nullptr; -} - -void WritePadding(std::ostream& o, size_t pad) { - char fill_buf[32]; - memset(fill_buf, o.fill(), sizeof(fill_buf)); - while (pad) { - size_t n = std::min(pad, sizeof(fill_buf)); - o.write(fill_buf, static_cast<std::streamsize>(n)); - pad -= n; - } -} - -class LookupTable { - public: - // For each character in wanted, sets the index corresponding - // to the ASCII code of that character. This is used by - // the find_.*_of methods below to tell whether or not a character is in - // the lookup table in constant time. - explicit LookupTable(string_view wanted) { - for (char c : wanted) { - table_[Index(c)] = true; - } - } - bool operator[](char c) const { return table_[Index(c)]; } - - private: - static unsigned char Index(char c) { return static_cast<unsigned char>(c); } - bool table_[UCHAR_MAX + 1] = {}; -}; - -} // namespace - -std::ostream& operator<<(std::ostream& o, string_view piece) { - std::ostream::sentry sentry(o); - if (sentry) { - size_t lpad = 0; - size_t rpad = 0; - if (static_cast<size_t>(o.width()) > piece.size()) { - size_t pad = static_cast<size_t>(o.width()) - piece.size(); - if ((o.flags() & o.adjustfield) == o.left) { - rpad = pad; - } else { - lpad = pad; - } - } - if (lpad) WritePadding(o, lpad); - o.write(piece.data(), static_cast<std::streamsize>(piece.size())); - if (rpad) WritePadding(o, rpad); - o.width(0); - } - return o; -} - -string_view::size_type string_view::find(string_view s, - size_type pos) const noexcept { - if (empty() || pos > length_) { - if (empty() && pos == 0 && s.empty()) return 0; - return npos; - } - const char* result = memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_); - return result ? static_cast<size_type>(result - ptr_) : npos; -} - -string_view::size_type string_view::find(char c, size_type pos) const noexcept { - if (empty() || pos >= length_) { - return npos; - } - const char* result = - static_cast<const char*>(memchr(ptr_ + pos, c, length_ - pos)); - return result != nullptr ? static_cast<size_type>(result - ptr_) : npos; -} - -string_view::size_type string_view::rfind(string_view s, - size_type pos) const noexcept { - if (length_ < s.length_) return npos; - if (s.empty()) return std::min(length_, pos); - const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_; - const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); - return result != last ? static_cast<size_type>(result - ptr_) : npos; -} - -// Search range is [0..pos] inclusive. If pos == npos, search everything. -string_view::size_type string_view::rfind(char c, - size_type pos) const noexcept { - // Note: memrchr() is not available on Windows. - if (empty()) return npos; - for (size_type i = std::min(pos, length_ - 1);; --i) { - if (ptr_[i] == c) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_first_of( - string_view s, size_type pos) const noexcept { - if (empty() || s.empty()) { - return npos; - } - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_first_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = pos; i < length_; ++i) { - if (tbl[ptr_[i]]) { - return i; - } - } - return npos; -} - -string_view::size_type string_view::find_first_not_of( - string_view s, size_type pos) const noexcept { - if (empty()) return npos; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_first_not_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = pos; i < length_; ++i) { - if (!tbl[ptr_[i]]) { - return i; - } - } - return npos; -} - -string_view::size_type string_view::find_first_not_of( - char c, size_type pos) const noexcept { - if (empty()) return npos; - for (; pos < length_; ++pos) { - if (ptr_[pos] != c) { - return pos; - } - } - return npos; -} - -string_view::size_type string_view::find_last_of(string_view s, - size_type pos) const noexcept { - if (empty() || s.empty()) return npos; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_last_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = std::min(pos, length_ - 1);; --i) { - if (tbl[ptr_[i]]) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_last_not_of( - string_view s, size_type pos) const noexcept { - if (empty()) return npos; - size_type i = std::min(pos, length_ - 1); - if (s.empty()) return i; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_last_not_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (;; --i) { - if (!tbl[ptr_[i]]) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_last_not_of( - char c, size_type pos) const noexcept { - if (empty()) return npos; - size_type i = std::min(pos, length_ - 1); - for (;; --i) { - if (ptr_[i] != c) { - return i; - } - if (i == 0) break; - } - return npos; -} - -ABSL_NAMESPACE_END -} // namespace absl - -#else - -// https://github.com/abseil/abseil-cpp/issues/1465 -// CMake builds on Apple platforms error when libraries are empty. -// Our CMake configuration can avoid this error on header-only libraries, -// but since this library is conditionally empty, including a single -// variable is an easy workaround. -#ifdef __APPLE__ -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace strings_internal { -extern const char kAvoidEmptyStringViewLibraryWarning; -const char kAvoidEmptyStringViewLibraryWarning = 0; -} // namespace strings_internal -ABSL_NAMESPACE_END -} // namespace absl -#endif // __APPLE__ - -#endif // ABSL_USES_STD_STRING_VIEW
diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index 358570e..9daa149 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h
@@ -1,4 +1,3 @@ -// // Copyright 2017 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,784 +16,24 @@ // File: string_view.h // ----------------------------------------------------------------------------- // -// This file contains the definition of the `absl::string_view` class. A -// `string_view` points to a contiguous span of characters, often part or all of -// another `std::string`, double-quoted string literal, character array, or even -// another `string_view`. -// -// This `absl::string_view` abstraction is designed to be a drop-in -// replacement for the C++17 `std::string_view` abstraction. +// Historical note: Abseil once provided an implementation of +// `absl::string_view` as a polyfill for `std::string_view` prior to C++17. Now +// that C++17 is required, `absl::string_view` is an alias for +// `std::string_view` + #ifndef ABSL_STRINGS_STRING_VIEW_H_ #define ABSL_STRINGS_STRING_VIEW_H_ -#include <algorithm> -#include <cassert> -#include <cstddef> -#include <cstring> -#include <iosfwd> -#include <iterator> -#include <limits> -#include <memory> -#include <string> -#include <type_traits> +#include <string_view> #include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/base/internal/throw_delegate.h" -#include "absl/base/macros.h" #include "absl/base/nullability.h" -#include "absl/base/optimization.h" -#include "absl/base/port.h" - -#ifdef ABSL_USES_STD_STRING_VIEW - -#include <string_view> // IWYU pragma: export - -namespace absl { -ABSL_NAMESPACE_BEGIN -using string_view = std::string_view; -ABSL_NAMESPACE_END -} // namespace absl - -#else // ABSL_USES_STD_STRING_VIEW - -#if ABSL_HAVE_ATTRIBUTE(diagnose_if) -#define ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(x) \ - __attribute__((diagnose_if( \ - x == nullptr, \ - "null passed to a callee that requires a non-null argument", "error"))) -#else -#define ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(x) -#endif - -#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \ - (defined(__GNUC__) && !defined(__clang__)) || \ - (defined(_MSC_VER) && _MSC_VER >= 1928) -#define ABSL_INTERNAL_STRING_VIEW_MEMCMP __builtin_memcmp -#else // ABSL_HAVE_BUILTIN(__builtin_memcmp) -#define ABSL_INTERNAL_STRING_VIEW_MEMCMP memcmp -#endif // ABSL_HAVE_BUILTIN(__builtin_memcmp) - -// If `std::ranges` is available, mark `string_view` as satisfying the -// `view` and `borrowed_range` concepts, just like `std::string_view`. -#ifdef __has_include -#if __has_include(<version>) -#include <version> -#endif -#endif - -#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L -#include <ranges> // NOLINT(build/c++20) - -namespace absl { -ABSL_NAMESPACE_BEGIN -class string_view; -ABSL_NAMESPACE_END -} // namespace absl - -template <> -// NOLINTNEXTLINE(build/c++20) -inline constexpr bool std::ranges::enable_view<absl::string_view> = true; -template <> -// NOLINTNEXTLINE(build/c++20) -inline constexpr bool std::ranges::enable_borrowed_range<absl::string_view> = - true; -#endif namespace absl { ABSL_NAMESPACE_BEGIN -// absl::string_view -// -// A `string_view` provides a lightweight view into the string data provided by -// a `std::string`, double-quoted string literal, character array, or even -// another `string_view`. A `string_view` does *not* own the string to which it -// points, and that data cannot be modified through the view. -// -// You can use `string_view` as a function or method parameter anywhere a -// parameter can receive a double-quoted string literal, `const char*`, -// `std::string`, or another `absl::string_view` argument with no need to copy -// the string data. Systematic use of `string_view` within function arguments -// reduces data copies and `strlen()` calls. -// -// Because of its small size, prefer passing `string_view` by value: -// -// void MyFunction(absl::string_view arg); -// -// If circumstances require, you may also pass one by const reference: -// -// void MyFunction(const absl::string_view& arg); // not preferred -// -// Passing by value generates slightly smaller code for many architectures. -// -// In either case, the source data of the `string_view` must outlive the -// `string_view` itself. -// -// A `string_view` is also suitable for local variables if you know that the -// lifetime of the underlying object is longer than the lifetime of your -// `string_view` variable. However, beware of binding a `string_view` to a -// temporary value: -// -// // BAD use of string_view: lifetime problem -// absl::string_view sv = obj.ReturnAString(); -// -// // GOOD use of string_view: str outlives sv -// std::string str = obj.ReturnAString(); -// absl::string_view sv = str; -// -// Due to lifetime issues, a `string_view` is sometimes a poor choice for a -// return value and usually a poor choice for a data member. If you do use a -// `string_view` this way, it is your responsibility to ensure that the object -// pointed to by the `string_view` outlives the `string_view`. -// -// A `string_view` may represent a whole string or just part of a string. For -// example, when splitting a string, `std::vector<absl::string_view>` is a -// natural data type for the output. -// -// For another example, a Cord is a non-contiguous, potentially very -// long string-like object. The Cord class has an interface that iteratively -// provides string_view objects that point to the successive pieces of a Cord -// object. -// -// When constructed from a source which is NUL-terminated, the `string_view` -// itself will not include the NUL-terminator unless a specific size (including -// the NUL) is passed to the constructor. As a result, common idioms that work -// on NUL-terminated strings do not work on `string_view` objects. If you write -// code that scans a `string_view`, you must check its length rather than test -// for nul, for example. Note, however, that nuls may still be embedded within -// a `string_view` explicitly. -// -// You may create a null `string_view` in two ways: -// -// absl::string_view sv; -// absl::string_view sv(nullptr, 0); -// -// For the above, `sv.data() == nullptr`, `sv.length() == 0`, and -// `sv.empty() == true`. Also, if you create a `string_view` with a non-null -// pointer then `sv.data() != nullptr`. Thus, you can use `string_view()` to -// signal an undefined value that is different from other `string_view` values -// in a similar fashion to how `const char* p1 = nullptr;` is different from -// `const char* p2 = "";`. However, in practice, it is not recommended to rely -// on this behavior. -// -// Be careful not to confuse a null `string_view` with an empty one. A null -// `string_view` is an empty `string_view`, but some empty `string_view`s are -// not null. Prefer checking for emptiness over checking for null. -// -// There are many ways to create an empty string_view: -// -// const char* nullcp = nullptr; -// // string_view.size() will return 0 in all cases. -// absl::string_view(); -// absl::string_view(nullcp, 0); -// absl::string_view(""); -// absl::string_view("", 0); -// absl::string_view("abcdef", 0); -// absl::string_view("abcdef" + 6, 0); -// -// All empty `string_view` objects whether null or not, are equal: -// -// absl::string_view() == absl::string_view("", 0) -// absl::string_view(nullptr, 0) == absl::string_view("abcdef"+6, 0) -class ABSL_ATTRIBUTE_VIEW string_view { - public: - using traits_type = std::char_traits<char>; - using value_type = char; - using pointer = char* absl_nullable; - using const_pointer = const char* absl_nullable; - using reference = char&; - using const_reference = const char&; - using const_iterator = const char* absl_nullable; - using iterator = const_iterator; - using const_reverse_iterator = std::reverse_iterator<const_iterator>; - using reverse_iterator = const_reverse_iterator; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using absl_internal_is_view = std::true_type; - - static constexpr size_type npos = static_cast<size_type>(-1); - - // Null `string_view` constructor - constexpr string_view() noexcept : ptr_(nullptr), length_(0) {} - - // Implicit constructors - - template <typename Allocator> - string_view( // NOLINT(runtime/explicit) - const std::basic_string<char, std::char_traits<char>, Allocator>& str - ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept - // This is implemented in terms of `string_view(p, n)` so `str.size()` - // doesn't need to be reevaluated after `ptr_` is set. - // The length check is also skipped since it is unnecessary and causes - // code bloat. - : string_view(str.data(), str.size(), SkipCheckLengthTag{}) {} - - // Implicit constructor of a `string_view` from NUL-terminated `str`. When - // accepting possibly null strings, use `absl::NullSafeStringView(str)` - // instead (see below). - // The length check is skipped since it is unnecessary and causes code bloat. - constexpr string_view( // NOLINT(runtime/explicit) - const char* absl_nonnull str) ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR(str) - : ptr_(str), length_(str ? StrlenInternal(str) : 0) { - ABSL_HARDENING_ASSERT(str != nullptr); - } - - // Constructor of a `string_view` from a `const char*` and length. - constexpr string_view(const char* absl_nullable data, size_type len) - : ptr_(data), length_(CheckLengthInternal(len)) { - ABSL_ASSERT(data != nullptr || len == 0); - } - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - template <std::contiguous_iterator It, std::sized_sentinel_for<It> End> - requires(std::is_same_v<std::iter_value_t<It>, value_type> && - !std::is_convertible_v<End, size_type>) - constexpr string_view(It begin, End end) - : ptr_(std::to_address(begin)), length_(end - begin) { - ABSL_HARDENING_ASSERT(end >= begin); - } -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - - // Deleted constructor from std::nullptr_t from C++23. - string_view(std::nullptr_t) = delete; - - constexpr string_view(const string_view&) noexcept = default; - string_view& operator=(const string_view&) noexcept = default; - - // Iterators - - // string_view::begin() - // - // Returns an iterator pointing to the first character at the beginning of the - // `string_view`, or `end()` if the `string_view` is empty. - constexpr const_iterator begin() const noexcept { return ptr_; } - - // string_view::end() - // - // Returns an iterator pointing just beyond the last character at the end of - // the `string_view`. This iterator acts as a placeholder; attempting to - // access it results in undefined behavior. - constexpr const_iterator end() const noexcept { return ptr_ + length_; } - - // string_view::cbegin() - // - // Returns a const iterator pointing to the first character at the beginning - // of the `string_view`, or `end()` if the `string_view` is empty. - constexpr const_iterator cbegin() const noexcept { return begin(); } - - // string_view::cend() - // - // Returns a const iterator pointing just beyond the last character at the end - // of the `string_view`. This pointer acts as a placeholder; attempting to - // access its element results in undefined behavior. - constexpr const_iterator cend() const noexcept { return end(); } - - // string_view::rbegin() - // - // Returns a reverse iterator pointing to the last character at the end of the - // `string_view`, or `rend()` if the `string_view` is empty. - const_reverse_iterator rbegin() const noexcept { - return const_reverse_iterator(end()); - } - - // string_view::rend() - // - // Returns a reverse iterator pointing just before the first character at the - // beginning of the `string_view`. This pointer acts as a placeholder; - // attempting to access its element results in undefined behavior. - const_reverse_iterator rend() const noexcept { - return const_reverse_iterator(begin()); - } - - // string_view::crbegin() - // - // Returns a const reverse iterator pointing to the last character at the end - // of the `string_view`, or `crend()` if the `string_view` is empty. - const_reverse_iterator crbegin() const noexcept { return rbegin(); } - - // string_view::crend() - // - // Returns a const reverse iterator pointing just before the first character - // at the beginning of the `string_view`. This pointer acts as a placeholder; - // attempting to access its element results in undefined behavior. - const_reverse_iterator crend() const noexcept { return rend(); } - - // Capacity Utilities - - // string_view::size() - // - // Returns the number of characters in the `string_view`. - constexpr size_type size() const noexcept { return length_; } - - // string_view::length() - // - // Returns the number of characters in the `string_view`. Alias for `size()`. - constexpr size_type length() const noexcept { return size(); } - - // string_view::max_size() - // - // Returns the maximum number of characters the `string_view` can hold. - constexpr size_type max_size() const noexcept { return kMaxSize; } - - // string_view::empty() - // - // Checks if the `string_view` is empty (refers to no characters). - constexpr bool empty() const noexcept { return length_ == 0; } - - // string_view::operator[] - // - // Returns the ith element of the `string_view` using the array operator. - // Note that this operator does not perform any bounds checking. - constexpr const_reference operator[](size_type i) const { - ABSL_HARDENING_ASSERT(i < size()); - return ptr_[i]; - } - - // string_view::at() - // - // Returns the ith element of the `string_view`. Bounds checking is performed, - // and an exception of type `std::out_of_range` will be thrown on invalid - // access. - constexpr const_reference at(size_type i) const { - if (ABSL_PREDICT_FALSE(i >= size())) { - base_internal::ThrowStdOutOfRange("absl::string_view::at"); - } - return ptr_[i]; - } - - // string_view::front() - // - // Returns the first element of a `string_view`. - constexpr const_reference front() const { - ABSL_HARDENING_ASSERT(!empty()); - return ptr_[0]; - } - - // string_view::back() - // - // Returns the last element of a `string_view`. - constexpr const_reference back() const { - ABSL_HARDENING_ASSERT(!empty()); - return ptr_[size() - 1]; - } - - // string_view::data() - // - // Returns a pointer to the underlying character array (which is of course - // stored elsewhere). Note that `string_view::data()` may contain embedded nul - // characters, but the returned buffer may or may not be NUL-terminated; - // therefore, do not pass `data()` to a routine that expects a NUL-terminated - // string. - constexpr const_pointer data() const noexcept { return ptr_; } - - // Modifiers - - // string_view::remove_prefix() - // - // Removes the first `n` characters from the `string_view`. Note that the - // underlying string is not changed, only the view. - constexpr void remove_prefix(size_type n) { - ABSL_HARDENING_ASSERT(n <= length_); - ptr_ += n; - length_ -= n; - } - - // string_view::remove_suffix() - // - // Removes the last `n` characters from the `string_view`. Note that the - // underlying string is not changed, only the view. - constexpr void remove_suffix(size_type n) { - ABSL_HARDENING_ASSERT(n <= length_); - length_ -= n; - } - - // string_view::swap() - // - // Swaps this `string_view` with another `string_view`. - constexpr void swap(string_view& s) noexcept { - auto t = *this; - *this = s; - s = t; - } - - // Explicit conversion operators - - // Converts to `std::basic_string`. - template <typename A> - explicit operator std::basic_string<char, traits_type, A>() const { - if (!data()) return {}; - return std::basic_string<char, traits_type, A>(data(), size()); - } - - // string_view::copy() - // - // Copies the contents of the `string_view` at offset `pos` and length `n` - // into `buf`. - size_type copy(char* absl_nonnull buf, size_type n, size_type pos = 0) const { - if (ABSL_PREDICT_FALSE(pos > length_)) { - base_internal::ThrowStdOutOfRange("absl::string_view::copy"); - } - size_type rlen = (std::min)(length_ - pos, n); - if (rlen > 0) { - const char* start = ptr_ + pos; - traits_type::copy(buf, start, rlen); - } - return rlen; - } - - // string_view::substr() - // - // Returns a "substring" of the `string_view` (at offset `pos` and length - // `n`) as another string_view. This function throws `std::out_of_bounds` if - // `pos > size`. - // Use absl::ClippedSubstr if you need a truncating substr operation. - constexpr string_view substr(size_type pos = 0, size_type n = npos) const { - if (ABSL_PREDICT_FALSE(pos > length_)) { - base_internal::ThrowStdOutOfRange("absl::string_view::substr"); - } - return string_view(ptr_ + pos, (std::min)(n, length_ - pos)); - } - - // string_view::compare() - // - // Performs a lexicographical comparison between this `string_view` and - // another `string_view` `x`, returning a negative value if `*this` is less - // than `x`, 0 if `*this` is equal to `x`, and a positive value if `*this` - // is greater than `x`. - constexpr int compare(string_view x) const noexcept { - return CompareImpl(length_, x.length_, - (std::min)(length_, x.length_) == 0 - ? 0 - : ABSL_INTERNAL_STRING_VIEW_MEMCMP( - ptr_, x.ptr_, (std::min)(length_, x.length_))); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // 'string_view` and another `absl::string_view`. - constexpr int compare(size_type pos1, size_type count1, string_view v) const { - return substr(pos1, count1).compare(v); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a substring of another `absl::string_view`. - constexpr int compare(size_type pos1, size_type count1, string_view v, - size_type pos2, size_type count2) const { - return substr(pos1, count1).compare(v.substr(pos2, count2)); - } - - // Overload of `string_view::compare()` for comparing a `string_view` and a - // a different C-style string `s`. - constexpr int compare(const char* absl_nonnull s) const { - return compare(string_view(s)); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a different string C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, - const char* absl_nonnull s) const { - return substr(pos1, count1).compare(string_view(s)); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a substring of a different C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, - const char* absl_nonnull s, size_type count2) const { - return substr(pos1, count1).compare(string_view(s, count2)); - } - - // Find Utilities - - // string_view::find() - // - // Finds the first occurrence of the substring `s` within the `string_view`, - // returning the position of the first character's match, or `npos` if no - // match was found. - size_type find(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find()` for finding the given character `c` - // within the `string_view`. - size_type find(char c, size_type pos = 0) const noexcept; - - // Overload of `string_view::find()` for finding a substring of a different - // C-style string `s` within the `string_view`. - size_type find(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find(string_view(s, count), pos); - } - - // Overload of `string_view::find()` for finding a different C-style string - // `s` within the `string_view`. - size_type find(const char* absl_nonnull s, size_type pos = 0) const { - return find(string_view(s), pos); - } - - // string_view::rfind() - // - // Finds the last occurrence of a substring `s` within the `string_view`, - // returning the position of the first character's match, or `npos` if no - // match was found. - size_type rfind(string_view s, size_type pos = npos) const noexcept; - - // Overload of `string_view::rfind()` for finding the last given character `c` - // within the `string_view`. - size_type rfind(char c, size_type pos = npos) const noexcept; - - // Overload of `string_view::rfind()` for finding a substring of a different - // C-style string `s` within the `string_view`. - size_type rfind(const char* absl_nonnull s, size_type pos, - size_type count) const { - return rfind(string_view(s, count), pos); - } - - // Overload of `string_view::rfind()` for finding a different C-style string - // `s` within the `string_view`. - size_type rfind(const char* absl_nonnull s, size_type pos = npos) const { - return rfind(string_view(s), pos); - } - - // string_view::find_first_of() - // - // Finds the first occurrence of any of the characters in `s` within the - // `string_view`, returning the start position of the match, or `npos` if no - // match was found. - size_type find_first_of(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_of()` for finding a character `c` - // within the `string_view`. - size_type find_first_of(char c, size_type pos = 0) const noexcept { - return find(c, pos); - } - - // Overload of `string_view::find_first_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_first_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_first_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_first_of()` for finding a different C-style - // string `s` within the `string_view`. - size_type find_first_of(const char* absl_nonnull s, size_type pos = 0) const { - return find_first_of(string_view(s), pos); - } - - // string_view::find_last_of() - // - // Finds the last occurrence of any of the characters in `s` within the - // `string_view`, returning the start position of the match, or `npos` if no - // match was found. - size_type find_last_of(string_view s, size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_of()` for finding a character `c` - // within the `string_view`. - size_type find_last_of(char c, size_type pos = npos) const noexcept { - return rfind(c, pos); - } - - // Overload of `string_view::find_last_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_last_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_last_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_last_of()` for finding a different C-style - // string `s` within the `string_view`. - size_type find_last_of(const char* absl_nonnull s, - size_type pos = npos) const { - return find_last_of(string_view(s), pos); - } - - // string_view::find_first_not_of() - // - // Finds the first occurrence of any of the characters not in `s` within the - // `string_view`, returning the start position of the first non-match, or - // `npos` if no non-match was found. - size_type find_first_not_of(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_not_of()` for finding a character - // that is not `c` within the `string_view`. - size_type find_first_not_of(char c, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_not_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_first_not_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_first_not_of()` for finding a different - // C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* absl_nonnull s, - size_type pos = 0) const { - return find_first_not_of(string_view(s), pos); - } - - // string_view::find_last_not_of() - // - // Finds the last occurrence of any of the characters not in `s` within the - // `string_view`, returning the start position of the last non-match, or - // `npos` if no non-match was found. - size_type find_last_not_of(string_view s, - size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_not_of()` for finding a character - // that is not `c` within the `string_view`. - size_type find_last_not_of(char c, size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_not_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_last_not_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_last_not_of()` for finding a different - // C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* absl_nonnull s, - size_type pos = npos) const { - return find_last_not_of(string_view(s), pos); - } - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - // string_view::starts_with() - // - // Returns true if the `string_view` starts with the prefix `s`. - // - // This method only exists when targeting at least C++20. - // If support for C++ prior to C++20 is required, use `absl::StartsWith()` - // from `//absl/strings/match.h` for compatibility. - constexpr bool starts_with(string_view s) const noexcept { - return s.empty() || - (size() >= s.size() && - ABSL_INTERNAL_STRING_VIEW_MEMCMP(data(), s.data(), s.size()) == 0); - } - - // Overload of `string_view::starts_with()` that returns true if `c` is the - // first character of the `string_view`. - constexpr bool starts_with(char c) const noexcept { - return !empty() && front() == c; - } - - // Overload of `string_view::starts_with()` that returns true if the - // `string_view` starts with the C-style prefix `s`. - constexpr bool starts_with(const char* absl_nonnull s) const { - return starts_with(string_view(s)); - } - - // string_view::ends_with() - // - // Returns true if the `string_view` ends with the suffix `s`. - // - // This method only exists when targeting at least C++20. - // If support for C++ prior to C++20 is required, use `absl::EndsWith()` - // from `//absl/strings/match.h` for compatibility. - constexpr bool ends_with(string_view s) const noexcept { - return s.empty() || (size() >= s.size() && ABSL_INTERNAL_STRING_VIEW_MEMCMP( - data() + (size() - s.size()), - s.data(), s.size()) == 0); - } - - // Overload of `string_view::ends_with()` that returns true if `c` is the - // last character of the `string_view`. - constexpr bool ends_with(char c) const noexcept { - return !empty() && back() == c; - } - - // Overload of `string_view::ends_with()` that returns true if the - // `string_view` ends with the C-style suffix `s`. - constexpr bool ends_with(const char* absl_nonnull s) const { - return ends_with(string_view(s)); - } -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - - private: - // The constructor from std::string delegates to this constructor. - // See the comment on that constructor for the rationale. - struct SkipCheckLengthTag {}; - string_view(const char* absl_nullable data, size_type len, - SkipCheckLengthTag) noexcept - : ptr_(data), length_(len) {} - - static constexpr size_type kMaxSize = - (std::numeric_limits<difference_type>::max)(); - - static constexpr size_type CheckLengthInternal(size_type len) { - ABSL_HARDENING_ASSERT(len <= kMaxSize); - return len; - } - - static constexpr size_type StrlenInternal(const char* absl_nonnull str) { -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC 2017+ can evaluate this at compile-time. - const char* begin = str; - while (*str != '\0') ++str; - return str - begin; -#elif ABSL_HAVE_BUILTIN(__builtin_strlen) || \ - (defined(__GNUC__) && !defined(__clang__)) - // GCC has __builtin_strlen according to - // https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Other-Builtins.html, but - // ABSL_HAVE_BUILTIN doesn't detect that, so we use the extra checks above. - // __builtin_strlen is constexpr. - return __builtin_strlen(str); -#else - return str ? strlen(str) : 0; -#endif - } - - static constexpr int CompareImpl(size_type length_a, size_type length_b, - int compare_result) { - return compare_result == 0 ? static_cast<int>(length_a > length_b) - - static_cast<int>(length_a < length_b) - : (compare_result < 0 ? -1 : 1); - } - - const char* absl_nullable ptr_; - size_type length_; -}; - -// This large function is defined inline so that in a fairly common case where -// one of the arguments is a literal, the compiler can elide a lot of the -// following comparisons. -constexpr bool operator==(string_view x, string_view y) noexcept { - return x.size() == y.size() && - (x.empty() || - ABSL_INTERNAL_STRING_VIEW_MEMCMP(x.data(), y.data(), x.size()) == 0); -} - -constexpr bool operator!=(string_view x, string_view y) noexcept { - return !(x == y); -} - -constexpr bool operator<(string_view x, string_view y) noexcept { - return x.compare(y) < 0; -} - -constexpr bool operator>(string_view x, string_view y) noexcept { - return y < x; -} - -constexpr bool operator<=(string_view x, string_view y) noexcept { - return !(y < x); -} - -constexpr bool operator>=(string_view x, string_view y) noexcept { - return !(x < y); -} - -// IO Insertion Operator -std::ostream& operator<<(std::ostream& o, string_view piece); - -ABSL_NAMESPACE_END -} // namespace absl - -#undef ABSL_INTERNAL_DIAGNOSE_IF_NULLPTR -#undef ABSL_INTERNAL_STRING_VIEW_MEMCMP - -#endif // ABSL_USES_STD_STRING_VIEW - -namespace absl { -ABSL_NAMESPACE_BEGIN +using std::string_view; // ClippedSubstr() //
diff --git a/absl/strings/string_view_benchmark.cc b/absl/strings/string_view_benchmark.cc deleted file mode 100644 index 546f6ed..0000000 --- a/absl/strings/string_view_benchmark.cc +++ /dev/null
@@ -1,380 +0,0 @@ -// Copyright 2018 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. - -#include <algorithm> -#include <cstddef> -#include <cstdint> -#include <map> -#include <random> -#include <string> -#include <unordered_set> -#include <vector> - -#include "absl/base/attributes.h" -#include "absl/base/internal/raw_logging.h" -#include "absl/base/macros.h" -#include "absl/random/random.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "benchmark/benchmark.h" - -namespace { - -void BM_StringViewFromString(benchmark::State& state) { - std::string s(state.range(0), 'x'); - std::string* ps = &s; - struct SV { - SV() = default; - explicit SV(const std::string& s) : sv(s) {} - absl::string_view sv; - } sv; - SV* psv = &sv; - benchmark::DoNotOptimize(ps); - benchmark::DoNotOptimize(psv); - for (auto _ : state) { - new (psv) SV(*ps); - benchmark::DoNotOptimize(sv); - } -} -BENCHMARK(BM_StringViewFromString)->Arg(12)->Arg(128); - -// Provide a forcibly out-of-line wrapper for operator== that can be used in -// benchmarks to measure the impact of inlining. -ABSL_ATTRIBUTE_NOINLINE -bool NonInlinedEq(absl::string_view a, absl::string_view b) { return a == b; } - -// We use functions that cannot be inlined to perform the comparison loops so -// that inlining of the operator== can't optimize away *everything*. -ABSL_ATTRIBUTE_NOINLINE -void DoEqualityComparisons(benchmark::State& state, absl::string_view a, - absl::string_view b) { - for (auto _ : state) { - benchmark::DoNotOptimize(a == b); - } -} - -void BM_EqualIdentical(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoEqualityComparisons(state, x, x); -} -BENCHMARK(BM_EqualIdentical)->DenseRange(0, 3)->Range(4, 1 << 10); - -void BM_EqualSame(benchmark::State& state) { - std::string x(state.range(0), 'a'); - std::string y = x; - DoEqualityComparisons(state, x, y); -} -BENCHMARK(BM_EqualSame) - ->DenseRange(0, 10) - ->Arg(20) - ->Arg(40) - ->Arg(70) - ->Arg(110) - ->Range(160, 4096); - -void BM_EqualDifferent(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - if (len > 0) { - y[len - 1] = 'b'; - } - DoEqualityComparisons(state, x, y); -} -BENCHMARK(BM_EqualDifferent)->DenseRange(0, 3)->Range(4, 1 << 10); - -// This benchmark is intended to check that important simplifications can be -// made with absl::string_view comparisons against constant strings. The idea is -// that if constant strings cause redundant components of the comparison, the -// compiler should detect and eliminate them. Here we use 8 different strings, -// each with the same size. Provided our comparison makes the implementation -// inline-able by the compiler, it should fold all of these away into a single -// size check once per loop iteration. -ABSL_ATTRIBUTE_NOINLINE -void DoConstantSizeInlinedEqualityComparisons(benchmark::State& state, - absl::string_view a) { - for (auto _ : state) { - benchmark::DoNotOptimize(a == "aaa"); - benchmark::DoNotOptimize(a == "bbb"); - benchmark::DoNotOptimize(a == "ccc"); - benchmark::DoNotOptimize(a == "ddd"); - benchmark::DoNotOptimize(a == "eee"); - benchmark::DoNotOptimize(a == "fff"); - benchmark::DoNotOptimize(a == "ggg"); - benchmark::DoNotOptimize(a == "hhh"); - } -} -void BM_EqualConstantSizeInlined(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoConstantSizeInlinedEqualityComparisons(state, x); -} -// We only need to check for size of 3, and <> 3 as this benchmark only has to -// do with size differences. -BENCHMARK(BM_EqualConstantSizeInlined)->DenseRange(2, 4); - -// This benchmark exists purely to give context to the above timings: this is -// what they would look like if the compiler is completely unable to simplify -// between two comparisons when they are comparing against constant strings. -ABSL_ATTRIBUTE_NOINLINE -void DoConstantSizeNonInlinedEqualityComparisons(benchmark::State& state, - absl::string_view a) { - for (auto _ : state) { - // Force these out-of-line to compare with the above function. - benchmark::DoNotOptimize(NonInlinedEq(a, "aaa")); - benchmark::DoNotOptimize(NonInlinedEq(a, "bbb")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ccc")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ddd")); - benchmark::DoNotOptimize(NonInlinedEq(a, "eee")); - benchmark::DoNotOptimize(NonInlinedEq(a, "fff")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ggg")); - benchmark::DoNotOptimize(NonInlinedEq(a, "hhh")); - } -} - -void BM_EqualConstantSizeNonInlined(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoConstantSizeNonInlinedEqualityComparisons(state, x); -} -// We only need to check for size of 3, and <> 3 as this benchmark only has to -// do with size differences. -BENCHMARK(BM_EqualConstantSizeNonInlined)->DenseRange(2, 4); - -void BM_CompareSame(benchmark::State& state) { - const int len = state.range(0); - std::string x; - for (int i = 0; i < len; i++) { - x += 'a'; - } - std::string y = x; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareSame)->DenseRange(0, 3)->Range(4, 1 << 10); - -void BM_CompareFirstOneLess(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - y.back() = 'b'; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareFirstOneLess)->DenseRange(1, 3)->Range(4, 1 << 10); - -void BM_CompareSecondOneLess(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - x.back() = 'b'; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareSecondOneLess)->DenseRange(1, 3)->Range(4, 1 << 10); - -void BM_find_string_view_len_one(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find("x")); // not present; length 1 - } -} -BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20); - -void BM_find_string_view_len_two(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find("xx")); // not present; length 2 - } -} -BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20); - -void BM_find_one_char(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find('x')); // not present - } -} -BENCHMARK(BM_find_one_char)->Range(1, 1 << 20); - -void BM_rfind_one_char(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.rfind('x')); // not present - } -} -BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20); - -void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) { - const int needle_len = state.range(0); - std::string needle; - for (int i = 0; i < needle_len; ++i) { - needle += 'a' + i; - } - std::string haystack(haystack_len, '0'); // 1000 zeros. - - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find_first_of(needle)); - } -} - -void BM_find_first_of_short(benchmark::State& state) { - BM_worst_case_find_first_of(state, 10); -} - -void BM_find_first_of_medium(benchmark::State& state) { - BM_worst_case_find_first_of(state, 100); -} - -void BM_find_first_of_long(benchmark::State& state) { - BM_worst_case_find_first_of(state, 1000); -} - -BENCHMARK(BM_find_first_of_short)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); -BENCHMARK(BM_find_first_of_medium)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); -BENCHMARK(BM_find_first_of_long)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); - -struct EasyMap : public std::map<absl::string_view, uint64_t> { - explicit EasyMap(size_t) {} -}; - -// This templated benchmark helper function is intended to stress operator== or -// operator< in a realistic test. It surely isn't entirely realistic, but it's -// a start. The test creates a map of type Map, a template arg, and populates -// it with table_size key/value pairs. Each key has WordsPerKey words. After -// creating the map, a number of lookups are done in random order. Some keys -// are used much more frequently than others in this phase of the test. -template <typename Map, int WordsPerKey> -void StringViewMapBenchmark(benchmark::State& state) { - const int table_size = state.range(0); - const double kFractionOfKeysThatAreHot = 0.2; - const int kNumLookupsOfHotKeys = 20; - const int kNumLookupsOfColdKeys = 1; - const char* words[] = {"the", "quick", "brown", "fox", "jumped", - "over", "the", "lazy", "dog", "and", - "found", "a", "large", "mushroom", "and", - "a", "couple", "crickets", "eating", "pie"}; - // Create some keys that consist of words in random order. - absl::InsecureBitGen rng; - std::vector<std::string> keys(table_size); - std::vector<int> all_indices; - const int kBlockSize = 1 << 12; - std::unordered_set<std::string> t(kBlockSize); - std::uniform_int_distribution<int> uniform(0, ABSL_ARRAYSIZE(words) - 1); - for (int i = 0; i < table_size; i++) { - all_indices.push_back(i); - do { - keys[i].clear(); - for (int j = 0; j < WordsPerKey; j++) { - absl::StrAppend(&keys[i], j > 0 ? " " : "", words[uniform(rng)]); - } - } while (!t.insert(keys[i]).second); - } - - // Create a list of strings to lookup: a permutation of the array of - // keys we just created, with repeats. "Hot" keys get repeated more. - std::shuffle(all_indices.begin(), all_indices.end(), rng); - const int num_hot = table_size * kFractionOfKeysThatAreHot; - const int num_cold = table_size - num_hot; - std::vector<int> hot_indices(all_indices.begin(), - all_indices.begin() + num_hot); - std::vector<int> indices; - for (int i = 0; i < kNumLookupsOfColdKeys; i++) { - indices.insert(indices.end(), all_indices.begin(), all_indices.end()); - } - for (int i = 0; i < kNumLookupsOfHotKeys - kNumLookupsOfColdKeys; i++) { - indices.insert(indices.end(), hot_indices.begin(), hot_indices.end()); - } - std::shuffle(indices.begin(), indices.end(), rng); - ABSL_RAW_CHECK( - num_cold * kNumLookupsOfColdKeys + num_hot * kNumLookupsOfHotKeys == - indices.size(), - ""); - // After constructing the array we probe it with absl::string_views built from - // test_strings. This means operator== won't see equal pointers, so - // it'll have to check for equal lengths and equal characters. - std::vector<std::string> test_strings(indices.size()); - for (int i = 0; i < indices.size(); i++) { - test_strings[i] = keys[indices[i]]; - } - - // Run the benchmark. It includes map construction but is mostly - // map lookups. - for (auto _ : state) { - Map h(table_size); - for (int i = 0; i < table_size; i++) { - h[keys[i]] = i * 2; - } - ABSL_RAW_CHECK(h.size() == table_size, ""); - uint64_t sum = 0; - for (int i = 0; i < indices.size(); i++) { - sum += h[test_strings[i]]; - } - benchmark::DoNotOptimize(sum); - } -} - -void BM_StdMap_4(benchmark::State& state) { - StringViewMapBenchmark<EasyMap, 4>(state); -} -BENCHMARK(BM_StdMap_4)->Range(1 << 10, 1 << 16); - -void BM_StdMap_8(benchmark::State& state) { - StringViewMapBenchmark<EasyMap, 8>(state); -} -BENCHMARK(BM_StdMap_8)->Range(1 << 10, 1 << 16); - -void BM_CopyToStringNative(benchmark::State& state) { - std::string src(state.range(0), 'x'); - absl::string_view sv(src); - std::string dst; - for (auto _ : state) { - dst.assign(sv.begin(), sv.end()); - } -} -BENCHMARK(BM_CopyToStringNative)->Range(1 << 3, 1 << 12); - -void BM_AppendToStringNative(benchmark::State& state) { - std::string src(state.range(0), 'x'); - absl::string_view sv(src); - std::string dst; - for (auto _ : state) { - dst.clear(); - dst.insert(dst.end(), sv.begin(), sv.end()); - } -} -BENCHMARK(BM_AppendToStringNative)->Range(1 << 3, 1 << 12); - -} // namespace
diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index 390173f..d6cec6f 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc
@@ -14,8 +14,6 @@ #include "absl/strings/string_view.h" -#include <stdlib.h> - #include <array> #include <cstddef> #include <cstdlib> @@ -35,740 +33,8 @@ #include "absl/base/config.h" #include "absl/meta/type_traits.h" -#ifdef __has_include -#if __has_include(<version>) -#include <version> // NOLINT(misc-include-cleaner) -#endif -#endif - -#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L -#include <ranges> // NOLINT(build/c++20) -#endif - -#if defined(ABSL_USES_STD_STRING_VIEW) || defined(__ANDROID__) -// We don't control the death messaging when using std::string_view. -// Android assert messages only go to system log, so death tests cannot inspect -// the message for matching. -#define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEATH_IF_SUPPORTED(statement, ".*") -#else -#define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEATH_IF_SUPPORTED(statement, regex) -#endif - namespace { -static_assert(!absl::type_traits_internal::IsOwner<absl::string_view>::value && - absl::type_traits_internal::IsView<absl::string_view>::value, - "string_view is a view, not an owner"); - -static_assert(absl::type_traits_internal::IsLifetimeBoundAssignment< - absl::string_view, std::string>::value, - "lifetimebound assignment not detected"); - -#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L -// NOLINTNEXTLINE(build/c++20) -static_assert(std::ranges::enable_view<absl::string_view>, - "std::ranges::view not enabled"); -// NOLINTNEXTLINE(build/c++20) -static_assert(std::ranges::enable_borrowed_range<absl::string_view>, - "std::ranges::borrowed_range not enabled"); -#endif - -// A minimal allocator that uses malloc(). -template <typename T> -struct Mallocator { - typedef T value_type; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef T* pointer; - typedef const T* const_pointer; - typedef T& reference; - typedef const T& const_reference; - - size_type max_size() const { - return size_t(std::numeric_limits<size_type>::max()) / sizeof(value_type); - } - template <typename U> - struct rebind { - typedef Mallocator<U> other; - }; - Mallocator() = default; - template <class U> - Mallocator(const Mallocator<U>&) {} // NOLINT(runtime/explicit) - - T* allocate(size_t n) { return static_cast<T*>(std::malloc(n * sizeof(T))); } - void deallocate(T* p, size_t) { std::free(p); } -}; -template <typename T, typename U> -bool operator==(const Mallocator<T>&, const Mallocator<U>&) { - return true; -} -template <typename T, typename U> -bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { - return false; -} - -TEST(StringViewTest, Ctor) { - { - // Null. - absl::string_view s10; - EXPECT_TRUE(s10.data() == nullptr); - EXPECT_EQ(0u, s10.length()); - } - - { - // const char* without length. - const char* hello = "hello"; - absl::string_view s20(hello); - EXPECT_TRUE(s20.data() == hello); - EXPECT_EQ(5u, s20.length()); - - // const char* with length. - absl::string_view s21(hello, 4); - EXPECT_TRUE(s21.data() == hello); - EXPECT_EQ(4u, s21.length()); - - // Not recommended, but valid C++ - absl::string_view s22(hello, 6); - EXPECT_TRUE(s22.data() == hello); - EXPECT_EQ(6u, s22.length()); - } - - { - // std::string. - std::string hola = "hola"; - absl::string_view s30(hola); - EXPECT_TRUE(s30.data() == hola.data()); - EXPECT_EQ(4u, s30.length()); - - // std::string with embedded '\0'. - hola.push_back('\0'); - hola.append("h2"); - hola.push_back('\0'); - absl::string_view s31(hola); - EXPECT_TRUE(s31.data() == hola.data()); - EXPECT_EQ(8u, s31.length()); - } - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - { - // Iterator constructor - std::string str = "hello"; - absl::string_view s1(str.begin(), str.end()); - EXPECT_EQ(s1, "hello"); - - std::array<char, 3> arr = { '1', '2', '3' }; - absl::string_view s2(arr.begin(), arr.end()); - EXPECT_EQ(s2, "123"); - - const char carr[] = "carr"; - absl::string_view s3(carr, carr + strlen(carr)); - EXPECT_EQ(s3, "carr"); - } -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - - { - using mstring = - std::basic_string<char, std::char_traits<char>, Mallocator<char>>; - mstring str1("BUNGIE-JUMPING!"); - const mstring str2("SLEEPING!"); - - absl::string_view s1(str1); - s1.remove_prefix(strlen("BUNGIE-JUM")); - - absl::string_view s2(str2); - s2.remove_prefix(strlen("SLEE")); - - EXPECT_EQ(s1, s2); - EXPECT_EQ(s1, "PING!"); - } - - // TODO(mec): absl::string_view(const absl::string_view&); -} - -TEST(StringViewTest, Swap) { - absl::string_view a("a"); - absl::string_view b("bbb"); - EXPECT_TRUE(noexcept(a.swap(b))); - a.swap(b); - EXPECT_EQ(a, "bbb"); - EXPECT_EQ(b, "a"); - a.swap(b); - EXPECT_EQ(a, "a"); - EXPECT_EQ(b, "bbb"); -} - -TEST(StringViewTest, STLComparator) { - std::string s1("foo"); - std::string s2("bar"); - std::string s3("baz"); - - absl::string_view p1(s1); - absl::string_view p2(s2); - absl::string_view p3(s3); - - typedef std::map<absl::string_view, int> TestMap; - TestMap map; - - map.insert(std::make_pair(p1, 0)); - map.insert(std::make_pair(p2, 1)); - map.insert(std::make_pair(p3, 2)); - EXPECT_EQ(map.size(), 3u); - - TestMap::const_iterator iter = map.begin(); - EXPECT_EQ(iter->second, 1); - ++iter; - EXPECT_EQ(iter->second, 2); - ++iter; - EXPECT_EQ(iter->second, 0); - ++iter; - EXPECT_TRUE(iter == map.end()); - - TestMap::iterator new_iter = map.find("zot"); - EXPECT_TRUE(new_iter == map.end()); - - new_iter = map.find("bar"); - EXPECT_TRUE(new_iter != map.end()); - - map.erase(new_iter); - EXPECT_EQ(map.size(), 2u); - - iter = map.begin(); - EXPECT_EQ(iter->second, 2); - ++iter; - EXPECT_EQ(iter->second, 0); - ++iter; - EXPECT_TRUE(iter == map.end()); -} - -#define COMPARE(result, op, x, y) \ - EXPECT_EQ(result, absl::string_view((x)) op absl::string_view((y))); \ - EXPECT_EQ(result, absl::string_view((x)).compare(absl::string_view((y))) op 0) - -TEST(StringViewTest, ComparisonOperators) { - COMPARE(true, ==, "", ""); - COMPARE(true, ==, "", absl::string_view()); - COMPARE(true, ==, absl::string_view(), ""); - COMPARE(true, ==, "a", "a"); - COMPARE(true, ==, "aa", "aa"); - COMPARE(false, ==, "a", ""); - COMPARE(false, ==, "", "a"); - COMPARE(false, ==, "a", "b"); - COMPARE(false, ==, "a", "aa"); - COMPARE(false, ==, "aa", "a"); - - COMPARE(false, !=, "", ""); - COMPARE(false, !=, "a", "a"); - COMPARE(false, !=, "aa", "aa"); - COMPARE(true, !=, "a", ""); - COMPARE(true, !=, "", "a"); - COMPARE(true, !=, "a", "b"); - COMPARE(true, !=, "a", "aa"); - COMPARE(true, !=, "aa", "a"); - - COMPARE(true, <, "a", "b"); - COMPARE(true, <, "a", "aa"); - COMPARE(true, <, "aa", "b"); - COMPARE(true, <, "aa", "bb"); - COMPARE(false, <, "a", "a"); - COMPARE(false, <, "b", "a"); - COMPARE(false, <, "aa", "a"); - COMPARE(false, <, "b", "aa"); - COMPARE(false, <, "bb", "aa"); - - COMPARE(true, <=, "a", "a"); - COMPARE(true, <=, "a", "b"); - COMPARE(true, <=, "a", "aa"); - COMPARE(true, <=, "aa", "b"); - COMPARE(true, <=, "aa", "bb"); - COMPARE(false, <=, "b", "a"); - COMPARE(false, <=, "aa", "a"); - COMPARE(false, <=, "b", "aa"); - COMPARE(false, <=, "bb", "aa"); - - COMPARE(false, >=, "a", "b"); - COMPARE(false, >=, "a", "aa"); - COMPARE(false, >=, "aa", "b"); - COMPARE(false, >=, "aa", "bb"); - COMPARE(true, >=, "a", "a"); - COMPARE(true, >=, "b", "a"); - COMPARE(true, >=, "aa", "a"); - COMPARE(true, >=, "b", "aa"); - COMPARE(true, >=, "bb", "aa"); - - COMPARE(false, >, "a", "a"); - COMPARE(false, >, "a", "b"); - COMPARE(false, >, "a", "aa"); - COMPARE(false, >, "aa", "b"); - COMPARE(false, >, "aa", "bb"); - COMPARE(true, >, "b", "a"); - COMPARE(true, >, "aa", "a"); - COMPARE(true, >, "b", "aa"); - COMPARE(true, >, "bb", "aa"); -} - -TEST(StringViewTest, ComparisonOperatorsByCharacterPosition) { - std::string x; - for (size_t i = 0; i < 256; i++) { - x += 'a'; - std::string y = x; - COMPARE(true, ==, x, y); - for (size_t j = 0; j < i; j++) { - std::string z = x; - z[j] = 'b'; // Differs in position 'j' - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - if (j + 1 < i) { - z[j + 1] = 'A'; // Differs in position 'j+1' as well - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - z[j + 1] = 'z'; // Differs in position 'j+1' as well - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - } - } - } -} -#undef COMPARE - -// Sadly, our users often confuse std::string::npos with -// absl::string_view::npos; So much so that we test here that they are the same. -// They need to both be unsigned, and both be the maximum-valued integer of -// their type. - -template <typename T> -struct is_type { - template <typename U> - static bool same(U) { - return false; - } - static bool same(T) { return true; } -}; - -TEST(StringViewTest, NposMatchesStdStringView) { - EXPECT_EQ(absl::string_view::npos, std::string::npos); - - EXPECT_TRUE(is_type<size_t>::same(absl::string_view::npos)); - EXPECT_FALSE(is_type<size_t>::same("")); - - // Make sure absl::string_view::npos continues to be a header constant. - char test[absl::string_view::npos & 1] = {0}; - EXPECT_EQ(0, test[0]); -} - -TEST(StringViewTest, STL1) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - const absl::string_view d("foobar"); - const absl::string_view e; - std::string temp("123"); - temp += '\0'; - temp += "456"; - const absl::string_view f(temp); - - EXPECT_EQ(a[6], 'g'); - EXPECT_EQ(b[0], 'a'); - EXPECT_EQ(c[2], 'z'); - EXPECT_EQ(f[3], '\0'); - EXPECT_EQ(f[5], '5'); - - EXPECT_EQ(*d.data(), 'f'); - EXPECT_EQ(d.data()[5], 'r'); - EXPECT_TRUE(e.data() == nullptr); - - EXPECT_EQ(*a.begin(), 'a'); - EXPECT_EQ(*(b.begin() + 2), 'c'); - EXPECT_EQ(*(c.end() - 1), 'z'); - - EXPECT_EQ(*a.rbegin(), 'z'); - EXPECT_EQ(*(b.rbegin() + 2), 'a'); - EXPECT_EQ(*(c.rend() - 1), 'x'); - EXPECT_TRUE(a.rbegin() + 26 == a.rend()); - - EXPECT_EQ(a.size(), 26u); - EXPECT_EQ(b.size(), 3u); - EXPECT_EQ(c.size(), 3u); - EXPECT_EQ(d.size(), 6u); - EXPECT_EQ(e.size(), 0u); - EXPECT_EQ(f.size(), 7u); - - EXPECT_TRUE(!d.empty()); - EXPECT_TRUE(d.begin() != d.end()); - EXPECT_TRUE(d.begin() + 6 == d.end()); - - EXPECT_TRUE(e.empty()); - EXPECT_TRUE(e.begin() == e.end()); - - char buf[4] = { '%', '%', '%', '%' }; - EXPECT_EQ(a.copy(buf, 4), 4u); - EXPECT_EQ(buf[0], a[0]); - EXPECT_EQ(buf[1], a[1]); - EXPECT_EQ(buf[2], a[2]); - EXPECT_EQ(buf[3], a[3]); - EXPECT_EQ(a.copy(buf, 3, 7), 3u); - EXPECT_EQ(buf[0], a[7]); - EXPECT_EQ(buf[1], a[8]); - EXPECT_EQ(buf[2], a[9]); - EXPECT_EQ(buf[3], a[3]); - EXPECT_EQ(c.copy(buf, 99), 3u); - EXPECT_EQ(buf[0], c[0]); - EXPECT_EQ(buf[1], c[1]); - EXPECT_EQ(buf[2], c[2]); - EXPECT_EQ(buf[3], a[3]); -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW(a.copy(buf, 1, 27), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED(a.copy(buf, 1, 27), "absl::string_view::copy"); -#endif -} - -// Separated from STL1() because some compilers produce an overly -// large stack frame for the combined function. -TEST(StringViewTest, STL2) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - - d = absl::string_view(); - EXPECT_EQ(d.size(), 0u); - EXPECT_TRUE(d.empty()); - EXPECT_TRUE(d.data() == nullptr); - EXPECT_TRUE(d.begin() == d.end()); - - EXPECT_EQ(a.find(b), 0u); - EXPECT_EQ(a.find(b, 1), absl::string_view::npos); - EXPECT_EQ(a.find(c), 23u); - EXPECT_EQ(a.find(c, 9), 23u); - EXPECT_EQ(a.find(c, absl::string_view::npos), absl::string_view::npos); - EXPECT_EQ(b.find(c), absl::string_view::npos); - EXPECT_EQ(b.find(c, absl::string_view::npos), absl::string_view::npos); - EXPECT_EQ(a.find(d), 0u); - EXPECT_EQ(a.find(e), 0u); - EXPECT_EQ(a.find(d, 12), 12u); - EXPECT_EQ(a.find(e, 17), 17u); - absl::string_view g("xx not found bb"); - EXPECT_EQ(a.find(g), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.find(b), absl::string_view::npos); - EXPECT_EQ(e.find(b), absl::string_view::npos); - EXPECT_EQ(d.find(b, 4), absl::string_view::npos); - EXPECT_EQ(e.find(b, 7), absl::string_view::npos); - - size_t empty_search_pos = std::string().find(std::string()); - EXPECT_EQ(d.find(d), empty_search_pos); - EXPECT_EQ(d.find(e), empty_search_pos); - EXPECT_EQ(e.find(d), empty_search_pos); - EXPECT_EQ(e.find(e), empty_search_pos); - EXPECT_EQ(d.find(d, 4), std::string().find(std::string(), 4)); - EXPECT_EQ(d.find(e, 4), std::string().find(std::string(), 4)); - EXPECT_EQ(e.find(d, 4), std::string().find(std::string(), 4)); - EXPECT_EQ(e.find(e, 4), std::string().find(std::string(), 4)); - - EXPECT_EQ(a.find('a'), 0u); - EXPECT_EQ(a.find('c'), 2u); - EXPECT_EQ(a.find('z'), 25u); - EXPECT_EQ(a.find('$'), absl::string_view::npos); - EXPECT_EQ(a.find('\0'), absl::string_view::npos); - EXPECT_EQ(f.find('\0'), 3u); - EXPECT_EQ(f.find('3'), 2u); - EXPECT_EQ(f.find('5'), 5u); - EXPECT_EQ(g.find('o'), 4u); - EXPECT_EQ(g.find('o', 4), 4u); - EXPECT_EQ(g.find('o', 5), 8u); - EXPECT_EQ(a.find('b', 5), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.find('\0'), absl::string_view::npos); - EXPECT_EQ(e.find('\0'), absl::string_view::npos); - EXPECT_EQ(d.find('\0', 4), absl::string_view::npos); - EXPECT_EQ(e.find('\0', 7), absl::string_view::npos); - EXPECT_EQ(d.find('x'), absl::string_view::npos); - EXPECT_EQ(e.find('x'), absl::string_view::npos); - EXPECT_EQ(d.find('x', 4), absl::string_view::npos); - EXPECT_EQ(e.find('x', 7), absl::string_view::npos); - - EXPECT_EQ(a.find(b.data(), 1, 0), 1u); - EXPECT_EQ(a.find(c.data(), 9, 0), 9u); - EXPECT_EQ(a.find(c.data(), absl::string_view::npos, 0), - absl::string_view::npos); - EXPECT_EQ(b.find(c.data(), absl::string_view::npos, 0), - absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.find(b.data(), 4, 0), absl::string_view::npos); - EXPECT_EQ(e.find(b.data(), 7, 0), absl::string_view::npos); - - EXPECT_EQ(a.find(b.data(), 1), absl::string_view::npos); - EXPECT_EQ(a.find(c.data(), 9), 23u); - EXPECT_EQ(a.find(c.data(), absl::string_view::npos), absl::string_view::npos); - EXPECT_EQ(b.find(c.data(), absl::string_view::npos), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.find(b.data(), 4), absl::string_view::npos); - EXPECT_EQ(e.find(b.data(), 7), absl::string_view::npos); - - EXPECT_EQ(a.rfind(b), 0u); - EXPECT_EQ(a.rfind(b, 1), 0u); - EXPECT_EQ(a.rfind(c), 23u); - EXPECT_EQ(a.rfind(c, 22), absl::string_view::npos); - EXPECT_EQ(a.rfind(c, 1), absl::string_view::npos); - EXPECT_EQ(a.rfind(c, 0), absl::string_view::npos); - EXPECT_EQ(b.rfind(c), absl::string_view::npos); - EXPECT_EQ(b.rfind(c, 0), absl::string_view::npos); - EXPECT_EQ(a.rfind(d), std::string(a).rfind(std::string())); - EXPECT_EQ(a.rfind(e), std::string(a).rfind(std::string())); - EXPECT_EQ(a.rfind(d, 12), 12u); - EXPECT_EQ(a.rfind(e, 17), 17u); - EXPECT_EQ(a.rfind(g), absl::string_view::npos); - EXPECT_EQ(d.rfind(b), absl::string_view::npos); - EXPECT_EQ(e.rfind(b), absl::string_view::npos); - EXPECT_EQ(d.rfind(b, 4), absl::string_view::npos); - EXPECT_EQ(e.rfind(b, 7), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); - EXPECT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(e, 7), std::string().rfind(std::string())); - EXPECT_EQ(d.rfind(d), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(d), std::string().rfind(std::string())); - EXPECT_EQ(d.rfind(e), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(e), std::string().rfind(std::string())); - - EXPECT_EQ(g.rfind('o'), 8u); - EXPECT_EQ(g.rfind('q'), absl::string_view::npos); - EXPECT_EQ(g.rfind('o', 8), 8u); - EXPECT_EQ(g.rfind('o', 7), 4u); - EXPECT_EQ(g.rfind('o', 3), absl::string_view::npos); - EXPECT_EQ(f.rfind('\0'), 3u); - EXPECT_EQ(f.rfind('\0', 12), 3u); - EXPECT_EQ(f.rfind('3'), 2u); - EXPECT_EQ(f.rfind('5'), 5u); - // empty string nonsense - EXPECT_EQ(d.rfind('o'), absl::string_view::npos); - EXPECT_EQ(e.rfind('o'), absl::string_view::npos); - EXPECT_EQ(d.rfind('o', 4), absl::string_view::npos); - EXPECT_EQ(e.rfind('o', 7), absl::string_view::npos); - - EXPECT_EQ(a.rfind(b.data(), 1, 0), 1u); - EXPECT_EQ(a.rfind(c.data(), 22, 0), 22u); - EXPECT_EQ(a.rfind(c.data(), 1, 0), 1u); - EXPECT_EQ(a.rfind(c.data(), 0, 0), 0u); - EXPECT_EQ(b.rfind(c.data(), 0, 0), 0u); - EXPECT_EQ(d.rfind(b.data(), 4, 0), 0u); - EXPECT_EQ(e.rfind(b.data(), 7, 0), 0u); -} - -// Continued from STL2 -TEST(StringViewTest, STL2FindFirst) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - absl::string_view g("xx not found bb"); - - d = absl::string_view(); - EXPECT_EQ(a.find_first_of(b), 0u); - EXPECT_EQ(a.find_first_of(b, 0), 0u); - EXPECT_EQ(a.find_first_of(b, 1), 1u); - EXPECT_EQ(a.find_first_of(b, 2), 2u); - EXPECT_EQ(a.find_first_of(b, 3), absl::string_view::npos); - EXPECT_EQ(a.find_first_of(c), 23u); - EXPECT_EQ(a.find_first_of(c, 23), 23u); - EXPECT_EQ(a.find_first_of(c, 24), 24u); - EXPECT_EQ(a.find_first_of(c, 25), 25u); - EXPECT_EQ(a.find_first_of(c, 26), absl::string_view::npos); - EXPECT_EQ(g.find_first_of(b), 13u); - EXPECT_EQ(g.find_first_of(c), 0u); - EXPECT_EQ(a.find_first_of(f), absl::string_view::npos); - EXPECT_EQ(f.find_first_of(a), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(a.find_first_of(d), absl::string_view::npos); - EXPECT_EQ(a.find_first_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_first_of(b), absl::string_view::npos); - EXPECT_EQ(e.find_first_of(b), absl::string_view::npos); - EXPECT_EQ(d.find_first_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_first_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_first_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_first_of(e), absl::string_view::npos); - - EXPECT_EQ(a.find_first_not_of(b), 3u); - EXPECT_EQ(a.find_first_not_of(c), 0u); - EXPECT_EQ(b.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(c.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(f.find_first_not_of(a), 0u); - EXPECT_EQ(a.find_first_not_of(f), 0u); - EXPECT_EQ(a.find_first_not_of(d), 0u); - EXPECT_EQ(a.find_first_not_of(e), 0u); - // empty string nonsense - EXPECT_EQ(a.find_first_not_of(d), 0u); - EXPECT_EQ(a.find_first_not_of(e), 0u); - EXPECT_EQ(a.find_first_not_of(d, 1), 1u); - EXPECT_EQ(a.find_first_not_of(e, 1), 1u); - EXPECT_EQ(a.find_first_not_of(d, a.size() - 1), a.size() - 1); - EXPECT_EQ(a.find_first_not_of(e, a.size() - 1), a.size() - 1); - EXPECT_EQ(a.find_first_not_of(d, a.size()), absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(e, a.size()), absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(d, absl::string_view::npos), - absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(e, absl::string_view::npos), - absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(e), absl::string_view::npos); - - absl::string_view h("===="); - EXPECT_EQ(h.find_first_not_of('='), absl::string_view::npos); - EXPECT_EQ(h.find_first_not_of('=', 3), absl::string_view::npos); - EXPECT_EQ(h.find_first_not_of('\0'), 0u); - EXPECT_EQ(g.find_first_not_of('x'), 2u); - EXPECT_EQ(f.find_first_not_of('\0'), 0u); - EXPECT_EQ(f.find_first_not_of('\0', 3), 4u); - EXPECT_EQ(f.find_first_not_of('\0', 2), 2u); - // empty string nonsense - EXPECT_EQ(d.find_first_not_of('x'), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of('x'), absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of('\0'), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of('\0'), absl::string_view::npos); -} - -// Continued from STL2 -TEST(StringViewTest, STL2FindLast) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - absl::string_view g("xx not found bb"); - absl::string_view h("===="); - absl::string_view i("56"); - - d = absl::string_view(); - EXPECT_EQ(h.find_last_of(a), absl::string_view::npos); - EXPECT_EQ(g.find_last_of(a), g.size() - 1); - EXPECT_EQ(a.find_last_of(b), 2u); - EXPECT_EQ(a.find_last_of(c), a.size() - 1); - EXPECT_EQ(f.find_last_of(i), 6u); - EXPECT_EQ(a.find_last_of('a'), 0u); - EXPECT_EQ(a.find_last_of('b'), 1u); - EXPECT_EQ(a.find_last_of('z'), 25u); - EXPECT_EQ(a.find_last_of('a', 5), 0u); - EXPECT_EQ(a.find_last_of('b', 5), 1u); - EXPECT_EQ(a.find_last_of('b', 0), absl::string_view::npos); - EXPECT_EQ(a.find_last_of('z', 25), 25u); - EXPECT_EQ(a.find_last_of('z', 24), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(i, 5), 5u); - EXPECT_EQ(f.find_last_of(i, 6), 6u); - EXPECT_EQ(f.find_last_of(a, 4), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(f.find_last_of(d), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(e, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(f), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(f), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(e, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(e, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(f, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(f, 4), absl::string_view::npos); - - EXPECT_EQ(a.find_last_not_of(b), a.size() - 1); - EXPECT_EQ(a.find_last_not_of(c), 22u); - EXPECT_EQ(b.find_last_not_of(a), absl::string_view::npos); - EXPECT_EQ(b.find_last_not_of(b), absl::string_view::npos); - EXPECT_EQ(f.find_last_not_of(i), 4u); - EXPECT_EQ(a.find_last_not_of(c, 24), 22u); - EXPECT_EQ(a.find_last_not_of(b, 3), 3u); - EXPECT_EQ(a.find_last_not_of(b, 2), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(f.find_last_not_of(d), f.size() - 1); - EXPECT_EQ(f.find_last_not_of(e), f.size() - 1); - EXPECT_EQ(f.find_last_not_of(d, 4), 4u); - EXPECT_EQ(f.find_last_not_of(e, 4), 4u); - EXPECT_EQ(d.find_last_not_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(f), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(f), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(d, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(e, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(d, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(e, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(f, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(f, 4), absl::string_view::npos); - - EXPECT_EQ(h.find_last_not_of('x'), h.size() - 1); - EXPECT_EQ(h.find_last_not_of('='), absl::string_view::npos); - EXPECT_EQ(b.find_last_not_of('c'), 1u); - EXPECT_EQ(h.find_last_not_of('x', 2), 2u); - EXPECT_EQ(h.find_last_not_of('=', 2), absl::string_view::npos); - EXPECT_EQ(b.find_last_not_of('b', 1), 0u); - // empty string nonsense - EXPECT_EQ(d.find_last_not_of('x'), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of('x'), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of('\0'), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of('\0'), absl::string_view::npos); -} - -// Continued from STL2 -TEST(StringViewTest, STL2Substr) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - - d = absl::string_view(); - EXPECT_EQ(a.substr(0, 3), b); - EXPECT_EQ(a.substr(23), c); - EXPECT_EQ(a.substr(23, 3), c); - EXPECT_EQ(a.substr(23, 99), c); - EXPECT_EQ(a.substr(0), a); - EXPECT_EQ(a.substr(), a); - EXPECT_EQ(a.substr(3, 2), "de"); - // empty string nonsense - EXPECT_EQ(d.substr(0, 99), e); - // use of npos - EXPECT_EQ(a.substr(0, absl::string_view::npos), a); - EXPECT_EQ(a.substr(23, absl::string_view::npos), c); - // throw exception -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW((void)a.substr(99, 2), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED((void)a.substr(99, 2), - "absl::string_view::substr"); -#endif -} - TEST(StringViewTest, TruncSubstr) { const absl::string_view hi("hi"); EXPECT_EQ("", absl::ClippedSubstr(hi, 0, 0)); @@ -780,272 +46,6 @@ EXPECT_EQ("", absl::ClippedSubstr(hi, 3, 2)); // truncation } -TEST(StringViewTest, UTF8) { - std::string utf8 = "\u00E1"; - std::string utf8_twice = utf8 + " " + utf8; - size_t utf8_len = strlen(utf8.data()); - EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" ")); - EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" \t")); -} - -TEST(StringViewTest, FindConformance) { - struct { - std::string haystack; - std::string needle; - } specs[] = { - {"", ""}, - {"", "a"}, - {"a", ""}, - {"a", "a"}, - {"a", "b"}, - {"aa", ""}, - {"aa", "a"}, - {"aa", "b"}, - {"ab", "a"}, - {"ab", "b"}, - {"abcd", ""}, - {"abcd", "a"}, - {"abcd", "d"}, - {"abcd", "ab"}, - {"abcd", "bc"}, - {"abcd", "cd"}, - {"abcd", "abcd"}, - }; - for (const auto& s : specs) { - SCOPED_TRACE(s.haystack); - SCOPED_TRACE(s.needle); - std::string st = s.haystack; - absl::string_view sp = s.haystack; - for (size_t i = 0; i <= sp.size(); ++i) { - size_t pos = (i == sp.size()) ? absl::string_view::npos : i; - SCOPED_TRACE(pos); - EXPECT_EQ(sp.find(s.needle, pos), - st.find(s.needle, pos)); - EXPECT_EQ(sp.rfind(s.needle, pos), - st.rfind(s.needle, pos)); - EXPECT_EQ(sp.find_first_of(s.needle, pos), - st.find_first_of(s.needle, pos)); - EXPECT_EQ(sp.find_first_not_of(s.needle, pos), - st.find_first_not_of(s.needle, pos)); - EXPECT_EQ(sp.find_last_of(s.needle, pos), - st.find_last_of(s.needle, pos)); - EXPECT_EQ(sp.find_last_not_of(s.needle, pos), - st.find_last_not_of(s.needle, pos)); - } - } -} - -TEST(StringViewTest, Remove) { - absl::string_view a("foobar"); - std::string s1("123"); - s1 += '\0'; - s1 += "456"; - absl::string_view e; - std::string s2; - - // remove_prefix - absl::string_view c(a); - c.remove_prefix(3); - EXPECT_EQ(c, "bar"); - c = a; - c.remove_prefix(0); - EXPECT_EQ(c, a); - c.remove_prefix(c.size()); - EXPECT_EQ(c, e); - - // remove_suffix - c = a; - c.remove_suffix(3); - EXPECT_EQ(c, "foo"); - c = a; - c.remove_suffix(0); - EXPECT_EQ(c, a); - c.remove_suffix(c.size()); - EXPECT_EQ(c, e); -} - -TEST(StringViewTest, Set) { - absl::string_view a("foobar"); - absl::string_view empty; - absl::string_view b; - - // set - b = absl::string_view("foobar", 6); - EXPECT_EQ(b, a); - b = absl::string_view("foobar", 0); - EXPECT_EQ(b, empty); - b = absl::string_view("foobar", 7); - EXPECT_NE(b, a); - - b = absl::string_view("foobar"); - EXPECT_EQ(b, a); -} - -TEST(StringViewTest, FrontBack) { - static const char arr[] = "abcd"; - const absl::string_view csp(arr, 4); - EXPECT_EQ(&arr[0], &csp.front()); - EXPECT_EQ(&arr[3], &csp.back()); -} - -TEST(StringViewTest, FrontBackSingleChar) { - static const char c = 'a'; - const absl::string_view csp(&c, 1); - EXPECT_EQ(&c, &csp.front()); - EXPECT_EQ(&c, &csp.back()); -} - -TEST(StringViewTest, FrontBackEmpty) { -#ifndef ABSL_USES_STD_STRING_VIEW -#if !defined(NDEBUG) || ABSL_OPTION_HARDENED - // Abseil's string_view implementation has debug assertions that check that - // front() and back() are not called on an empty string_view. - absl::string_view sv; - ABSL_EXPECT_DEATH_IF_SUPPORTED(sv.front(), ""); - ABSL_EXPECT_DEATH_IF_SUPPORTED(sv.back(), ""); -#endif -#endif -} - -TEST(StringViewTest, DefaultConstructor) { - absl::string_view s; - EXPECT_EQ(s.data(), nullptr); - EXPECT_EQ(s.size(), 0u); -} - -TEST(StringViewTest, Comparisons2) { - // The `compare` member has 6 overloads (v: string_view, s: const char*): - // (1) compare(v) - // (2) compare(pos1, count1, v) - // (3) compare(pos1, count1, v, pos2, count2) - // (4) compare(s) - // (5) compare(pos1, count1, s) - // (6) compare(pos1, count1, s, count2) - - absl::string_view abc("abcdefghijklmnopqrstuvwxyz"); - - // check comparison operations on strings longer than 4 bytes. - EXPECT_EQ(abc, absl::string_view("abcdefghijklmnopqrstuvwxyz")); - EXPECT_EQ(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxyz")), 0); - - EXPECT_LT(abc, absl::string_view("abcdefghijklmnopqrstuvwxzz")); - EXPECT_LT(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxzz")), 0); - - EXPECT_GT(abc, absl::string_view("abcdefghijklmnopqrstuvwxyy")); - EXPECT_GT(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxyy")), 0); - - // The "substr" variants of `compare`. - absl::string_view digits("0123456789"); - auto npos = absl::string_view::npos; - - // Taking string_view - EXPECT_EQ(digits.compare(3, npos, absl::string_view("3456789")), 0); // 2 - EXPECT_EQ(digits.compare(3, 4, absl::string_view("3456")), 0); // 2 - EXPECT_EQ(digits.compare(10, 0, absl::string_view()), 0); // 2 - EXPECT_EQ(digits.compare(3, 4, absl::string_view("0123456789"), 3, 4), - 0); // 3 - EXPECT_LT(digits.compare(3, 4, absl::string_view("0123456789"), 3, 5), - 0); // 3 - EXPECT_LT(digits.compare(0, npos, absl::string_view("0123456789"), 3, 5), - 0); // 3 - // Taking const char* - EXPECT_EQ(digits.compare(3, 4, "3456"), 0); // 5 - EXPECT_EQ(digits.compare(3, npos, "3456789"), 0); // 5 - EXPECT_EQ(digits.compare(10, 0, ""), 0); // 5 - EXPECT_EQ(digits.compare(3, 4, "0123456789", 3, 4), 0); // 6 - EXPECT_LT(digits.compare(3, 4, "0123456789", 3, 5), 0); // 6 - EXPECT_LT(digits.compare(0, npos, "0123456789", 3, 5), 0); // 6 -} - -TEST(StringViewTest, At) { - absl::string_view abc = "abc"; - EXPECT_EQ(abc.at(0), 'a'); - EXPECT_EQ(abc.at(1), 'b'); - EXPECT_EQ(abc.at(2), 'c'); -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW((void)abc.at(3), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED((void)abc.at(3), "absl::string_view::at"); -#endif -} - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L -TEST(StringViewTest, StartsWith) { - const absl::string_view a("foobar"); - const absl::string_view b("123\0abc", 7); - const absl::string_view e; - EXPECT_TRUE(a.starts_with(a)); - EXPECT_TRUE(a.starts_with("foo")); - EXPECT_TRUE(a.starts_with('f')); - EXPECT_TRUE(a.starts_with(e)); - EXPECT_TRUE(b.starts_with(b)); - EXPECT_TRUE(b.starts_with('1')); - EXPECT_TRUE(b.starts_with(e)); - EXPECT_TRUE(e.starts_with("")); - EXPECT_FALSE(a.starts_with(b)); - EXPECT_FALSE(b.starts_with(a)); - EXPECT_FALSE(e.starts_with(a)); - EXPECT_FALSE(a.starts_with('r')); - EXPECT_FALSE(a.starts_with('\0')); - EXPECT_FALSE(e.starts_with('r')); - EXPECT_FALSE(e.starts_with('\0')); - - // Test that constexpr compiles. - constexpr absl::string_view kFooBar("foobar"); - constexpr absl::string_view kFoo("foo"); - constexpr absl::string_view kBar("bar"); - constexpr bool k1 = kFooBar.starts_with(kFoo); - EXPECT_TRUE(k1); - constexpr bool k2 = kFooBar.starts_with(kBar); - EXPECT_FALSE(k2); - constexpr bool k3 = kFooBar.starts_with('f'); - EXPECT_TRUE(k3); - constexpr bool k4 = kFooBar.starts_with("fo"); - EXPECT_TRUE(k4); -} - -TEST(StringViewTest, EndsWith) { - const absl::string_view a("foobar"); - const absl::string_view b("123\0abc", 7); - const absl::string_view e; - EXPECT_TRUE(a.ends_with(a)); - EXPECT_TRUE(a.ends_with('r')); - EXPECT_TRUE(a.ends_with("bar")); - EXPECT_TRUE(a.ends_with(e)); - EXPECT_TRUE(b.ends_with(b)); - EXPECT_TRUE(b.ends_with('c')); - EXPECT_TRUE(b.ends_with(e)); - EXPECT_TRUE(e.ends_with("")); - EXPECT_FALSE(a.ends_with(b)); - EXPECT_FALSE(b.ends_with(a)); - EXPECT_FALSE(e.ends_with(a)); - EXPECT_FALSE(a.ends_with('f')); - EXPECT_FALSE(a.ends_with('\0')); - EXPECT_FALSE(e.ends_with('r')); - EXPECT_FALSE(e.ends_with('\0')); - - // Test that constexpr compiles. - constexpr absl::string_view kFooBar("foobar"); - constexpr absl::string_view kFoo("foo"); - constexpr absl::string_view kBar("bar"); - constexpr bool k1 = kFooBar.ends_with(kFoo); - EXPECT_FALSE(k1); - constexpr bool k2 = kFooBar.ends_with(kBar); - EXPECT_TRUE(k2); - constexpr bool k3 = kFooBar.ends_with('r'); - EXPECT_TRUE(k3); - constexpr bool k4 = kFooBar.ends_with("ar"); - EXPECT_TRUE(k4); -} -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - -struct MyCharAlloc : std::allocator<char> {}; - -TEST(StringViewTest, ExplicitConversionOperator) { - absl::string_view sp = "hi"; - EXPECT_EQ(sp, std::string(sp)); -} - TEST(StringViewTest, NullSafeStringView) { { absl::string_view s = absl::NullSafeStringView(nullptr); @@ -1083,317 +83,4 @@ } } -TEST(StringViewTest, ConstexprCompiles) { - constexpr absl::string_view sp; - // With `-Wnonnull` turned on, there is no way to test the defensive null - // check in the `string_view(const char*)` constructor in a constexpr context, - // as the argument needs to be constexpr. The compiler will therefore always - // know at compile time that the argument is nullptr and complain because the - // parameter is annotated nonnull. We hence turn the warning off for this - // test. - constexpr absl::string_view cstr_len("cstr", 4); - -#if defined(ABSL_USES_STD_STRING_VIEW) - // In libstdc++ (as of 7.2), `std::string_view::string_view(const char*)` - // calls `std::char_traits<char>::length(const char*)` to get the string - // length, but it is not marked constexpr yet. See GCC bug: - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78156 - // Also, there is a LWG issue that adds constexpr to length() which was just - // resolved 2017-06-02. See - // http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2232 - // TODO(zhangxy): Update the condition when libstdc++ adopts the constexpr - // length(). -#if !defined(__GLIBCXX__) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#endif // !__GLIBCXX__ - -#else // ABSL_USES_STD_STRING_VIEW - -// This duplicates the check for __builtin_strlen in the header. -#if ABSL_HAVE_BUILTIN(__builtin_strlen) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#elif defined(__GNUC__) // GCC or clang -#error GCC/clang should have constexpr string_view. -#endif - -// MSVC 2017+ should be able to construct a constexpr string_view from a cstr. -#if defined(_MSC_VER) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#endif - -#endif // ABSL_USES_STD_STRING_VIEW - -#ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR - constexpr absl::string_view cstr_strlen("foo"); - EXPECT_EQ(cstr_strlen.length(), 3u); - constexpr absl::string_view cstr_strlen2 = "bar"; - EXPECT_EQ(cstr_strlen2, "bar"); - -#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_COMPARISON 1 -#endif -#ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_COMPARISON - constexpr absl::string_view foo = "foo"; - constexpr absl::string_view bar = "bar"; - constexpr bool foo_eq_bar = foo == bar; - constexpr bool foo_ne_bar = foo != bar; - constexpr bool foo_lt_bar = foo < bar; - constexpr bool foo_le_bar = foo <= bar; - constexpr bool foo_gt_bar = foo > bar; - constexpr bool foo_ge_bar = foo >= bar; - constexpr int foo_compare_bar = foo.compare(bar); - EXPECT_FALSE(foo_eq_bar); - EXPECT_TRUE(foo_ne_bar); - EXPECT_FALSE(foo_lt_bar); - EXPECT_FALSE(foo_le_bar); - EXPECT_TRUE(foo_gt_bar); - EXPECT_TRUE(foo_ge_bar); - EXPECT_GT(foo_compare_bar, 0); -#endif -#endif - - constexpr absl::string_view::iterator const_begin_empty = sp.begin(); - constexpr absl::string_view::iterator const_end_empty = sp.end(); - EXPECT_EQ(const_begin_empty, const_end_empty); - - constexpr absl::string_view::iterator const_begin = cstr_len.begin(); - constexpr absl::string_view::iterator const_end = cstr_len.end(); - constexpr absl::string_view::size_type const_size = cstr_len.size(); - constexpr absl::string_view::size_type const_length = cstr_len.length(); - static_assert(const_begin + const_size == const_end, - "pointer arithmetic check"); - static_assert(const_begin + const_length == const_end, - "pointer arithmetic check"); -#ifndef _MSC_VER - // MSVC has bugs doing constexpr pointer arithmetic. - // https://developercommunity.visualstudio.com/content/problem/482192/bad-pointer-arithmetic-in-constepxr-2019-rc1-svc1.html - EXPECT_EQ(const_begin + const_size, const_end); - EXPECT_EQ(const_begin + const_length, const_end); -#endif - - constexpr bool isempty = sp.empty(); - EXPECT_TRUE(isempty); - - constexpr const char c = cstr_len[2]; - EXPECT_EQ(c, 't'); - - constexpr const char cfront = cstr_len.front(); - constexpr const char cback = cstr_len.back(); - EXPECT_EQ(cfront, 'c'); - EXPECT_EQ(cback, 'r'); - - constexpr const char* np = sp.data(); - constexpr const char* cstr_ptr = cstr_len.data(); - EXPECT_EQ(np, nullptr); - EXPECT_NE(cstr_ptr, nullptr); - - constexpr size_t sp_npos = sp.npos; - EXPECT_EQ(sp_npos, static_cast<size_t>(-1)); - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - { - static constexpr std::array<char, 3> arr = { '1', '2', '3' }; - constexpr absl::string_view s2(arr.begin(), arr.end()); - EXPECT_EQ(s2, "123"); - - static constexpr char carr[] = "carr"; - constexpr absl::string_view s3(carr, carr + 4); - EXPECT_EQ(s3, "carr"); - } -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L -} - -constexpr char ConstexprMethodsHelper() { -#if defined(__cplusplus) && __cplusplus >= 201402L - absl::string_view str("123", 3); - str.remove_prefix(1); - str.remove_suffix(1); - absl::string_view bar; - str.swap(bar); - return bar.front(); -#else - return '2'; -#endif -} - -TEST(StringViewTest, ConstexprMethods) { - // remove_prefix, remove_suffix, swap - static_assert(ConstexprMethodsHelper() == '2', ""); - - // substr - constexpr absl::string_view foobar("foobar", 6); - constexpr absl::string_view foo = foobar.substr(0, 3); - constexpr absl::string_view bar = foobar.substr(3); - EXPECT_EQ(foo, "foo"); - EXPECT_EQ(bar, "bar"); -} - -TEST(StringViewTest, Noexcept) { - EXPECT_TRUE((std::is_nothrow_constructible<absl::string_view, - const std::string&>::value)); - EXPECT_TRUE((std::is_nothrow_constructible<absl::string_view, - const std::string&>::value)); - EXPECT_TRUE(std::is_nothrow_constructible<absl::string_view>::value); - constexpr absl::string_view sp; - EXPECT_TRUE(noexcept(sp.begin())); - EXPECT_TRUE(noexcept(sp.end())); - EXPECT_TRUE(noexcept(sp.cbegin())); - EXPECT_TRUE(noexcept(sp.cend())); - EXPECT_TRUE(noexcept(sp.rbegin())); - EXPECT_TRUE(noexcept(sp.rend())); - EXPECT_TRUE(noexcept(sp.crbegin())); - EXPECT_TRUE(noexcept(sp.crend())); - EXPECT_TRUE(noexcept(sp.size())); - EXPECT_TRUE(noexcept(sp.length())); - EXPECT_TRUE(noexcept(sp.empty())); - EXPECT_TRUE(noexcept(sp.data())); - EXPECT_TRUE(noexcept(sp.compare(sp))); - EXPECT_TRUE(noexcept(sp.find(sp))); - EXPECT_TRUE(noexcept(sp.find('f'))); - EXPECT_TRUE(noexcept(sp.rfind(sp))); - EXPECT_TRUE(noexcept(sp.rfind('f'))); - EXPECT_TRUE(noexcept(sp.find_first_of(sp))); - EXPECT_TRUE(noexcept(sp.find_first_of('f'))); - EXPECT_TRUE(noexcept(sp.find_last_of(sp))); - EXPECT_TRUE(noexcept(sp.find_last_of('f'))); - EXPECT_TRUE(noexcept(sp.find_first_not_of(sp))); - EXPECT_TRUE(noexcept(sp.find_first_not_of('f'))); - EXPECT_TRUE(noexcept(sp.find_last_not_of(sp))); - EXPECT_TRUE(noexcept(sp.find_last_not_of('f'))); -} - -TEST(StringViewTest, BoundsCheck) { -#ifndef ABSL_USES_STD_STRING_VIEW -#if !defined(NDEBUG) || ABSL_OPTION_HARDENED - // Abseil's string_view implementation has bounds-checking in debug mode. - absl::string_view h = "hello"; - ABSL_EXPECT_DEATH_IF_SUPPORTED(h[5], ""); - ABSL_EXPECT_DEATH_IF_SUPPORTED(h[static_cast<size_t>(-1)], ""); -#endif -#endif -} - -TEST(ComparisonOpsTest, StringCompareNotAmbiguous) { - EXPECT_EQ("hello", std::string("hello")); - EXPECT_LT("hello", std::string("world")); -} - -TEST(ComparisonOpsTest, HeterogeneousStringViewEquals) { - EXPECT_EQ(absl::string_view("hello"), std::string("hello")); - EXPECT_EQ("hello", absl::string_view("hello")); -} - -TEST(FindOneCharTest, EdgeCases) { - absl::string_view a("xxyyyxx"); - - // Set a = "xyyyx". - a.remove_prefix(1); - a.remove_suffix(1); - - EXPECT_EQ(0u, a.find('x')); - EXPECT_EQ(0u, a.find('x', 0)); - EXPECT_EQ(4u, a.find('x', 1)); - EXPECT_EQ(4u, a.find('x', 4)); - EXPECT_EQ(absl::string_view::npos, a.find('x', 5)); - - EXPECT_EQ(4u, a.rfind('x')); - EXPECT_EQ(4u, a.rfind('x', 5)); - EXPECT_EQ(4u, a.rfind('x', 4)); - EXPECT_EQ(0u, a.rfind('x', 3)); - EXPECT_EQ(0u, a.rfind('x', 0)); - - // Set a = "yyy". - a.remove_prefix(1); - a.remove_suffix(1); - - EXPECT_EQ(absl::string_view::npos, a.find('x')); - EXPECT_EQ(absl::string_view::npos, a.rfind('x')); -} - -#ifndef ABSL_HAVE_THREAD_SANITIZER // Allocates too much memory for tsan. -TEST(HugeStringView, TwoPointTwoGB) { - if (sizeof(size_t) <= 4) - return; - // Try a huge string piece. - const size_t size = size_t{2200} * 1000 * 1000; - std::string s(size, 'a'); - absl::string_view sp(s); - EXPECT_EQ(size, sp.length()); - sp.remove_prefix(1); - EXPECT_EQ(size - 1, sp.length()); - sp.remove_suffix(2); - EXPECT_EQ(size - 1 - 2, sp.length()); -} -#endif // ABSL_HAVE_THREAD_SANITIZER - -#if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) -TEST(NonNegativeLenTest, NonNegativeLen) { - ABSL_EXPECT_DEATH_IF_SUPPORTED( - absl::string_view("xyz", static_cast<size_t>(-1)), "len <= kMaxSize"); -} - -TEST(LenExceedsMaxSizeTest, LenExceedsMaxSize) { - auto max_size = absl::string_view().max_size(); - - // This should construct ok (although the view itself is obviously invalid). - absl::string_view ok_view("", max_size); - - // Adding one to the max should trigger an assertion. - ABSL_EXPECT_DEATH_IF_SUPPORTED(absl::string_view("", max_size + 1), - "len <= kMaxSize"); -} -#endif // !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) - -class StringViewStreamTest : public ::testing::Test { - public: - // Set negative 'width' for right justification. - template <typename T> - std::string Pad(const T& s, int width, char fill = 0) { - std::ostringstream oss; - if (fill != 0) { - oss << std::setfill(fill); - } - if (width < 0) { - width = -width; - oss << std::right; - } - oss << std::setw(width) << s; - return oss.str(); - } -}; - -TEST_F(StringViewStreamTest, Padding) { - std::string s("hello"); - absl::string_view sp(s); - for (int w = -64; w < 64; ++w) { - SCOPED_TRACE(w); - EXPECT_EQ(Pad(s, w), Pad(sp, w)); - } - for (int w = -64; w < 64; ++w) { - SCOPED_TRACE(w); - EXPECT_EQ(Pad(s, w, '#'), Pad(sp, w, '#')); - } -} - -TEST_F(StringViewStreamTest, ResetsWidth) { - // Width should reset after one formatted write. - // If we weren't resetting width after formatting the string_view, - // we'd have width=5 carrying over to the printing of the "]", - // creating "[###hi####]". - std::string s = "hi"; - absl::string_view sp = s; - { - std::ostringstream oss; - oss << "[" << std::setfill('#') << std::setw(5) << s << "]"; - ASSERT_EQ("[###hi]", oss.str()); - } - { - std::ostringstream oss; - oss << "[" << std::setfill('#') << std::setw(5) << sp << "]"; - EXPECT_EQ("[###hi]", oss.str()); - } -} - } // namespace
diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 9c6b31a..b3b55e7 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel
@@ -72,9 +72,7 @@ "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/time", - ] + select({ - "//conditions:default": [], - }), + ], ) cc_test(
diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h index 06404a7..c24fa86 100644 --- a/absl/synchronization/internal/kernel_timeout.h +++ b/absl/synchronization/internal/kernel_timeout.h
@@ -129,8 +129,8 @@ std::chrono::nanoseconds ToChronoDuration() const; // Returns true if steady (aka monotonic) clocks are supported by the system. - // This method exists because go/btm requires synchronized clocks, and - // thus requires we use the system (aka walltime) clock. + // This currently returns true on all platforms, but we have encountered + // platforms that once lacked steady clock support. static constexpr bool SupportsSteadyClock() { return true; } private:
diff --git a/absl/synchronization/internal/kernel_timeout_test.cc b/absl/synchronization/internal/kernel_timeout_test.cc index 33962f8..811246c 100644 --- a/absl/synchronization/internal/kernel_timeout_test.cc +++ b/absl/synchronization/internal/kernel_timeout_test.cc
@@ -24,12 +24,22 @@ #include "absl/time/time.h" #include "gtest/gtest.h" -// Test go/btm support by randomizing the value of clock_gettime() for -// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc. +#if 0 // All supported platforms currently have steady clocks. +#define ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK 0 +#else +#define ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK 1 +#endif + +static_assert( + absl::synchronization_internal::KernelTimeout::SupportsSteadyClock() == + static_cast<bool>(ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK)); + +// Randomizing the value of clock_gettime() for CLOCK_MONOTONIC. +// This works by overriding a weak symbol in glibc. // We should be resistant to this randomization when !SupportsSteadyClock(). -#if defined(__GOOGLE_GRTE_VERSION__) && \ - !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ - !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ +#if !ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK && \ + !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ !defined(ABSL_HAVE_THREAD_SANITIZER) extern "C" int __clock_gettime(clockid_t c, struct timespec* ts);
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index ef468ad..cb3be9a 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@ -2025b +2025c
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab index 402c015..4ae3523 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab
@@ -3,22 +3,22 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2023-09-06): +# From Paul Eggert (2025-07-01): # This file contains a table of two-letter country codes. Columns are -# separated by a single tab. Lines beginning with '#' are comments. +# separated by a single tab. Lines beginning with ‘#’ are comments. # All text uses UTF-8 encoding. The columns of the table are as follows: # # 1. ISO 3166-1 alpha-2 country code, current as of -# ISO/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents +# ISO/TC 46 N1127 (2024-02-29). See: ISO/TC 46 Documents # https://www.iso.org/committee/48750.html?view=documents # 2. The usual English name for the coded region. This sometimes # departs from ISO-listed names, sometimes so that sorted subsets -# of names are useful (e.g., "Samoa (American)" and "Samoa -# (western)" rather than "American Samoa" and "Samoa"), +# of names are useful (e.g., “Samoa (American)” and “Samoa +# (western)” rather than “American Samoa” and “Samoa”), # sometimes to avoid confusion among non-experts (e.g., -# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"), -# and sometimes to omit needless detail or churn (e.g., "Netherlands" -# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)"). +# “Czech Republic” and “Turkey” rather than “Czechia” and “Türkiye”), +# and sometimes to omit needless detail or churn (e.g., “Netherlands” +# rather than “Netherlands (the)” or “Netherlands (Kingdom of the)”). # # The table is sorted by country code. # @@ -71,7 +71,7 @@ CF Central African Rep. CG Congo (Rep.) CH Switzerland -CI Côte d'Ivoire +CI Côte d’Ivoire CK Cook Islands CL Chile CM Cameroon
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index 36535bd..cd43e3d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -2,15 +2,15 @@ # # This file is in the public domain. # -# From Paul Eggert (2018-06-27): +# From Paul Eggert (2025-05-15): # This file contains a table where each row stands for a timezone where # civil timestamps have agreed since 1970. Columns are separated by -# a single tab. Lines beginning with '#' are comments. All text uses +# a single tab. Lines beginning with ‘#’ are comments. All text uses # UTF-8 encoding. The columns of the table are as follows: # # 1. The countries that overlap the timezone, as a comma-separated list -# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. -# 2. Latitude and longitude of the timezone's principal location +# of ISO 3166 2-character country codes. +# 2. Latitude and longitude of the timezone’s principal location # in ISO 6709 sign-degrees-minutes-seconds format, # either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, # first latitude (+ is north), then longitude (+ is east). @@ -197,7 +197,7 @@ KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau -KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev +KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur’yev KZ +5113+05121 Asia/Oral West Kazakhstan LB +3353+03530 Asia/Beirut LK +0656+07951 Asia/Colombo @@ -245,7 +245,7 @@ PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d'Urville +PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d’Urville PG -0613+15534 Pacific/Bougainville Bougainville PH +143512+1205804 Asia/Manila PK +2452+06703 Asia/Karachi @@ -265,7 +265,7 @@ RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -# Mention RU and UA alphabetically. See "territorial claims" above. +# Mention RU and UA alphabetically. See “territorial claims” above. RU,UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd @@ -353,20 +353,20 @@ # The next section contains experimental tab-separated comments for # use by user agents like tzselect that identify continents and oceans. # -# For example, the comment "#@AQ<tab>Antarctica/" means the country code +# For example, the comment ‘#@AQ<tab>Antarctica/’ means the country code # AQ is in the continent Antarctica regardless of the Zone name, # so Pacific/Auckland should be listed under Antarctica as well as -# under the Pacific because its line's country codes include AQ. +# under the Pacific because its line’s country codes include AQ. # # If more than one country code is affected each is listed separated -# by commas, e.g., #@IS,SH<tab>Atlantic/". If a country code is in +# by commas, e.g., ‘#@IS,SH<tab>Atlantic/’. If a country code is in # more than one continent or ocean, each is listed separated by -# commas, e.g., the second column of "#@CY,TR<tab>Asia/,Europe/". +# commas, e.g., the second column of ‘#@CY,TR<tab>Asia/,Europe/’. # # These experimental comments are present only for country codes where # the continent or ocean is not already obvious from the Zone name. # For example, there is no such comment for RU since it already -# corresponds to Zone names starting with both "Europe/" and "Asia/". +# corresponds to Zone names starting with both ‘Europe/’ and ‘Asia/’. # #@AQ Antarctica/ #@IS,SH Atlantic/
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab index 093f0a0..1d64b39 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
@@ -5,12 +5,12 @@ # From Paul Eggert (2023-12-18): # This file contains a table where each row stands for a timezone # where civil timestamps are predicted to agree from now on. -# This file is like zone1970.tab (see zone1970.tab's comments), +# This file is like zone1970.tab (see zone1970.tab’s comments), # but with the following changes: # # 1. Each timezone corresponds to a set of clocks that are planned # to agree from now on. This is a larger set of clocks than in -# zone1970.tab, where each timezone's clocks must agree from 1970 on. +# zone1970.tab, where each timezone’s clocks must agree from 1970 on. # 2. The first column is irrelevant and ignored. # 3. The table is sorted in a different way: # first by standard time UTC offset; @@ -29,19 +29,19 @@ #XX coordinates TZ comments # # -11 - SST -XX -1416-17042 Pacific/Pago_Pago Midway; Samoa ("SST") +XX -1416-17042 Pacific/Pago_Pago Midway; Samoa (SST) # # -11 XX -1901-16955 Pacific/Niue Niue # # -10 - HST -XX +211825-1575130 Pacific/Honolulu Hawaii ("HST") +XX +211825-1575130 Pacific/Honolulu Hawaii (HST) # # -10 XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands # # -10/-09 - HST / HDT (North America DST) -XX +515248-1763929 America/Adak western Aleutians in Alaska ("HST/HDT") +XX +515248-1763929 America/Adak western Aleutians in Alaska (HST/HDT) # # -09:30 XX -0900-13930 Pacific/Marquesas Marquesas @@ -50,58 +50,58 @@ XX -2308-13457 Pacific/Gambier Gambier # # -09/-08 - AKST/AKDT (North America DST) -XX +611305-1495401 America/Anchorage most of Alaska ("AKST/AKDT") +XX +611305-1495401 America/Anchorage most of Alaska (AKST/AKDT) # # -08 XX -2504-13005 Pacific/Pitcairn Pitcairn # # -08/-07 - PST/PDT (North America DST) -XX +340308-1181434 America/Los_Angeles Pacific ("PST/PDT") - US & Canada; Mexico near US border +XX +340308-1181434 America/Los_Angeles Pacific (PST/PDT) - US & Canada; Mexico near US border # # -07 - MST -XX +332654-1120424 America/Phoenix Mountain Standard ("MST") - Arizona; western Mexico; Yukon +XX +332654-1120424 America/Phoenix Mountain Standard (MST) - Arizona; western Mexico; Yukon # # -07/-06 - MST/MDT (North America DST) -XX +394421-1045903 America/Denver Mountain ("MST/MDT") - US & Canada; Mexico near US border +XX +394421-1045903 America/Denver Mountain (MST/MDT) - US & Canada; Mexico near US border # # -06 XX -0054-08936 Pacific/Galapagos Galápagos # # -06 - CST -XX +1924-09909 America/Mexico_City Central Standard ("CST") - Saskatchewan; central Mexico; Central America +XX +1924-09909 America/Mexico_City Central Standard (CST) - Saskatchewan; central Mexico; Central America # # -06/-05 (Chile DST) XX -2709-10926 Pacific/Easter Easter Island # # -06/-05 - CST/CDT (North America DST) -XX +415100-0873900 America/Chicago Central ("CST/CDT") - US & Canada; Mexico near US border +XX +415100-0873900 America/Chicago Central (CST/CDT) - US & Canada; Mexico near US border # # -05 XX -1203-07703 America/Lima eastern South America # # -05 - EST -XX +175805-0764736 America/Jamaica Eastern Standard ("EST") - Caymans; Jamaica; eastern Mexico; Panama +XX +175805-0764736 America/Jamaica Eastern Standard (EST) - Caymans; Jamaica; eastern Mexico; Panama # # -05/-04 - CST/CDT (Cuba DST) XX +2308-08222 America/Havana Cuba # # -05/-04 - EST/EDT (North America DST) -XX +404251-0740023 America/New_York Eastern ("EST/EDT") - US & Canada +XX +404251-0740023 America/New_York Eastern (EST/EDT) - US & Canada # # -04 XX +1030-06656 America/Caracas western South America # # -04 - AST -XX +1828-06954 America/Santo_Domingo Atlantic Standard ("AST") - eastern Caribbean +XX +1828-06954 America/Santo_Domingo Atlantic Standard (AST) - eastern Caribbean # # -04/-03 (Chile DST) XX -3327-07040 America/Santiago most of Chile # # -04/-03 - AST/ADT (North America DST) -XX +4439-06336 America/Halifax Atlantic ("AST/ADT") - Canada; Bermuda +XX +4439-06336 America/Halifax Atlantic (AST/ADT) - Canada; Bermuda # # -03:30/-02:30 - NST/NDT (North America DST) -XX +4734-05243 America/St_Johns Newfoundland ("NST/NDT") +XX +4734-05243 America/St_Johns Newfoundland (NST/NDT) # # -03 XX -2332-04637 America/Sao_Paulo eastern and southern South America @@ -122,43 +122,43 @@ XX +3744-02540 Atlantic/Azores Azores # # +00 - GMT -XX +0519-00402 Africa/Abidjan far western Africa; Iceland ("GMT") +XX +0519-00402 Africa/Abidjan far western Africa; Iceland (GMT) # # +00/+01 - GMT/BST (EU DST) -XX +513030-0000731 Europe/London United Kingdom ("GMT/BST") +XX +513030-0000731 Europe/London United Kingdom (GMT/BST) # # +00/+01 - WET/WEST (EU DST) -XX +3843-00908 Europe/Lisbon western Europe ("WET/WEST") +XX +3843-00908 Europe/Lisbon western Europe (WET/WEST) # # +00/+02 - Troll DST XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica # # +01 - CET -XX +3647+00303 Africa/Algiers Algeria, Tunisia ("CET") +XX +3647+00303 Africa/Algiers Algeria, Tunisia (CET) # # +01 - WAT -XX +0627+00324 Africa/Lagos western Africa ("WAT") +XX +0627+00324 Africa/Lagos western Africa (WAT) # # +01/+00 - IST/GMT (EU DST in reverse) -XX +5320-00615 Europe/Dublin Ireland ("IST/GMT") +XX +5320-00615 Europe/Dublin Ireland (IST/GMT) # # +01/+00 - (Morocco DST) XX +3339-00735 Africa/Casablanca Morocco # # +01/+02 - CET/CEST (EU DST) -XX +4852+00220 Europe/Paris central Europe ("CET/CEST") +XX +4852+00220 Europe/Paris central Europe (CET/CEST) # # +02 - CAT -XX -2558+03235 Africa/Maputo central Africa ("CAT") +XX -2558+03235 Africa/Maputo central Africa (CAT) # # +02 - EET -XX +3254+01311 Africa/Tripoli Libya; Kaliningrad ("EET") +XX +3254+01311 Africa/Tripoli Libya; Kaliningrad (EET) # # +02 - SAST -XX -2615+02800 Africa/Johannesburg southern Africa ("SAST") +XX -2615+02800 Africa/Johannesburg southern Africa (SAST) # # +02/+03 - EET/EEST (EU DST) -XX +3758+02343 Europe/Athens eastern Europe ("EET/EEST") +XX +3758+02343 Europe/Athens eastern Europe (EET/EEST) # # +02/+03 - EET/EEST (Egypt DST) XX +3003+03115 Africa/Cairo Egypt @@ -179,10 +179,10 @@ XX +4101+02858 Europe/Istanbul Near East; Belarus # # +03 - EAT -XX -0117+03649 Africa/Nairobi eastern Africa ("EAT") +XX -0117+03649 Africa/Nairobi eastern Africa (EAT) # # +03 - MSK -XX +554521+0373704 Europe/Moscow Moscow ("MSK") +XX +554521+0373704 Europe/Moscow Moscow (MSK) # # +03:30 XX +3540+05126 Asia/Tehran Iran @@ -197,13 +197,13 @@ XX +4120+06918 Asia/Tashkent Russia; Kazakhstan; Tajikistan; Turkmenistan; Uzbekistan; Maldives # # +05 - PKT -XX +2452+06703 Asia/Karachi Pakistan ("PKT") +XX +2452+06703 Asia/Karachi Pakistan (PKT) # # +05:30 XX +0656+07951 Asia/Colombo Sri Lanka # # +05:30 - IST -XX +2232+08822 Asia/Kolkata India ("IST") +XX +2232+08822 Asia/Kolkata India (IST) # # +05:45 XX +2743+08519 Asia/Kathmandu Nepal @@ -218,25 +218,25 @@ XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island # # +07 - WIB -XX -0610+10648 Asia/Jakarta Indonesia ("WIB") +XX -0610+10648 Asia/Jakarta Indonesia (WIB) # # +08 XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore; Concordia # # +08 - AWST -XX -3157+11551 Australia/Perth Western Australia ("AWST") +XX -3157+11551 Australia/Perth Western Australia (AWST) # # +08 - CST -XX +3114+12128 Asia/Shanghai China ("CST") +XX +3114+12128 Asia/Shanghai China (CST) # # +08 - HKT -XX +2217+11409 Asia/Hong_Kong Hong Kong ("HKT") +XX +2217+11409 Asia/Hong_Kong Hong Kong (HKT) # # +08 - PHT -XX +143512+1205804 Asia/Manila Philippines ("PHT") +XX +143512+1205804 Asia/Manila Philippines (PHT) # # +08 - WITA -XX -0507+11924 Asia/Makassar Indonesia ("WITA") +XX -0507+11924 Asia/Makassar Indonesia (WITA) # # +08:45 XX -3143+12852 Australia/Eucla Eucla @@ -245,31 +245,31 @@ XX +5203+11328 Asia/Chita Russia; Palau; East Timor # # +09 - JST -XX +353916+1394441 Asia/Tokyo Japan ("JST"); Eyre Bird Observatory +XX +353916+1394441 Asia/Tokyo Japan (JST); Eyre Bird Observatory # # +09 - KST -XX +3733+12658 Asia/Seoul Korea ("KST") +XX +3733+12658 Asia/Seoul Korea (KST) # # +09 - WIT -XX -0232+14042 Asia/Jayapura Indonesia ("WIT") +XX -0232+14042 Asia/Jayapura Indonesia (WIT) # # +09:30 - ACST -XX -1228+13050 Australia/Darwin Northern Territory ("ACST") +XX -1228+13050 Australia/Darwin Northern Territory (ACST) # # +09:30/+10:30 - ACST/ACDT (Australia DST) -XX -3455+13835 Australia/Adelaide South Australia ("ACST/ACDT") +XX -3455+13835 Australia/Adelaide South Australia (ACST/ACDT) # # +10 -XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d'Urville +XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d’Urville # # +10 - AEST -XX -2728+15302 Australia/Brisbane Queensland ("AEST") +XX -2728+15302 Australia/Brisbane Queensland (AEST) # # +10 - ChST -XX +1328+14445 Pacific/Guam Mariana Islands ("ChST") +XX +1328+14445 Pacific/Guam Mariana Islands (ChST) # # +10/+11 - AEST/AEDT (Australia DST) -XX -3352+15113 Australia/Sydney southeast Australia ("AEST/AEDT") +XX -3352+15113 Australia/Sydney southeast Australia (AEST/AEDT) # # +10:30/+11 XX -3133+15905 Australia/Lord_Howe Lord Howe Island @@ -284,7 +284,7 @@ XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc. # # +12/+13 (New Zealand DST) -XX -3652+17446 Pacific/Auckland New Zealand ("NZST/NZDT") +XX -3652+17446 Pacific/Auckland New Zealand (NZST/NZDT) # # +12:45/+13:45 (Chatham DST) XX -4357-17633 Pacific/Chatham Chatham Islands
diff --git a/ci/absl_alternate_options.h b/ci/absl_alternate_options.h index 20bf010..d5567f3 100644 --- a/ci/absl_alternate_options.h +++ b/ci/absl_alternate_options.h
@@ -20,7 +20,6 @@ #ifndef ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_ #define ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_ -#define ABSL_OPTION_USE_STD_STRING_VIEW 0 #define ABSL_OPTION_USE_STD_ORDERING 0 #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 #define ABSL_OPTION_INLINE_NAMESPACE_NAME ns