Skip to content

Commit affc2bd

Browse files
Abseil Teamcopybara-github
authored andcommitted
Make absl::FunctionRef support non-const callables, aligning it with std::function_ref from C++26
PiperOrigin-RevId: 807349765 Change-Id: I2a59d749818c5df669f6332f88bc1c9d59a2174d
1 parent 23b9b75 commit affc2bd

File tree

6 files changed

+186
-23
lines changed

6 files changed

+186
-23
lines changed

absl/functional/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ cc_library(
104104
linkopts = ABSL_DEFAULT_LINKOPTS,
105105
deps = [
106106
":any_invocable",
107+
"//absl/base:config",
107108
"//absl/base:core_headers",
108109
"//absl/meta:type_traits",
110+
"//absl/utility",
109111
],
110112
)
111113

@@ -117,8 +119,10 @@ cc_test(
117119
deps = [
118120
":any_invocable",
119121
":function_ref",
122+
"//absl/base:config",
120123
"//absl/container:test_instance_tracker",
121124
"//absl/memory",
125+
"//absl/utility",
122126
"@googletest//:gtest",
123127
"@googletest//:gtest_main",
124128
],

absl/functional/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ absl_cc_library(
8787
COPTS
8888
${ABSL_DEFAULT_COPTS}
8989
DEPS
90+
absl::config
9091
absl::core_headers
9192
absl::any_invocable
9293
absl::meta
94+
absl::utility
9395
PUBLIC
9496
)
9597

@@ -101,9 +103,11 @@ absl_cc_test(
101103
COPTS
102104
${ABSL_TEST_COPTS}
103105
DEPS
106+
absl::config
104107
absl::function_ref
105108
absl::memory
106109
absl::test_instance_tracker
110+
absl::utility
107111
GTest::gmock_main
108112
)
109113

absl/functional/function_ref.h

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@
4747
#define ABSL_FUNCTIONAL_FUNCTION_REF_H_
4848

4949
#include <cassert>
50-
#include <functional>
5150
#include <type_traits>
5251

5352
#include "absl/base/attributes.h"
53+
#include "absl/base/config.h"
5454
#include "absl/functional/internal/function_ref.h"
5555
#include "absl/meta/type_traits.h"
56+
#include "absl/utility/utility.h"
5657

5758
namespace absl {
5859
ABSL_NAMESPACE_BEGIN
@@ -89,15 +90,17 @@ class FunctionRef<R(Args...)> {
8990
// signature of this FunctionRef.
9091
template <typename F, typename FR = std::invoke_result_t<F, Args&&...>>
9192
using EnableIfCompatible =
92-
typename std::enable_if<std::is_void<R>::value ||
93-
std::is_convertible<FR, R>::value>::type;
93+
std::enable_if_t<std::conditional_t<std::is_void_v<R>, std::true_type,
94+
std::is_invocable_r<R, FR()>>::value>;
9495

9596
public:
9697
// Constructs a FunctionRef from any invocable type.
97-
template <typename F, typename = EnableIfCompatible<const F&>>
98-
// NOLINTNEXTLINE(runtime/explicit)
99-
FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND)
100-
: invoker_(&absl::functional_internal::InvokeObject<F, R, Args...>) {
98+
template <typename F,
99+
typename = EnableIfCompatible<std::enable_if_t<
100+
!std::is_same_v<FunctionRef, absl::remove_cvref_t<F>>, F&>>>
101+
// NOLINTNEXTLINE(google-explicit-constructor)
102+
FunctionRef(F&& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept
103+
: invoker_(&absl::functional_internal::InvokeObject<F&, R, Args...>) {
101104
absl::functional_internal::AssertNonNull(f);
102105
ptr_.obj = &f;
103106
}
@@ -111,14 +114,39 @@ class FunctionRef<R(Args...)> {
111114
template <
112115
typename F, typename = EnableIfCompatible<F*>,
113116
absl::functional_internal::EnableIf<absl::is_function<F>::value> = 0>
114-
FunctionRef(F* f) // NOLINT(runtime/explicit)
117+
// NOLINTNEXTLINE(google-explicit-constructor)
118+
FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept
115119
: invoker_(&absl::functional_internal::InvokeFunction<F*, R, Args...>) {
116120
assert(f != nullptr);
117121
ptr_.fun = reinterpret_cast<decltype(ptr_.fun)>(f);
118122
}
119123

120-
FunctionRef& operator=(const FunctionRef& rhs) = default;
121-
FunctionRef(const FunctionRef& rhs) = default;
124+
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
125+
// Similar to the other overloads, but passes the address of a known callable
126+
// `F` at compile time. This allows calling arbitrary functions while avoiding
127+
// an indirection.
128+
// Needs C++20 as `nontype_t` needs C++20 for `auto` template parameters.
129+
template <auto F>
130+
FunctionRef(nontype_t<F>) noexcept // NOLINT(google-explicit-constructor)
131+
: invoker_(&absl::functional_internal::InvokeFunction<decltype(F), F, R,
132+
Args...>) {}
133+
134+
template <auto F, typename Obj>
135+
// NOLINTNEXTLINE(google-explicit-constructor)
136+
FunctionRef(nontype_t<F>, Obj&& obj) noexcept
137+
: invoker_(&absl::functional_internal::InvokeObject<Obj&, decltype(F), F,
138+
R, Args...>) {
139+
ptr_.obj = std::addressof(obj);
140+
}
141+
142+
template <auto F, typename Obj>
143+
// NOLINTNEXTLINE(google-explicit-constructor)
144+
FunctionRef(nontype_t<F>, Obj* obj) noexcept
145+
: invoker_(&absl::functional_internal::InvokePtr<Obj, decltype(F), F, R,
146+
Args...>) {
147+
ptr_.obj = obj;
148+
}
149+
#endif
122150

123151
// Call the underlying object.
124152
R operator()(Args... args) const {
@@ -134,8 +162,39 @@ class FunctionRef<R(Args...)> {
134162
// constness anyway we can just make this a no-op.
135163
template <typename R, typename... Args>
136164
class FunctionRef<R(Args...) const> : public FunctionRef<R(Args...)> {
165+
using Base = FunctionRef<R(Args...)>;
166+
167+
template <typename F, typename T = void>
168+
using EnableIfCallable =
169+
std::enable_if_t<!std::is_same_v<FunctionRef, absl::remove_cvref_t<F>> &&
170+
std::is_invocable_r_v<R, F, Args...> &&
171+
std::is_constructible_v<Base, F>,
172+
T>;
173+
137174
public:
138-
using FunctionRef<R(Args...)>::FunctionRef;
175+
template <typename F, typename = EnableIfCallable<const F&>>
176+
// NOLINTNEXTLINE(google-explicit-constructor)
177+
FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {}
178+
179+
template <typename F,
180+
typename = std::enable_if_t<std::is_constructible_v<Base, F*>>>
181+
// NOLINTNEXTLINE(google-explicit-constructor)
182+
FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {}
183+
184+
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
185+
template <auto F, typename = EnableIfCallable<decltype(F)>>
186+
// NOLINTNEXTLINE(google-explicit-constructor)
187+
FunctionRef(nontype_t<F> arg) noexcept : Base(arg) {}
188+
189+
template <auto F, typename Obj, typename = EnableIfCallable<decltype(F)>>
190+
// NOLINTNEXTLINE(google-explicit-constructor)
191+
FunctionRef(nontype_t<F> arg, Obj&& obj) noexcept
192+
: Base(arg, std::forward<Obj>(obj)) {}
193+
194+
template <auto F, typename Obj, typename = EnableIfCallable<decltype(F)>>
195+
// NOLINTNEXTLINE(google-explicit-constructor)
196+
FunctionRef(nontype_t<F> arg, Obj* obj) noexcept : Base(arg, obj) {}
197+
#endif
139198
};
140199

141200
ABSL_NAMESPACE_END

absl/functional/function_ref_test.cc

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,31 @@
1616

1717
#include <functional>
1818
#include <memory>
19+
#include <type_traits>
20+
#include <utility>
1921

2022
#include "gmock/gmock.h"
2123
#include "gtest/gtest.h"
2224
#include "absl/container/internal/test_instance_tracker.h"
2325
#include "absl/functional/any_invocable.h"
2426
#include "absl/memory/memory.h"
27+
#include "absl/utility/utility.h"
2528

2629
namespace absl {
2730
ABSL_NAMESPACE_BEGIN
2831
namespace {
2932

30-
void RunFun(FunctionRef<void()> f) { f(); }
33+
int Function() { return 1337; }
3134

32-
TEST(FunctionRefTest, Lambda) {
33-
bool ran = false;
34-
RunFun([&] { ran = true; });
35-
EXPECT_TRUE(ran);
35+
template <typename T>
36+
T Dereference(const T* v) {
37+
return *v;
3638
}
3739

38-
int Function() { return 1337; }
40+
template <typename T>
41+
T Copy(const T& v) {
42+
return v;
43+
}
3944

4045
TEST(FunctionRefTest, Function1) {
4146
FunctionRef<int()> ref(&Function);
@@ -251,11 +256,11 @@ TEST(FunctionRef, PassByValueTypes) {
251256
std::is_same<Invoker<void, Trivial>, void (*)(VoidPtr, Trivial)>::value,
252257
"Small trivial types should be passed by value");
253258
static_assert(std::is_same<Invoker<void, LargeTrivial>,
254-
void (*)(VoidPtr, LargeTrivial &&)>::value,
259+
void (*)(VoidPtr, LargeTrivial&&)>::value,
255260
"Large trivial types should be passed by rvalue reference");
256261
static_assert(
257262
std::is_same<Invoker<void, CopyableMovableInstance>,
258-
void (*)(VoidPtr, CopyableMovableInstance &&)>::value,
263+
void (*)(VoidPtr, CopyableMovableInstance&&)>::value,
259264
"Types with copy/move ctor should be passed by rvalue reference");
260265

261266
// References are passed as references.
@@ -268,7 +273,7 @@ TEST(FunctionRef, PassByValueTypes) {
268273
"Reference types should be preserved");
269274
static_assert(
270275
std::is_same<Invoker<void, CopyableMovableInstance&&>,
271-
void (*)(VoidPtr, CopyableMovableInstance &&)>::value,
276+
void (*)(VoidPtr, CopyableMovableInstance&&)>::value,
272277
"Reference types should be preserved");
273278

274279
// Make sure the address of an object received by reference is the same as the
@@ -298,6 +303,61 @@ TEST(FunctionRef, ReferenceToIncompleteType) {
298303
ref(obj);
299304
}
300305

306+
TEST(FunctionRefTest, CorrectConstQualifiers) {
307+
struct S {
308+
int operator()() { return 42; }
309+
int operator()() const { return 1337; }
310+
};
311+
S s;
312+
EXPECT_EQ(42, FunctionRef<int()>(s)());
313+
EXPECT_EQ(1337, FunctionRef<int() const>(s)());
314+
EXPECT_EQ(1337, FunctionRef<int()>(std::as_const(s))());
315+
}
316+
317+
TEST(FunctionRefTest, Lambdas) {
318+
// Stateless lambdas implicitly convert to function pointers, so their
319+
// mutability is irrelevant.
320+
EXPECT_TRUE(FunctionRef<bool()>([]() /*const*/ { return true; })());
321+
EXPECT_TRUE(FunctionRef<bool()>([]() mutable { return true; })());
322+
EXPECT_TRUE(FunctionRef<bool() const>([]() /*const*/ { return true; })());
323+
#if defined(__clang__) || (ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L && \
324+
defined(_MSC_VER) && !defined(__EDG__))
325+
// MSVC has problems compiling the following code pre-C++20:
326+
// const auto f = []() mutable {};
327+
// f();
328+
// EDG's MSVC-compatible mode (which Visual C++ uses for Intellisense)
329+
// exhibits the bug in C++20 as well. So we don't support them.
330+
EXPECT_TRUE(FunctionRef<bool() const>([]() mutable { return true; })());
331+
#endif
332+
333+
// Stateful lambdas are not implicitly convertible to function pointers, so
334+
// a const stateful lambda is not mutably callable.
335+
EXPECT_TRUE(FunctionRef<bool()>([v = true]() /*const*/ { return v; })());
336+
EXPECT_TRUE(FunctionRef<bool()>([v = true]() mutable { return v; })());
337+
EXPECT_TRUE(
338+
FunctionRef<bool() const>([v = true]() /*const*/ { return v; })());
339+
const auto func = [v = true]() mutable { return v; };
340+
static_assert(
341+
!std::is_convertible_v<decltype(func), FunctionRef<bool() const>>);
342+
}
343+
344+
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
345+
TEST(FunctionRefTest, NonTypeParameter) {
346+
EXPECT_EQ(1337, FunctionRef<int()>(nontype<&Function>)());
347+
EXPECT_EQ(42, FunctionRef<int()>(nontype<&Copy<int>>, 42)());
348+
EXPECT_EQ(42, FunctionRef<int()>(nontype<&Dereference<int>>,
349+
&std::integral_constant<int, 42>::value)());
350+
}
351+
#endif
352+
353+
TEST(FunctionRefTest, OptionalArguments) {
354+
struct S {
355+
int operator()(int = 0) const { return 1337; }
356+
};
357+
S s;
358+
EXPECT_EQ(1337, FunctionRef<int()>(s)());
359+
}
360+
301361
} // namespace
302362
ABSL_NAMESPACE_END
303363
} // namespace absl

absl/functional/internal/function_ref.h

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,25 @@ using Invoker = R (*)(VoidPtr, typename ForwardT<Args>::type...);
7272
// static_cast<R> handles the case the return type is void.
7373
template <typename Obj, typename R, typename... Args>
7474
R InvokeObject(VoidPtr ptr, typename ForwardT<Args>::type... args) {
75-
auto o = static_cast<const Obj*>(ptr.obj);
76-
return static_cast<R>(std::invoke(*o, std::forward<Args>(args)...));
75+
using T = std::remove_reference_t<Obj>;
76+
return static_cast<R>(std::invoke(
77+
std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))),
78+
std::forward<typename ForwardT<Args>::type>(args)...));
79+
}
80+
81+
template <typename Obj, typename Fun, Fun F, typename R, typename... Args>
82+
R InvokeObject(VoidPtr ptr, typename ForwardT<Args>::type... args) {
83+
using T = std::remove_reference_t<Obj>;
84+
return static_cast<R>(
85+
F(std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))),
86+
std::forward<typename ForwardT<Args>::type>(args)...));
87+
}
88+
89+
template <typename T, typename Fun, Fun F, typename R, typename... Args>
90+
R InvokePtr(VoidPtr ptr, typename ForwardT<Args>::type... args) {
91+
return static_cast<R>(
92+
F(const_cast<T*>(static_cast<const T*>(ptr.obj)),
93+
std::forward<typename ForwardT<Args>::type>(args)...));
7794
}
7895

7996
template <typename Fun, typename R, typename... Args>
@@ -82,6 +99,12 @@ R InvokeFunction(VoidPtr ptr, typename ForwardT<Args>::type... args) {
8299
return static_cast<R>(std::invoke(f, std::forward<Args>(args)...));
83100
}
84101

102+
template <typename Fun, Fun F, typename R, typename... Args>
103+
R InvokeFunction(VoidPtr, typename ForwardT<Args>::type... args) {
104+
return static_cast<R>(
105+
F(std::forward<typename ForwardT<Args>::type>(args)...));
106+
}
107+
85108
template <typename Sig>
86109
void AssertNonNull(const std::function<Sig>& f) {
87110
assert(f != nullptr);
@@ -98,7 +121,7 @@ template <typename F>
98121
void AssertNonNull(const F&) {}
99122

100123
template <typename F, typename C>
101-
void AssertNonNull(F C::*f) {
124+
void AssertNonNull(F C::* f) {
102125
assert(f != nullptr);
103126
(void)f;
104127
}

absl/utility/utility.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ using std::make_index_sequence;
4949
using std::make_integer_sequence;
5050
using std::move;
5151

52+
#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
53+
// Backfill for std::nontype_t. An instance of this class can be provided as a
54+
// disambiguation tag to `absl::function_ref` to pass the address of a known
55+
// callable at compile time.
56+
// Requires C++20 due to `auto` template parameter.
57+
template <auto>
58+
struct nontype_t {
59+
explicit nontype_t() = default;
60+
};
61+
template <auto V>
62+
constexpr nontype_t<V> nontype{};
63+
#endif
64+
5265
ABSL_NAMESPACE_END
5366
} // namespace absl
5467

0 commit comments

Comments
 (0)