[libcxx-commits] [libcxx] [libc++][functional] Implement `std::bind_front<NTTP>` (PR #165096)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Nov 2 01:44:38 PST 2025
================
@@ -0,0 +1,355 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <functional>
+
+// template<auto f, class... Args>
+// constexpr unspecified bind_front(Args&&...);
+
+#include <functional>
+
+#include <cassert>
+#include <concepts>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "types.h"
+
+constexpr void test_basic_bindings() {
+ { // Bind arguments, call without arguments
+ {
+ auto f = std::bind_front<MakeTuple{}>();
+ assert(f() == std::make_tuple());
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{});
+ assert(f() == std::make_tuple(Elem<1>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+ assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+ }
+ }
+
+ { // Bind no arguments, call with arguments
+ {
+ auto f = std::bind_front<MakeTuple{}>();
+ assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>();
+ assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>();
+ assert(f(Elem<1>{}, Elem<2>{}, Elem<3>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+ }
+ }
+
+ { // Bind arguments, call with arguments
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}));
+ }
+
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}, Elem<11>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}, Elem<11>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}, Elem<11>{}));
+ }
+ {
+ auto f = std::bind_front<MakeTuple{}>(Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f(Elem<10>{}, Elem<11>{}, Elem<12>{}) ==
+ std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}, Elem<11>{}, Elem<12>{}));
+ }
+ }
+
+ { // Basic tests with fundamental types
+ const int n = 2;
+ const int m = 1;
+ int o = 0;
+
+ auto add = [](int x, int y) { return x + y; };
+ auto add6 = [](int a, int b, int c, int d, int e, int f) { return a + b + c + d + e + f; };
+ auto increment = [](int& x) { return ++x; };
+
+ auto a = std::bind_front<add>(m, n);
+ assert(a() == 3);
+
+ auto b = std::bind_front<add6>(m, n, m, m, m, m);
+ assert(b() == 7);
+
+ auto c = std::bind_front<add6>(n, m);
+ assert(c(1, 1, 1, 1) == 7);
+
+ auto f = std::bind_front<add>(n);
+ assert(f(3) == 5);
+
+ auto g = std::bind_front<add>(n, 1);
+ assert(g() == 3);
+
+ auto h = std::bind_front<add6>(1, 1, 1);
+ assert(h(2, 2, 2) == 9);
+
+ auto i = std::bind_front<increment>();
+ assert(i(o) == 1);
+ assert(o == 1);
+
+ auto j = std::bind_front<increment>(std::ref(o));
+ assert(j() == 2);
+ assert(o == 2);
+ }
+}
+
+constexpr void test_edge_cases() {
+ { // Make sure we don't treat std::reference_wrapper specially.
+ auto sub = [](std::reference_wrapper<int> a, std::reference_wrapper<int> b) { return a.get() - b.get(); };
+
+ int i = 1;
+ int j = 2;
+ auto f = std::bind_front<sub>(std::ref(i));
+ assert(f(std::ref(j)) == -1);
+ }
+
+ { // Make sure we can call a function that's a pointer to a member function.
+ struct MemberFunction {
+ constexpr int mul(int x, int y) { return x * y; }
+ };
+
+ MemberFunction value;
+ auto fn = std::bind_front<&MemberFunction::mul>(value, 2);
+ assert(fn(3) == 6);
+ }
+
+ { // Make sure we can call a function that's a pointer to a member object.
+ struct MemberObject {
+ int obj;
+ };
+
+ MemberObject value{.obj = 3};
+ auto fn1 = std::bind_front<&MemberObject::obj>();
+ assert(fn1(value) == 3);
+ auto fn2 = std::bind_front<&MemberObject::obj>(value);
+ assert(fn2() == 3);
+ }
+}
+
+constexpr void test_passing_arguments() {
+ { // Make sure that we copy the bound arguments into the unspecified-type.
+ int n = 2;
+ auto f = std::bind_front<[](int x, int y) { return x + y; }>(n, 1);
+ n = 100;
+ assert(f() == 3);
+ }
+
+ { // Make sure we pass the bound arguments to the function object
+ // with the right value category.
+ {
+ auto was_copied = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::copy; };
+ CopyMoveInfo info;
+ auto f = std::bind_front<was_copied>(info);
+ assert(f());
+ }
+
+ {
+ auto was_moved = [](CopyMoveInfo info) { return info.copy_kind == CopyMoveInfo::move; };
+ CopyMoveInfo info;
+ auto f = std::bind_front<was_moved>(info);
+ assert(std::move(f)());
+ }
+ }
+}
+
+constexpr void test_perfect_forwarding_call_wrapper() {
+ { // Make sure we call the correctly cv-ref qualified operator()
+ // based on the value category of the bind_front<NTTP> unspecified-type.
+ struct X {
+ constexpr int operator()() & { return 1; }
+ constexpr int operator()() const& { return 2; }
+ constexpr int operator()() && { return 3; }
+ constexpr int operator()() const&& { return 4; }
+ };
+
+ auto f = std::bind_front<X{}>();
+ using F = decltype(f);
+ assert(static_cast<F&>(f)() == 2);
+ assert(static_cast<const F&>(f)() == 2);
+ assert(static_cast<F&&>(f)() == 2);
+ assert(static_cast<const F&&>(f)() == 2);
+ }
+
+ // Call to `bind_front<NTTP>` unspecified-type's operator() should always result in call to the const& overload of the underlying function object.
+ {
+ { // Make sure unspecified-type is still callable when we delete the & overload.
+ struct X {
+ int operator()() & = delete;
+ int operator()() const&;
+ int operator()() &&;
+ int operator()() const&&;
+ };
+
+ using F = decltype(std::bind_front<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is not callable when we delete the const& overload.
+ struct X {
+ int operator()() &;
+ int operator()() const& = delete;
+ int operator()() &&;
+ int operator()() const&&;
+ };
+
+ using F = decltype(std::bind_front<X{}>());
+ static_assert(!std::invocable<F&>);
+ static_assert(!std::invocable<const F&>);
+ static_assert(!std::invocable<F>);
+ static_assert(!std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is still callable when we delete the && overload.
+ struct X {
+ int operator()() &;
+ int operator()() const&;
+ int operator()() && = delete;
+ int operator()() const&&;
+ };
+
+ using F = decltype(std::bind_front<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+
+ { // Make sure unspecified-type is still callable when we delete the const&& overload.
+ struct X {
+ int operator()() &;
+ int operator()() const&;
+ int operator()() &&;
+ int operator()() const&& = delete;
+ };
+
+ using F = decltype(std::bind_front<X{}>());
+ static_assert(std::invocable<F&>);
+ static_assert(std::invocable<const F&>);
+ static_assert(std::invocable<F>);
+ static_assert(std::invocable<const F>);
+ }
+ }
+
+ { // Test perfect forwarding
----------------
huixie90 wrote:
I think what is missing and important is that the bond arguments are correct perfectly forward-like as the returned object. given
```
constexpr struct F {
int operator()(int&, double) const;
int operator()(const int&, double) const;
int operator()(int&&, double) const;
int operator()(const int&&, double) const;
} f{};
```
and
```
auto r = bind_front<f>(5);
```
the results of
```
r(1.0);
std::as_const(r)(1.0);
std::move(r)(1.0);
std::move(std::as_const(r))(1.0);
```
should call different overloads
https://github.com/llvm/llvm-project/pull/165096
More information about the libcxx-commits
mailing list