diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel index f9c58d4b..b7aa31f5 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel @@ -104,8 +104,10 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":any_invocable", + "//absl/base:config", "//absl/base:core_headers", "//absl/meta:type_traits", + "//absl/utility", ], ) @@ -117,8 +119,10 @@ cc_test( deps = [ ":any_invocable", ":function_ref", + "//absl/base:config", "//absl/container:test_instance_tracker", "//absl/memory", + "//absl/utility", "@googletest//:gtest", "@googletest//:gtest_main", ], diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index 34d285dc..07f3dc0b 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt @@ -87,9 +87,11 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::core_headers absl::any_invocable absl::meta + absl::utility PUBLIC ) @@ -101,9 +103,11 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::function_ref absl::memory absl::test_instance_tracker + absl::utility GTest::gmock_main ) diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h index f1d087a7..edf61de7 100644 --- a/absl/functional/function_ref.h +++ b/absl/functional/function_ref.h @@ -47,12 +47,13 @@ #define ABSL_FUNCTIONAL_FUNCTION_REF_H_ #include -#include #include #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/functional/internal/function_ref.h" #include "absl/meta/type_traits.h" +#include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -89,15 +90,17 @@ class FunctionRef { // signature of this FunctionRef. template > using EnableIfCompatible = - typename std::enable_if::value || - std::is_convertible::value>::type; + std::enable_if_t, std::true_type, + std::is_invocable_r>::value>; public: // Constructs a FunctionRef from any invocable type. - template > - // NOLINTNEXTLINE(runtime/explicit) - FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) - : invoker_(&absl::functional_internal::InvokeObject) { + template >, F&>>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F&& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : invoker_(&absl::functional_internal::InvokeObject) { absl::functional_internal::AssertNonNull(f); ptr_.obj = &f; } @@ -111,14 +114,39 @@ class FunctionRef { template < typename F, typename = EnableIfCompatible, absl::functional_internal::EnableIf::value> = 0> - FunctionRef(F* f) // NOLINT(runtime/explicit) + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : invoker_(&absl::functional_internal::InvokeFunction) { assert(f != nullptr); ptr_.fun = reinterpret_cast(f); } - FunctionRef& operator=(const FunctionRef& rhs) = default; - FunctionRef(const FunctionRef& rhs) = default; +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + // Similar to the other overloads, but passes the address of a known callable + // `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 + FunctionRef(nontype_t) noexcept // NOLINT(google-explicit-constructor) + : invoker_(&absl::functional_internal::InvokeFunction) {} + + template + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t, Obj&& obj) noexcept + : invoker_(&absl::functional_internal::InvokeObject) { + ptr_.obj = std::addressof(obj); + } + + template + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t, Obj* obj) noexcept + : invoker_(&absl::functional_internal::InvokePtr) { + ptr_.obj = obj; + } +#endif // Call the underlying object. R operator()(Args... args) const { @@ -134,8 +162,39 @@ class FunctionRef { // constness anyway we can just make this a no-op. template class FunctionRef : public FunctionRef { + using Base = FunctionRef; + + template + using EnableIfCallable = + std::enable_if_t> && + std::is_invocable_r_v && + std::is_constructible_v, + T>; + public: - using FunctionRef::FunctionRef; + template > + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {} + + template >> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + template > + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t arg) noexcept : Base(arg) {} + + template > + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t arg, Obj&& obj) noexcept + : Base(arg, std::forward(obj)) {} + + template > + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t arg, Obj* obj) noexcept : Base(arg, obj) {} +#endif }; ABSL_NAMESPACE_END diff --git a/absl/functional/function_ref_test.cc b/absl/functional/function_ref_test.cc index 98d11f72..c8ff0804 100644 --- a/absl/functional/function_ref_test.cc +++ b/absl/functional/function_ref_test.cc @@ -16,26 +16,31 @@ #include #include +#include +#include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/container/internal/test_instance_tracker.h" #include "absl/functional/any_invocable.h" #include "absl/memory/memory.h" +#include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace { -void RunFun(FunctionRef f) { f(); } +int Function() { return 1337; } -TEST(FunctionRefTest, Lambda) { - bool ran = false; - RunFun([&] { ran = true; }); - EXPECT_TRUE(ran); +template +T Dereference(const T* v) { + return *v; } -int Function() { return 1337; } +template +T Copy(const T& v) { + return v; +} TEST(FunctionRefTest, Function1) { FunctionRef ref(&Function); @@ -251,11 +256,11 @@ TEST(FunctionRef, PassByValueTypes) { std::is_same, void (*)(VoidPtr, Trivial)>::value, "Small trivial types should be passed by value"); static_assert(std::is_same, - void (*)(VoidPtr, LargeTrivial &&)>::value, + void (*)(VoidPtr, LargeTrivial&&)>::value, "Large trivial types should be passed by rvalue reference"); static_assert( std::is_same, - void (*)(VoidPtr, CopyableMovableInstance &&)>::value, + void (*)(VoidPtr, CopyableMovableInstance&&)>::value, "Types with copy/move ctor should be passed by rvalue reference"); // References are passed as references. @@ -268,7 +273,7 @@ TEST(FunctionRef, PassByValueTypes) { "Reference types should be preserved"); static_assert( std::is_same, - void (*)(VoidPtr, CopyableMovableInstance &&)>::value, + void (*)(VoidPtr, CopyableMovableInstance&&)>::value, "Reference types should be preserved"); // Make sure the address of an object received by reference is the same as the @@ -298,6 +303,61 @@ TEST(FunctionRef, ReferenceToIncompleteType) { ref(obj); } +TEST(FunctionRefTest, CorrectConstQualifiers) { + struct S { + int operator()() { return 42; } + int operator()() const { return 1337; } + }; + S s; + EXPECT_EQ(42, FunctionRef(s)()); + EXPECT_EQ(1337, FunctionRef(s)()); + EXPECT_EQ(1337, FunctionRef(std::as_const(s))()); +} + +TEST(FunctionRefTest, Lambdas) { + // Stateless lambdas implicitly convert to function pointers, so their + // mutability is irrelevant. + EXPECT_TRUE(FunctionRef([]() /*const*/ { return true; })()); + EXPECT_TRUE(FunctionRef([]() mutable { return true; })()); + EXPECT_TRUE(FunctionRef([]() /*const*/ { return true; })()); +#if defined(__clang__) || (ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L && \ + defined(_MSC_VER) && !defined(__EDG__)) + // MSVC has problems compiling the following code pre-C++20: + // const auto f = []() mutable {}; + // f(); + // EDG's MSVC-compatible mode (which Visual C++ uses for Intellisense) + // exhibits the bug in C++20 as well. So we don't support them. + EXPECT_TRUE(FunctionRef([]() mutable { return true; })()); +#endif + + // Stateful lambdas are not implicitly convertible to function pointers, so + // a const stateful lambda is not mutably callable. + EXPECT_TRUE(FunctionRef([v = true]() /*const*/ { return v; })()); + EXPECT_TRUE(FunctionRef([v = true]() mutable { return v; })()); + EXPECT_TRUE( + FunctionRef([v = true]() /*const*/ { return v; })()); + const auto func = [v = true]() mutable { return v; }; + static_assert( + !std::is_convertible_v>); +} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +TEST(FunctionRefTest, NonTypeParameter) { + EXPECT_EQ(1337, FunctionRef(nontype<&Function>)()); + EXPECT_EQ(42, FunctionRef(nontype<&Copy>, 42)()); + EXPECT_EQ(42, FunctionRef(nontype<&Dereference>, + &std::integral_constant::value)()); +} +#endif + +TEST(FunctionRefTest, OptionalArguments) { + struct S { + int operator()(int = 0) const { return 1337; } + }; + S s; + EXPECT_EQ(1337, FunctionRef(s)()); +} + } // namespace ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h index 27d45b88..0796364a 100644 --- a/absl/functional/internal/function_ref.h +++ b/absl/functional/internal/function_ref.h @@ -72,8 +72,25 @@ using Invoker = R (*)(VoidPtr, typename ForwardT::type...); // static_cast handles the case the return type is void. template R InvokeObject(VoidPtr ptr, typename ForwardT::type... args) { - auto o = static_cast(ptr.obj); - return static_cast(std::invoke(*o, std::forward(args)...)); + using T = std::remove_reference_t; + return static_cast(std::invoke( + std::forward(*const_cast(static_cast(ptr.obj))), + std::forward::type>(args)...)); +} + +template +R InvokeObject(VoidPtr ptr, typename ForwardT::type... args) { + using T = std::remove_reference_t; + return static_cast( + F(std::forward(*const_cast(static_cast(ptr.obj))), + std::forward::type>(args)...)); +} + +template +R InvokePtr(VoidPtr ptr, typename ForwardT::type... args) { + return static_cast( + F(const_cast(static_cast(ptr.obj)), + std::forward::type>(args)...)); } template @@ -82,6 +99,12 @@ R InvokeFunction(VoidPtr ptr, typename ForwardT::type... args) { return static_cast(std::invoke(f, std::forward(args)...)); } +template +R InvokeFunction(VoidPtr, typename ForwardT::type... args) { + return static_cast( + F(std::forward::type>(args)...)); +} + template void AssertNonNull(const std::function& f) { assert(f != nullptr); @@ -98,7 +121,7 @@ template void AssertNonNull(const F&) {} template -void AssertNonNull(F C::*f) { +void AssertNonNull(F C::* f) { assert(f != nullptr); (void)f; } diff --git a/absl/utility/utility.h b/absl/utility/utility.h index 4637b03d..4d72c31a 100644 --- a/absl/utility/utility.h +++ b/absl/utility/utility.h @@ -49,6 +49,19 @@ using std::make_index_sequence; using std::make_integer_sequence; using std::move; +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +// Backfill for std::nontype_t. An instance of this class can be provided as a +// disambiguation tag to `absl::function_ref` to pass the address of a known +// callable at compile time. +// Requires C++20 due to `auto` template parameter. +template +struct nontype_t { + explicit nontype_t() = default; +}; +template +constexpr nontype_t nontype{}; +#endif + ABSL_NAMESPACE_END } // namespace absl