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
>>
>>

Reply via email to