diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 47d0efd..1d55240 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -153,6 +153,14 @@ "debugging/internal/utf8_for_code_point.h" "debugging/internal/vdso_support.cc" "debugging/internal/vdso_support.h" + "extend/internal/aggregate.h" + "extend/internal/dependencies.h" + "extend/internal/is_tuple_hashable.h" + "extend/internal/num_bases.h" + "extend/internal/num_initializers.h" + "extend/internal/reflection.cc" + "extend/internal/reflection.h" + "extend/internal/tuple.h" "functional/any_invocable.h" "functional/internal/front_binder.h" "functional/bind_front.h" @@ -421,8 +429,12 @@ "time/civil_time.h" "time/clock.cc" "time/clock.h" + "time/clock_interface.cc" + "time/clock_interface.h" "time/duration.cc" "time/format.cc" + "time/simulated_clock.cc" + "time/simulated_clock.h" "time/time.cc" "time/time.h" "time/internal/cctz/include/cctz/civil_time.h"
diff --git a/MODULE.bazel b/MODULE.bazel index 00a1083..edb49d0 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -39,5 +39,5 @@ # intended to be used by Abseil users depend on GoogleTest. bazel_dep( name = "googletest", - version = "1.17.0", + version = "1.17.0.bcr.2", )
diff --git a/absl/CMakeLists.txt b/absl/CMakeLists.txt index 810d7f3..2a986c3 100644 --- a/absl/CMakeLists.txt +++ b/absl/CMakeLists.txt
@@ -20,6 +20,7 @@ add_subdirectory(container) add_subdirectory(crc) add_subdirectory(debugging) +add_subdirectory(extend) add_subdirectory(flags) add_subdirectory(functional) add_subdirectory(hash)
diff --git a/absl/extend/CMakeLists.txt b/absl/extend/CMakeLists.txt new file mode 100644 index 0000000..dba0c5c --- /dev/null +++ b/absl/extend/CMakeLists.txt
@@ -0,0 +1,140 @@ +# Copyright 2026 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. + +absl_cc_library( + NAME + dependencies_internal + HDRS + "internal/dependencies.h" + COPTS + ${ABSL_DEFAULT_COPTS} +) + +absl_cc_test( + NAME + dependencies_internal_test + SRCS + "internal/dependencies_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::dependencies_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + num_initializers_internal + HDRS + "internal/num_initializers.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::core_headers +) + +absl_cc_test( + NAME + num_initializers_internal_test + SRCS + "internal/num_initializers_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::num_initializers_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + num_bases_internal + HDRS + "internal/num_bases.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::num_initializers_internal + absl::type_traits + absl::optional +) + +absl_cc_test( + NAME + num_bases_internal_test + SRCS + "internal/num_bases_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::num_bases_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + aggregate_internal + HDRS + "internal/aggregate.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::num_bases_internal + absl::num_initializers_internal +) + +absl_cc_test( + NAME + aggregate_internal_test + SRCS + "internal/aggregate_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::aggregate_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + tuple_internal + HDRS + "internal/tuple.h" + COPTS + ${ABSL_DEFAULT_COPTS} +) + +absl_cc_library( + NAME + is_tuple_hashable_internal + HDRS + "internal/is_tuple_hashable.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::hash +) + +absl_cc_library( + NAME + reflection_internal + HDRS + "internal/reflection.h" + SRCS + "internal/reflection.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::strings + absl::span +)
diff --git a/absl/extend/internal/BUILD.bazel b/absl/extend/internal/BUILD.bazel new file mode 100644 index 0000000..79b8361 --- /dev/null +++ b/absl/extend/internal/BUILD.bazel
@@ -0,0 +1,127 @@ +# Copyright 2026 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. + +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +# Internal extend utilities and implementation details. +package( + default_visibility = [ + "//absl/extend:__subpackages__", + ], + features = ["header_modules"], +) + +cc_library( + name = "tuple", + hdrs = ["tuple.h"], + deps = ["//absl/base:config"], +) + +cc_library( + name = "is_tuple_hashable", + hdrs = ["is_tuple_hashable.h"], + deps = [ + "//absl/base:config", + "//absl/hash", + ], +) + +cc_library( + name = "reflection", + srcs = ["reflection.cc"], + hdrs = ["reflection.h"], + deps = [ + "//absl/base:config", + "//absl/strings", + "//absl/types:span", + ], +) + +cc_library( + name = "aggregate", + hdrs = ["aggregate.h"], + deps = [ + ":num_bases", + ":num_initializers", + "//absl/base:config", + ], +) + +cc_test( + name = "aggregate_test", + srcs = ["aggregate_test.cc"], + deps = [ + ":aggregate", + "//absl/base:config", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "num_bases", + hdrs = ["num_bases.h"], + deps = [ + ":num_initializers", + "//absl/base:config", + ], +) + +cc_test( + name = "num_bases_test", + srcs = ["num_bases_test.cc"], + deps = [ + ":num_bases", + "//absl/base:config", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "num_initializers", + hdrs = ["num_initializers.h"], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "num_initializers_test", + srcs = ["num_initializers_test.cc"], + tags = [ + "no_test_ios_arm64", + ], + deps = [ + ":num_initializers", + "//absl/base:config", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "dependencies", + hdrs = ["dependencies.h"], + deps = ["//absl/base:config"], +) + +cc_test( + name = "dependencies_test", + srcs = ["dependencies_test.cc"], + deps = [ + ":dependencies", + "//absl/base:config", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/extend/internal/aggregate.h b/absl/extend/internal/aggregate.h new file mode 100644 index 0000000..815865c --- /dev/null +++ b/absl/extend/internal/aggregate.h
@@ -0,0 +1,1687 @@ +// Copyright 2026 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_EXTEND_INTERNAL_AGGREGATE_H_ +#define ABSL_EXTEND_INTERNAL_AGGREGATE_H_ + +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/extend/internal/num_bases.h" +#include "absl/extend/internal/num_initializers.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { + +// This collection of constants to serialize information about type qualifiers +// in C++. Together with `ExtractQualifiers` and `ApplyQualifiers` below, this +// allows us to apply metafunctions that change the type but preserve +// qualifiers. For example, a meta-function that converted `T` to +// `std::vector<T>` could use `CopyQualifiers` below to ensure that +// `std::string` became `std::vector<std::string>`, and that +// `const std::string&` became `const std::vector<std::string>&`. +// +// Note: We use a non-power-of-2 for kRef so that operator| matches reference +// collapsing semantics. That is, `(kRefRef | kRef) == kRef` in the same way +// that `T&& &` is `T&`. +inline constexpr int kRefRef = 1; +inline constexpr int kRef = 3; +inline constexpr int kConst = 4; +inline constexpr int kVolatile = 8; + +// `ExtractQualifiers` returns an integer value representing the qualifiers that +// are applied to the template parameter `T`. +template <typename T> +constexpr int ExtractQualifiers() { + int quals = 0; + if constexpr (std::is_lvalue_reference_v<T>) { + quals |= kRef; + } else if constexpr (std::is_rvalue_reference_v<T>) { + quals |= kRefRef; + } + + using dereffed_type = std::remove_reference_t<T>; + if constexpr (std::is_const_v<dereffed_type>) quals |= kConst; + if constexpr (std::is_volatile_v<dereffed_type>) quals |= kVolatile; + + return quals; +} + +// `ApplyQualifiers<T, kQuals>::type` evaluates to a type which has the same +// underlying type as `T` but with additional qualifiers from `kQuals` applied. +// Note that we do not remove the qualifiers from `T`. So, for example, +// `ApplyQualifiers<const int, kRef>::type` will be `const int&` rather than +// `int&`. +// +// Also note that, because ` +template <typename T, int kQuals> +struct ApplyQualifiers { + private: + static_assert(!std::is_reference_v<T>, + "Apply qualifiers cannot be called on a reference type."); + using handle_volatile = + std::conditional_t<((kQuals & kVolatile) == kVolatile), volatile T, T>; + using handle_const = + std::conditional_t<((kQuals & kConst) == kConst), const handle_volatile, + handle_volatile>; + using handle_refref = std::conditional_t<((kQuals & kRefRef) == kRefRef), + handle_const&&, handle_const>; + using handle_ref = std::conditional_t<((kQuals & kRef) == kRef), + handle_refref&, handle_refref>; + + public: + using type = handle_ref; +}; + +// `CopyQualifiers` extracts qualifiers from `From` and overwrites the +// qualifiers on `To`. +template <typename From, typename To> +using CopyQualifiers = + typename ApplyQualifiers<To, ExtractQualifiers<From>()>::type; + +// `CorrectQualifiers` is specific to the implementation of `GetFields`. In +// particular, when constructing a tuple of references to fields of a struct, we +// need to know which fields should be referenced and which should be forwarded. +// `CorrectQualifiers` forwards reference-members and otherwise copies the +// qualifiers on the struct to its members being referenced in the tuple. +template <typename Aggregate, typename Field, typename T> +constexpr decltype(auto) CorrectQualifiers(T& val) { + if constexpr (std::is_reference_v<Field>) { + return static_cast<Field>(val); + } else { + using type = CopyQualifiers<Aggregate&&, Field>; + return static_cast<type>(val); + } +} + +// `RemoveQualifiersAndReferencesFromTuple` removes volatile and const +// qualifiers from the underlying type of each reference type within a tuple. +template <typename T> +struct RemoveQualifiersAndReferencesFromTuple; + +template <typename... U> +struct RemoveQualifiersAndReferencesFromTuple<std::tuple<U...>> { + using type = + std::tuple<typename std::remove_cv_t<std::remove_reference_t<U>>...>; +}; + +template <typename T, int kNumBases = -1> +constexpr int NumFields() { + if constexpr (std::is_empty_v<T>) { + return 0; + } else if constexpr (std::is_aggregate_v<T>) { + if constexpr (kNumBases == -1) { + constexpr auto num_bases = aggregate_internal::NumBases<T>(); + static_assert(num_bases.has_value(), + "This library can only detect the number of bases when the " + "type is non-empty and has exactly 0 or 1 base classes. " + "However, the type has >= 2 bases."); + return aggregate_internal::NumInitializers<T>() - num_bases.value(); + } else { + return aggregate_internal::NumInitializers<T>() - kNumBases; + } + } else { + return -1; + } +} + +// Structured binding declarations are weird in that they produce the same +// bindings whether the object is an rvalue or lvalue. Hence we add reference +// qualifiers ourselves. + +struct FieldGetter { + struct Error {}; + template <int kNumBases, int kFieldCount, typename T> + constexpr static auto Unpack(T&& t) { + static_assert( + kNumBases == -1 || kNumBases == 0 || kNumBases == 1, + "Unpack() only supports aggregate types that have either no " + "base class or 1 empty base class (absl::Extend). You can " + "also set the number of bases to -1 to let the library try " + "to detect the number of bases. All other values are invalid."); + if constexpr (kFieldCount == -1) { + constexpr int kNumFields = NumFields<std::decay_t<T>, kNumBases>(); + if constexpr (kNumFields != -1) { + return Unpack<kNumBases, kNumFields>(std::forward<T>(t)); + } else { + return Error{}; + } + } else if constexpr (kFieldCount == 0) { + return std::make_tuple(); + } else if constexpr (kFieldCount == 1) { + auto&& [f0] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0)); + } else if constexpr (kFieldCount == 2) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1)); + } else if constexpr (kFieldCount == 3) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2)); + } else if constexpr (kFieldCount == 4) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3)); + } else if constexpr (kFieldCount == 5) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4)); + } else if constexpr (kFieldCount == 6) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5)); + } else if constexpr (kFieldCount == 7) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6)); + } else if constexpr (kFieldCount == 8) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7)); + } else if constexpr (kFieldCount == 9) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8)); + } else if constexpr (kFieldCount == 10) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9)); + } else if constexpr (kFieldCount == 11) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10)); + } else if constexpr (kFieldCount == 12) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11)); + } else if constexpr (kFieldCount == 13) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12)); + } else if constexpr (kFieldCount == 14) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13)); + } else if constexpr (kFieldCount == 15) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14)); + } else if constexpr (kFieldCount == 16) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, + f15] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15)); + } else if constexpr (kFieldCount == 17) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16)); + } else if constexpr (kFieldCount == 18) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17)); + } else if constexpr (kFieldCount == 19) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18)); + } else if constexpr (kFieldCount == 20) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19)); + } else if constexpr (kFieldCount == 21) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20)); + } else if constexpr (kFieldCount == 22) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21)); + } else if constexpr (kFieldCount == 23) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22)); + } else if constexpr (kFieldCount == 24) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23)); + } else if constexpr (kFieldCount == 25) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24)); + } else if constexpr (kFieldCount == 26) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25)); + } else if constexpr (kFieldCount == 27) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26)); + } else if constexpr (kFieldCount == 28) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27)); + } else if constexpr (kFieldCount == 29) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28] = + t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28)); + } else if constexpr (kFieldCount == 30) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29)); + } else if constexpr (kFieldCount == 31) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30)); + } else if constexpr (kFieldCount == 32) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31)); + } else if constexpr (kFieldCount == 33) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32)); + } else if constexpr (kFieldCount == 34) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33)); + } else if constexpr (kFieldCount == 35) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34)); + } else if constexpr (kFieldCount == 36) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35)); + } else if constexpr (kFieldCount == 37) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36)); + } else if constexpr (kFieldCount == 38) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37)); + } else if constexpr (kFieldCount == 39) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38)); + } else if constexpr (kFieldCount == 40) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39)); + } else if constexpr (kFieldCount == 41) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40)); + } else if constexpr (kFieldCount == 42) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41] = + t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41)); + } else if constexpr (kFieldCount == 43) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42)); + } else if constexpr (kFieldCount == 44) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43)); + } else if constexpr (kFieldCount == 45) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44)); + } else if constexpr (kFieldCount == 46) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44, f45] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44), + CorrectQualifiers<T, decltype(f45)>(f45)); + } else if constexpr (kFieldCount == 47) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44, f45, f46] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44), + CorrectQualifiers<T, decltype(f45)>(f45), + CorrectQualifiers<T, decltype(f46)>(f46)); + } else if constexpr (kFieldCount == 48) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44, f45, f46, f47] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44), + CorrectQualifiers<T, decltype(f45)>(f45), + CorrectQualifiers<T, decltype(f46)>(f46), + CorrectQualifiers<T, decltype(f47)>(f47)); + } else if constexpr (kFieldCount == 49) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44, f45, f46, f47, f48] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44), + CorrectQualifiers<T, decltype(f45)>(f45), + CorrectQualifiers<T, decltype(f46)>(f46), + CorrectQualifiers<T, decltype(f47)>(f47), + CorrectQualifiers<T, decltype(f48)>(f48)); + } else if constexpr (kFieldCount == 50) { + auto&& [f0, // Did you forget `friend absl::EnableExtensions`? + f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, f24, f25, f26, f27, f28, + f29, f30, f31, f32, f33, f34, f35, f36, f37, f38, f39, f40, f41, + f42, f43, f44, f45, f46, f47, f48, f49] = t; + return std::forward_as_tuple(CorrectQualifiers<T, decltype(f0)>(f0), + CorrectQualifiers<T, decltype(f1)>(f1), + CorrectQualifiers<T, decltype(f2)>(f2), + CorrectQualifiers<T, decltype(f3)>(f3), + CorrectQualifiers<T, decltype(f4)>(f4), + CorrectQualifiers<T, decltype(f5)>(f5), + CorrectQualifiers<T, decltype(f6)>(f6), + CorrectQualifiers<T, decltype(f7)>(f7), + CorrectQualifiers<T, decltype(f8)>(f8), + CorrectQualifiers<T, decltype(f9)>(f9), + CorrectQualifiers<T, decltype(f10)>(f10), + CorrectQualifiers<T, decltype(f11)>(f11), + CorrectQualifiers<T, decltype(f12)>(f12), + CorrectQualifiers<T, decltype(f13)>(f13), + CorrectQualifiers<T, decltype(f14)>(f14), + CorrectQualifiers<T, decltype(f15)>(f15), + CorrectQualifiers<T, decltype(f16)>(f16), + CorrectQualifiers<T, decltype(f17)>(f17), + CorrectQualifiers<T, decltype(f18)>(f18), + CorrectQualifiers<T, decltype(f19)>(f19), + CorrectQualifiers<T, decltype(f20)>(f20), + CorrectQualifiers<T, decltype(f21)>(f21), + CorrectQualifiers<T, decltype(f22)>(f22), + CorrectQualifiers<T, decltype(f23)>(f23), + CorrectQualifiers<T, decltype(f24)>(f24), + CorrectQualifiers<T, decltype(f25)>(f25), + CorrectQualifiers<T, decltype(f26)>(f26), + CorrectQualifiers<T, decltype(f27)>(f27), + CorrectQualifiers<T, decltype(f28)>(f28), + CorrectQualifiers<T, decltype(f29)>(f29), + CorrectQualifiers<T, decltype(f30)>(f30), + CorrectQualifiers<T, decltype(f31)>(f31), + CorrectQualifiers<T, decltype(f32)>(f32), + CorrectQualifiers<T, decltype(f33)>(f33), + CorrectQualifiers<T, decltype(f34)>(f34), + CorrectQualifiers<T, decltype(f35)>(f35), + CorrectQualifiers<T, decltype(f36)>(f36), + CorrectQualifiers<T, decltype(f37)>(f37), + CorrectQualifiers<T, decltype(f38)>(f38), + CorrectQualifiers<T, decltype(f39)>(f39), + CorrectQualifiers<T, decltype(f40)>(f40), + CorrectQualifiers<T, decltype(f41)>(f41), + CorrectQualifiers<T, decltype(f42)>(f42), + CorrectQualifiers<T, decltype(f43)>(f43), + CorrectQualifiers<T, decltype(f44)>(f44), + CorrectQualifiers<T, decltype(f45)>(f45), + CorrectQualifiers<T, decltype(f46)>(f46), + CorrectQualifiers<T, decltype(f47)>(f47), + CorrectQualifiers<T, decltype(f48)>(f48), + CorrectQualifiers<T, decltype(f49)>(f49)); + } else { + static_assert( + kFieldCount <= 50, + "We are unlikely to extend support beyond 50 fields, both because of " + "the compile-time cost associated with doing so, and because we " + "believe structs this large are probably better off grouped into " + "well-designed smaller structs that can be nested."); + } + } +}; + +template <typename T, + typename = std::enable_if_t<!std::is_same_v<T, FieldGetter::Error>>> +T NotAnError(T); + +// Given a reference to an aggregate `T`, constructs a tuple of references to +// the fields in the aggregate. This only works for types that have either no +// base class or 1 empty base class. +template <typename T> +auto Unpack(T&& t) -> decltype(NotAnError( + FieldGetter::Unpack< + 0, absl::aggregate_internal::NumFields<std::decay_t<T>>(), T>( + std::forward<T>(t)))) { + return FieldGetter::Unpack< + 0, absl::aggregate_internal::NumFields<std::decay_t<T>>(), T>( + std::forward<T>(t)); +} + +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_AGGREGATE_H_
diff --git a/absl/extend/internal/aggregate_test.cc b/absl/extend/internal/aggregate_test.cc new file mode 100644 index 0000000..30316c9 --- /dev/null +++ b/absl/extend/internal/aggregate_test.cc
@@ -0,0 +1,867 @@ +// Copyright 2026 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/extend/internal/aggregate.h" + +#include <tuple> +#include <type_traits> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { +namespace { + +TEST(Qualifiers, Collapse) { + EXPECT_EQ(kRef | kRefRef, kRef); + EXPECT_NE(kConst, kVolatile); + EXPECT_NE(kConst, kRef); + EXPECT_NE(kConst, kRefRef); + EXPECT_NE(kVolatile, kRef); + EXPECT_NE(kVolatile, kRefRef); + EXPECT_NE(kRef, kRefRef); +} + +TEST(ExtractQualifiers, Works) { + EXPECT_EQ(ExtractQualifiers<int>(), 0); + EXPECT_EQ(ExtractQualifiers<int&>(), kRef); + EXPECT_EQ(ExtractQualifiers<int&&>(), kRefRef); + EXPECT_EQ(ExtractQualifiers<const int>(), kConst); + EXPECT_EQ(ExtractQualifiers<const int&>(), kConst | kRef); + EXPECT_EQ(ExtractQualifiers<const int&&>(), kConst | kRefRef); + EXPECT_EQ(ExtractQualifiers<volatile int>(), kVolatile); + EXPECT_EQ(ExtractQualifiers<volatile int&>(), kVolatile | kRef); + EXPECT_EQ(ExtractQualifiers<volatile int&&>(), kVolatile | kRefRef); + EXPECT_EQ(ExtractQualifiers<volatile const int>(), kVolatile | kConst); + EXPECT_EQ(ExtractQualifiers<volatile const int&>(), + kVolatile | kConst | kRef); + EXPECT_EQ(ExtractQualifiers<volatile const int&&>(), + kVolatile | kConst | kRefRef); +} + +TEST(ApplyQualifiers, OnBareType) { + EXPECT_TRUE((std::is_same_v<typename ApplyQualifiers<int, 0>::type, int>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kRef>::type, int&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kRefRef>::type, int&&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kConst>::type, const int>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kConst | kRef>::type, + const int&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kConst | kRefRef>::type, + const int&&>)); + EXPECT_TRUE((std::is_same_v<typename ApplyQualifiers<int, kVolatile>::type, + volatile int>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kVolatile | kRef>::type, + volatile int&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kVolatile | kRefRef>::type, + volatile int&&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<int, kVolatile | kConst>::type, + volatile const int>)); + EXPECT_TRUE((std::is_same_v< + typename ApplyQualifiers<int, kVolatile | kConst | kRef>::type, + volatile const int&>)); + EXPECT_TRUE( + (std::is_same_v< + typename ApplyQualifiers<int, kVolatile | kConst | kRefRef>::type, + volatile const int&&>)); +} + +TEST(ApplyQualifiers, OnConstType) { + EXPECT_TRUE(( + std::is_same_v<typename ApplyQualifiers<const int, 0>::type, const int>)); + EXPECT_TRUE((std::is_same_v<typename ApplyQualifiers<const int, kRef>::type, + const int&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<const int, kRefRef>::type, + const int&&>)); + EXPECT_TRUE((std::is_same_v<typename ApplyQualifiers<const int, kConst>::type, + const int>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<const int, kConst | kRef>::type, + const int&>)); + EXPECT_TRUE((std::is_same_v< + typename ApplyQualifiers<const int, kConst | kRefRef>::type, + const int&&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<const int, kVolatile>::type, + const volatile int>)); + EXPECT_TRUE((std::is_same_v< + typename ApplyQualifiers<const int, kVolatile | kRef>::type, + const volatile int&>)); + EXPECT_TRUE((std::is_same_v< + typename ApplyQualifiers<const int, kVolatile | kRefRef>::type, + const volatile int&&>)); + EXPECT_TRUE((std::is_same_v< + typename ApplyQualifiers<const int, kVolatile | kConst>::type, + volatile const int>)); + EXPECT_TRUE( + (std::is_same_v< + typename ApplyQualifiers<const int, kVolatile | kConst | kRef>::type, + volatile const int&>)); + EXPECT_TRUE( + (std::is_same_v<typename ApplyQualifiers<const int, kVolatile | kConst | + kRefRef>::type, + volatile const int&&>)); +} + +TEST(CorrectQualifiers, Type) { + struct A { + int val; + int&& rval; + const int cval; + const int& clval; + const int&& crval; + }; + + A a{ + 3, // .val + static_cast<int&&>(a.val), // .rval + 3, // .cval + a.cval, // .clval + static_cast<const int&&>(a.cval) // .crval + }; + auto&& [v, r, c, cl, cr] = a; + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A, decltype(v)>(v)), + int&&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A, decltype(r)>(r)), + int&&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A, decltype(c)>(c)), + const int&&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A, decltype(cl)>(cl)), + const int&>(); + + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A&, decltype(v)>(v)), + int&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A&, decltype(r)>(r)), + int&&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A&, decltype(c)>(c)), + const int&>(); + testing::StaticAssertTypeEq<decltype(CorrectQualifiers<A&, decltype(cl)>(cl)), + const int&>(); + + testing::StaticAssertTypeEq< + decltype(CorrectQualifiers<const A&, decltype(v)>(v)), const int&>(); + testing::StaticAssertTypeEq< + decltype(CorrectQualifiers<const A&, decltype(r)>(r)), int&&>(); +} + +TEST(RemoveQualifiersAndReferencesFromTuple, Type) { + EXPECT_TRUE( + (std::is_same_v<RemoveQualifiersAndReferencesFromTuple<std::tuple< + int, int&, const int&, volatile const int&>>::type, + std::tuple<int, int, int, int>>)); +} + +TEST(Unpack, Basic) { + struct A1 { + int a; + }; + struct A2 { + int a; + int b; + }; + struct A3 { + int a; + int&& b; + }; + + { + A1 a; + auto [f1] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + } + + { + A2 a; + auto [f1, f2] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + EXPECT_EQ(&f2, &a.b); + } + + { + int b = 0; + A3 a{0, std::move(b)}; // NOLINT(performance-move-const-arg) + auto [f1, f2] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + EXPECT_EQ(&f2, &a.b); + } +} + +TEST(Unpack, Types) { + struct A3 { + int field_v; + const int& field_l; + int&& field_r; + }; + + int x = 0, y = 0; + A3 my_struct{0, x, std::move(y)}; // NOLINT(performance-move-const-arg) + auto unpacked = Unpack(my_struct); + const auto& const_lvalue_ref = my_struct; + auto unpacked_const_lvalue_ref = Unpack(const_lvalue_ref); + auto unpacked_rvalue_ref = Unpack(std::move(my_struct)); + testing::StaticAssertTypeEq<decltype(unpacked), + std::tuple<int&, const int&, int&&>>(); + testing::StaticAssertTypeEq<decltype(unpacked_const_lvalue_ref), + std::tuple<const int&, const int&, int&&>>(); + testing::StaticAssertTypeEq<decltype(unpacked_rvalue_ref), + std::tuple<int&&, const int&, int&&>>(); +} + +TEST(Unpack, Const) { + struct A1 { + const int a; + }; + struct A2 { + const int a; + const int b; + }; + struct A3 { + const int a; + const int& b; + int&& c; + }; + + { + A1 a{}; + auto [f1] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + } + + { + A2 a{}; + auto [f1, f2] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + EXPECT_EQ(&f2, &a.b); + } + + { + int b = 0, c = 0; + A3 a{0, b, std::move(c)}; // NOLINT(performance-move-const-arg) + auto [f1, f2, f3] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + EXPECT_EQ(&f2, &a.b); + EXPECT_EQ(&f3, &a.c); + } +} + +TEST(Unpack, BaseClass) { + struct Base {}; + struct A2 : Base { + int a1, a2; + }; + + A2 a; + auto [f1, f2] = Unpack(a); + EXPECT_EQ(&f1, &a.a1); + EXPECT_EQ(&f2, &a.a2); +} + +// Not supported on older compilers due to mysterious compiler bug(s) that are +// somewhat difficult to track down. (See b/478243383.) +// +// TODO(b/479561657): Enable this unconditionally. +#if !defined(_MSC_VER) || _MSC_VER >= 1939 +TEST(Unpack, Immovable) { + struct Immovable { + Immovable() = default; + Immovable(Immovable&&) = delete; + }; + struct A2 { + const Immovable& a; + Immovable&& b; + }; + + Immovable i1, i2; + A2 a{i1, std::move(i2)}; + auto [f1, f2] = Unpack(a); + EXPECT_EQ(&f1, &a.a); + EXPECT_EQ(&f2, &a.b); +} +#endif // _MSC_VER >= 1939 + +TEST(Unpack, Empty) { + struct Empty {}; + EXPECT_TRUE((std::is_same_v<std::tuple<>, decltype(Unpack(Empty{}))>)); + Empty lvalue_empty; + EXPECT_TRUE((std::is_same_v<std::tuple<>, decltype(Unpack(lvalue_empty))>)); + + struct EmptyWithBase : Empty {}; + EXPECT_TRUE( + (std::is_same_v<std::tuple<>, decltype(Unpack(EmptyWithBase{}))>)); +} + +TEST(Unpack, Autodetect) { + struct NoBases { + int i = 7; + } no_bases; + auto [i2] = Unpack(no_bases); + EXPECT_EQ(i2, 7); + + struct Base {}; + struct OneBase : Base { + int j = 17; + } one_base; + auto [j2] = Unpack(one_base); + EXPECT_EQ(j2, 17); + + constexpr auto try_unpack = + [](auto&& v) -> decltype(NumBases(std::forward<decltype(v)>(v))) {}; + + struct Base2 {}; + struct TwoBases : Base, Base2 { + int k; + }; + EXPECT_FALSE((std::is_invocable_v<decltype(try_unpack), TwoBases>)); +} + +TEST(Unpack, FailsSubstitution) { + struct Aggregate { + int i; + }; + struct NonAggregate { + explicit NonAggregate(int) {} + int i; + }; + + const auto unpack = + [](auto&& v) -> decltype(Unpack(std::forward<decltype(v)>(v))) {}; + EXPECT_TRUE((std::is_invocable_v<decltype(unpack), Aggregate>)); + EXPECT_FALSE((std::is_invocable_v<decltype(unpack), NonAggregate>)); +} + +// Not supported on older compilers due to mysterious compiler bug(s) that are +// somewhat difficult to track down. (See b/478243383.) +// +// TODO(b/479561657): Enable this unconditionally. +#if !defined(_MSC_VER) || _MSC_VER >= 1939 +template <int N, class T> +void CheckTupleElementsEqualToIndex(const T& t) { + if constexpr (N > 0) { + EXPECT_EQ(std::get<N - 1>(t), N - 1); + CheckTupleElementsEqualToIndex<N - 1>(t); + } +} + +TEST(Unpack, CorrectOrder1) { + struct S1 { + int a0 = 0; + }; + CheckTupleElementsEqualToIndex<1>(Unpack(S1())); +} + +TEST(Unpack, CorrectOrder2) { + struct S2 { + int a0 = 0, a1 = 1; + }; + CheckTupleElementsEqualToIndex<2>(Unpack(S2())); +} + +TEST(Unpack, CorrectOrder3) { + struct S3 { + int a0 = 0, a1 = 1, a2 = 2; + }; + CheckTupleElementsEqualToIndex<3>(Unpack(S3())); +} + +TEST(Unpack, CorrectOrder4) { + struct S4 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3; + }; + CheckTupleElementsEqualToIndex<4>(Unpack(S4())); +} + +TEST(Unpack, CorrectOrder5) { + struct S5 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4; + }; + CheckTupleElementsEqualToIndex<5>(Unpack(S5())); +} + +TEST(Unpack, CorrectOrder6) { + struct S6 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5; + }; + CheckTupleElementsEqualToIndex<6>(Unpack(S6())); +} + +TEST(Unpack, CorrectOrder7) { + struct S7 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6; + }; + CheckTupleElementsEqualToIndex<7>(Unpack(S7())); +} + +TEST(Unpack, CorrectOrder8) { + struct S8 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7; + }; + CheckTupleElementsEqualToIndex<8>(Unpack(S8())); +} + +TEST(Unpack, CorrectOrder9) { + struct S9 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8; + }; + CheckTupleElementsEqualToIndex<9>(Unpack(S9())); +} + +TEST(Unpack, CorrectOrder10) { + struct S10 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9; + }; + CheckTupleElementsEqualToIndex<10>(Unpack(S10())); +} + +TEST(Unpack, CorrectOrder11) { + struct S11 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10; + }; + CheckTupleElementsEqualToIndex<11>(Unpack(S11())); +} + +TEST(Unpack, CorrectOrder12) { + struct S12 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11; + }; + CheckTupleElementsEqualToIndex<12>(Unpack(S12())); +} + +TEST(Unpack, CorrectOrder13) { + struct S13 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12; + }; + CheckTupleElementsEqualToIndex<13>(Unpack(S13())); +} + +TEST(Unpack, CorrectOrder14) { + struct S14 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13; + }; + CheckTupleElementsEqualToIndex<14>(Unpack(S14())); +} + +TEST(Unpack, CorrectOrder15) { + struct S15 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14; + }; + CheckTupleElementsEqualToIndex<15>(Unpack(S15())); +} + +TEST(Unpack, CorrectOrder16) { + struct S16 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15; + }; + CheckTupleElementsEqualToIndex<16>(Unpack(S16())); +} + +TEST(Unpack, CorrectOrder17) { + struct S17 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16; + }; + CheckTupleElementsEqualToIndex<17>(Unpack(S17())); +} + +TEST(Unpack, CorrectOrder18) { + struct S18 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17; + }; + CheckTupleElementsEqualToIndex<18>(Unpack(S18())); +} + +TEST(Unpack, CorrectOrder19) { + struct S19 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18; + }; + CheckTupleElementsEqualToIndex<19>(Unpack(S19())); +} + +TEST(Unpack, CorrectOrder20) { + struct S20 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19; + }; + CheckTupleElementsEqualToIndex<20>(Unpack(S20())); +} + +TEST(Unpack, CorrectOrder21) { + struct S21 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20; + }; + CheckTupleElementsEqualToIndex<21>(Unpack(S21())); +} + +TEST(Unpack, CorrectOrder22) { + struct S22 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21; + }; + CheckTupleElementsEqualToIndex<22>(Unpack(S22())); +} + +TEST(Unpack, CorrectOrder23) { + struct S23 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22; + }; + CheckTupleElementsEqualToIndex<23>(Unpack(S23())); +} + +TEST(Unpack, CorrectOrder24) { + struct S24 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23; + }; + CheckTupleElementsEqualToIndex<24>(Unpack(S24())); +} + +TEST(Unpack, CorrectOrder25) { + struct S25 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24; + }; + CheckTupleElementsEqualToIndex<25>(Unpack(S25())); +} + +TEST(Unpack, CorrectOrder26) { + struct S26 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25; + }; + CheckTupleElementsEqualToIndex<26>(Unpack(S26())); +} + +TEST(Unpack, CorrectOrder27) { + struct S27 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26; + }; + CheckTupleElementsEqualToIndex<27>(Unpack(S27())); +} + +TEST(Unpack, CorrectOrder28) { + struct S28 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27; + }; + CheckTupleElementsEqualToIndex<28>(Unpack(S28())); +} + +TEST(Unpack, CorrectOrder29) { + struct S29 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28; + }; + CheckTupleElementsEqualToIndex<29>(Unpack(S29())); +} + +TEST(Unpack, CorrectOrder30) { + struct S30 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29; + }; + CheckTupleElementsEqualToIndex<30>(Unpack(S30())); +} + +TEST(Unpack, CorrectOrder31) { + struct S31 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30; + }; + CheckTupleElementsEqualToIndex<31>(Unpack(S31())); +} + +TEST(Unpack, CorrectOrder32) { + struct S32 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31; + }; + CheckTupleElementsEqualToIndex<32>(Unpack(S32())); +} + +#ifndef _MSC_VER // MSVC constexpr evaluation hits a default limit +TEST(Unpack, CorrectOrder33) { + struct S33 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32; + }; + CheckTupleElementsEqualToIndex<33>(Unpack(S33())); +} + +TEST(Unpack, CorrectOrder34) { + struct S34 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33; + }; + CheckTupleElementsEqualToIndex<34>(Unpack(S34())); +} + +TEST(Unpack, CorrectOrder35) { + struct S35 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34; + }; + CheckTupleElementsEqualToIndex<35>(Unpack(S35())); +} + +TEST(Unpack, CorrectOrder36) { + struct S36 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35; + }; + CheckTupleElementsEqualToIndex<36>(Unpack(S36())); +} + +TEST(Unpack, CorrectOrder37) { + struct S37 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36; + }; + CheckTupleElementsEqualToIndex<37>(Unpack(S37())); +} + +TEST(Unpack, CorrectOrder38) { + struct S38 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37; + }; + CheckTupleElementsEqualToIndex<38>(Unpack(S38())); +} + +TEST(Unpack, CorrectOrder39) { + struct S39 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38; + }; + CheckTupleElementsEqualToIndex<39>(Unpack(S39())); +} + +TEST(Unpack, CorrectOrder40) { + struct S40 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39; + }; + CheckTupleElementsEqualToIndex<40>(Unpack(S40())); +} + +TEST(Unpack, CorrectOrder41) { + struct S41 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40; + }; + CheckTupleElementsEqualToIndex<41>(Unpack(S41())); +} + +TEST(Unpack, CorrectOrder42) { + struct S42 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41; + }; + CheckTupleElementsEqualToIndex<42>(Unpack(S42())); +} + +TEST(Unpack, CorrectOrder43) { + struct S43 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42; + }; + CheckTupleElementsEqualToIndex<43>(Unpack(S43())); +} + +TEST(Unpack, CorrectOrder44) { + struct S44 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43; + }; + CheckTupleElementsEqualToIndex<44>(Unpack(S44())); +} + +TEST(Unpack, CorrectOrder45) { + struct S45 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44; + }; + CheckTupleElementsEqualToIndex<45>(Unpack(S45())); +} + +TEST(Unpack, CorrectOrder46) { + struct S46 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44, a45 = 45; + }; + CheckTupleElementsEqualToIndex<46>(Unpack(S46())); +} + +TEST(Unpack, CorrectOrder47) { + struct S47 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44, a45 = 45, a46 = 46; + }; + CheckTupleElementsEqualToIndex<47>(Unpack(S47())); +} + +TEST(Unpack, CorrectOrder48) { + struct S48 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44, a45 = 45, a46 = 46, a47 = 47; + }; + CheckTupleElementsEqualToIndex<48>(Unpack(S48())); +} + +TEST(Unpack, CorrectOrder49) { + struct S49 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44, a45 = 45, a46 = 46, a47 = 47, a48 = 48; + }; + CheckTupleElementsEqualToIndex<49>(Unpack(S49())); +} + +TEST(Unpack, CorrectOrder50) { + struct S50 { + int a0 = 0, a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5, a6 = 6, a7 = 7, a8 = 8, + a9 = 9, a10 = 10, a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15, + a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20, a21 = 21, a22 = 22, + a23 = 23, a24 = 24, a25 = 25, a26 = 26, a27 = 27, a28 = 28, a29 = 29, + a30 = 30, a31 = 31, a32 = 32, a33 = 33, a34 = 34, a35 = 35, a36 = 36, + a37 = 37, a38 = 38, a39 = 39, a40 = 40, a41 = 41, a42 = 42, a43 = 43, + a44 = 44, a45 = 45, a46 = 46, a47 = 47, a48 = 48, a49 = 49; + }; + CheckTupleElementsEqualToIndex<50>(Unpack(S50())); +} + +// When adding support for more fields, add corresponding CorrectOrderNN test(s) +// here. +#endif // _MSC_VER +#endif // _MSC_VER >= 1939 + +} // namespace +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/extend/internal/dependencies.h b/absl/extend/internal/dependencies.h new file mode 100644 index 0000000..572f7a3 --- /dev/null +++ b/absl/extend/internal/dependencies.h
@@ -0,0 +1,83 @@ +// Copyright 2026 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_EXTEND_INTERNAL_DEPENDENCIES_H_ +#define ABSL_EXTEND_INTERNAL_DEPENDENCIES_H_ + +#include <type_traits> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace dependencies_internal { + +// Dependencies is a meta-function which constructs a collection which +// recursively looks through types and either adds them to the list, or, if they +// have a `using deps = void(...);` takes those listed dependencies as well. For +// example, given: +// +// struct X {}; +// struct Y {}; +// struct A {}; +// struct B { using deps = void(A, X); }; +// struct C { using deps = void(A, Y); }; +// struct D { using deps = void(B, C); }; +// +// Dependencies<D>() will return a function pointer returning void and accepting +// arguments of types A, B, C, D, X, and Y in some unspecified order each +// exactly once, and no other types. +template <typename T> +auto GetDependencies(T*) -> typename T::deps*; +auto GetDependencies(void*) -> void (*)(); + +template <typename FnPtr, typename... Ts> +struct CombineDeps {}; +template <typename... Ps, typename... Ts> +struct CombineDeps<void (*)(Ps...), Ts...> { + using type = void (*)(Ps..., Ts...); +}; + +template <typename... Processed> +auto DependenciesImpl(void (*)(), void (*)(Processed...)) { + return static_cast<void (*)(Processed...)>(nullptr); +} + +template <typename T, typename... Ts, typename... Processed> +auto DependenciesImpl(void (*)(T, Ts...), void (*)(Processed...)) { + if constexpr ((std::is_same_v<T, Processed> || ...)) { + return DependenciesImpl(static_cast<void (*)(Ts...)>(nullptr), + static_cast<void (*)(Processed...)>(nullptr)); + } else { + using deps = decltype(GetDependencies(static_cast<T*>(nullptr))); + if constexpr (std ::is_same_v<deps, void (*)()>) { + return DependenciesImpl(static_cast<void (*)(Ts...)>(nullptr), + static_cast<void (*)(T, Processed...)>(nullptr)); + } else { + return DependenciesImpl( + static_cast<typename CombineDeps<deps, Ts...>::type>(nullptr), + static_cast<void (*)(T, Processed...)>(nullptr)); + } + } +} + +template <typename... Ts> +using Dependencies = decltype(DependenciesImpl( + static_cast<void (*)(Ts...)>(nullptr), static_cast<void (*)()>(nullptr))); + +} // namespace dependencies_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_DEPENDENCIES_H_
diff --git a/absl/extend/internal/dependencies_test.cc b/absl/extend/internal/dependencies_test.cc new file mode 100644 index 0000000..7fa4a13 --- /dev/null +++ b/absl/extend/internal/dependencies_test.cc
@@ -0,0 +1,117 @@ +// Copyright 2026 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/extend/internal/dependencies.h" + +#include <type_traits> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace dependencies_internal { +namespace { + +struct X {}; +struct Y {}; +struct A {}; +struct B { + using deps = void(A, X); +}; +struct C { + using deps = void(A, Y); +}; +struct D { + using deps = void(B, C); +}; + +// We don't actually want to check equality because Dependencies doesn't +// guarantee any ordering or repetition. Instead, we just want to check +// containment. +template <typename, typename> +struct Contains : std::false_type {}; + +template <typename Needle, typename... Haystack> +struct Contains<void(Haystack...), Needle> + : std::integral_constant<bool, (std::is_same_v<Needle, Haystack> || ...)> { +}; + +template <typename T> +struct Inspect; + +template <typename... Args> +struct Inspect<void (*)(Args...)> { + template <typename T> + static constexpr int Count() { + return (std::is_same_v<T, Args> + ...); + } + + static constexpr int Total() { return sizeof...(Args); } +}; + +TEST(Dependencies, Works) { + testing::StaticAssertTypeEq<decltype(Dependencies<X>()), void (*)(X)>(); + + EXPECT_EQ(Inspect<Dependencies<B>>::Count<A>(), 1); + EXPECT_EQ(Inspect<Dependencies<B>>::Count<B>(), 1); + EXPECT_EQ(Inspect<Dependencies<B>>::Count<C>(), 0); + EXPECT_EQ(Inspect<Dependencies<B>>::Count<D>(), 0); + EXPECT_EQ(Inspect<Dependencies<B>>::Count<X>(), 1); + EXPECT_EQ(Inspect<Dependencies<B>>::Count<Y>(), 0); + EXPECT_EQ(Inspect<Dependencies<B>>::Total(), 3); + + EXPECT_EQ(Inspect<Dependencies<D>>::Count<A>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Count<B>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Count<C>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Count<D>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Count<X>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Count<Y>(), 1); + EXPECT_EQ(Inspect<Dependencies<D>>::Total(), 6); +} + +struct Recursion { + using deps = void(Recursion); +}; + +template <bool B> +struct MutualRecursion { + using deps = void(MutualRecursion<!B>); +}; + +TEST(Dependencies, Cycles) { + EXPECT_EQ(Inspect<Dependencies<Recursion>>::Count<Recursion>(), 1); + EXPECT_EQ(Inspect<Dependencies<Recursion>>::Total(), 1); + + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<true>>>::Count< + MutualRecursion<true>>(), + 1); + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<true>>>::Count< + MutualRecursion<false>>(), + 1); + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<false>>>::Total(), 2); + + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<false>>>::Count< + MutualRecursion<true>>(), + 1); + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<false>>>::Count< + MutualRecursion<false>>(), + 1); + EXPECT_EQ(Inspect<Dependencies<MutualRecursion<true>>>::Total(), 2); +} + +} // namespace +} // namespace dependencies_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/extend/internal/is_tuple_hashable.h b/absl/extend/internal/is_tuple_hashable.h new file mode 100644 index 0000000..74495b5 --- /dev/null +++ b/absl/extend/internal/is_tuple_hashable.h
@@ -0,0 +1,45 @@ +// Copyright 2026 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_EXTEND_INTERNAL_IS_TUPLE_HASHABLE_H_ +#define ABSL_EXTEND_INTERNAL_IS_TUPLE_HASHABLE_H_ + +#include <tuple> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/hash/hash.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace extend_internal { + +// Trait to check if all members of a tuple are absl::Hash-able. +// +template <typename T> +struct IsTupleHashable {}; + +// TODO(b/185498964): We wouldn't need absl::Hash if the hasher object provided +// an "is_hashable" trait. +template <typename... Ts> +struct IsTupleHashable<std::tuple<Ts...>> + : std::integral_constant<bool, + (std::is_constructible_v<absl::Hash<Ts>> && ...)> { +}; + +} // namespace extend_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_IS_TUPLE_HASHABLE_H_
diff --git a/absl/extend/internal/num_bases.h b/absl/extend/internal/num_bases.h new file mode 100644 index 0000000..a7bcac6 --- /dev/null +++ b/absl/extend/internal/num_bases.h
@@ -0,0 +1,87 @@ +// Copyright 2026 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_EXTEND_INTERNAL_NUM_BASES_H_ +#define ABSL_EXTEND_INTERNAL_NUM_BASES_H_ + +#include <stddef.h> + +#include <optional> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/extend/internal/num_initializers.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { + +template <typename U> +struct OnlyBases { + template <typename T> + using IsStrictBase = + std::enable_if_t<std::is_base_of_v<T, U> && !std::is_same_v<T, U>, int>; + + template <typename T, IsStrictBase<T> = 0> + operator T&&() const; // NOLINT(google-explicit-constructor) + template <typename T, IsStrictBase<T> = 0> + operator T&() const; // NOLINT(google-explicit-constructor) +}; + +template <typename T, size_t... I> +constexpr std::optional<int> NumBasesImpl(std::index_sequence<I...>) { + if constexpr (BraceInitializableWith< + T, OnlyBases<T>, OnlyBases<T>, + decltype( // Cast to void to avoid unintentionally invoking + // comma operator or warning on unused LHS + (void)I, Anything{})...>::value) { + // The first two initializers look like bases. + // The second might be a member or a base, so we give up. + return std::nullopt; + } else if constexpr (BraceInitializableWith< + T, OnlyBases<T>, Anything, + decltype( // Cast to void to avoid unintentionally + // invoking comma operator or warning on + // unused LHS + (void)I, Anything{})...>::value) { + // The first initializer is a base, but not the second. We have 1 base. + return 1; + } else { + // The first initializer is not a base. We have 0 bases. + return 0; + } +} + +// Returns the number of base classes of the given type, with best effort: +// +// - If the type is empty (i.e. `std::is_empty_v<T>` is true), returns nullopt. +// - If the type has no bases, returns 0. +// - If the type has exactly 1 base, returns 1. +// - Otherwise, returns nullopt. +template <typename T> +constexpr std::optional<int> NumBases() { + if constexpr (std::is_empty_v<T>) { + return std::nullopt; + } else { + constexpr int num_initializers = NumInitializers<T>(); + return NumBasesImpl<T>(std::make_index_sequence<num_initializers - 1>()); + } +} + +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_NUM_BASES_H_
diff --git a/absl/extend/internal/num_bases_test.cc b/absl/extend/internal/num_bases_test.cc new file mode 100644 index 0000000..ca6a063 --- /dev/null +++ b/absl/extend/internal/num_bases_test.cc
@@ -0,0 +1,91 @@ +// Copyright 2026 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/extend/internal/num_bases.h" + +#include <optional> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { +namespace { + +using ::testing::Eq; +using ::testing::Optional; + +TEST(NumBases, NoBases) { + struct S { + int i; + }; + EXPECT_THAT(NumBases<S>(), Optional(0)); + + struct Empty {}; + // We can't deal with empty classes right now. + EXPECT_THAT(NumBases<Empty>(), Eq(std::nullopt)); +} + +TEST(NumBases, OneBase) { + struct EmptyBase {}; + struct NonEmptyBase { + int i; + }; + + // We can't deal with empty classes right now. + struct EmptyEmpty : EmptyBase {}; + EXPECT_THAT(NumBases<EmptyEmpty>(), Eq(std::nullopt)); + + struct EmptyNonEmpty : NonEmptyBase {}; + EXPECT_THAT(NumBases<EmptyNonEmpty>(), Optional(1)); + + struct NonEmptyEmpty : EmptyBase { + int i; + }; + EXPECT_THAT(NumBases<NonEmptyEmpty>(), Optional(1)); + + struct NonEmptyNonEmpty : NonEmptyBase { + int j; + }; + EXPECT_THAT(NumBases<NonEmptyNonEmpty>(), Optional(1)); +} + +TEST(NumBases, TwoBases) { + struct Base1 {}; + struct Base2 {}; + struct S : Base1, Base2 { + int i; + }; + + EXPECT_THAT(NumBases<S>(), Eq(std::nullopt)); +} + +TEST(NumBases, OneBaseAndField) { + // Special case for 1 base where we fail to detect it. + // The first member also happens to be a base. + + struct Base {}; + struct S : Base { + Base member; + }; + + EXPECT_THAT(NumBases<S>(), Eq(std::nullopt)); +} + +} // namespace +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/extend/internal/num_initializers.h b/absl/extend/internal/num_initializers.h new file mode 100644 index 0000000..1224e72 --- /dev/null +++ b/absl/extend/internal/num_initializers.h
@@ -0,0 +1,113 @@ +// Copyright 2026 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_EXTEND_INTERNAL_NUM_INITIALIZERS_H_ +#define ABSL_EXTEND_INTERNAL_NUM_INITIALIZERS_H_ + +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/optimization.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { + +template <typename U> +struct AnythingExcept { + template <typename T, + std::enable_if_t<!std::is_same_v<U, std::decay_t<T>>, int> = 0> + operator T() const { // NOLINT(google-explicit-constructor) + ABSL_UNREACHABLE(); + } +}; + +struct Anything { + template <typename T> + operator T() const { // NOLINT(google-explicit-constructor) + ABSL_UNREACHABLE(); + } +}; + +struct NotDefaultConstructible { + NotDefaultConstructible() = delete; + explicit NotDefaultConstructible(int); +}; + +template <typename T> +struct Tester { + T t; + NotDefaultConstructible n; +}; + +template <class Void, typename T, typename... Args> +struct BraceInitializableWithImpl : std::false_type { + static_assert(std::is_void_v<Void>, "First template parameter must be void"); +}; + +template <typename T, typename... Args> +struct BraceInitializableWithImpl< + std::enable_if_t< + !std::is_null_pointer_v<decltype(Tester<T>{std::declval<Args>()...})>>, + T, Args...> : std::true_type {}; + +#ifdef __clang__ +#pragma clang diagnostic push +// -Wmissing-braces triggers here because we are expanding arguments into the +// first member of `Tester`. Not having the braces is necessary because we don't +// know how many arguments are going to be used to initialize the `T` and we +// need them to be tried greedily. +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + +template <typename T, typename... Args> +using BraceInitializableWith = BraceInitializableWithImpl<void, T, Args...>; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// Count the number of initializers required for a struct of type `T`. Note that +// we do not support C-style array members. Prefer using a std::array. +// +// Repeatedly tests if `T` is brace-constructible with a given number of +// arguments. Each time it is not, one more argument is added and +// `NumInitializers` is called recursively. +template <typename T, typename... Args> +constexpr int NumInitializers(Args... args) { + static_assert(!std::is_empty_v<T>); + // By attempting to brace-initialize a Tester<T> where the first member is + // `AnythingExcept<T>`, we force the aggregate to be expanded, meaning the + // first some-number of arguments are passed to `T`'s aggregate + // initialization. This will fail (and therefore recurse into + // `NumInitializers` with one more argument) if we have not found enough + // arguments to initialize `T`. This is because the second member of the + // `Tester<T>` has type `NotDefaultConstructible` which cannot be default + // constructed. + if constexpr (BraceInitializableWith<T, AnythingExcept<T>, Args...>::value) { + return sizeof...(Args); + } else { + static_assert(sizeof...(Args) <= sizeof(T), + "Automatic field count does not support fields like l-value " + "references or bit fields. Try specifying the field count."); + return NumInitializers<T>(args..., Anything()); + } +} + +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_NUM_INITIALIZERS_H_
diff --git a/absl/extend/internal/num_initializers_test.cc b/absl/extend/internal/num_initializers_test.cc new file mode 100644 index 0000000..96c7b41 --- /dev/null +++ b/absl/extend/internal/num_initializers_test.cc
@@ -0,0 +1,250 @@ +// Copyright 2026 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/extend/internal/num_initializers.h" + +#include <stdint.h> + +#include <any> +#include <array> +#include <optional> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace aggregate_internal { +namespace { + +TEST(NumInitializers, SimpleStructs) { + struct A1 { + int a; + }; + EXPECT_EQ(NumInitializers<A1>(), 1); + + struct A2 { + int a; + int b; + }; + EXPECT_EQ(NumInitializers<A2>(), 2); + + struct A3 { + int a; + const int& b; + int&& c; + }; + EXPECT_EQ(NumInitializers<A3>(), 3); +} + +TEST(NumInitializers, SimpleStructsWithDefaults) { + struct A1 { + int a = 1; + }; + EXPECT_EQ(NumInitializers<A1>(), 1); + + struct A2 { + int a = 1; + int b = 2; + }; + EXPECT_EQ(NumInitializers<A2>(), 2); + + struct A3 { + int a = 0; + const int& b; + int&& c; + }; + EXPECT_EQ(NumInitializers<A3>(), 3); +} + +TEST(NumInitializers, EmptyBase) { + struct Base {}; + + struct A1 : Base { + int a; + }; + EXPECT_EQ(NumInitializers<A1>(), 2); + + struct A2 : Base { + int a; + int b; + }; + EXPECT_EQ(NumInitializers<A2>(), 3); + + struct A3 : Base { + int a; + const int& b; + int&& c; + }; + EXPECT_EQ(NumInitializers<A3>(), 4); +} + +TEST(NumInitializers, Base) { + struct Base { + int x; + int y; + }; + + struct A1 : Base { + int a; + }; + EXPECT_EQ(NumInitializers<A1>(), 2); + + struct A2 : Base { + int a; + int b; + }; + EXPECT_EQ(NumInitializers<A2>(), 3); + + struct A3 : Base { + int a; + const int& b; + int&& c; + }; + EXPECT_EQ(NumInitializers<A3>(), 4); +} + +TEST(NumInitializers, MultipleBases) { + struct Base1 { + int x1; + int y1; + }; + struct Base2 { + int x2; + int y2; + }; + + struct A1 : Base1, Base2 { + int a; + }; + EXPECT_EQ(NumInitializers<A1>(), 3); + + struct A2 : Base1, Base2 { + int a; + int b; + }; + EXPECT_EQ(NumInitializers<A2>(), 4); + + struct A3 : Base1, Base2 { + int a; + const int& b; + int&& c; + }; + EXPECT_EQ(NumInitializers<A3>(), 5); +} + +TEST(NumInitializers, StdArrayMembers) { + struct A { + std::array<int, 3> a; + int b; + }; + EXPECT_EQ(NumInitializers<A>(), 2); +} + +TEST(NumInitializers, StructMembers) { + struct A { + struct Nested { + int x; + int y; + } n; + int b; + }; + EXPECT_EQ(NumInitializers<A>(), 2); +} + +TEST(NumInitializers, MoveOnlyMembers) { + class MoveOnly { + public: + MoveOnly() = default; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; + + MoveOnly(MoveOnly const&) = delete; + MoveOnly& operator=(MoveOnly const&) = delete; + }; + + struct A { + MoveOnly a; + int b; + MoveOnly c; + MoveOnly&& d; + // MoveOnly& e; // does not compile, until a brave soul implements it. + }; + EXPECT_EQ(NumInitializers<A>(), 4); +} + +// Not supported on older compilers due to mysterious compiler bug(s) that are +// somewhat difficult to track down. (See b/478243383.) +// +// TODO(b/479561657): Enable this unconditionally. +#if !defined(_MSC_VER) || _MSC_VER >= 1939 +TEST(NumInitializers, MutableRefConstructibleMembers) { + class MutableRefConstructible { + public: + MutableRefConstructible() = default; + + MutableRefConstructible(MutableRefConstructible&) { + { + // Dummy braces to avoid modernize-use-equals-default diagnostic. + // We avoid `= default` due to compiler bug on _MSC_VER < 1940. + } + } + + MutableRefConstructible(const MutableRefConstructible&) = delete; + }; + + struct A { + MutableRefConstructible a; + int b; + MutableRefConstructible c; + MutableRefConstructible&& d; + }; + EXPECT_EQ(NumInitializers<A>(), 4); +} + +TEST(NumInitializers, ImmovableMembers) { + struct Immovable { + Immovable() = default; + Immovable(Immovable&&) = delete; + }; + + struct A { + const Immovable& a; + Immovable&& b; + }; + EXPECT_EQ(NumInitializers<A>(), 2); +} +#endif // _MSC_VER >= 1939 + +TEST(NumInitializers, StdAnyMembers) { + struct A1 { + std::any a; // cover `AnythingExcept` + int b; + std::any c; + }; + EXPECT_EQ(NumInitializers<A1>(), 3); + + struct A2 { + int a; + std::any b; // cover `Anything` + const std::any& c; + std::any&& d; + }; + EXPECT_EQ(NumInitializers<A2>(), 4); +} + +} // namespace +} // namespace aggregate_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/extend/internal/reflection.cc b/absl/extend/internal/reflection.cc new file mode 100644 index 0000000..1d47998 --- /dev/null +++ b/absl/extend/internal/reflection.cc
@@ -0,0 +1,112 @@ +// Copyright 2026 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/extend/internal/reflection.h" + +#include <stddef.h> + +#include <cstdarg> + +#include "absl/base/config.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace extend_internal { + +// `PrintfHijack` is called repeatedly with each `fmt` line and corresponding +// arguments `va` via `__builtin_dump_struct`. While there is no guarantee about +// the `fmt` strings provided, it is typical that lines come in 5 flavors: +// 1. A string containing only "%s", accepting the name of the struct itself. +// 2. A string containing only "%s%s", accepting the name of a base class. +// 3. A string containing only whitespace and an open brace "{" indicating that +// we are entering a new struct. +// 4. A string with "=" where the value passed into the format specifier which +// immediately precedes "=" is the name of a struct field. +// 5. A string ending with a "}" indicating the conclusion of a struct. +// +// For example, for a struct +// ``` +// struct Bacon { +// int repeatability; +// double flavor; +// double fattiness; +// struct Metadata { +// bool is_pork; +// bool is_expired; +// } metadata; +// }; +// ``` +// we would expect the following values for `fmt` to be passed to `ParseLine`, +// along with the following data in `va`: +// `fmt` | `va` +// --------------------|------------- +// "%s" | {"Bacon"} +// " {\n" | {} +// "%s%s %s = %d\n" | {" ", "int", "repeatability", ???} +// "%s%s %s = %f\n" | {" ", "double", "flavor", ???} +// "%s%s %s = %f\n" | {" ", "double", "fattiness", ???} +// "%s%s %s =" | {" ", "Metadata", "metadata"} +// " {\n" | {} +// "%s%s %s = %d\n" | {" ", "_Bool", "is_pork", ???} +// "%s%s %s = %d\n" | {" ", "_Bool", "is_expired", ???} +// "%s}\n" | {" "} +// "}\n" | {} +// +// `PrintfHijack` inspects each format string and argument set, extracting field +// names for the given struct and writes them to `fields`. If an unexpected +// format string is encountered, `state.index` will be set to -1 indicating a +// failure. Parsing succeeds if after every call to `PrintfHijack`, +// `state.index == fields.size()`. +int PrintfHijack(ParsingState& state, absl::Span<absl::string_view> fields, + const char* fmt, ...) { + if (state.index < 0) return 0; + + if (absl::EndsWith(fmt, "{\n")) { + ++state.brace_count; + return 0; + } else if (absl::EndsWith(fmt, "}") || absl::EndsWith(fmt, "}\n")) { + --state.brace_count; + return 0; + } else if (state.brace_count != 1) { + // Ignore everything that's not at the top-level (we only care about the + // names of fields in this type. + return 0; + } else if (absl::StartsWith(fmt, "%s%s %s =")) { + std::va_list va; + va_start(va, fmt); + + static_cast<void>(va_arg(va, const char*)); // Indentation whitespace + static_cast<void>(va_arg(va, const char*)); // Field's type name + fields[static_cast<size_t>(state.index)] = + va_arg(va, const char*); // Field name + ++state.index; + + va_end(va); + } else if (fmt == absl::string_view("%s%s")) { + // Ignore base classes. + return 0; + } else { + // Unexpected case. Mark parsing as failed. + state.index = -1; + } + + return 0; +} + +} // namespace extend_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/extend/internal/reflection.h b/absl/extend/internal/reflection.h new file mode 100644 index 0000000..6751374 --- /dev/null +++ b/absl/extend/internal/reflection.h
@@ -0,0 +1,75 @@ +// Copyright 2026 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_EXTEND_INTERNAL_REFLECTION_H_ +#define ABSL_EXTEND_INTERNAL_REFLECTION_H_ + +#include <array> +#include <cstddef> + +#include "absl/base/config.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +#if defined(ABSL_EXTEND_INTERNAL_TURN_OFF_FIELD_NAMES_FOR_TEST) +#define ABSL_INTERNAL_EXTEND_PARSE_FIELD_NAMES 0 +#elif defined(TARGET_OS_OSX) || defined(__ANDROID__) +// TODO(b/234010485): Implement field names for these. +#define ABSL_INTERNAL_EXTEND_PARSE_FIELD_NAMES 0 +#elif ABSL_HAVE_BUILTIN(__builtin_dump_struct) +#define ABSL_INTERNAL_EXTEND_PARSE_FIELD_NAMES 1 +#else +#define ABSL_INTERNAL_EXTEND_PARSE_FIELD_NAMES 0 +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace extend_internal { + +struct ParsingState { + // The index of the field currently being parsed. + int index = 0; + // The number of unmatched '{' characters encountered so far while parsing. + int brace_count = 0; +}; + +// Called by` __builtin_dump_struct` for each line of the struct representation. +// Rather than printing the struct representation, we parse `fmt` to extract the +// lines containing struct field names and store them in `T::kFieldNames`. +// Note: `__builtin_dump_struct` provides no guarantees whatsoever on its +// output, so our parsing is entirely best-effort. +int PrintfHijack(ParsingState& state, absl::Span<absl::string_view> fields, + const char* fmt, ...); + +// Forward declaration of test helper. +template <typename T> +struct ReflectionTestingExtension; + +// Not nested in ReflectionExtension to reduce template instantiations. +template <size_t N> +struct FieldNameInfo { + static constexpr size_t kFieldCount = N; + +#if ABSL_INTERNAL_EXTEND_PARSE_FIELD_NAMES + std::array<absl::string_view, kFieldCount> field_names; +#endif + // Indicates whether parsing was successful. + bool success; +}; + +} // namespace extend_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_REFLECTION_H_
diff --git a/absl/extend/internal/tuple.h b/absl/extend/internal/tuple.h new file mode 100644 index 0000000..747e549 --- /dev/null +++ b/absl/extend/internal/tuple.h
@@ -0,0 +1,51 @@ +// Copyright 2026 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_EXTEND_INTERNAL_TUPLE_H_ +#define ABSL_EXTEND_INTERNAL_TUPLE_H_ + +#include <cstddef> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace extend_internal { + +template <std::size_t... N, typename F, typename... T> +void ZippedForEachImpl(std::index_sequence<N...>, F f, T&&... t) { + const auto for_i = [&](auto i) { f(std::get<i>(std::forward<T>(t))...); }; + (for_i(std::integral_constant<size_t, N>{}), ...); +} + +template <typename F, typename... T> +void ZippedForEach(F f, T&... t) { + using tuple_t = std::decay_t<decltype((std::declval<T>(), ...))>; + using seq_t = std::make_index_sequence<std::tuple_size_v<tuple_t>>; + (ZippedForEachImpl)(seq_t{}, f, t...); +} + +template <typename F, size_t... Ns> +auto MakeTupleFromCallable(std::index_sequence<Ns...>, F f) { + return std::make_tuple(f(std::integral_constant<size_t, Ns>{})...); +} + +} // namespace extend_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_EXTEND_INTERNAL_TUPLE_H_
diff --git a/absl/hash/internal/hash.cc b/absl/hash/internal/hash.cc index 23caae9..74b6d57 100644 --- a/absl/hash/internal/hash.cc +++ b/absl/hash/internal/hash.cc
@@ -39,6 +39,13 @@ #include <xmmintrin.h> #endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD +#ifdef ABSL_AES_INTERNAL_HAVE_ARM_SIMD +#error ABSL_AES_INTERNAL_HAVE_ARM_SIMD cannot be directly set +#elif defined(ABSL_INTERNAL_HAVE_ARM_NEON) && defined(__ARM_FEATURE_CRYPTO) +#include <arm_neon.h> +#define ABSL_AES_INTERNAL_HAVE_ARM_SIMD +#endif // ABSL_INTERNAL_HAVE_ARM_NEON + namespace absl { ABSL_NAMESPACE_BEGIN namespace hash_internal { @@ -49,39 +56,136 @@ 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)); +#if defined(ABSL_AES_INTERNAL_HAVE_X86_SIMD) || \ + defined(ABSL_AES_INTERNAL_HAVE_ARM_SIMD) + +#if defined(ABSL_AES_INTERNAL_HAVE_X86_SIMD) +using Vector128 = __m128i; + +inline Vector128 Load128(const uint8_t* ptr) { + return _mm_loadu_si128(reinterpret_cast<const Vector128*>(ptr)); +} + +inline Vector128 Set128(uint64_t a, uint64_t b) { + return _mm_set_epi64x(static_cast<int64_t>(a), static_cast<int64_t>(b)); +} + +inline Vector128 Add128(Vector128 a, Vector128 b) { + return _mm_add_epi64(a, b); +} + +inline Vector128 Sub128(Vector128 a, Vector128 b) { + return _mm_sub_epi64(a, b); +} + +inline Vector128 Encrypt128(Vector128 data, Vector128 key) { + return _mm_aesenc_si128(data, key); +} + +inline Vector128 Decrypt128(Vector128 data, Vector128 key) { + return _mm_aesdec_si128(data, key); +} + +inline uint64_t ExtractLow64(Vector128 v) { + return static_cast<uint64_t>(_mm_cvtsi128_si64(v)); +} + +inline uint64_t ExtractHigh64(Vector128 v) { + return static_cast<uint64_t>(_mm_extract_epi64(v, 1)); +} + +inline uint64_t Mix4x16Vectors(Vector128 a, Vector128 b, Vector128 c, + Vector128 d) { + Vector128 res128 = + Add128(Encrypt128(Add128(a, c), d), Decrypt128(Sub128(b, d), a)); + uint64_t x64 = ExtractLow64(res128); + uint64_t y64 = ExtractHigh64(res128); return x64 ^ y64; } +#else // ABSL_AES_INTERNAL_HAVE_ARM_SIMD + +using Vector128 = uint8x16_t; + +inline Vector128 Load128(const uint8_t* ptr) { return vld1q_u8(ptr); } + +inline Vector128 Set128(uint64_t a, uint64_t b) { + return vreinterpretq_u8_u64(vsetq_lane_u64(a, vdupq_n_u64(b), 1)); +} + +inline Vector128 Add128(Vector128 a, Vector128 b) { + return vreinterpretq_u8_u64( + vaddq_u64(vreinterpretq_u64_u8(a), vreinterpretq_u64_u8(b))); +} + +inline Vector128 Sub128(Vector128 a, Vector128 b) { + return vreinterpretq_u8_u64( + vsubq_u64(vreinterpretq_u64_u8(a), vreinterpretq_u64_u8(b))); +} + +// Encrypt128 and Decrypt128 are equivalent to x86 versions. +// `vaeseq_u8` and `vaesdq_u8` perform `^ key` before encryption/decryption. +// We need to perform `^ key` after encryption/decryption to improve hash +// quality and match x86 version. + +inline Vector128 Encrypt128(Vector128 data, Vector128 key) { + return vaesmcq_u8(vaeseq_u8(data, Vector128{})) ^ key; +} + +inline Vector128 Decrypt128(Vector128 data, Vector128 key) { + return vaesimcq_u8(vaesdq_u8(data, Vector128{})) ^ key; +} + +// ArmEncrypt128 and ArmDecrypt128 are ARM specific versions that use ARM +// instructions directly. +// We can only use these versions in the second round of encryption/decryption +// to have good hash quality. + +inline Vector128 ArmEncrypt128(Vector128 data, Vector128 key) { + return vaesmcq_u8(vaeseq_u8(data, key)); +} + +inline Vector128 ArmDecrypt128(Vector128 data, Vector128 key) { + return vaesimcq_u8(vaesdq_u8(data, key)); +} + +inline uint64_t ExtractLow64(Vector128 v) { + return vgetq_lane_u64(vreinterpretq_u64_u8(v), 0); +} + +inline uint64_t ExtractHigh64(Vector128 v) { + return vgetq_lane_u64(vreinterpretq_u64_u8(v), 1); +} + +uint64_t Mix4x16Vectors(Vector128 a, Vector128 b, Vector128 c, Vector128 d) { + Vector128 res128 = Add128(ArmEncrypt128(a, c), ArmDecrypt128(b, d)); + uint64_t x64 = ExtractLow64(res128); + uint64_t y64 = ExtractHigh64(res128); + return x64 ^ y64; +} + +#endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD + uint64_t LowLevelHash33To64(uint64_t seed, const uint8_t* ptr, size_t len) { assert(len > 32); assert(len <= 64); - __m128i state = - _mm_set_epi64x(static_cast<int64_t>(seed), static_cast<int64_t>(len)); - auto a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(ptr)); - auto b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(ptr + 16)); + Vector128 state = Set128(seed, len); + Vector128 a = Load128(ptr); + Vector128 b = Load128(ptr + 16); auto* last32_ptr = ptr + len - 32; - auto c = _mm_loadu_si128(reinterpret_cast<const __m128i*>(last32_ptr)); - auto d = _mm_loadu_si128(reinterpret_cast<const __m128i*>(last32_ptr + 16)); + Vector128 c = Load128(last32_ptr); + Vector128 d = Load128(last32_ptr + 16); - // Bits of the second argument to _mm_aesdec_si128/_mm_aesenc_si128 are - // XORed with the state argument after encryption. - // We use each value as the first argument to shuffle all the bits around. - // We do not add any salt to the state or loaded data, instead we vary - // instructions used to mix bits _mm_aesdec_si128/_mm_aesenc_si128 and - // _mm_add_epi64/_mm_sub_epi64. - // _mm_add_epi64/_mm_sub_epi64 are combined to one instruction with data - // loading like `vpaddq xmm1, xmm0, xmmword ptr [rdi]`. - auto na = _mm_aesdec_si128(_mm_add_epi64(state, a), state); - auto nb = _mm_aesdec_si128(_mm_sub_epi64(state, b), state); - auto nc = _mm_aesenc_si128(_mm_add_epi64(state, c), state); - auto nd = _mm_aesenc_si128(_mm_sub_epi64(state, d), state); + // Bits of the second argument to Decrypt128/Encrypt128 are XORed with the + // state argument after encryption. We use each value as the first argument to + // shuffle all the bits around. We do not add any salt to the state or loaded + // data, instead we vary instructions used to mix bits Decrypt128/Encrypt128 + // and Add128/Sub128. On x86 Add128/Sub128 are combined to one instruction + // with data loading like `vpaddq xmm1, xmm0, xmmword ptr [rdi]`. + Vector128 na = Decrypt128(Add128(state, a), state); + Vector128 nb = Decrypt128(Sub128(state, b), state); + Vector128 nc = Encrypt128(Add128(state, c), state); + Vector128 nd = Encrypt128(Sub128(state, d), state); // We perform another round of encryption to mix bits between two halves of // the input. @@ -97,11 +201,10 @@ // 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; + Vector128 state0 = Set128(seed, len); + Vector128 state1 = state0; + Vector128 state2 = state1; + Vector128 state3 = state2; // Mixing two 128-bit vectors at a time with corresponding states. // All variables are mixed slightly differently to avoid hash collision @@ -111,25 +214,17 @@ // 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); + Vector128 a = Load128(p); + Vector128 b = Load128(p + 16); + state0 = Decrypt128(Add128(state0, a), state0); + state1 = Decrypt128(Sub128(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); + Vector128 c = Load128(p); + Vector128 d = Load128(p + 16); + state2 = Encrypt128(Add128(state2, c), state2); + state3 = Encrypt128(Sub128(state3, d), state3); }; do {
diff --git a/absl/hash/internal/low_level_hash_test.cc b/absl/hash/internal/low_level_hash_test.cc index d054337..6476462 100644 --- a/absl/hash/internal/low_level_hash_test.cc +++ b/absl/hash/internal/low_level_hash_test.cc
@@ -368,7 +368,7 @@ << "We only maintain golden data for little endian 64 bit systems with " "128 bit intristics."; } -#elif defined(__SSE4_2__) && defined(__AES__) +#elif (defined(__SSE4_2__) && defined(__AES__)) constexpr uint64_t kGolden[kNumGoldenOutputs] = { 0xd6bdb2c9ba5e55f2, 0xffd3e23d4115a8ae, 0x2c3218ef486127de, 0x554fa7f3a262b886, 0x06304cbf82e312d3, 0x490b3fb5af80622c, @@ -403,6 +403,41 @@ 0x724110fb509e4376, 0xebe74b016b5cfb88, 0x3b0fe11dcf175fc9, 0x20b737b9c0490538, 0x0db21c429b45fd17, }; +#elif (defined(ABSL_INTERNAL_HAVE_ARM_NEON) && defined(__ARM_FEATURE_CRYPTO)) + constexpr uint64_t kGolden[kNumGoldenOutputs] = { + 0x248c82373f7f0d24, 0x0a4f8cbf55047086, 0x498dd0a4445ed65f, + 0x6d418b193638ab0f, 0x4112c1ce9142980e, 0x02132db6a189f206, + 0x36e550ca9139ee44, 0xe0a67fdbd8627314, 0x2b9528fe18f65d1d, + 0x037d9cf48ca9fd1c, 0xa87222631332ca06, 0x31f35e09af065022, + 0xacd6f1d29071c8e5, 0x54eb4f229a0d15a4, 0x132c27c6a747e136, + 0x13d7cb427efe7e2f, 0x71a5d21e00f00dcd, 0xdb909cbf1b0fbb64, + 0x4dc943ad8901fa5b, 0xacf4b3d41a46feb5, 0x12a37d19c14ffd65, + 0xdb511011bcdd95b9, 0xd6d75af1c8b86dbd, 0xc65aefdcff941a8e, + 0xbea311ef212c0306, 0xc49861afe7a6888b, 0x598424b541daf528, + 0x4cc264fbb57c4640, 0x1a7376211a5e674a, 0xcb82900ad06bf89b, + 0xd9d6201d685a4971, 0x77ed120a277ce616, 0x9bd5ad973b4d358c, + 0x880850ff91b6b772, 0xf9d24d40448fce38, 0x870b8ee54a31707d, + 0x613130fe647bd169, 0x04a0cc02a81989f3, 0x998adbda0ab3f8fa, + 0x2b28729269102040, 0xbdb9be95e352a6d5, 0xd5e2a55d78bd9fb0, + 0xef609c2b22eb93d6, 0xac23eb02494ae8e0, 0xcb8c5ab08163d2a3, + 0x63f822fc21b42698, 0xe7e8814288c470cc, 0x143b07aae2ccd592, + 0xc5d142f4806e13a3, 0xe695de2a1d8a344b, 0xc8ddc3ed542a5988, + 0x60ec526cc1e5274d, 0x732a04dcf34a7ac9, 0xf1daef52096a872a, + 0x541f04b87b3de158, 0xeb143a708b621f94, 0x0849cd39e156b25f, + 0x36eb0746caa62c5c, 0xbfa14eb3c31f78bf, 0x256637f35dc41f20, + 0x08293113693a58e2, 0x064202395a685840, 0x0593285ee1ed42ea, + 0xdcbf16fd8a44f213, 0xe9f5586745f4f23d, 0x66808a2c18365ae9, + 0xa70496836a5166e1, 0xe9ed7d0f9f572246, 0x024ba6063287d0cb, + 0xa441f6ac287479db, 0x72502c190698ee02, 0xb79705c6ced58c29, + 0x5c03f52968cb1fdc, 0x6f4b7c6bed6cc232, 0xed834775697438d3, + 0x6273b075725ffb6f, 0x60df77a69e9aafb2, 0x84483bf48b989c4e, + 0x37e42a1d35795a31, 0x280dcdb36b853ae5, 0x63309d698f2dd42c, + 0x24e65be2c805ea5b, 0x1db08e0d041efdf9, 0xb94aea8c4648772b, + 0x109f2b81aa4660d2, 0xcae92809feb1a390, 0x0a1cbf9628383b41, + 0xca0bf416706fc5c8, 0x9d4751bd7e638488, 0x343b363d5d96c7c7, + 0x6bacaedde1daf5aa, 0x721ead1618c4e405, 0xcfc19e400cb6dbc6, + 0x7ac0dd9128ec8cc3, 0xb7dd428bb44fc744, + }; #else constexpr uint64_t kGolden[kNumGoldenOutputs] = { 0x669da02f8d009e0f, 0xceb19bf2255445cd, 0x0e746992d6d43a7c,
diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index b68dd85..a182c65 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel
@@ -68,6 +68,48 @@ ) cc_library( + name = "clock_interface", + srcs = [ + "clock_interface.cc", + ], + hdrs = [ + "clock_interface.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":time", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:no_destructor", + "//absl/base:nullability", + "//absl/base:raw_logging_internal", + "//absl/synchronization", + ], +) + +cc_library( + name = "simulated_clock", + testonly = True, + srcs = [ + "simulated_clock.cc", + ], + hdrs = [ + "simulated_clock.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":clock_interface", + ":time", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:nullability", + "//absl/synchronization", + ], +) + +cc_library( name = "test_util", testonly = True, srcs = ["internal/test_util.cc"], @@ -111,6 +153,44 @@ ) cc_test( + name = "clock_interface_test", + srcs = [ + "clock_interface_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":clock_interface", + ":time", + "//absl/functional:bind_front", + "//absl/synchronization", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "simulated_clock_test", + srcs = [ + "simulated_clock_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":clock_interface", + ":simulated_clock", + ":time", + "//absl/base:core_headers", + "//absl/base:raw_logging_internal", + "//absl/functional:bind_front", + "//absl/random", + "//absl/synchronization", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( name = "flag_test", srcs = [ "flag_test.cc",
diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index 34a5ad4..c3ae98c 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt
@@ -131,6 +131,79 @@ GTest::gmock_main ) +absl_cc_library( + NAME + clock_interface + HDRS + "clock_interface.h" + SRCS + "clock_interface.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::time + absl::config + absl::core_headers + absl::no_destructor + absl::nullability + absl::raw_logging_internal + absl::synchronization +) + +absl_cc_test( + NAME + clock_interface_test + SRCS + "clock_interface_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::clock_interface + absl::time + absl::config + absl::bind_front + absl::synchronization + GTest::gmock_main +) + +absl_cc_library( + NAME + simulated_clock + HDRS + "simulated_clock.h" + SRCS + "simulated_clock.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::clock_interface + absl::time + absl::config + absl::core_headers + absl::nullability + absl::synchronization + TESTONLY +) + +absl_cc_test( + NAME + simulated_clock_test + SRCS + "simulated_clock_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::clock_interface + absl::simulated_clock + absl::time + absl::config + absl::bind_front + absl::random_random + absl::raw_logging_internal + absl::synchronization + GTest::gmock_main +) + absl_cc_test( NAME flag_test
diff --git a/absl/time/clock_interface.cc b/absl/time/clock_interface.cc new file mode 100644 index 0000000..800d0b1 --- /dev/null +++ b/absl/time/clock_interface.cc
@@ -0,0 +1,71 @@ +// Copyright 2026 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/time/clock_interface.h" + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/no_destructor.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace { + +class RealTimeClock final : public Clock { + private: +#ifdef _MSC_VER + // Disable MSVC warning "destructor never returns, potential memory leak" +#pragma warning(push) +#pragma warning(disable : 4722) +#endif + ~RealTimeClock() override { + ABSL_RAW_LOG(FATAL, "RealTimeClock should never be destroyed"); + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + public: + absl::Time TimeNow() override { return absl::Now(); } + + void Sleep(absl::Duration d) override { absl::SleepFor(d); } + + void SleepUntil(absl::Time wakeup_time) override { + absl::Duration d = wakeup_time - TimeNow(); + if (d > absl::ZeroDuration()) { + Sleep(d); + } + } + + bool AwaitWithDeadline(absl::Mutex* mu, const absl::Condition& cond, + absl::Time deadline) override { + return mu->AwaitWithDeadline(cond, deadline); + } +}; + +} // namespace + +Clock::~Clock() = default; // go/key-method + +Clock& Clock::GetRealClock() { + static absl::NoDestructor<RealTimeClock> rtclock; + return *rtclock; +} + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/time/clock_interface.h b/absl/time/clock_interface.h new file mode 100644 index 0000000..25ccf6e --- /dev/null +++ b/absl/time/clock_interface.h
@@ -0,0 +1,91 @@ +// Copyright 2026 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: clock_interface.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_TIME_CLOCK_INTERFACE_H_ +#define ABSL_TIME_CLOCK_INTERFACE_H_ + +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// An abstract interface representing a Clock, which is an object that can +// tell you the current time, sleep, and wait for a condition variable. +// +// This interface allows decoupling code that uses time from the code that +// creates a point in time. You can use this to your advantage by injecting +// Clocks into interfaces rather than having implementations call absl::Now() +// directly. +// +// The Clock::GetRealClock() function returns a reference to the global realtime +// clock. +// +// Example: +// +// bool IsWeekend(Clock& clock) { +// absl::Time now = clock.TimeNow(); +// // ... code to check if 'now' is a weekend. +// } +// +// // Production code. +// IsWeekend(Clock::GetRealClock()); +// +// // Test code: +// MyTestClock test_clock(SATURDAY); +// IsWeekend(test_clock); +// +class Clock { + public: + // Returns a reference to the global realtime clock. + // The returned clock is thread-safe. + static Clock& GetRealClock(); + + virtual ~Clock(); + + // Returns the current time. + virtual absl::Time TimeNow() = 0; + + // Sleeps for the specified duration. + virtual void Sleep(absl::Duration d) = 0; + + // Sleeps until the specified time. + virtual void SleepUntil(absl::Time wakeup_time) = 0; + + // Returns when cond is true or the deadline has passed. Returns true iff + // cond holds when returning. + // + // Requires *mu to be held at least in shared mode. It will be held when + // evaluating cond, and upon return, but it may be released and reacquired + // in the meantime. + // + // This method is similar to mu->AwaitWithDeadline() except that the + // latter only works with real-time deadlines. This call works properly + // with simulated time if invoked on a simulated clock. + virtual bool AwaitWithDeadline(absl::Mutex* absl_nonnull mu, + const absl::Condition& cond, + absl::Time deadline) = 0; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TIME_CLOCK_INTERFACE_H_
diff --git a/absl/time/clock_interface_test.cc b/absl/time/clock_interface_test.cc new file mode 100644 index 0000000..6434bc0 --- /dev/null +++ b/absl/time/clock_interface_test.cc
@@ -0,0 +1,128 @@ +// Copyright 2026 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/time/clock_interface.h" + +#include <cstdint> +#include <functional> +#include <thread> // NOLINT(build/c++11) +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/functional/bind_front.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace { + +constexpr absl::Duration kLongPause = absl::Milliseconds(150); + +#ifdef _MSC_VER +// As of 2026-01-29, multithreaded tests on MSVC are too flaky. +const char* kSkipFlakyReason = + "Skipping this timing test because it is too flaky"; +#else +const char* kSkipFlakyReason = nullptr; +#endif + + +void AwaitWithDeadlineAndNotify(absl::Clock* clock, absl::Mutex* mu, + absl::Condition* cond, absl::Time wakeup_time, + absl::Notification* note, bool* return_val) { + mu->lock_shared(); + *return_val = clock->AwaitWithDeadline(mu, *cond, wakeup_time); + mu->unlock_shared(); + note->Notify(); +} + +TEST(RealClockTest, AwaitWithVeryLargeDeadline) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::Clock& clock = absl::Clock::GetRealClock(); + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note; + bool return_val; + + std::thread thread(AwaitWithDeadlineAndNotify, &clock, &mu, &cond, + absl::InfiniteFuture(), ¬e, &return_val); + + EXPECT_FALSE(note.HasBeenNotified()); + + mu.lock(); + f = true; + mu.unlock(); + + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + EXPECT_TRUE(return_val); + note.WaitForNotification(); // In case the expectation fails. + thread.join(); +} + +TEST(RealClockTest, AwaitWithPastDeadline) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::Clock& clock = absl::Clock::GetRealClock(); + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note; + bool return_val; + + std::thread thread(AwaitWithDeadlineAndNotify, &clock, &mu, &cond, + clock.TimeNow() - absl::Seconds(10), ¬e, &return_val); + absl::Time start = absl::Now(); + note.WaitForNotification(); // AwaitWithDeadline() should ping note. + EXPECT_GE(kLongPause, absl::Now() - start); + EXPECT_FALSE(return_val); + thread.join(); +} + +TEST(RealClockTest, AwaitWithSmallDeadline) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::Clock& clock = absl::Clock::GetRealClock(); + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + + for (int i = 0; i < 5; ++i) { + absl::Duration wait_time = absl::Milliseconds(i); + absl::Duration elapsed_time; + { + absl::MutexLock lock(mu); + absl::Time start = clock.TimeNow(); + absl::Time wakeup_time = start + wait_time; + EXPECT_FALSE(clock.AwaitWithDeadline(&mu, cond, wakeup_time)); + elapsed_time = clock.TimeNow() - start; + } + if (elapsed_time >= absl::ZeroDuration()) { // ignore jumps backwards + // 60 microseconds of slop to allow for non-monotonic clocks. + EXPECT_GE(elapsed_time, wait_time - absl::Microseconds(60)); + } + } +} + +} // namespace
diff --git a/absl/time/simulated_clock.cc b/absl/time/simulated_clock.cc new file mode 100644 index 0000000..6c98260 --- /dev/null +++ b/absl/time/simulated_clock.cc
@@ -0,0 +1,225 @@ +// Copyright 2026 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/time/simulated_clock.h" + +#include <cstdint> +#include <map> +#include <memory> +#include <optional> +#include <utility> +#include <vector> + +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// There are a few tricky details in the implementation of SimulatedClock. +// +// The external mutex that is passed as a param of AwaitWithDeadline() is not +// under the control of SimulatedClock; in particular, it can be destroyed any +// time after AwaitWithDeadline() returns. This requires the wakeup call from +// AdvanceTime() to avoid grabbing the external mutex if AwaitWithDeadline() +// has returned. This is accomplished by allowing the waiter to be cancelled +// via a bool guarded by a mutex in WakeUpInfo. +// +// Once AwaitWithDeadline() has released lock_, someone nefarious might call +// the SimulatedClock destructor, so it isn't possible for AwaitWithDeadline() +// to remove the wakeup call from waiters_ if the condition it is awaiting +// becomes true; it cancels the waiter instead. This means that, +// theoretically, many obsolete entries could pile up in waiters_ if +// AwaitWithDeadline() keeps being called but simulated time is not advanced. +// This seems unlikely to happen in practice. +// +// The WakeUpInfo in waiters_ are always awoken via WakeUp() (and +// removed) or cancelled before AwaitWithDeadline() returns. If a +// waiters_ value is cancelled then calling its WakeUp() method will +// short-circuit before touching the external mutex. + +class SimulatedClock::WakeUpInfo { + public: + WakeUpInfo(absl::Mutex* mu, absl::Condition cond) + : mu_(mu), + cond_(cond), + wakeup_time_passed_(false), + cancelled_(false), + wakeup_called_(false) {} + + void WakeUp() { + // If we are cancelled then AwaitWithDeadline may have returned, in which + // case we can't lock mu_. + { + absl::MutexLock lock(cancellation_mu_); + if (cancelled_) return; + wakeup_called_ = true; + } + absl::MutexLock lock(*mu_); + wakeup_time_passed_ = true; + } + + void AwaitConditionOrWakeUp() { + mu_->Await(absl::Condition(this, &WakeUpInfo::Ready)); + } + + void CancelOrAwaitWakeUp() { + bool wakeup_called; + { + absl::MutexLock lock(cancellation_mu_); + cancelled_ = true; + wakeup_called = wakeup_called_; + } + if (wakeup_called && !wakeup_time_passed_) { + // Wait for WakeUp to complete. + // + // Note that this will unlock 'mu_'; this is actually necessary + // so that WakeUp() can unblock and complete. This does allow + // for 'cond_' to potentially change from true to false; that is + // OK, since WakeUp() is being called, so the deadline must be + // past, and so this method is fulfilling its duties. (Well, the + // destructor might be calling WakeUp(), but if you're deleting + // time itself while waiting for a deadline to pass, you deserve + // what you get.) + mu_->Await(absl::Condition(&wakeup_time_passed_)); + } + } + + private: + bool Ready() const { return wakeup_time_passed_ || cond_.Eval(); } + + absl::Mutex* mu_; + absl::Condition cond_; + bool wakeup_time_passed_; + absl::Mutex cancellation_mu_; + bool cancelled_ ABSL_GUARDED_BY(cancellation_mu_); + bool wakeup_called_ ABSL_GUARDED_BY(cancellation_mu_); +}; + +SimulatedClock::SimulatedClock(absl::Time t) : now_(t) {} + +SimulatedClock::~SimulatedClock() { + // Wake up all existing waiters. + WaiterList waiters; + { + absl::MutexLock l(lock_); + waiters.swap(waiters_); + } + for (auto& iter : waiters) { + iter.second->WakeUp(); + } +} + +absl::Time SimulatedClock::TimeNow() { + absl::ReaderMutexLock l(lock_); + return now_; +} + +void SimulatedClock::Sleep(absl::Duration d) { SleepUntil(TimeNow() + d); } + +int64_t SimulatedClock::SetTime(absl::Time t) ABSL_NO_THREAD_SAFETY_ANALYSIS { + return UpdateTime([this, t]() + ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_) { now_ = t; }); +} + +int64_t SimulatedClock::AdvanceTime(absl::Duration d) + ABSL_NO_THREAD_SAFETY_ANALYSIS { + return UpdateTime([this, d]() + ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_) { now_ += d; }); +} + +template <class T> +int64_t SimulatedClock::UpdateTime(const T& now_updater) { + // Deadlock could occur if UpdateTime() were to hold lock_ while waking up + // waiters, since waking up requires acquiring the external mutex, and + // AwaitWithDeadline() acquires the mutexes in the opposite order. So let's + // first grab all the wakeup callbacks, then release lock_, then call the + // callbacks. + std::vector<WaiterList::mapped_type> wakeup_calls; + + lock_.lock(); + now_updater(); // reset now_ + WaiterList::iterator iter; + while (((iter = waiters_.begin()) != waiters_.end()) && + (iter->first <= now_)) { + wakeup_calls.push_back(std::move(iter->second)); + waiters_.erase(iter); + } + lock_.unlock(); + + for (const auto& wakeup_call : wakeup_calls) { + wakeup_call->WakeUp(); + } + + return static_cast<int64_t>(wakeup_calls.size()); +} + +void SimulatedClock::SleepUntil(absl::Time wakeup_time) { + absl::Mutex mu; + absl::MutexLock lock(mu); + bool f = false; + AwaitWithDeadline(&mu, absl::Condition(&f), wakeup_time); +} + +bool SimulatedClock::AwaitWithDeadline(absl::Mutex* mu, + const absl::Condition& cond, + absl::Time deadline) { + mu->AssertReaderHeld(); + + // Evaluate cond outside our own lock to minimize contention. + const bool ready = cond.Eval(); + + lock_.lock(); + num_await_calls_++; + + // Return now if the deadline is already past, or if the condition is true. + // This avoids creating a WakeUpInfo that won't be deleted until an + // appropriate UpdateTime() call. + if (deadline <= now_ || ready) { + lock_.unlock(); + return ready; + } + + auto wakeup_info = std::make_shared<WakeUpInfo>(mu, cond); + waiters_.insert(std::make_pair(deadline, wakeup_info)); + + lock_.unlock(); + // SimulatedClock may be destroyed any time after this, so we can't + // acquire lock_ again. + + // Wait until either cond.Eval() becomes true, or the deadline has passed. + wakeup_info->AwaitConditionOrWakeUp(); + + // Cancel the wakeup call, or if it's already in progress, wait for it to + // finish, since we must ensure no one touches 'mu' or 'cond' after we return. + wakeup_info->CancelOrAwaitWakeUp(); + + return cond.Eval(); +} + +std::optional<absl::Time> SimulatedClock::GetEarliestWakeupTime() const { + absl::ReaderMutexLock l(lock_); + if (waiters_.empty()) { + return std::nullopt; + } + return waiters_.begin()->first; +} + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/time/simulated_clock.h b/absl/time/simulated_clock.h new file mode 100644 index 0000000..2225eee --- /dev/null +++ b/absl/time/simulated_clock.h
@@ -0,0 +1,108 @@ +// Copyright 2026 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: simulated_clock.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_TIME_SIMULATED_CLOCK_H_ +#define ABSL_TIME_SIMULATED_CLOCK_H_ + +#include <cstdint> +#include <map> +#include <memory> +#include <optional> + +#include "absl/base/config.h" +#include "absl/base/nullability.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/clock_interface.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// A simulated clock is a concrete Clock implementation that does not "tick" +// on its own. Time is advanced by explicit calls to the AdvanceTime() or +// SetTime() functions. +// +// Example: +// absl::SimulatedClock sim_clock; +// absl::Time now = sim_clock.TimeNow(); +// // now == absl::UnixEpoch() +// +// now = sim_clock.TimeNow(); +// // now == absl::UnixEpoch() (still) +// +// sim_clock.AdvanceTime(absl::Seconds(3)); +// now = sim_clock.TimeNow(); +// // now == absl::UnixEpoch() + absl::Seconds(3) +// +// This class is thread-safe. +class SimulatedClock : public Clock { + public: + explicit SimulatedClock(absl::Time t); + SimulatedClock() : SimulatedClock(absl::UnixEpoch()) {} + + // The destructor should be called only if all Sleep(), etc. and + // AdvanceTime() calls have completed. The code does its best to let + // any pending calls finish gracefully, but there are no guarantees. + ~SimulatedClock() override; + + // Returns the simulated time. + absl::Time TimeNow() override; + + // Sleeps until the specified duration has elapsed according to this clock. + void Sleep(absl::Duration d) override; + + // Sleeps until the specified wakeup_time. + void SleepUntil(absl::Time wakeup_time) override; + + // Sets the simulated time to the argument. Wakes up any threads whose + // sleeps have now expired. Returns the number of woken threads. + int64_t SetTime(absl::Time t); + + // Advances the simulated time by the specified duration. Wakes up any + // threads whose sleeps have now expired. Returns the number of woken threads. + int64_t AdvanceTime(absl::Duration d); + + // Blocks until the condition is true or until the simulated clock is + // advanced to or beyond the wakeup time (or both). + bool AwaitWithDeadline(absl::Mutex* absl_nonnull mu, + const absl::Condition& cond, + absl::Time deadline) override + ABSL_SHARED_LOCKS_REQUIRED(mu); + + // Returns the earliest wakeup time. + std::optional<absl::Time> GetEarliestWakeupTime() const; + + private: + template <class T> + int64_t UpdateTime(const T& now_updater) ABSL_LOCKS_EXCLUDED(lock_); + + class WakeUpInfo; + using WaiterList = std::multimap<absl::Time, std::shared_ptr<WakeUpInfo>>; + + mutable absl::Mutex lock_; + absl::Time now_ ABSL_GUARDED_BY(lock_); + WaiterList waiters_ ABSL_GUARDED_BY(lock_); + int64_t num_await_calls_ ABSL_GUARDED_BY(lock_) = 0; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TIME_SIMULATED_CLOCK_H_
diff --git a/absl/time/simulated_clock_test.cc b/absl/time/simulated_clock_test.cc new file mode 100644 index 0000000..4618338 --- /dev/null +++ b/absl/time/simulated_clock_test.cc
@@ -0,0 +1,614 @@ +// Copyright 2026 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/time/simulated_clock.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <thread> // NOLINT(build/c++11) +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/thread_annotations.h" +#include "absl/random/random.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/clock_interface.h" +#include "absl/time/time.h" + +namespace { + +constexpr absl::Duration kShortPause = absl::Milliseconds(20); +constexpr absl::Duration kLongPause = absl::Milliseconds(1000); + +#ifdef _MSC_VER +// As of 2026-01-29, multithreaded tests on MSVC are too flaky. +const char* kSkipFlakyReason = + "Skipping this timing test because it is too flaky"; +#else +const char* kSkipFlakyReason = nullptr; +#endif + +TEST(SimulatedClock, TimeInitializedToZero) { + absl::SimulatedClock simclock; + EXPECT_EQ(absl::UnixEpoch(), simclock.TimeNow()); +} + +TEST(SimulatedClock, NowSetTime) { + absl::SimulatedClock simclock; + absl::Time now = simclock.TimeNow(); + + now += absl::Seconds(123); + simclock.SetTime(now); + EXPECT_EQ(now, simclock.TimeNow()); + + now += absl::Seconds(123); + simclock.SetTime(now); + EXPECT_EQ(now, simclock.TimeNow()); + + now += absl::ZeroDuration(); + simclock.SetTime(now); + EXPECT_EQ(now, simclock.TimeNow()); +} + +TEST(SimulatedClock, NowAdvanceTime) { + absl::SimulatedClock simclock; + absl::Time now = simclock.TimeNow(); + + simclock.AdvanceTime(absl::Seconds(123)); + now += absl::Seconds(123); + EXPECT_EQ(now, simclock.TimeNow()); + + simclock.AdvanceTime(absl::Seconds(123)); + now += absl::Seconds(123); + EXPECT_EQ(now, simclock.TimeNow()); + + simclock.AdvanceTime(absl::ZeroDuration()); + now += absl::ZeroDuration(); + EXPECT_EQ(now, simclock.TimeNow()); +} + +void SleepAndNotify(absl::Clock* clock, absl::Duration sleep_secs, + absl::Notification* note) { + clock->Sleep(sleep_secs); + note->Notify(); +} + +TEST(SimulatedClock, Sleep_SetToSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); + + // wait for SleepAndNotify() to block + absl::SleepFor(kLongPause); + simclock.SetTime(absl::FromUnixSeconds(122)); + // give Sleep() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.SetTime(absl::FromUnixSeconds(122 + 1)); + // wait for Sleep() to return + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepAdvanceToSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); + // wait for SleepAndNotify() to block + absl::SleepFor(kLongPause); + simclock.AdvanceTime(absl::Seconds(122)); + // give Sleep() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.AdvanceTime(absl::Seconds(1)); + // wait for Sleep() to return + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepSetPastSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); + // wait for SleepAndNotify() to block + absl::SleepFor(kLongPause); + simclock.SetTime(absl::FromUnixSeconds(122)); + // give Sleep() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.SetTime(absl::FromUnixSeconds(122 + 2)); + // wait for Sleep() to return + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepAdvancePastSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + std::thread tr(SleepAndNotify, &simclock, absl::Seconds(123), ¬e); + // wait for SleepAndNotify() to block + absl::SleepFor(kLongPause); + simclock.AdvanceTime(absl::Seconds(122)); + // give Sleep() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.AdvanceTime(absl::Seconds(2)); + // wait for Sleep() to return + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepZeroSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + std::thread tr(SleepAndNotify, &simclock, absl::ZeroDuration(), ¬e); + // wait for SleepAndNotify() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +void SleepUntilAndNotify(absl::Clock* clock, absl::Time wakeup_time, + absl::Notification* note) { + clock->SleepUntil(wakeup_time); + note->Notify(); +} + +TEST(SimulatedClock, SleepUntilSetToSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + simclock.SetTime(absl::FromUnixSeconds(123)); + std::thread tr(SleepUntilAndNotify, &simclock, + absl::UnixEpoch() + absl::Seconds(246), ¬e); + // wait for SleepUntilAndNotify() to block + absl::SleepFor(kLongPause); + simclock.SetTime(absl::FromUnixSeconds(123 + 122)); + // give SleepUntil() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.SetTime(absl::FromUnixSeconds(123 + 122 + 1)); + absl::Time start = absl::Now(); + note.WaitForNotification(); // SleepUntilAndNotify() should ping note + EXPECT_GE(absl::Milliseconds(50), absl::Now() - start); + tr.join(); +} + +TEST(SimulatedClock, SleepUntilAdvanceToSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + simclock.AdvanceTime(absl::Seconds(123)); + std::thread tr(SleepUntilAndNotify, &simclock, + absl::UnixEpoch() + absl::Seconds(246), ¬e); + // wait for SleepUntilAndNotify() to block + absl::SleepFor(kLongPause); + simclock.AdvanceTime(absl::Seconds(122)); + // give SleepUntil() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.AdvanceTime(absl::Seconds(1)); + absl::Time start = absl::Now(); + note.WaitForNotification(); // SleepUntilAndNotify() should ping note + EXPECT_GE(absl::Milliseconds(70), absl::Now() - start); + tr.join(); +} + +TEST(SimulatedClock, SleepUntilSetPastSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + simclock.SetTime(absl::FromUnixSeconds(123)); + std::thread tr(SleepUntilAndNotify, &simclock, + absl::UnixEpoch() + absl::Seconds(246), ¬e); + // wait for SleepUntilAndNotify() to block + absl::SleepFor(kLongPause); + simclock.SetTime(absl::FromUnixSeconds(123 + 122)); + // give SleepUntil() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.SetTime(absl::FromUnixSeconds(123 + 122 + 2)); + // wait for SleepUntilAndNotify() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepUntilAdvancePastSleepTime) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + simclock.AdvanceTime(absl::Seconds(123)); + std::thread tr(SleepUntilAndNotify, &simclock, + absl::UnixEpoch() + absl::Seconds(246), ¬e); + // wait for SleepUntilAndNotify() to block + absl::SleepFor(kLongPause); + simclock.AdvanceTime(absl::Seconds(122)); + // give SleepUntil() the opportunity to fail + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.AdvanceTime(absl::Seconds(2)); + // wait for SleepUntilAndNotify() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, SleepUntilTimeAlreadyPassed) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Notification note; + + simclock.AdvanceTime(absl::Seconds(123)); + std::thread tr(SleepUntilAndNotify, &simclock, + absl::UnixEpoch() + absl::Seconds(123), ¬e); + // wait for SleepUntilAndNotify() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +void AwaitWithDeadlineAndNotify(absl::Clock* clock, absl::Mutex* mu, + absl::Condition* cond, absl::Time wakeup_time, + absl::Notification* note, bool* return_val) { + mu->lock_shared(); + *return_val = clock->AwaitWithDeadline(mu, *cond, wakeup_time); + mu->unlock_shared(); + note->Notify(); +} + +TEST(SimulatedClock, AwaitWithDeadlineConditionInitiallyTrue) { + absl::SimulatedClock simclock; + absl::Mutex mu; + bool f = true; + absl::Condition cond(&f); + absl::MutexLock lock(mu); + ASSERT_TRUE(simclock.AwaitWithDeadline(&mu, cond, absl::InfiniteFuture())); +} + +TEST(SimulatedClock, AwaitWithDeadlineConditionInitiallyFalse) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note; + bool return_val; + + std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, + absl::UnixEpoch() + absl::Seconds(123), ¬e, &return_val); + // wait for AwaitWithDeadline...() to block + absl::SleepFor(kShortPause); + EXPECT_FALSE(note.HasBeenNotified()); + mu.lock(); + f = true; + mu.unlock(); + // wait for AwaitWithDeadline...() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + EXPECT_TRUE(return_val); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, AwaitWithDeadlineDeadlinePassed) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note; + bool return_val; + + std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, + absl::UnixEpoch() + absl::Seconds(123), ¬e, &return_val); + // wait for AwaitWithDeadline...() to block + absl::SleepFor(kLongPause); + EXPECT_FALSE(note.HasBeenNotified()); + simclock.AdvanceTime(absl::Seconds(124)); + // wait for AwaitWithDeadline...() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + EXPECT_FALSE(return_val); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +TEST(SimulatedClock, AwaitWithDeadlineDeadlineAlreadyPassed) { + if (kSkipFlakyReason != nullptr) { + GTEST_SKIP() << kSkipFlakyReason; + } + + absl::SimulatedClock simclock; + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note; + bool return_val; + + std::thread tr(AwaitWithDeadlineAndNotify, &simclock, &mu, &cond, + absl::UnixEpoch(), ¬e, &return_val); + // wait for AwaitWithDeadline...() to ping note + absl::SleepFor(kLongPause); + EXPECT_TRUE(note.HasBeenNotified()); + EXPECT_FALSE(return_val); + note.WaitForNotification(); // in case the expectation fails + tr.join(); +} + +void RacerMakesConditionTrue(absl::Notification* start_note, absl::Mutex* mu, + bool* f, absl::BlockingCounter* threads_done) { + start_note->WaitForNotification(); + absl::SleepFor(absl::Milliseconds(1)); + mu->lock(); + *f = true; + mu->unlock(); + threads_done->DecrementCount(); +} + +void RacerAdvancesTime(absl::Notification* start_note, + absl::SimulatedClock* simclock, absl::Duration d, + absl::BlockingCounter* threads_done) { + start_note->WaitForNotification(); + absl::SleepFor(absl::Milliseconds(1)); + simclock->AdvanceTime(d); + threads_done->DecrementCount(); +} + +TEST(SimulatedClock, SimultaneousConditionTrueAndDeadline) { + absl::SimulatedClock simclock; + for (int iteration = 0; iteration < 100; ++iteration) { + auto mu = std::make_unique<absl::Mutex>(); + bool f = false; + absl::Condition cond(&f); + absl::Notification note_start; + absl::BlockingCounter threads_done(2); + std::thread tr1(RacerMakesConditionTrue, ¬e_start, mu.get(), &f, + &threads_done); + std::thread tr2(RacerAdvancesTime, ¬e_start, &simclock, + absl::Seconds(20), &threads_done); + note_start.Notify(); + mu->lock(); + absl::Time deadline = simclock.TimeNow() + absl::Seconds(10); + simclock.AwaitWithDeadline(mu.get(), cond, deadline); + EXPECT_TRUE(f || (simclock.TimeNow() >= deadline)); + if (f) { + // RacerMakesConditionTrue has unlocked mu and AwaitWithDeadline has + // returned, so it is safe to destruct mu. Do so while RacerAdvancesTime + // is possibly still running in an attempt to catch simclock holding on + // to a reference to mu and using it after AwaitWithDeadline returns. + mu->unlock(); + mu = nullptr; + } else { + mu->unlock(); + } + threads_done.Wait(); + tr1.join(); + tr2.join(); + } +} + +void RacerDeletesClock(absl::Mutex* mu, absl::Notification* start_note, + absl::Clock* clock, + absl::BlockingCounter* threads_done) { + start_note->WaitForNotification(); + // mu is acquired temporarily to make sure that AwaitWithDeadline() in + // SimultaneousConditionTrueAndDestruction has blocked. + mu->lock(); + mu->unlock(); + absl::SleepFor(absl::Milliseconds(1)); + delete clock; + threads_done->DecrementCount(); +} + +TEST(SimulatedClock, SimultaneousConditionTrueAndDestruction) { + for (int iteration = 0; iteration < 100; ++iteration) { + absl::Clock* clock = new absl::SimulatedClock(); + absl::Mutex mu; + bool f = false; + absl::Condition cond(&f); + absl::Notification note_start; + absl::BlockingCounter threads_done(2); + std::thread tr1([¬e_start, &mu, &f, &threads_done] { + RacerMakesConditionTrue(¬e_start, &mu, &f, &threads_done); + }); + std::thread tr2([&mu, ¬e_start, clock, &threads_done] { + RacerDeletesClock(&mu, ¬e_start, clock, &threads_done); + }); + mu.lock(); + note_start.Notify(); + absl::Time deadline = absl::UnixEpoch() + absl::Seconds(100000); + clock->AwaitWithDeadline(&mu, cond, deadline); + mu.unlock(); + threads_done.Wait(); + tr1.join(); + tr2.join(); + } +} + +class SimulatedClockTorturer { + public: + SimulatedClockTorturer(absl::SimulatedClock* simclock, int num_threads, + int num_iterations) + : simclock_(simclock), + num_threads_(num_threads), + num_iterations_(num_iterations), + num_flags_(2 * num_threads), + mutex_and_flag_(static_cast<size_t>(num_flags_)) {} + + // Implements a torture test. + // + // This method uses several groups of: + // SimulatedClock + // Mutex protected flag + // It starts several threads that call AwaitWithDeadline() and several + // threads that call AdvanceTime() or toggle flag values. + void DoTorture() { + // The threads calling AwaitWithDeadline() have a separate BlockingCounter + // than the threads calling AdvanceTime()/toggling flags, since the former + // would be deadlocked if all the threads that might unblock them had + // already finished. + absl::Notification go; + absl::BlockingCounter await_threads_done(num_threads_); + absl::Notification signal_threads_should_exit; + absl::BlockingCounter signal_threads_done(num_threads_); + std::vector<std::thread> trs; + for (int i = 0; i < num_threads_; ++i) { + trs.emplace_back(&SimulatedClockTorturer::AwaitRandomly, this, &go, + &await_threads_done); + } + for (int i = 0; i < num_threads_; ++i) { + trs.emplace_back(&SimulatedClockTorturer::SignalRandomly, this, &go, + &signal_threads_should_exit, &signal_threads_done); + } + go.Notify(); + await_threads_done.Wait(); + signal_threads_should_exit.Notify(); + signal_threads_done.Wait(); + for (auto& thread : trs) { + thread.join(); + } + } + + private: + // Randomly call AwaitWithDeadline() for num_iterations_ times. + void AwaitRandomly(absl::Notification* go, + absl::BlockingCounter* threads_done) { + go->WaitForNotification(); + + absl::BitGen gen; + for (int i = 0; i < num_iterations_; ++i) { + auto& [mu, f] = mutex_and_flag_[absl::Uniform<size_t>(gen, size_t{0}, + static_cast<size_t>(num_flags_))]; + absl::MutexLock lock(mu); + absl::Time deadline = simclock_->TimeNow() + absl::Seconds(1); + simclock_->AwaitWithDeadline(&mu, absl::Condition(&f), deadline); + ABSL_RAW_CHECK(f || simclock_->TimeNow() >= deadline, ""); + } + + threads_done->DecrementCount(); + } + + // Randomly call AdvanceTime() or toggle a flag value until notified to + // stop. + void SignalRandomly(absl::Notification* go, absl::Notification* should_exit, + absl::BlockingCounter* threads_done) { + go->WaitForNotification(); + + absl::BitGen gen; + while (!should_exit->HasBeenNotified()) { + int action = absl::Uniform<int>(gen, 0, num_flags_ + 1); + if (action < num_flags_) { + // Change a flag value. + auto& [mutex, flag] = mutex_and_flag_[static_cast<size_t>(action)]; + absl::MutexLock lock(mutex); + flag = !flag; + } else { + // Advance time. + simclock_->AdvanceTime(absl::Seconds(1)); + } + } + + threads_done->DecrementCount(); + } + + absl::SimulatedClock* simclock_; + int num_threads_; + int num_iterations_; + int num_flags_; + + struct MutexAndFlag { + absl::Mutex mutex; + bool flag ABSL_GUARDED_BY(mutex) = false; + }; + std::vector<MutexAndFlag> mutex_and_flag_; +}; + +TEST(SimulatedClock, Torture) { + absl::SimulatedClock simclock; + constexpr int kNumThreads = 10; + constexpr int kNumIterations = 1000; + SimulatedClockTorturer torturer(&simclock, kNumThreads, kNumIterations); + torturer.DoTorture(); +} + +} // namespace
diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh index 5ad9fcc..4c04c77 100755 --- a/ci/linux_arm_clang-latest_libcxx_bazel.sh +++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh
@@ -39,6 +39,11 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_ARM_CLANG_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -64,6 +69,11 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/llvm/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ + --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/aarch64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/aarch64-unknown-linux-gnu" \ + --env="CPLUS_INCLUDE_PATH=/opt/llvm/include/c++/v1:/opt/llvm/include/aarch64-unknown-linux-gnu/c++/v1/" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ --tmpfs=/abseil-cpp \ --workdir=/abseil-cpp \ @@ -77,17 +87,13 @@ cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1 fi /usr/local/bin/bazel test ... \ - --action_env=CC=clang-19 \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \ --copt=-Werror \ - --cxxopt=-std=${std} \ - --cxxopt=-stdlib=libc++ \ --define=\"absl=1\" \ --features=external_include_paths \ --keep_going \ - --linkopt=-stdlib=libc++ \ --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \
diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index 1200ef0..0c1ba32 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh
@@ -39,6 +39,11 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -67,6 +72,11 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/llvm/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ + --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/x86_64-unknown-linux-gnu" \ + --env="CPLUS_INCLUDE_PATH=/opt/llvm/include/c++/v1:/opt/llvm/include/x86_64-unknown-linux-gnu/c++/v1/" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ @@ -75,10 +85,6 @@ ${DOCKER_CONTAINER} \ /bin/bash --login -c " /usr/local/bin/bazel test ... \ - --action_env=\"CC=/opt/llvm/clang/bin/clang\" \ - --action_env=\"BAZEL_CXXOPTS=-std=${std}:-nostdinc++\" \ - --action_env=\"BAZEL_LINKOPTS=-L/opt/llvm/libcxx/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib\" \ - --action_env=\"CPLUS_INCLUDE_PATH=/opt/llvm/libcxx/include/c++/v1\" \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ --copt=\"-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG\" \ @@ -95,10 +101,10 @@ --linkopt=\"-fsanitize-link-c++-runtime\" \ --per_file_copt=external/.*@-w \ --show_timestamps \ - --test_env=\"ASAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer\" \ + --test_env=\"ASAN_SYMBOLIZER_PATH=/opt/llvm/bin/llvm-symbolizer\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \ --test_env=\"UBSAN_OPTIONS=print_stacktrace=1\" \ - --test_env=\"UBSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer\" \ + --test_env=\"UBSAN_SYMBOLIZER_PATH=/opt/llvm/bin/llvm-symbolizer\" \ --test_output=errors \ --test_tag_filters=\"-benchmark,-noasan\" \ ${BAZEL_EXTRA_ARGS:-}"
diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 74af10c..47a21ea 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh
@@ -39,6 +39,11 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -64,6 +69,11 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/llvm/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ + --env="BAZEL_LINKOPTS=-L/opt/llvm/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/lib/x86_64-unknown-linux-gnu" \ + --env="CPLUS_INCLUDE_PATH=/opt/llvm/include/c++/v1:/opt/llvm/include/x86_64-unknown-linux-gnu/c++/v1/" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ --tmpfs=/abseil-cpp \ --workdir=/abseil-cpp \ @@ -77,10 +87,6 @@ cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1 fi /usr/local/bin/bazel test ... \ - --action_env=CC=/opt/llvm/clang/bin/clang \ - --action_env=BAZEL_CXXOPTS=-std=${std}:-nostdinc++ \ - --action_env=BAZEL_LINKOPTS=-L/opt/llvm/libcxx/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib \ - --action_env=CPLUS_INCLUDE_PATH=/opt/llvm/libcxx/include/c++/v1 \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index 8634e8d..94f9c8a 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
@@ -39,6 +39,11 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -64,6 +69,11 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/llvm/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}:-nostdinc++" \ + --env="BAZEL_LINKOPTS=-L/opt/llvm-tsan/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm-tsan/lib/x86_64-unknown-linux-gnu" \ + --env="CPLUS_INCLUDE_PATH=/opt/llvm-tsan/include/c++/v1:/opt/llvm-tsan/include/x86_64-unknown-linux-gnu/c++/v1/" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ @@ -72,10 +82,6 @@ ${DOCKER_CONTAINER} \ /bin/bash --login -c " /usr/local/bin/bazel test ... \ - --action_env=\"CC=/opt/llvm/clang/bin/clang\" \ - --action_env=\"BAZEL_CXXOPTS=-std=${std}:-nostdinc++\" \ - --action_env=\"BAZEL_LINKOPTS=-L/opt/llvm/libcxx-tsan/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx-tsan/lib\" \ - --action_env=\"CPLUS_INCLUDE_PATH=/opt/llvm/libcxx-tsan/include/c++/v1\" \ --build_tag_filters=\"-notsan\" \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ @@ -89,7 +95,7 @@ --linkopt=\"-fsanitize=thread\" \ --per_file_copt=external/.*@-w \ --show_timestamps \ - --test_env=\"TSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer\" \ + --test_env=\"TSAN_SYMBOLIZER_PATH=/opt/llvm/bin/llvm-symbolizer\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \ --test_output=errors \ --test_tag_filters=\"-benchmark,-notsan\" \
diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 3175e41..6b9f0b4 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh
@@ -39,6 +39,12 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_CLANG_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /usr/local/bin/gcc -v +docker run "${DOCKER_CONTAINER}" /opt/llvm/bin/clang -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -64,6 +70,9 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/llvm/bin/clang" \ + --env="BAZEL_CXXOPTS=-std=${std}" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp,readonly \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ @@ -72,8 +81,6 @@ ${DOCKER_CONTAINER} \ /bin/bash --login -c " /usr/local/bin/bazel test ... \ - --action_env=\"CC=/opt/llvm/clang/bin/clang\" \ - --action_env=\"BAZEL_CXXOPTS=-std=${std}\" \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"--gcc-toolchain=/usr/local\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh index cb0904c..8cb17f7 100644 --- a/ci/linux_docker_containers.sh +++ b/ci/linux_docker_containers.sh
@@ -16,7 +16,7 @@ # Test scripts should source this file to get the identifiers. readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20230612" -readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250527" -readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20250430" -readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250527" -readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20250430" +readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131" +readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20260131" +readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131" +readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20260131"
diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh index 0ee412d..b1ca8ef 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh
@@ -37,7 +37,12 @@ fi source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" -readonly DOCKER_CONTAINER=${LINUX_GCC_FLOOR_CONTAINER} +readonly DOCKER_CONTAINER="${LINUX_GCC_FLOOR_CONTAINER}" + +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /opt/gcc-9/bin/gcc -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. @@ -64,6 +69,10 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/opt/gcc-9/bin/gcc" \ + --env="BAZEL_CXXOPTS=-std=${std}" \ + --env="BAZEL_LINKOPTS=-L/opt/gcc-9/lib64:-Wl,-rpath=/opt/gcc-9/lib64" \ --volume="${ABSEIL_ROOT}:/abseil-cpp:ro" \ --workdir=/abseil-cpp \ --cap-add=SYS_PTRACE \ @@ -72,8 +81,6 @@ ${DOCKER_CONTAINER} \ /bin/bash --login -c " /usr/local/bin/bazel test ... \ - --action_env=\"CC=/usr/local/bin/gcc\" \ - --action_env=\"BAZEL_CXXOPTS=-std=${std}\" \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index 6e9b5ec..86130f5 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh
@@ -39,6 +39,11 @@ source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER} +# Print information about the environment. +docker run "${DOCKER_CONTAINER}" /usr/bin/fastfetch -c ci +docker run "${DOCKER_CONTAINER}" /usr/local/bin/gcc -v +docker run "${DOCKER_CONTAINER}" cat /root/cached_bazel_versions + # USE_BAZEL_CACHE=1 only works on Kokoro. # Without access to the credentials this won't work. if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then @@ -64,6 +69,9 @@ for exceptions_mode in ${EXCEPTIONS_MODE}; do echo "--------------------------------------------------------------------" time docker run \ + --env="USE_BAZEL_VERSION=9.0.0" \ + --env="CC=/usr/local/bin/gcc" \ + --env="BAZEL_CXXOPTS=-std=${std}" \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ --tmpfs=/abseil-cpp \ --workdir=/abseil-cpp \ @@ -77,8 +85,6 @@ cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1 fi /usr/local/bin/bazel test ... \ - --action_env=CC=/usr/local/bin/gcc \ - --action_env=BAZEL_CXXOPTS=-std=${std} \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh index a09b405..d50f979 100755 --- a/ci/macos_xcode_bazel.sh +++ b/ci/macos_xcode_bazel.sh
@@ -19,15 +19,15 @@ set -euox pipefail -# Use Xcode 16.3 -sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer +# Use Xcode 26.2 +sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer if [[ -z ${ABSEIL_ROOT:-} ]]; then ABSEIL_ROOT="$(realpath $(dirname ${0})/..)" fi # If we are running on Kokoro, check for a versioned Bazel binary. -KOKORO_GFILE_BAZEL_BIN="bazel-8.2.1-darwin-x86_64" +KOKORO_GFILE_BAZEL_BIN="bazel-9.0.0-darwin-x86_64" if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}" chmod +x ${BAZEL_BIN}
diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index 37be939..7a6ccd5 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh
@@ -16,8 +16,8 @@ set -euox pipefail -# Use Xcode 16.3 -sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer +# Use Xcode 26.2 +sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer brew install cmake
diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat index 94d27aa..ed5bc5a 100755 --- a/ci/windows_clangcl_bazel.bat +++ b/ci/windows_clangcl_bazel.bat
@@ -47,7 +47,7 @@ :: /google/data/rw/teams/absl/kokoro/windows. :: :: TODO(absl-team): Remove -Wno-microsoft-cast -%KOKORO_GFILE_DIR%\bazel-8.2.1-windows-x86_64.exe ^ +%KOKORO_GFILE_DIR%\bazel-9.0.0-windows-x86_64.exe ^ test ... ^ --compilation_mode=%COMPILATION_MODE% ^ --compiler=clang-cl ^
diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat index 6318ff1..f07bbfd 100755 --- a/ci/windows_msvc_bazel.bat +++ b/ci/windows_msvc_bazel.bat
@@ -42,7 +42,7 @@ :: To upgrade Bazel, first download a new binary from :: https://github.com/bazelbuild/bazel/releases and copy it to :: /google/data/rw/teams/absl/kokoro/windows. -"%KOKORO_GFILE_DIR%\bazel-8.2.1-windows-x86_64.exe" ^ +"%KOKORO_GFILE_DIR%\bazel-9.0.0-windows-x86_64.exe" ^ test ... ^ --compilation_mode=%COMPILATION_MODE% ^ --copt=/WX ^