diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index f7d6b56d..63712987 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -153,9 +153,11 @@ set(ABSL_INTERNAL_DLL_FILES "debugging/symbolize.cc" "debugging/symbolize.h" "functional/any_invocable.h" + "functional/bind_back.h" "functional/bind_front.h" "functional/function_ref.h" "functional/internal/any_invocable.h" + "functional/internal/back_binder.h" "functional/internal/front_binder.h" "functional/internal/function_ref.h" "functional/overload.h" diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel index dcf808bc..5e855235 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel @@ -70,6 +70,32 @@ cc_test( ], ) +cc_library( + name = "bind_back", + srcs = ["internal/back_binder.h"], + hdrs = ["bind_back.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/container:compressed_tuple", + "//absl/utility", + ], +) + +cc_test( + name = "bind_back_test", + srcs = ["bind_back_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":bind_back", + "//absl/memory", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_library( name = "bind_front", srcs = ["internal/front_binder.h"], diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index 4ffb513e..362c1497 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt @@ -50,6 +50,35 @@ absl_cc_test( GTest::gmock_main ) +absl_cc_library( + NAME + bind_back + SRCS + "internal/back_binder.h" + HDRS + "bind_back.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::compressed_tuple + absl::config + absl::utility + PUBLIC +) + +absl_cc_test( + NAME + bind_back_test + SRCS + "bind_back_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::bind_back + absl::memory + GTest::gmock_main +) + absl_cc_library( NAME bind_front diff --git a/absl/functional/bind_back.h b/absl/functional/bind_back.h new file mode 100644 index 00000000..5d81ad23 --- /dev/null +++ b/absl/functional/bind_back.h @@ -0,0 +1,79 @@ +// 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: bind_back.h +// ----------------------------------------------------------------------------- +// +// `absl::bind_back()` returns a functor by binding a number of arguments to +// the back of a provided (usually more generic) functor. Unlike `std::bind`, +// it does not require the use of argument placeholders. The simpler syntax of +// `absl::bind_back()` allows you to avoid known misuses with `std::bind()`. +// +// `absl::bind_back()` is meant as a drop-in replacement for C++23's +// `std::bind_back()`, which similarly resolves these issues with +// `std::bind()`. Both `bind_back()` alternatives, unlike `std::bind()`, allow +// partial function application. (See +// https://en.wikipedia.org/wiki/Partial_application). + +#ifndef ABSL_FUNCTIONAL_BIND_BACK_H_ +#define ABSL_FUNCTIONAL_BIND_BACK_H_ + +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +#if defined(__cpp_lib_bind_back) && __cpp_lib_bind_back >= 202202L +#include // For std::bind_back. +#endif + +#include + +#include "absl/base/config.h" +#include "absl/functional/internal/back_binder.h" +#include "absl/utility/utility.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// bind_back() +// +// Binds the last N arguments of an invocable object and stores them by value. +// +// Like `std::bind()`, `absl::bind_back()` is implicitly convertible to +// `std::function`. In particular, it may be used as a simpler replacement for +// `std::bind()` in most cases, as it does not require placeholders to be +// specified. More importantly, it provides more reliable correctness guarantees +// than `std::bind()`; while `std::bind()` will silently ignore passing more +// parameters than expected, for example, `absl::bind_back()` will report such +// mis-uses as errors. In C++23, `absl::bind_back` is replaced by +// `std::bind_back`. +// +#if defined(__cpp_lib_bind_back) && __cpp_lib_bind_back >= 202202L +using std::bind_back; +#else +template +constexpr functional_internal::bind_back_t bind_back( + F&& func, BoundArgs&&... args) { + return functional_internal::bind_back_t( + absl::in_place, std::forward(func), std::forward(args)...); +} +#endif + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_FUNCTIONAL_BIND_BACK_H_ diff --git a/absl/functional/bind_back_test.cc b/absl/functional/bind_back_test.cc new file mode 100644 index 00000000..37b8180e --- /dev/null +++ b/absl/functional/bind_back_test.cc @@ -0,0 +1,237 @@ +// 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/functional/bind_back.h" + +#include + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/memory/memory.h" + +namespace { + +char CharAt(const char* s, size_t index) { return s[index]; } + +TEST(BindTest, Basics) { + EXPECT_EQ('C', absl::bind_back(CharAt)("ABC", 2)); + EXPECT_EQ('C', absl::bind_back(CharAt, 2)("ABC")); + EXPECT_EQ('C', absl::bind_back(CharAt, "ABC", 2)()); +} + +TEST(BindTest, Lambda) { + auto lambda = [](int x, int y, int z) { return x + y + z; }; + EXPECT_EQ(6, absl::bind_back(lambda)(1, 2, 3)); + EXPECT_EQ(6, absl::bind_back(lambda, 3)(1, 2)); + EXPECT_EQ(6, absl::bind_back(lambda, 2, 3)(1)); + EXPECT_EQ(6, absl::bind_back(lambda, 1, 2, 3)()); +} + +struct Functor { + std::string operator()() & { return "&"; } + std::string operator()() const& { return "const&"; } + std::string operator()() && { return "&&"; } + std::string operator()() const&& { return "const&&"; } +}; + +TEST(BindTest, PerfectForwardingOfBoundArgs) { + auto f = absl::bind_back(Functor()); + const auto& cf = f; + EXPECT_EQ("&", f()); + EXPECT_EQ("const&", cf()); + EXPECT_EQ("&&", std::move(f)()); + EXPECT_EQ("const&&", std::move(cf)()); +} + +struct ArgDescribe { + std::string operator()(int&) const { return "&"; } // NOLINT + std::string operator()(const int&) const { return "const&"; } // NOLINT + std::string operator()(int&&) const { return "&&"; } + std::string operator()(const int&&) const { return "const&&"; } +}; + +TEST(BindTest, PerfectForwardingOfFreeArgs) { + ArgDescribe f; + int i; + EXPECT_EQ("&", absl::bind_back(f)(static_cast(i))); + EXPECT_EQ("const&", absl::bind_back(f)(static_cast(i))); + EXPECT_EQ("&&", absl::bind_back(f)(static_cast(i))); + EXPECT_EQ("const&&", absl::bind_back(f)(static_cast(i))); +} + +struct NonCopyableFunctor { + NonCopyableFunctor() = default; + NonCopyableFunctor(const NonCopyableFunctor&) = delete; + NonCopyableFunctor& operator=(const NonCopyableFunctor&) = delete; + const NonCopyableFunctor* operator()() const { return this; } +}; + +TEST(BindTest, RefToFunctor) { + // It won't copy/move the functor and use the original object. + NonCopyableFunctor ncf; + auto bound_ncf = absl::bind_back(std::ref(ncf)); + auto bound_ncf_copy = bound_ncf; + EXPECT_EQ(&ncf, bound_ncf_copy()); +} + +struct Struct { + std::string value; +}; + +TEST(BindTest, StoreByCopy) { + Struct s = {"hello"}; + auto f = absl::bind_back(&Struct::value, s); + auto g = f; + EXPECT_EQ("hello", f()); + EXPECT_EQ("hello", g()); + EXPECT_NE(&s.value, &f()); + EXPECT_NE(&s.value, &g()); + EXPECT_NE(&g(), &f()); +} + +struct NonCopyable { + explicit NonCopyable(const std::string& s) : value(s) {} + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(const NonCopyable&) = delete; + + std::string value; +}; + +const std::string& GetNonCopyableValue(const NonCopyable& n) { return n.value; } + +TEST(BindTest, StoreByRef) { + NonCopyable s("hello"); + auto f = absl::bind_back(&GetNonCopyableValue, std::ref(s)); + EXPECT_EQ("hello", f()); + EXPECT_EQ(&s.value, &f()); + auto g = std::move(f); // NOLINT + EXPECT_EQ("hello", g()); + EXPECT_EQ(&s.value, &g()); + s.value = "goodbye"; + EXPECT_EQ("goodbye", g()); +} + +TEST(BindTest, StoreByCRef) { + NonCopyable s("hello"); + auto f = absl::bind_back(&GetNonCopyableValue, std::cref(s)); + EXPECT_EQ("hello", f()); + EXPECT_EQ(&s.value, &f()); + auto g = std::move(f); // NOLINT + EXPECT_EQ("hello", g()); + EXPECT_EQ(&s.value, &g()); + s.value = "goodbye"; + EXPECT_EQ("goodbye", g()); +} + +const std::string& GetNonCopyableValueByWrapper( + std::reference_wrapper n) { + return n.get().value; +} + +TEST(BindTest, StoreByRefInvokeByWrapper) { + NonCopyable s("hello"); + auto f = absl::bind_back(GetNonCopyableValueByWrapper, std::ref(s)); + EXPECT_EQ("hello", f()); + EXPECT_EQ(&s.value, &f()); + auto g = std::move(f); + EXPECT_EQ("hello", g()); + EXPECT_EQ(&s.value, &g()); + s.value = "goodbye"; + EXPECT_EQ("goodbye", g()); +} + +TEST(BindTest, StoreByPointer) { + NonCopyable s("hello"); + auto f = absl::bind_back(&NonCopyable::value, &s); + EXPECT_EQ("hello", f()); + EXPECT_EQ(&s.value, &f()); + auto g = std::move(f); + EXPECT_EQ("hello", g()); + EXPECT_EQ(&s.value, &g()); +} + +struct MyStruct { + int x; + int Add(int y) const { return x + y; } +}; + +TEST(BindTest, MemberFunctionFreeInstance) { + MyStruct s{10}; + auto f = absl::bind_back(&MyStruct::Add, 5); + EXPECT_EQ(f(s), 15); +} + +int Sink(std::unique_ptr p) { return *p; } + +std::unique_ptr Factory(int n) { return absl::make_unique(n); } + +TEST(BindTest, NonCopyableArg) { + EXPECT_EQ(42, absl::bind_back(Sink)(absl::make_unique(42))); + EXPECT_EQ(42, absl::bind_back(Sink, absl::make_unique(42))()); +} + +TEST(BindTest, NonCopyableResult) { + EXPECT_THAT(absl::bind_back(Factory)(42), ::testing::Pointee(42)); + EXPECT_THAT(absl::bind_back(Factory, 42)(), ::testing::Pointee(42)); +} + +// is_copy_constructible> is true but an attempt to +// instantiate the copy constructor leads to a compile error. This is similar +// to how standard containers behave. +template +struct FalseCopyable { + FalseCopyable() = default; + FalseCopyable(const FalseCopyable& other) : m(other.m) {} + FalseCopyable(FalseCopyable&& other) : m(std::move(other.m)) {} + T m; +}; + +int GetMember(FalseCopyable> x) { return *x.m; } + +TEST(BindTest, WrappedMoveOnly) { + FalseCopyable> x; + x.m = absl::make_unique(42); + auto f = absl::bind_back(&GetMember, std::move(x)); + EXPECT_EQ(42, std::move(f)()); +} + +int Plus(int a, int b) { return a + b; } + +TEST(BindTest, ConstExpr) { + constexpr auto f = absl::bind_back(CharAt); + EXPECT_EQ(f("ABC", 1), 'B'); + static constexpr int five = 5; + constexpr auto plus5 = absl::bind_back(Plus, five); + EXPECT_EQ(plus5(1), 6); + + static constexpr char data[] = "DEF"; + constexpr auto g = absl::bind_back(CharAt, 1); + EXPECT_EQ(g(data), 'E'); +} + +struct ManglingCall { + int operator()(int, double, std::string) const { return 0; } +}; + +TEST(BindTest, Mangling) { + // We just want to generate a particular instantiation to see its mangling. + absl::bind_back(ManglingCall{}, 3.3, "A")(1); +} + +} // namespace diff --git a/absl/functional/internal/back_binder.h b/absl/functional/internal/back_binder.h new file mode 100644 index 00000000..35423da9 --- /dev/null +++ b/absl/functional/internal/back_binder.h @@ -0,0 +1,95 @@ +// 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. + +// Implementation details for `absl::bind_back()`. + +#ifndef ABSL_FUNCTIONAL_INTERNAL_BACK_BINDER_H_ +#define ABSL_FUNCTIONAL_INTERNAL_BACK_BINDER_H_ + +#include +#include +#include + +#include "absl/base/config.h" +#include "absl/container/internal/compressed_tuple.h" +#include "absl/utility/utility.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace functional_internal { + +// Invoke the method, expanding the tuple of bound arguments. +template +constexpr R ApplyBack(Tuple&& bound, absl::index_sequence, + Args&&... free) { + return std::invoke(std::forward(bound).template get<0>(), + std::forward(free)..., + std::forward(bound).template get()...); +} + +template +class BackBinder { + using BoundArgsT = absl::container_internal::CompressedTuple; + using Idx = absl::make_index_sequence; + + BoundArgsT bound_args_; + + public: + template + constexpr explicit BackBinder(absl::in_place_t, Ts&&... ts) + : bound_args_(std::forward(ts)...) {} + + template > + constexpr R operator()(FreeArgs&&... free_args) & { + return functional_internal::ApplyBack( + bound_args_, Idx(), std::forward(free_args)...); + } + + template > + constexpr R operator()(FreeArgs&&... free_args) const& { + return functional_internal::ApplyBack( + bound_args_, Idx(), std::forward(free_args)...); + } + + template > + constexpr R operator()(FreeArgs&&... free_args) && { + // This overload is called when *this is an rvalue. If some of the bound + // arguments are stored by value or rvalue reference, we move them. + return functional_internal::ApplyBack( + std::move(bound_args_), Idx(), std::forward(free_args)...); + } + + template > + constexpr R operator()(FreeArgs&&... free_args) const&& { + // This overload is called when *this is an rvalue. If some of the bound + // arguments are stored by value or rvalue reference, we move them. + return functional_internal::ApplyBack( + std::move(bound_args_), Idx(), std::forward(free_args)...); + } +}; + +template +using bind_back_t = BackBinder, std::decay_t...>; + +} // namespace functional_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_FUNCTIONAL_INTERNAL_BACK_BINDER_H_