diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index a003649..55bd054 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -25,6 +25,7 @@ "base/internal/low_level_alloc.cc" "base/internal/low_level_alloc.h" "base/internal/low_level_scheduling.h" + "base/internal/nullability_traits.h" "base/internal/per_thread_tls.h" "base/internal/poison.cc" "base/internal/poison.h"
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 2b380e6..c68883e 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel
@@ -95,6 +95,20 @@ ) cc_library( + name = "nullability_traits_internal", + hdrs = ["internal/nullability_traits.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":config", + ":nullability", + ], +) + +cc_library( name = "raw_logging_internal", srcs = ["internal/raw_logging.cc"], hdrs = ["internal/raw_logging.h"], @@ -643,6 +657,18 @@ ) cc_test( + name = "nullability_traits_test", + srcs = ["internal/nullability_traits_test.cc"], + deps = [ + ":config", + ":nullability", + ":nullability_traits_internal", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( name = "raw_logging_test", srcs = ["raw_logging_test.cc"], copts = ABSL_TEST_COPTS,
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 4eb1390..9dca989 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt
@@ -107,6 +107,34 @@ # Internal-only target, do not depend on directly. absl_cc_library( NAME + nullability_traits_internal + HDRS + "internal/nullability_traits.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::nullability + PUBLIC +) + +absl_cc_test( + NAME + nullability_traits_test + SRCS + "internal/nullability_traits_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::config + absl::nullability + absl::nullability_traits_internal + GTest::gtest_main +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME raw_logging_internal HDRS "internal/raw_logging.h"
diff --git a/absl/base/call_once_test.cc b/absl/base/call_once_test.cc index 11d26c4..630e7ce 100644 --- a/absl/base/call_once_test.cc +++ b/absl/base/call_once_test.cc
@@ -39,25 +39,25 @@ // Function to be called from absl::call_once. Waits for a notification. void WaitAndIncrement() { - counters_mu.Lock(); + counters_mu.lock(); ++call_once_invoke_count; - counters_mu.Unlock(); + counters_mu.unlock(); counters_mu.LockWhen(Condition(&done_blocking)); ++call_once_finished_count; - counters_mu.Unlock(); + counters_mu.unlock(); } void ThreadBody() { - counters_mu.Lock(); + counters_mu.lock(); ++running_thread_count; - counters_mu.Unlock(); + counters_mu.unlock(); absl::call_once(once, WaitAndIncrement); - counters_mu.Lock(); + counters_mu.lock(); ++call_once_return_count; - counters_mu.Unlock(); + counters_mu.unlock(); } // Returns true if all threads are set up for the test. @@ -89,17 +89,17 @@ // Allow WaitAndIncrement to finish executing. Once it does, the other // call_once waiters will be unblocked. done_blocking = true; - counters_mu.Unlock(); + counters_mu.unlock(); for (std::thread& thread : threads) { thread.join(); } - counters_mu.Lock(); + counters_mu.lock(); EXPECT_EQ(call_once_invoke_count, 1); EXPECT_EQ(call_once_finished_count, 1); EXPECT_EQ(call_once_return_count, 10); - counters_mu.Unlock(); + counters_mu.unlock(); } } // namespace
diff --git a/absl/base/internal/nullability_traits.h b/absl/base/internal/nullability_traits.h new file mode 100644 index 0000000..790ec90 --- /dev/null +++ b/absl/base/internal/nullability_traits.h
@@ -0,0 +1,71 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_ +#define ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_ + +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/base/nullability.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +// `value` is true if the type `T` is compatible with nullability annotations +// (is a raw pointer, a smart pointer, or marked with +// ABSL_NULLABILITY_COMPATIBLE). Prefer to use the higher-level +// `AddNonnullIfCompatible` if that is sufficient. +// +// NOTE: This should not be used to detect if the compiler is Clang (since +// Clang is the only compiler that supports nullability annotations). +#if defined(__clang__) && !defined(__OBJC__) && \ + ABSL_HAVE_FEATURE(nullability_on_classes) +template <class T, class = void> +struct IsNullabilityCompatibleType { + constexpr static bool value = false; +}; + +template <class T> +struct IsNullabilityCompatibleType<T, std::void_t<absl_nullable T>> { + constexpr static bool value = true; +}; +#else +// False when absl_nullable is a no-op (for non-Clang compilers or Objective-C.) +template <class T, class = void> +struct IsNullabilityCompatibleType { + constexpr static bool value = false; +}; +#endif + +// A trait to add `absl_nonnull` to a type if it is compatible with nullability +// annotations. +template <typename T, bool ShouldAdd = IsNullabilityCompatibleType<T>::value> +struct AddNonnullIfCompatible; + +template <typename T> +struct AddNonnullIfCompatible<T, false> { + using type = T; +}; +template <typename T> +struct AddNonnullIfCompatible<T, true> { + using type = absl_nonnull T; +}; + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_
diff --git a/absl/base/internal/nullability_traits_test.cc b/absl/base/internal/nullability_traits_test.cc new file mode 100644 index 0000000..2451239 --- /dev/null +++ b/absl/base/internal/nullability_traits_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/internal/nullability_traits.h" + +#include <memory> +#include <type_traits> + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { +namespace { + +struct NotSmartPtr { + int* x; +}; + +class ABSL_NULLABILITY_COMPATIBLE MySmartPtr : std::unique_ptr<int> {}; + +// The IsNullabilityCompatibleType trait value can only be true when we define +// `absl_nullable` (isn't a no-op). +#if defined(__clang__) && !defined(__OBJC__) && \ + ABSL_HAVE_FEATURE(nullability_on_classes) +#define EXPECT_TRUE_IF_SUPPORTED EXPECT_TRUE +#else +#define EXPECT_TRUE_IF_SUPPORTED EXPECT_FALSE +#endif + +TEST(NullabilityTraitsTest, IsNullabilityEligibleTypePrimitives) { + EXPECT_FALSE(IsNullabilityCompatibleType<int>::value); + EXPECT_FALSE(IsNullabilityCompatibleType<int*&>::value); + + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int**>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int* const>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<const int*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<void (*)(int)>::value); +} + +TEST(NullabilityTraitsTest, IsNullabilityCompatibleTypeAliases) { + using MyInt = int; + using MyIntPtr = int*; + EXPECT_FALSE(IsNullabilityCompatibleType<MyInt>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<MyIntPtr>::value); +} + +TEST(NullabilityTraitsTest, IsNullabilityCompatibleTypeSmartPointers) { + EXPECT_TRUE_IF_SUPPORTED( + IsNullabilityCompatibleType<std::unique_ptr<int>>::value); + EXPECT_TRUE_IF_SUPPORTED( + IsNullabilityCompatibleType<std::shared_ptr<int>>::value); + + EXPECT_FALSE(IsNullabilityCompatibleType<NotSmartPtr>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<NotSmartPtr*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<MySmartPtr>::value); +} + +#undef EXPECT_TRUE_IF_SUPPORTED + +TEST(NullabilityTraitsTest, AddNonnullIfCompatiblePassThroughPrimitives) { + EXPECT_TRUE((std::is_same_v<AddNonnullIfCompatible<int>::type, int>)); + EXPECT_TRUE((std::is_same_v<AddNonnullIfCompatible<int*>::type, int*>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<int* const>::type, int* const>)); +} + +TEST(NullabilityTraitsTest, AddNonnullIfCompatiblePassThroughSmartPointers) { + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<std::unique_ptr<int>>::type, + std::unique_ptr<int>>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<std::shared_ptr<int>>::type, + std::shared_ptr<int>>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<NotSmartPtr>::type, NotSmartPtr>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<MySmartPtr>::type, MySmartPtr>)); +} + +} // namespace +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index 875a36c..e750655 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -19,6 +19,7 @@ #include <cstddef> #include <cstdint> #include <cstring> +#include <tuple> #include <utility> #include "absl/base/attributes.h" @@ -154,21 +155,31 @@ namespace { +// Probes an array of control bits using a probe sequence, +// and returns the mask corresponding to the first group with a deleted or empty +// slot. +inline Group::NonIterableBitMaskType probe_till_first_non_full_group( + const ctrl_t* ctrl, probe_seq<Group::kWidth>& seq, + [[maybe_unused]] size_t capacity) { + while (true) { + GroupFullEmptyOrDeleted g{ctrl + seq.offset()}; + auto mask = g.MaskEmptyOrDeleted(); + if (mask) { + return mask; + } + seq.next(); + ABSL_SWISSTABLE_ASSERT(seq.index() <= capacity && "full table!"); + } +} + FindInfo find_first_non_full_from_h1(const ctrl_t* ctrl, size_t h1, size_t capacity) { auto seq = probe_h1(capacity, h1); if (IsEmptyOrDeleted(ctrl[seq.offset()])) { return {seq.offset(), /*probe_length=*/0}; } - while (true) { - GroupFullEmptyOrDeleted g{ctrl + seq.offset()}; - auto mask = g.MaskEmptyOrDeleted(); - if (mask) { - return {seq.offset(mask.LowestBitSet()), seq.index()}; - } - seq.next(); - ABSL_SWISSTABLE_ASSERT(seq.index() <= capacity && "full table!"); - } + auto mask = probe_till_first_non_full_group(ctrl, seq, capacity); + return {seq.offset(mask.LowestBitSet()), seq.index()}; } // Probes an array of control bits using a probe sequence derived from `hash`, @@ -183,6 +194,16 @@ common.capacity()); } +// Same as `find_first_non_full`, but returns the mask corresponding to the +// first group with a deleted or empty slot. +std::pair<FindInfo, Group::NonIterableBitMaskType> find_first_non_full_group( + const CommonFields& common, size_t hash) { + auto seq = probe(common, hash); + auto mask = + probe_till_first_non_full_group(common.control(), seq, common.capacity()); + return {{seq.offset(), seq.index()}, mask}; +} + // Whether a table fits in half a group. A half-group table fits entirely into a // probing group, i.e., has a capacity < `Group::kWidth`. // @@ -749,7 +770,7 @@ ABSL_SWISSTABLE_ASSERT(IsValidCapacity(new_capacity)); ABSL_SWISSTABLE_ASSERT(new_capacity > policy.soo_capacity()); - const size_t old_capacity = common.capacity(); + [[maybe_unused]] const size_t old_capacity = common.capacity(); [[maybe_unused]] ctrl_t* old_ctrl; [[maybe_unused]] void* old_slots; if constexpr (kMode == ResizeNonSooMode::kGuaranteedAllocated) { @@ -758,7 +779,7 @@ } const size_t slot_size = policy.slot_size; - const size_t slot_align = policy.slot_align; + [[maybe_unused]] const size_t slot_align = policy.slot_align; const bool has_infoz = infoz.IsSampled(); void* alloc = policy.get_char_alloc(common); @@ -1924,7 +1945,9 @@ namespace { size_t PrepareInsertLargeImpl(CommonFields& common, const PolicyFunctions& __restrict policy, - size_t hash, FindInfo target) { + size_t hash, + Group::NonIterableBitMaskType mask_empty, + FindInfo target_group) { ABSL_SWISSTABLE_ASSERT(!common.is_small()); const GrowthInfo growth_info = common.growth_info(); // When there are no deleted slots in the table @@ -1935,23 +1958,27 @@ } PrepareInsertCommon(common); common.growth_info().OverwriteEmptyAsFull(); - SetCtrl(common, target.offset, H2(hash), policy.slot_size); - common.infoz().RecordInsert(hash, target.probe_length); - return target.offset; + target_group.offset += mask_empty.LowestBitSet(); + target_group.offset &= common.capacity(); + SetCtrl(common, target_group.offset, H2(hash), policy.slot_size); + common.infoz().RecordInsert(hash, target_group.probe_length); + return target_group.offset; } } // namespace size_t PrepareInsertLarge(CommonFields& common, const PolicyFunctions& __restrict policy, size_t hash, - FindInfo target) { + Group::NonIterableBitMaskType mask_empty, + FindInfo target_group) { // NOLINTNEXTLINE(misc-static-assert) ABSL_SWISSTABLE_ASSERT(!SwisstableGenerationsEnabled()); - return PrepareInsertLargeImpl(common, policy, hash, target); + return PrepareInsertLargeImpl(common, policy, hash, mask_empty, target_group); } size_t PrepareInsertLargeGenerationsEnabled( CommonFields& common, const PolicyFunctions& policy, size_t hash, - FindInfo target, absl::FunctionRef<size_t(size_t)> recompute_hash) { + Group::NonIterableBitMaskType mask_empty, FindInfo target_group, + absl::FunctionRef<size_t(size_t)> recompute_hash) { // NOLINTNEXTLINE(misc-static-assert) ABSL_SWISSTABLE_ASSERT(SwisstableGenerationsEnabled()); if (common.should_rehash_for_bug_detection_on_insert()) { @@ -1960,9 +1987,10 @@ ResizeAllocatedTableWithSeedChange( common, policy, common.growth_left() > 0 ? cap : NextCapacity(cap)); hash = recompute_hash(common.seed().seed()); - target = find_first_non_full(common, hash); + std::tie(target_group, mask_empty) = + find_first_non_full_group(common, hash); } - return PrepareInsertLargeImpl(common, policy, hash, target); + return PrepareInsertLargeImpl(common, policy, hash, mask_empty, target_group); } namespace {
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 9f62700..f3e2065 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -1728,29 +1728,35 @@ // empty class cases. void* GetRefForEmptyClass(CommonFields& common); -// Given the hash of a value not currently in the table and the first empty -// slot in the probe sequence, finds a viable slot index to insert it at. +// Given the hash of a value not currently in the table and the first group with +// an empty slot in the probe sequence, finds a viable slot index to insert it +// at. // // In case there's no space left, the table can be resized or rehashed // (for tables with deleted slots, see FindInsertPositionWithGrowthOrRehash). // // In the case of absence of deleted slots and positive growth_left, the element -// can be inserted in the provided `target` position. +// can be inserted in one of the empty slots in the provided `target_group`. // // When the table has deleted slots (according to GrowthInfo), the target // position will be searched one more time using `find_first_non_full`. // // REQUIRES: `!common.is_small()`. // REQUIRES: At least one non-full slot available. -// REQUIRES: `target` is a valid empty position to insert. +// REQUIRES: `mask_empty` is a mask containing empty slots for the +// `target_group`. +// REQUIRES: `target_group` is a starting position for the group that has +// at least one empty slot. size_t PrepareInsertLarge(CommonFields& common, const PolicyFunctions& policy, - size_t hash, FindInfo target); + size_t hash, Group::NonIterableBitMaskType mask_empty, + FindInfo target_group); // Same as above, but with generations enabled, we may end up changing the seed, // which means we need to be able to recompute the hash. size_t PrepareInsertLargeGenerationsEnabled( CommonFields& common, const PolicyFunctions& policy, size_t hash, - FindInfo target, absl::FunctionRef<size_t(size_t)> recompute_hash); + Group::NonIterableBitMaskType mask_empty, FindInfo target_group, + absl::FunctionRef<size_t(size_t)> recompute_hash); // A SwissTable. // @@ -3203,14 +3209,15 @@ } auto mask_empty = g.MaskEmpty(); if (ABSL_PREDICT_TRUE(mask_empty)) { - size_t target = seq.offset(mask_empty.LowestBitSet()); + size_t target_group_offset = seq.offset(); index = SwisstableGenerationsEnabled() ? PrepareInsertLargeGenerationsEnabled( - common(), GetPolicyFunctions(), hash, - FindInfo{target, seq.index()}, + common(), GetPolicyFunctions(), hash, mask_empty, + FindInfo{target_group_offset, seq.index()}, HashKey<hasher, K, kIsDefaultHash>{hash_ref(), key}) - : PrepareInsertLarge(common(), GetPolicyFunctions(), hash, - FindInfo{target, seq.index()}); + : PrepareInsertLarge( + common(), GetPolicyFunctions(), hash, mask_empty, + FindInfo{target_group_offset, seq.index()}); inserted = true; return; }
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index ed7cc49..7cc053e 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h index edf61de..10244b8 100644 --- a/absl/functional/function_ref.h +++ b/absl/functional/function_ref.h
@@ -85,13 +85,12 @@ // callback); template <typename R, typename... Args> class FunctionRef<R(Args...)> { - private: + protected: // Used to disable constructors for objects that are not compatible with the // signature of this FunctionRef. - template <typename F, typename FR = std::invoke_result_t<F, Args&&...>> + template <typename F, typename... U> using EnableIfCompatible = - std::enable_if_t<std::conditional_t<std::is_void_v<R>, std::true_type, - std::is_invocable_r<R, FR()>>::value>; + std::enable_if_t<std::is_invocable_r<R, F, U..., Args...>::value>; public: // Constructs a FunctionRef from any invocable type. @@ -111,9 +110,8 @@ // // This overload is also used for references to functions, since references to // functions can decay to function pointers implicitly. - template < - typename F, typename = EnableIfCompatible<F*>, - absl::functional_internal::EnableIf<absl::is_function<F>::value> = 0> + template <typename F, typename = EnableIfCompatible<F*>, + absl::functional_internal::EnableIf<std::is_function_v<F>> = 0> // NOLINTNEXTLINE(google-explicit-constructor) FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : invoker_(&absl::functional_internal::InvokeFunction<F*, R, Args...>) { @@ -126,22 +124,27 @@ // `F` at compile time. This allows calling arbitrary functions while avoiding // an indirection. // Needs C++20 as `nontype_t` needs C++20 for `auto` template parameters. - template <auto F> + template <auto F, typename = EnableIfCompatible<decltype(F)>> FunctionRef(nontype_t<F>) noexcept // NOLINT(google-explicit-constructor) : invoker_(&absl::functional_internal::InvokeFunction<decltype(F), F, R, Args...>) {} - template <auto F, typename Obj> + template < + auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), std::remove_reference_t<Obj>&>, + absl::functional_internal::EnableIf<!std::is_rvalue_reference_v<Obj&&>> = + 0> // NOLINTNEXTLINE(google-explicit-constructor) - FunctionRef(nontype_t<F>, Obj&& obj) noexcept + FunctionRef(nontype_t<F>, Obj&& obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : invoker_(&absl::functional_internal::InvokeObject<Obj&, decltype(F), F, R, Args...>) { ptr_.obj = std::addressof(obj); } - template <auto F, typename Obj> + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), Obj*>> // NOLINTNEXTLINE(google-explicit-constructor) - FunctionRef(nontype_t<F>, Obj* obj) noexcept + FunctionRef(nontype_t<F>, Obj* obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : invoker_(&absl::functional_internal::InvokePtr<Obj, decltype(F), F, R, Args...>) { ptr_.obj = obj; @@ -161,42 +164,72 @@ // Allow const qualified function signatures. Since FunctionRef requires // constness anyway we can just make this a no-op. template <typename R, typename... Args> -class FunctionRef<R(Args...) const> : public FunctionRef<R(Args...)> { +class FunctionRef<R(Args...) const> : private FunctionRef<R(Args...)> { using Base = FunctionRef<R(Args...)>; - template <typename F, typename T = void> - using EnableIfCallable = - std::enable_if_t<!std::is_same_v<FunctionRef, absl::remove_cvref_t<F>> && - std::is_invocable_r_v<R, F, Args...> && - std::is_constructible_v<Base, F>, - T>; + template <typename F, typename... U> + using EnableIfCompatible = + typename Base::template EnableIfCompatible<F, U...>; public: - template <typename F, typename = EnableIfCallable<const F&>> + template < + typename F, + typename = EnableIfCompatible<std::enable_if_t< + !std::is_same_v<FunctionRef, absl::remove_cvref_t<F>>, const F&>>> // NOLINTNEXTLINE(google-explicit-constructor) FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {} - template <typename F, - typename = std::enable_if_t<std::is_constructible_v<Base, F*>>> + template <typename F, typename = EnableIfCompatible<F*>, + absl::functional_internal::EnableIf<std::is_function_v<F>> = 0> // NOLINTNEXTLINE(google-explicit-constructor) FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {} #if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - template <auto F, typename = EnableIfCallable<decltype(F)>> + template <auto F, typename = EnableIfCompatible<decltype(F)>> // NOLINTNEXTLINE(google-explicit-constructor) FunctionRef(nontype_t<F> arg) noexcept : Base(arg) {} - template <auto F, typename Obj, typename = EnableIfCallable<decltype(F)>> + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), + const std::remove_reference_t<Obj>&>, + absl::functional_internal::EnableIf< + !std::is_rvalue_reference_v<Obj&&>> = 0> // NOLINTNEXTLINE(google-explicit-constructor) - FunctionRef(nontype_t<F> arg, Obj&& obj) noexcept + FunctionRef(nontype_t<F> arg, + Obj&& obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(arg, std::forward<Obj>(obj)) {} - template <auto F, typename Obj, typename = EnableIfCallable<decltype(F)>> + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), const Obj*>> // NOLINTNEXTLINE(google-explicit-constructor) - FunctionRef(nontype_t<F> arg, Obj* obj) noexcept : Base(arg, obj) {} + FunctionRef(nontype_t<F> arg, + const Obj* obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : Base(arg, obj) {} #endif + + using Base::operator(); }; +template <class F> +FunctionRef(F*) -> FunctionRef<F>; + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +template <auto Func> +FunctionRef(nontype_t<Func>) + -> FunctionRef<std::remove_pointer_t<decltype(Func)>>; + +template <class M, class T, M T::* Func, class U> +FunctionRef(nontype_t<Func>, U&&) + -> FunctionRef<std::enable_if_t<std::is_member_pointer_v<M T::*>, M>>; + +template <class M, class T, M T::* Func, class U> +FunctionRef(nontype_t<Func>, U&&) -> FunctionRef<std::enable_if_t< + std::is_object_v<M>, std::invoke_result_t<decltype(Func), U&>()>>; + +template <class R, class T, class... Args, R (*Func)(T, Args...), class U> +FunctionRef(nontype_t<Func>, U&&) -> FunctionRef<R(Args...)>; +#endif + ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/functional/function_ref_test.cc b/absl/functional/function_ref_test.cc index c8ff080..26db103 100644 --- a/absl/functional/function_ref_test.cc +++ b/absl/functional/function_ref_test.cc
@@ -30,6 +30,11 @@ ABSL_NAMESPACE_BEGIN namespace { +struct Class { + int Func() { return 42; } + int CFunc() const { return 43; } +}; + int Function() { return 1337; } template <typename T> @@ -312,6 +317,7 @@ EXPECT_EQ(42, FunctionRef<int()>(s)()); EXPECT_EQ(1337, FunctionRef<int() const>(s)()); EXPECT_EQ(1337, FunctionRef<int()>(std::as_const(s))()); + EXPECT_EQ(1337, FunctionRef<int() const>(std::as_const(s))()); } TEST(FunctionRefTest, Lambdas) { @@ -342,11 +348,46 @@ } #if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L -TEST(FunctionRefTest, NonTypeParameter) { - EXPECT_EQ(1337, FunctionRef<int()>(nontype<&Function>)()); - EXPECT_EQ(42, FunctionRef<int()>(nontype<&Copy<int>>, 42)()); - EXPECT_EQ(42, FunctionRef<int()>(nontype<&Dereference<int>>, - &std::integral_constant<int, 42>::value)()); +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::Func>, + std::declval<Class&>())), + FunctionRef<int()>>); +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::CFunc>, + std::declval<Class&>())), + FunctionRef<int() const>>); + +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::Func>, + std::declval<Class*>())), + FunctionRef<int()>>); +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::CFunc>, + std::declval<Class*>())), + FunctionRef<int() const>>); + +TEST(FunctionRefTest, NonTypeParameterWithTemporaries) { + static_assert(!std::is_constructible_v<FunctionRef<int()>, + nontype_t<&Class::Func>, Class&&>); + static_assert( + !std::is_constructible_v<FunctionRef<int()>, nontype_t<&Class::Func>, + const Class&&>); + static_assert(!std::is_constructible_v<FunctionRef<int() const>, + nontype_t<&Class::CFunc>, Class&&>); + static_assert( + !std::is_constructible_v<FunctionRef<int() const>, + nontype_t<&Class::CFunc>, const Class&&>); +} + +TEST(FunctionRefTest, NonTypeParameterWithDeductionGuides) { + EXPECT_EQ(1337, FunctionRef(nontype<&Function>)()); + EXPECT_EQ(42, FunctionRef(nontype<&Copy<int>>, + std::integral_constant<int, 42>::value)()); + EXPECT_EQ(42, FunctionRef(nontype<&Dereference<int>>, + &std::integral_constant<int, 42>::value)()); + + Class c; + EXPECT_EQ(42, FunctionRef<int()>(nontype<&Class::Func>, c)()); + EXPECT_EQ(43, FunctionRef<int() const>(nontype<&Class::CFunc>, c)()); + + EXPECT_EQ(42, FunctionRef<int()>(nontype<&Class::Func>, &c)()); + EXPECT_EQ(43, FunctionRef<int() const>(nontype<&Class::CFunc>, &c)()); } #endif
diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h index 0796364..543b30f 100644 --- a/absl/functional/internal/function_ref.h +++ b/absl/functional/internal/function_ref.h
@@ -81,22 +81,37 @@ template <typename Obj, typename Fun, Fun F, typename R, typename... Args> R InvokeObject(VoidPtr ptr, typename ForwardT<Args>::type... args) { using T = std::remove_reference_t<Obj>; - return static_cast<R>( - F(std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))), + Obj&& obj = + std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))); + // Avoid std::invoke() since the callee is a known function at compile time + if constexpr (std::is_member_function_pointer_v<Fun>) { + return static_cast<R>((std::forward<Obj>(obj).*F)( std::forward<typename ForwardT<Args>::type>(args)...)); + } else { + return static_cast<R>( + F(std::forward<Obj>(obj), + std::forward<typename ForwardT<Args>::type>(args)...)); + } } template <typename T, typename Fun, Fun F, typename R, typename... Args> R InvokePtr(VoidPtr ptr, typename ForwardT<Args>::type... args) { - return static_cast<R>( - F(const_cast<T*>(static_cast<const T*>(ptr.obj)), - std::forward<typename ForwardT<Args>::type>(args)...)); + T* obj = const_cast<T*>(static_cast<const T*>(ptr.obj)); + // Avoid std::invoke() since the callee is a known function at compile time + if constexpr (std::is_member_function_pointer_v<Fun>) { + return static_cast<R>( + (obj->*F)(std::forward<typename ForwardT<Args>::type>(args)...)); + } else { + return static_cast<R>( + F(obj, std::forward<typename ForwardT<Args>::type>(args)...)); + } } template <typename Fun, typename R, typename... Args> R InvokeFunction(VoidPtr ptr, typename ForwardT<Args>::type... args) { auto f = reinterpret_cast<Fun>(ptr.fun); - return static_cast<R>(std::invoke(f, std::forward<Args>(args)...)); + return static_cast<R>( + std::invoke(f, std::forward<typename ForwardT<Args>::type>(args)...)); } template <typename Fun, Fun F, typename R, typename... Args>
diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index 3e965ab..5bc7150 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel
@@ -80,6 +80,8 @@ ":log", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", + "//absl/base:nullability_traits_internal", "//absl/strings", ], )
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index c972c17..51f399e 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt
@@ -466,6 +466,8 @@ absl::config absl::core_headers absl::log + absl::nullability + absl::nullability_traits_internal absl::strings PUBLIC )
diff --git a/absl/log/die_if_null.cc b/absl/log/die_if_null.cc index 19c6a28..0d0b78e 100644 --- a/absl/log/die_if_null.cc +++ b/absl/log/die_if_null.cc
@@ -15,6 +15,7 @@ #include "absl/log/die_if_null.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/log/log.h" #include "absl/strings/str_cat.h" @@ -22,7 +23,8 @@ ABSL_NAMESPACE_BEGIN namespace log_internal { -void DieBecauseNull(const char* file, int line, const char* exprtext) { +void DieBecauseNull(const char* absl_nonnull file, int line, + const char* absl_nonnull exprtext) { LOG(FATAL).AtLocation(file, line) << absl::StrCat("Check failed: '", exprtext, "' Must be non-null"); }
diff --git a/absl/log/die_if_null.h b/absl/log/die_if_null.h index 8597976..ac7dbe6 100644 --- a/absl/log/die_if_null.h +++ b/absl/log/die_if_null.h
@@ -23,10 +23,13 @@ #include <stdint.h> +#include <type_traits> #include <utility> #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/nullability_traits.h" +#include "absl/base/nullability.h" #include "absl/base/optimization.h" // ABSL_DIE_IF_NULL() @@ -56,12 +59,30 @@ // generates less code than its implementation would if inlined, for a slight // code size reduction each time `ABSL_DIE_IF_NULL` is called. [[noreturn]] ABSL_ATTRIBUTE_NOINLINE void DieBecauseNull( - const char* file, int line, const char* exprtext); + const char* absl_nonnull file, int line, const char* absl_nonnull exprtext); // Helper for `ABSL_DIE_IF_NULL`. + +// Since we use `remove_reference_t` before `AddNonnullIfCompatible`, we need +// to explicitly have overloads for both lvalue reference and rvalue reference +// arguments and returns. template <typename T> -[[nodiscard]] T DieIfNull(const char* file, int line, const char* exprtext, - T&& t) { +[[nodiscard]] typename absl::base_internal::AddNonnullIfCompatible< + std::remove_reference_t<T>>::type& +DieIfNull(const char* absl_nonnull file, int line, + const char* absl_nonnull exprtext, T& t) { + if (ABSL_PREDICT_FALSE(t == nullptr)) { + // Call a non-inline helper function for a small code size improvement. + DieBecauseNull(file, line, exprtext); + } + return t; +} + +template <typename T> +[[nodiscard]] typename absl::base_internal::AddNonnullIfCompatible< + std::remove_reference_t<T>>::type&& +DieIfNull(const char* absl_nonnull file, int line, + const char* absl_nonnull exprtext, T&& t) { if (ABSL_PREDICT_FALSE(t == nullptr)) { // Call a non-inline helper function for a small code size improvement. DieBecauseNull(file, line, exprtext);
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index 3aed3a2..52ab3d9 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc
@@ -150,7 +150,7 @@ } // namespace struct LogMessage::LogMessageData final { - LogMessageData(const char* absl_nonnull file, int line, + LogMessageData(absl::string_view file, int line, absl::LogSeverity severity, absl::Time timestamp); LogMessageData(const LogMessageData&) = delete; LogMessageData& operator=(const LogMessageData&) = delete; @@ -202,7 +202,7 @@ void FinalizeEncodingAndFormat(); }; -LogMessage::LogMessageData::LogMessageData(const char* absl_nonnull file, +LogMessage::LogMessageData::LogMessageData(absl::string_view file, int line, absl::LogSeverity severity, absl::Time timestamp) : extra_sinks_only(false), manipulated(nullptr) { @@ -275,6 +275,9 @@ LogMessage::LogMessage(const char* absl_nonnull file, int line, absl::LogSeverity severity) + : LogMessage(absl::string_view(file), line, severity) {} +LogMessage::LogMessage(absl::string_view file, int line, + absl::LogSeverity severity) : data_(absl::make_unique<LogMessageData>(file, line, severity, absl::Now())) { data_->first_fatal = false;
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index 1aaf05e..5b6eed3 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h
@@ -64,9 +64,13 @@ struct WarningTag {}; struct ErrorTag {}; - // Used for `LOG`. + // Used for `LOG`. Taking `const char *` instead of `string_view` keeps + // callsites a little bit smaller at the cost of doing `strlen` at runtime. LogMessage(const char* absl_nonnull file, int line, absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; + // Used for FFI integrations that don't have a NUL-terminated string. + LogMessage(absl::string_view file, int line, + absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; // These constructors are slightly smaller/faster to call; the severity is // curried into the function pointer. LogMessage(const char* absl_nonnull file, int line,