To test forwarding, I would suggest using a by-value member: struct F { quals operator()(int&) const { return { false, true }; } quals operator()(int const&) const { return { true, true }; } quals operator()(int&&) const { return { false, false }; } quals operator()(int const&&) const { return { true, false }; } }; // Note that all functions are all const qualified, alternatively they could be made static.
Then for g equal either to bind_front(F{}, 10) or bind_front<F{}>(10), g() should cal int& as_const(g) should call int const& .... int i; And for bind_front(F{}, std::ref(i)) and bind_front<F{}>(std::ref(i)), regardless if called on const/mutable, lvalue or rvalue you should always call int& overload. And if cref(i) is used, the call int const& overload. On Tue, Jul 8, 2025 at 9:45 AM Tomasz Kaminski <tkami...@redhat.com> wrote: > > > On Tue, Jul 8, 2025 at 5:41 AM Nathan Myers <n...@cantrip.org> wrote: > >> This is a snapshot of work in progress, for reference. >> bind_front<f>(...) is uglified directly from the sample >> implementation in P2714, at include/std/functional:1284 . >> >> Test failures: >> >> bind_front/1.cc:53: error: static assertion failed >> bind_front/1.cc:57: error: static assertion failed >> bind_front/1.cc:214: error: static assertion failed >> bind_front/1.cc:215: error: static assertion failed >> bind_front/1.cc:216: required from here >> functional:1301: error: invalid conversion from >> 'std::invoke_result_t<const test03()::F&, std::reference_wrapper<int>&, >> void*&>' {aka 'void*'} to 'int' [-fpermissive] >> [... etc. ] >> Also complains about 218, 220, 231, 233-6, 264, 267 >> > The issue is raised on the line: > int& i6 = g6(vp); > VERIFY( &i6 == &i ); > Where G6 is defined as follows: > auto g6 = bind_front<f>(std::ref(i)); // bound arg of type int& > using G6 = decltype(g6); > And f: > struct F > { > int& operator()(int& i, void*) { return i; } > void* operator()(int, void* p) const { return p; } > }; > constexpr static F f{}; > > As the template parameter object, i.e. what id-expression f refers to in > bind_front<f> is always constant, > g6(vp) i.e. bind_front<f>(ref(i), vp) calls f(ref(i), vp), and because f > is const qualified, the only viable candiate is: > void* operator()(int, void* p) const { return p; } > So you get void* returned, that int& obviously cannot bind to. > > >> >> libstdc++-v3/ChangeLog: >> PR libstdc++/119744 >> * include/bits/version.def: Redefine __cpp_lib_bind_front etc. >> * include/bits/version.h: Ditto. >> * include/std/functional: Add new bind_front etc. overloads >> * testsuite/20_util/function_objects/bind_front/1.cc >> --- >> libstdc++-v3/include/bits/version.def | 12 ++ >> libstdc++-v3/include/bits/version.h | 21 ++- >> libstdc++-v3/include/std/functional | 124 +++++++++++++++++- >> .../20_util/function_objects/bind_front/1.cc | 103 ++++++++++++++- >> 4 files changed, 278 insertions(+), 5 deletions(-) >> >> diff --git a/libstdc++-v3/include/bits/version.def >> b/libstdc++-v3/include/bits/version.def >> index 5d5758bf203..8ab9a7207e7 100644 >> --- a/libstdc++-v3/include/bits/version.def >> +++ b/libstdc++-v3/include/bits/version.def >> @@ -463,6 +463,10 @@ ftms = { >> >> ftms = { >> name = not_fn; >> + values = { >> + v = 202306; >> + cxxmin = 26; >> + }; >> values = { >> v = 201603; >> cxxmin = 17; >> @@ -776,6 +780,10 @@ ftms = { >> >> ftms = { >> name = bind_front; >> + values = { >> + v = 202306; >> + cxxmin = 26; >> + }; >> values = { >> v = 201907; >> cxxmin = 20; >> @@ -784,6 +792,10 @@ ftms = { >> >> ftms = { >> name = bind_back; >> + values = { >> + v = 202306; >> + cxxmin = 26; >> + }; >> values = { >> v = 202202; >> cxxmin = 23; >> diff --git a/libstdc++-v3/include/bits/version.h >> b/libstdc++-v3/include/bits/version.h >> index 2b00e8419b3..c204ae3c48c 100644 >> --- a/libstdc++-v3/include/bits/version.h >> +++ b/libstdc++-v3/include/bits/version.h >> @@ -511,7 +511,12 @@ >> #undef __glibcxx_want_make_from_tuple >> >> #if !defined(__cpp_lib_not_fn) >> -# if (__cplusplus >= 201703L) >> +# if (__cplusplus > 202302L) >> +# define __glibcxx_not_fn 202306L >> +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) >> +# define __cpp_lib_not_fn 202306L >> +# endif >> +# elif (__cplusplus >= 201703L) >> # define __glibcxx_not_fn 201603L >> # if defined(__glibcxx_want_all) || defined(__glibcxx_want_not_fn) >> # define __cpp_lib_not_fn 201603L >> @@ -866,7 +871,12 @@ >> #undef __glibcxx_want_atomic_value_initialization >> >> #if !defined(__cpp_lib_bind_front) >> -# if (__cplusplus >= 202002L) >> +# if (__cplusplus > 202302L) >> +# define __glibcxx_bind_front 202306L >> +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) >> +# define __cpp_lib_bind_front 202306L >> +# endif >> +# elif (__cplusplus >= 202002L) >> # define __glibcxx_bind_front 201907L >> # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_front) >> # define __cpp_lib_bind_front 201907L >> @@ -876,7 +886,12 @@ >> #undef __glibcxx_want_bind_front >> >> #if !defined(__cpp_lib_bind_back) >> -# if (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) >> +# if (__cplusplus > 202302L) >> +# define __glibcxx_bind_back 202306L >> +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) >> +# define __cpp_lib_bind_back 202306L >> +# endif >> +# elif (__cplusplus >= 202100L) && (__cpp_explicit_this_parameter) >> # define __glibcxx_bind_back 202202L >> # if defined(__glibcxx_want_all) || defined(__glibcxx_want_bind_back) >> # define __cpp_lib_bind_back 202202L >> diff --git a/libstdc++-v3/include/std/functional >> b/libstdc++-v3/include/std/functional >> index 307bcb95bcc..21f0b1cb2d5 100644 >> --- a/libstdc++-v3/include/std/functional >> +++ b/libstdc++-v3/include/std/functional >> @@ -940,7 +940,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> _M_bound_args(std::forward<_Args>(__args)...) >> { static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); } >> >> -#if __cpp_explicit_this_parameter >> +#ifdef __cpp_explicit_this_parameter >> template<typename _Self, typename... _CallArgs> >> constexpr >> invoke_result_t<__like_t<_Self, _Fd>, __like_t<_Self, >> _BoundArgs>..., _CallArgs...> >> @@ -1218,8 +1218,130 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> { >> return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; >> } >> +#if __cpp_lib_not_fn >= 202306 >> + /** Wrap a function type to create a function object that negates its >> result. >> + * >> + * The function template `std::not_fn` creates a "forwarding call >> wrapper", >> + * which is a function object that when called forwards its arguments >> to >> + * its invocable template argument. >> + * >> + * The result of invoking the wrapper is the negation (using `!`) of >> + * the wrapped function object. >> + * >> + * @ingroup functors >> + * @since C++26 >> + */ >> + template<auto __fn> >> + constexpr auto >> + not_fn() noexcept >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return []<typename... _Tp> (_Tp&&... __call_args) >> + noexcept(is_nothrow_invocable_v<_Fn, _Tp...>) >> + -> invoke_result_t<_Fn, _Tp...> >> + { >> + return !invoke(__fn, forward<_Tp>(__call_args)...); >> + }; >> + } >> +#endif // __cpp_lib_not_fn >= 202306 >> +#endif // __cpp_lib_not_fn >> + >> +#if __cpp_lib_bind_front >= 202306 || __cpp_lib_bind_back >= 202306 >> + >> + template<typename _Tp, typename _Up> >> + using __copy_const = conditional<is_const_v<_Tp>, const _Up, _Up>; >> + >> + template<typename _Tp, typename _Up, >> + typename _Xp = __copy_const<remove_reference_t<_Tp>, >> _Up>::type> >> + using __copy_value_category = >> + conditional<is_lvalue_reference_v<_Tp&&>, _Xp&, _Xp&&>; >> + >> + template<typename _Tp, typename _Up> >> + using __type_forward_like = >> + __copy_value_category<_Tp, remove_reference_t<_Up>>; >> + >> + template<typename _Tp, typename _Up> >> + using __type_forward_like_t = __type_forward_like<_Tp, _Up>::type; >> + >> #endif >> >> +#if __cpp_lib_bind_front >= 202306 >> + /** Create call wrapper by partial application of arguments to >> function. >> + * >> + * The result of `std::bind_front<f>(args...)` is a function object >> that >> + * stores the bound arguments, `args...`. When that function object is >> + * invoked with `call_args...` it returns the result of calling >> + * `f(args..., call_args...)`. >> + * >> + * @since C++26 >> + */ >> + >> + template<auto __fn, typename... _Args> >> + constexpr auto >> + bind_front(_Args&&... __args) >> + requires ( >> + (is_constructible_v<decay_t<_Args>, _Args> and ...) and >> + (is_move_constructible_v<decay_t<_Args>> and ...)) >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return [... __bound_args(std::forward<_Args>(__args))]< >> + typename _Self, typename... _Tp> >> + (this _Self&&, _Tp&&... __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, __type_forward_like_t<_Self, decay_t<_Args>>..., _Tp...>) >> + -> invoke_result_t< >> + _Fn, __type_forward_like_t<_Self, decay_t<_Args>>..., _Tp...> >> + { >> + return invoke(__fn, >> + forward_like<_Self>(__bound_args)..., >> + forward<_Tp>(__call_args)...); >> + }; >> + } >> +#endif // __cpp_lib_bind_front >> + >> +#if __cpp_lib_bind_back >= 202306 >> + /** Create call wrapper by partial application of arguments to >> function. >> + * >> + * The result of `std::bind_back<f>(args...)` is a function object that >> + * stores the bound arguments, `args...`. When that function object is >> + * invoked with `call_args...` it returns the result of calling >> + * `f(call_args..., args...)`. >> + * >> + * @since C++26 >> + */ >> + >> + template<auto __fn, typename... _Args> >> + constexpr auto >> + bind_back(_Args&&... __args) >> + requires ( >> + (is_constructible_v<decay_t<_Args>, _Args> and ...) and >> + (is_move_constructible_v<decay_t<_Args>> and ...)) >> + { >> + using _Fn = decltype(__fn); >> + if constexpr (is_pointer_v<_Fn> or is_member_pointer_v<_Fn>) { >> + static_assert(__fn != nullptr); >> + } >> + return [... __bound_args(std::forward<_Args>(__args))]< >> + typename _Self, typename... _Tp> >> + (this _Self&&, _Tp&&... __call_args) >> + noexcept(is_nothrow_invocable_v< >> + _Fn, _Tp..., __type_forward_like_t<_Self, decay_t<_Args>>...>) >> + -> invoke_result_t< >> + _Fn, _Tp..., __type_forward_like_t<_Self, decay_t<_Args>>...> >> + { >> + return invoke(__fn, >> + forward<_Tp>(__call_args)..., >> + forward_like<_Self>(__bound_args)...); >> + }; >> + } >> +#endif // __cpp_lib_bind_back >> + >> #if __cplusplus >= 201703L >> // Searchers >> >> diff --git >> a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> index 57482c52263..d21432f9629 100644 >> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/1.cc >> @@ -47,6 +47,18 @@ test01() >> decltype(bind_front(std::declval<F>(), std::declval<int>())), >> decltype(bind_front(std::declval<const F&>(), std::declval<const >> int&>())) >> >); >> +#if __cpp_lib_bind_front >= 202306 >> + const F f{}; >> + >> + static_assert(std::is_same_v< >> + decltype(bind_front<f>(std::declval<int>())), >> + decltype(bind_front<f>(std::declval<int&>())) >> + >); >> + static_assert(std::is_same_v< >> + decltype(bind_front<f>(std::declval<int>())), >> + decltype(bind_front<f>(std::declval<const int&>())) >> + >); >> +#endif >> >> // Reference wrappers should be handled: >> static_assert(!std::is_same_v< >> @@ -61,6 +73,20 @@ test01() >> decltype(bind_front(std::declval<F>(), >> std::ref(std::declval<int&>()))), >> decltype(bind_front(std::declval<F>(), >> std::cref(std::declval<int&>()))) >> >); >> +#if __cpp_lib_bind_front >= 202306 >> + static_assert(!std::is_same_v< >> + decltype(bind_front<f>(std::declval<int&>())), >> + decltype(bind_front<f>(std::ref(std::declval<int&>()))) >> + >); >> + static_assert(!std::is_same_v< >> + decltype(bind_front<f>(std::declval<const int&>())), >> + decltype(bind_front<f>(std::cref(std::declval<int&>()))) >> + >); >> + static_assert(!std::is_same_v< >> + decltype(bind_front<f>(std::ref(std::declval<int&>()))), >> + decltype(bind_front<f>(std::cref(std::declval<int&>()))) >> + >); >> +#endif >> } >> >> void >> @@ -81,6 +107,7 @@ test02() >> }; >> >> F f; >> + >> auto g = bind_front(f); >> const auto& cg = g; >> quals q; >> @@ -94,6 +121,23 @@ test02() >> VERIFY( q.as_const && q.as_lvalue ); >> q = std::move(cg)(); >> VERIFY( q.as_const && ! q.as_lvalue ); >> + >> +#if __cpp_lib_bind_front >= 202306 >> + const F f2; >> + auto g2 = bind_front<f2>(); >> + const auto& cg2 = g2; >> + quals q2; >> + >> + // constness and value category should be forwarded to the target >> object: >> + q2 = g2(); >> + VERIFY( ! q2.as_const && q2.as_lvalue ); >> + q2 = std::move(g)(); >> + VERIFY( ! q2.as_const && ! q2.as_lvalue ); >> + q2 = cg2(); >> + VERIFY( q2.as_const && q2.as_lvalue ); >> + q2 = std::move(cg2)(); >> + VERIFY( q2.as_const && ! q2.as_lvalue ); >> +#endif >> } >> >> void >> @@ -147,9 +191,53 @@ test03() >> static_assert(is_invocable_r_v<int&, G4&&>); >> static_assert(is_invocable_r_v<void*, const G4&>); >> static_assert(is_invocable_r_v<void*, const G4&&>); >> + >> +#if __cpp_lib_bind_front >= 202306 >> + constexpr static F f{}; >> + >> + auto g5 = bind_front<f>(i); // call wrapper has bound arg of type int >> + using G5 = decltype(g5); >> + // Invoking G5& will pass g5's bound arg as int&, so calls first >> overload: >> + static_assert(is_invocable_r_v<int&, G5&, void*>); >> + // Invoking const G5& or G&& calls second overload: >> + static_assert(is_invocable_r_v<void*, const G5&, void*>); >> + static_assert(is_invocable_r_v<void*, G5&&, void*>); >> + void* p5 = static_cast<G5&&>(g5)(vp); >> + VERIFY( p5 == vp ); >> + >> + auto g6 = bind_front<f>(std::ref(i)); // bound arg of type int& >> + using G6 = decltype(g6); >> + // Bound arg always forwarded as int& even from G6&& or const G6& >> + static_assert(is_invocable_r_v<int&, G6&, void*>); >> + static_assert(is_invocable_r_v<int&, G6&&, void*>); >> + // But cannot call first overload on const G6: >> + static_assert(is_invocable_r_v<void*, const G6&, void*>); >> + static_assert(is_invocable_r_v<void*, const G6&&, void*>); >> + int& i6 = g6(vp); >> + VERIFY( &i6 == &i ); >> + int& i6r = static_cast<G6&&>(g6)(vp); >> + VERIFY( &i6r == &i ); >> + void* p6 = const_cast<const G6&>(g6)(vp); >> + VERIFY( p6 == vp ); >> + >> + auto g7 = bind_front<f>(std::cref(i)); // bound arg of type const int& >> + using G7 = decltype(g7); >> + // Bound arg always forwarded as const int& so can only call second >> overload: >> + static_assert(is_invocable_r_v<void*, G7&, void*>); >> + static_assert(is_invocable_r_v<void*, G7&&, void*>); >> + static_assert(is_invocable_r_v<void*, const G7&, void*>); >> + static_assert(is_invocable_r_v<void*, const G7&&, void*>); >> + >> + auto g8 = bind_front<g7>(nullptr); >> + using G8 = decltype(g8); >> + static_assert(is_invocable_r_v<int&, G8&>); >> + static_assert(is_invocable_r_v<int&, G8&&>); >> + static_assert(is_invocable_r_v<void*, const G8&>); >> + static_assert(is_invocable_r_v<void*, const G8&&>); >> +#endif >> } >> >> -int f(int i, int j, int k) { return i + j + k; } >> +constexpr int f(int i, int j, int k) { return i + j + k; } >> >> void >> test04() >> @@ -165,6 +253,19 @@ test04() >> auto g3 = bind_front(f, 1, 2, 3); >> VERIFY( g3() == 6 ); >> VERIFY( bind_front(g2, 3)() == 6 ); >> +#if __cpp_lib_bind_front >= 202306 >> + const auto g4 = bind_front<f>(); >> + VERIFY( g4(1, 2, 3) == 6 ); >> + const auto g5 = bind_front<f>(1); >> + VERIFY( g5(2, 3) == 6 ); >> + VERIFY( bind_front<g4>(1)(2, 3) == 6 ); >> + const auto g6 = bind_front<f>(1, 2); >> + VERIFY( g6(3) == 6 ); >> + VERIFY( bind_front<g5>(2)(3) == 6 ); >> + const auto g7 = bind_front<f>(1, 2, 3); >> + VERIFY( g7() == 6 ); >> + VERIFY( bind_front<g6>(3)() == 6 ); >> +#endif >> } >> >> int >> -- >> 2.50.0 >> >>