[SNAPv4] libstdc++: Add NTTP bind_front, -back, not_fn (P2714) [PR119744]
Tomasz Kaminski
tkaminsk@redhat.com
Tue Jul 8 07:45:43 GMT 2025
On Tue, Jul 8, 2025 at 5:41 AM Nathan Myers <ncm@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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250708/5a21ca5f/attachment-0001.htm>
More information about the Libstdc++
mailing list