Add absl::bind_back PiperOrigin-RevId: 905242962 Change-Id: I7ba08d1b610db5be723b2313e11f7a5d4d7ddb6e
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index f7d6b56..6371298 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -153,9 +153,11 @@ "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 dcf808b..5e85523 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel
@@ -71,6 +71,32 @@ ) 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"], hdrs = ["bind_front.h"],
diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index 4ffb513..362c149 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt
@@ -52,6 +52,35 @@ 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 SRCS "internal/front_binder.h"
diff --git a/absl/functional/bind_back.h b/absl/functional/bind_back.h new file mode 100644 index 0000000..5d81ad2 --- /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(<version>) +#include <version> +#endif +#endif + +#if defined(__cpp_lib_bind_back) && __cpp_lib_bind_back >= 202202L +#include <functional> // For std::bind_back. +#endif + +#include <utility> + +#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 <class F, class... BoundArgs> +constexpr functional_internal::bind_back_t<F, BoundArgs...> bind_back( + F&& func, BoundArgs&&... args) { + return functional_internal::bind_back_t<F, BoundArgs...>( + absl::in_place, std::forward<F>(func), std::forward<BoundArgs>(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 0000000..37b8180 --- /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 <stddef.h> + +#include <functional> +#include <memory> +#include <string> +#include <utility> + +#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<int&>(i))); + EXPECT_EQ("const&", absl::bind_back(f)(static_cast<const int&>(i))); + EXPECT_EQ("&&", absl::bind_back(f)(static_cast<int&&>(i))); + EXPECT_EQ("const&&", absl::bind_back(f)(static_cast<const int&&>(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<NonCopyable> 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<int> p) { return *p; } + +std::unique_ptr<int> Factory(int n) { return absl::make_unique<int>(n); } + +TEST(BindTest, NonCopyableArg) { + EXPECT_EQ(42, absl::bind_back(Sink)(absl::make_unique<int>(42))); + EXPECT_EQ(42, absl::bind_back(Sink, absl::make_unique<int>(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<FalseCopyable<unique_ptr<T>> is true but an attempt to +// instantiate the copy constructor leads to a compile error. This is similar +// to how standard containers behave. +template <class T> +struct FalseCopyable { + FalseCopyable() = default; + FalseCopyable(const FalseCopyable& other) : m(other.m) {} + FalseCopyable(FalseCopyable&& other) : m(std::move(other.m)) {} + T m; +}; + +int GetMember(FalseCopyable<std::unique_ptr<int>> x) { return *x.m; } + +TEST(BindTest, WrappedMoveOnly) { + FalseCopyable<std::unique_ptr<int>> x; + x.m = absl::make_unique<int>(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 0000000..35423da --- /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 <cstddef> +#include <type_traits> +#include <utility> + +#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 <class R, class Tuple, size_t... Idx, class... Args> +constexpr R ApplyBack(Tuple&& bound, absl::index_sequence<Idx...>, + Args&&... free) { + return std::invoke(std::forward<Tuple>(bound).template get<0>(), + std::forward<Args>(free)..., + std::forward<Tuple>(bound).template get<Idx + 1>()...); +} + +template <class F, class... BoundArgs> +class BackBinder { + using BoundArgsT = absl::container_internal::CompressedTuple<F, BoundArgs...>; + using Idx = absl::make_index_sequence<sizeof...(BoundArgs)>; + + BoundArgsT bound_args_; + + public: + template <class... Ts> + constexpr explicit BackBinder(absl::in_place_t, Ts&&... ts) + : bound_args_(std::forward<Ts>(ts)...) {} + + template <class... FreeArgs, + class R = std::invoke_result_t<F&, FreeArgs&&..., BoundArgs&...>> + constexpr R operator()(FreeArgs&&... free_args) & { + return functional_internal::ApplyBack<R>( + bound_args_, Idx(), std::forward<FreeArgs>(free_args)...); + } + + template <class... FreeArgs, + class R = std::invoke_result_t<const F&, FreeArgs&&..., + const BoundArgs&...>> + constexpr R operator()(FreeArgs&&... free_args) const& { + return functional_internal::ApplyBack<R>( + bound_args_, Idx(), std::forward<FreeArgs>(free_args)...); + } + + template <class... FreeArgs, + class R = std::invoke_result_t<F&&, FreeArgs&&..., BoundArgs&&...>> + 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<R>( + std::move(bound_args_), Idx(), std::forward<FreeArgs>(free_args)...); + } + + template <class... FreeArgs, + class R = std::invoke_result_t<const F&&, FreeArgs&&..., + const BoundArgs&&...>> + 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<R>( + std::move(bound_args_), Idx(), std::forward<FreeArgs>(free_args)...); + } +}; + +template <class F, class... BoundArgs> +using bind_back_t = BackBinder<std::decay_t<F>, std::decay_t<BoundArgs>...>; + +} // namespace functional_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_FUNCTIONAL_INTERNAL_BACK_BINDER_H_