[PATCH v2] libstdc++: Refactor bound arguments storage for bind_front/back

Patrick Palka ppalka@redhat.com
Tue Aug 26 13:53:49 GMT 2025


On Wed, 20 Aug 2025, Tomasz Kamiński wrote:

> This patch refactors the implementation of bind_front and bind_back to
> avoid using std::tuple for argument storage. Instead, bound arguments are
> now:
> * stored directly if there is only one,
> * within a dedicated _Bound_arg_storage otehrwise.

I think I forgot the motivation of this change.  What's the benefit of
replacing std::tuple here?  Does _Bound_arg_storage allow for more a
more compact memory layout?

> 
> _Bound_arg_storage holds each argument in an _Indexed_bound_arg base object.
> The base class is parameterized by both type and index to allow storing
> multiple arguments of the same type. Invocations are handled by _S_apply_front
> amd _S_apply_back static functions, which simulate explicit object parameters.
> To facilitate this, the __like_t alias template is now unconditionally available
> since C++11 in bits/move.h.
> 
> libstdc++-v3/ChangeLog:
> 
> 	* include/bits/move.h (std::__like_impl, std::__like_t): Make
> 	available in c++11.
> 	* include/std/functional (std::_Indexed_bound_arg)
> 	(std::_Bound_arg_storage, std::__make_bound_args): Define.
> 	(std::_Bind_front, std::_Bind_back): Use _Bound_arg_storage.
> 	* testsuite/20_util/function_objects/bind_back/1.cc: Expand
> 	test to cover cases of 0, 1, many bound args.
> 	* testsuite/20_util/function_objects/bind_back/111327.cc: Likewise.
> 	* testsuite/20_util/function_objects/bind_front/1.cc: Likewise.
> 	* testsuite/20_util/function_objects/bind_front/111327.cc: Likewise.
> 
> Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> ---
> Changes in v2:
> * replace few 8 spaces with tabs
> * add more test cases, covering materialization
> 
> Testing on x86_64-linux, all bind test passed.
> OK for trunk when test passes.
> 
>  libstdc++-v3/include/bits/move.h              |   2 +-
>  libstdc++-v3/include/std/functional           | 113 +++++++++---
>  .../20_util/function_objects/bind_back/1.cc   | 166 ++++++++++++++++--
>  .../function_objects/bind_back/111327.cc      |  11 ++
>  .../20_util/function_objects/bind_front/1.cc  | 164 +++++++++++++++--
>  .../function_objects/bind_front/111327.cc     |  11 ++
>  6 files changed, 418 insertions(+), 49 deletions(-)
> 
> diff --git a/libstdc++-v3/include/bits/move.h b/libstdc++-v3/include/bits/move.h
> index 061e6b4de3d..8c4f461a110 100644
> --- a/libstdc++-v3/include/bits/move.h
> +++ b/libstdc++-v3/include/bits/move.h
> @@ -89,7 +89,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        return static_cast<_Tp&&>(__t);
>      }
>  
> -#if __glibcxx_forward_like // C++ >= 23
>    template<typename _Tp, typename _Up>
>    struct __like_impl; // _Tp must be a reference and _Up an lvalue reference
>  
> @@ -112,6 +111,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    template<typename _Tp, typename _Up>
>      using __like_t = typename __like_impl<_Tp&&, _Up&>::type;
>  
> +#if __glibcxx_forward_like // C++ >= 23
>    /** @brief Forward with the cv-qualifiers and value category of another type.
>     *  @tparam _Tp An lvalue reference or rvalue reference.
>     *  @tparam _Up An lvalue reference type deduced from the function argument.
> diff --git a/libstdc++-v3/include/std/functional b/libstdc++-v3/include/std/functional
> index 307bcb95bcc..b1cda87929d 100644
> --- a/libstdc++-v3/include/std/functional
> +++ b/libstdc++-v3/include/std/functional
> @@ -922,6 +922,53 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      }
>  
>  #ifdef __cpp_lib_bind_front // C++ >= 20
> +  template<size_t, typename _Tp>
> +  struct _Indexed_bound_arg
> +    {
> +      [[no_unique_address]] _Tp _M_val;
> +    };
> +
> +  template<typename... _IndexedArgs>
> +    struct _Bound_arg_storage : _IndexedArgs...
> +    {
> +      template<typename _Fd, typename _Self, typename... _CallArgs>
> +	static constexpr
> +	decltype(auto)
> +	_S_apply_front(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args)
> +	{
> +	  return std::invoke(std::forward<_Fd>(__fd),
> +			     __like_t<_Self, _IndexedArgs>(__self)._M_val...,
> +			     std::forward<_CallArgs>(__call_args)...);
> +	}
> +
> +      template<typename _Fd, typename _Self, typename... _CallArgs>
> +	static constexpr
> +	decltype(auto)
> +	_S_apply_back(_Fd&& __fd, _Self&& __self, _CallArgs&&... __call_args)
> +	{
> +	  return std::invoke(std::forward<_Fd>(__fd),
> +			     std::forward<_CallArgs>(__call_args)...,
> +			     __like_t<_Self, _IndexedArgs>(__self)._M_val...);
> +	}
> +    };
> +
> +  template<typename... _BoundArgs, typename... _Args>
> +    constexpr auto
> +    __make_bound_args(_Args&&... __args)
> +    {
> +      if constexpr (sizeof...(_BoundArgs) == 1)
> +	// pack has one element, so return copy of arg
> +	return (_BoundArgs(std::forward<_Args>(__args)), ...);
> +      else
> +	{
> +	  auto __impl = [&]<size_t... _Inds>(index_sequence<_Inds...>)
> +	  {
> +	    return _Bound_arg_storage<_Indexed_bound_arg<_Inds, _BoundArgs>...>
> +		   { {_BoundArgs(std::forward<_Args>(__args))}... };
> +	  };
> +	  return __impl(index_sequence_for<_BoundArgs...>());
> +	}
> +    }
>  
>    template<typename _Fd, typename... _BoundArgs>
>      struct _Bind_front
> @@ -937,7 +984,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>,
>  			is_nothrow_constructible<_BoundArgs, _Args>...>::value)
>  	: _M_fd(std::forward<_Fn>(__fn)),
> -	  _M_bound_args(std::forward<_Args>(__args)...)
> +	  _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...))
>  	{ static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); }
>  
>  #if __cpp_explicit_this_parameter
> @@ -948,7 +995,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>,
>  					__like_t<_Self, _BoundArgs>..., _CallArgs...>)
>  	{
> -	  return _S_call(__like_t<_Self, _Bind_front>(__self), _BoundIndices(),
> +	  return _S_call(__like_t<_Self, _Bind_front>(__self),
>  			 std::forward<_CallArgs>(__call_args)...);
>  	}
>  #else
> @@ -959,8 +1006,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	operator()(_CallArgs&&... __call_args) &
>  	noexcept(is_nothrow_invocable_v<_Fd&, _BoundArgs&..., _CallArgs...>)
>  	{
> -	  return _S_call(*this, _BoundIndices(),
> -	      std::forward<_CallArgs>(__call_args)...);
> +	  return _S_call(*this, std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        template<typename... _CallArgs>
> @@ -971,8 +1017,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(is_nothrow_invocable_v<const _Fd&, const _BoundArgs&...,
>  					_CallArgs...>)
>  	{
> -	  return _S_call(*this, _BoundIndices(),
> -	      std::forward<_CallArgs>(__call_args)...);
> +	  return _S_call(*this, std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        template<typename... _CallArgs>
> @@ -982,8 +1027,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	operator()(_CallArgs&&... __call_args) &&
>  	noexcept(is_nothrow_invocable_v<_Fd, _BoundArgs..., _CallArgs...>)
>  	{
> -	  return _S_call(std::move(*this), _BoundIndices(),
> -	      std::forward<_CallArgs>(__call_args)...);
> +	  return _S_call(std::move(*this),
> +			 std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        template<typename... _CallArgs>
> @@ -994,8 +1039,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(is_nothrow_invocable_v<const _Fd, const _BoundArgs...,
>  					_CallArgs...>)
>  	{
> -	  return _S_call(std::move(*this), _BoundIndices(),
> -	      std::forward<_CallArgs>(__call_args)...);
> +	  return _S_call(std::move(*this),
> +			 std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        template<typename... _CallArgs>
> @@ -1012,20 +1057,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  #endif
>  
>      private:
> -      using _BoundIndices = index_sequence_for<_BoundArgs...>;
> +      using _BoundArgsStorage
> +	// _BoundArgs are required to be move-constructible, so this is valid.
> +	= decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...));
>  
> -      template<typename _Tp, size_t... _Ind, typename... _CallArgs>
> +      template<typename _Tp, typename... _CallArgs>
>  	static constexpr
>  	decltype(auto)
> -	_S_call(_Tp&& __g, index_sequence<_Ind...>, _CallArgs&&... __call_args)
> +	_S_call(_Tp&& __g, _CallArgs&&... __call_args)
>  	{
> -	  return std::invoke(std::forward<_Tp>(__g)._M_fd,
> -	      std::get<_Ind>(std::forward<_Tp>(__g)._M_bound_args)...,
> -	      std::forward<_CallArgs>(__call_args)...);
> +	  if constexpr (sizeof...(_BoundArgs) == 1)
> +	    return std::invoke(std::forward<_Tp>(__g)._M_fd,
> +			       std::forward<_Tp>(__g)._M_bound_args,
> +			       std::forward<_CallArgs>(__call_args)...);
> +	  else
> +	    return _BoundArgsStorage::_S_apply_front(
> +		      std::forward<_Tp>(__g)._M_fd,
> +		      std::forward<_Tp>(__g)._M_bound_args,
> +		      std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        [[no_unique_address]] _Fd _M_fd;
> -      [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args;
> +      [[no_unique_address]] _BoundArgsStorage _M_bound_args;
>      };
>  
>    template<typename _Fn, typename... _Args>
> @@ -1066,7 +1119,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(__and_<is_nothrow_constructible<_Fd, _Fn>,
>  			is_nothrow_constructible<_BoundArgs, _Args>...>::value)
>  	: _M_fd(std::forward<_Fn>(__fn)),
> -	  _M_bound_args(std::forward<_Args>(__args)...)
> +	  _M_bound_args(__make_bound_args<_BoundArgs...>(std::forward<_Args>(__args)...))
>  	{ static_assert(sizeof...(_Args) == sizeof...(_BoundArgs)); }
>  
>        template<typename _Self, typename... _CallArgs>
> @@ -1076,25 +1129,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>  	noexcept(is_nothrow_invocable_v<__like_t<_Self, _Fd>,
>  					_CallArgs..., __like_t<_Self, _BoundArgs>...>)
>  	{
> -	  return _S_call(__like_t<_Self, _Bind_back>(__self), _BoundIndices(),
> +	  return _S_call(__like_t<_Self, _Bind_back>(__self),
>  			 std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>      private:
> -      using _BoundIndices = index_sequence_for<_BoundArgs...>;
> +      using _BoundArgsStorage
> +	// _BoundArgs are required to be move-constructible, so this is valid.
> +	= decltype(__make_bound_args<_BoundArgs...>(std::declval<_BoundArgs>()...));
>  
> -      template<typename _Tp, size_t... _Ind, typename... _CallArgs>
> +      template<typename _Tp, typename... _CallArgs>
>  	static constexpr
>  	decltype(auto)
> -	_S_call(_Tp&& __g, index_sequence<_Ind...>, _CallArgs&&... __call_args)
> +	_S_call(_Tp&& __g, _CallArgs&&... __call_args)
>  	{
> -	  return std::invoke(std::forward<_Tp>(__g)._M_fd,
> -	      std::forward<_CallArgs>(__call_args)...,
> -	      std::get<_Ind>(std::forward<_Tp>(__g)._M_bound_args)...);
> +	  if constexpr (sizeof...(_BoundArgs) == 1)
> +	    return std::invoke(std::forward<_Tp>(__g)._M_fd,
> +			       std::forward<_CallArgs>(__call_args)...,
> +			       std::forward<_Tp>(__g)._M_bound_args);
> +	  else
> +	    return _BoundArgsStorage::_S_apply_back(
> +		      std::forward<_Tp>(__g)._M_fd,
> +		      std::forward<_Tp>(__g)._M_bound_args,
> +		      std::forward<_CallArgs>(__call_args)...);
>  	}
>  
>        [[no_unique_address]] _Fd _M_fd;
> -      [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args;
> +      [[no_unique_address]] _BoundArgsStorage _M_bound_args;
>      };
>  
>    template<typename _Fn, typename... _Args>
> diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> index c31d3228815..a31528fc755 100644
> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/1.cc
> @@ -48,6 +48,16 @@ test01()
>        decltype(bind_back(std::declval<const F&>(), std::declval<const int&>()))
>        >);
>  
> +  static_assert(std::is_same_v<
> +      decltype(bind_back(std::declval<F>(), std::declval<int>(), std::declval<float>())),
> +      decltype(bind_back(std::declval<F&>(), std::declval<int&>(), std::declval<float&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_back(std::declval<F>(), std::declval<int>(), std::declval<float>())),
> +      decltype(bind_back(std::declval<const F&>(), std::declval<const int&>(), std::declval<const float&>()))
> +      >);
> +
> +
>    // Reference wrappers should be handled:
>    static_assert(!std::is_same_v<
>        decltype(bind_back(std::declval<F>(), std::declval<int&>())),
> @@ -63,29 +73,58 @@ test01()
>        >);
>  }
>  
> +struct quals
> +{
> +  bool as_const;
> +  bool as_lvalue;
> +};
> +
> +template<typename... Args>
>  void
> -test02()
> +testTarget(Args... args)
>  {
> -  struct quals
> +  struct F
>    {
> -    bool as_const;
> -    bool as_lvalue;
> +    quals operator()(Args...) & { return { false, true }; }
> +    quals operator()(Args...) const & { return { true, true }; }
> +    quals operator()(Args...) && { return { false, false }; }
> +    quals operator()(Args...) const && { return { true, false }; }
>    };
>  
> +  F f;
> +  auto g = bind_back(f, args...);
> +  const auto& cg = g;
> +  quals q;
> +
> +  // constness and value category should be forwarded to the target object:
> +  q = g();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(g)();
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = cg();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(cg)();
> +  VERIFY( q.as_const && ! q.as_lvalue );
> +}
> +
> +template<typename... Args>
> +void
> +testBoundArgs(Args... args)
> +{
>    struct F
>    {
> -    quals operator()() & { return { false, true }; }
> -    quals operator()() const & { return { true, true }; }
> -    quals operator()() && { return { false, false }; }
> -    quals operator()() const && { return { true, false }; }
> +    quals operator()(Args..., int&) const { return { false, true }; }
> +    quals operator()(Args..., int const&) const { return { true, true }; }
> +    quals operator()(Args..., int&&) const { return { false, false }; }
> +    quals operator()(Args..., int const&&) const { return { true, false }; }
>    };
>  
>    F f;
> -  auto g = bind_back(f);
> +  auto g = bind_back(f, args..., 10);
>    const auto& cg = g;
>    quals q;
>  
> -  // constness and value category should be forwarded to the target object:
> +  // constness and value category should be forwarded to the bound objects:
>    q = g();
>    VERIFY( ! q.as_const && q.as_lvalue );
>    q = std::move(g)();
> @@ -94,6 +133,70 @@ test02()
>    VERIFY( q.as_const && q.as_lvalue );
>    q = std::move(cg)();
>    VERIFY( q.as_const && ! q.as_lvalue );
> +
> +  int i = 0;
> +  auto gr = bind_back(f, args..., std::ref(i));
> +  const auto& cgr = gr;
> +
> +  // bound object is reference wrapper
> +  q = gr();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(gr)();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = cgr();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(cgr)();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +
> +  auto gcr = bind_back(f, args..., std::cref(i));
> +  const auto& cgcr = gcr;
> +
> +  q = gcr();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(gcr)();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = cgcr();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(cgcr)();
> +  VERIFY( q.as_const && q.as_lvalue );
> +}
> +
> +template<typename... Args>
> +void
> +testCallArgs(Args... args)
> +{
> +  struct F
> +  {
> +    quals operator()(int&, Args...) const { return { false, true }; }
> +    quals operator()(int const&, Args...) const { return { true, true }; }
> +    quals operator()(int&&, Args...) const { return { false, false }; }
> +    quals operator()(int const&&, Args...) const { return { true, false }; }
> +  };
> +
> +  F f;
> +  auto g = bind_back(f, args...);
> +  const auto& cg = g;
> +  quals q;
> +  int i = 10;
> +  const int ci = i;
> +
> +  q = g(i);
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = g(std::move(i));
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = g(ci);
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = g(std::move(ci));
> +  VERIFY( q.as_const && ! q.as_lvalue );
> +
> +  q = cg(i);
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = cg(std::move(i));
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = cg(ci);
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = cg(std::move(ci));
> +  VERIFY( q.as_const && ! q.as_lvalue );
>  }
>  
>  void
> @@ -168,11 +271,52 @@ test04()
>    return true;
>  }
>  
> +struct CountedArg
> +{
> +  CountedArg() = default;
> +  CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; }
> +  CountedArg& operator=(CountedArg&&) = delete;
> +
> +  int counter = 0;
> +};
> +CountedArg const c;
> +
> +void
> +testMaterialization()
> +{
> +  struct F
> +  {
> +    int operator()(CountedArg arg, int) const
> +    { return arg.counter; };
> +  };
> +
> +  // CountedArg is bound to rvalue-reference thus moved
> +  auto f0 = std::bind_back(F{});
> +  VERIFY( f0(CountedArg(), 10) == 1 );
> +
> +  auto f1 = std::bind_back(F{}, 10);
> +  VERIFY( f1(CountedArg()) == 1 );
> +}
> +
>  int
>  main()
>  {
>    test01();
> -  test02();
>    test03();
> +
> +  testTarget();
> +  testTarget(10);
> +  testTarget(10, 20, 30);
> +
> +  testBoundArgs();
> +  testBoundArgs(10);
> +  testBoundArgs(10, 20, 30);
> +
> +  testCallArgs();
> +  testCallArgs(10);
> +  testCallArgs(10, 20, 30);
> +
> +  testMaterialization();
> +
>    static_assert(test04());
>  }
> diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> index d634db9dc1d..de3ae47e37f 100644
> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/111327.cc
> @@ -37,6 +37,17 @@ int main() {
>    g1(); // { dg-error "deleted|no match" }
>    std::move(g1)(); // { dg-error "deleted|no match" }
>    std::move(std::as_const(g1))();
> +
> +  auto f2 = std::bind_back(F{}, 42, 10);
> +  f2(); // { dg-error "deleted|no match" }
> +  std::move(f2)();
> +  std::as_const(f2)();
> +  std::move(std::as_const(f2))();
> +
> +  auto g2 = std::bind_back(G{}, 42, 10);
> +  g2(); // { dg-error "deleted|no match" }
> +  std::move(g2)(); // { dg-error "deleted|no match" }
> +  std::move(std::as_const(g2))();
>  }
>  
>  // { dg-error "no type named 'type' in 'struct std::invoke_result" "" { target c++23 } 0 }
> 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..ef28de8321b 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
> @@ -48,6 +48,15 @@ test01()
>        decltype(bind_front(std::declval<const F&>(), std::declval<const int&>()))
>        >);
>  
> +  static_assert(std::is_same_v<
> +      decltype(bind_front(std::declval<F>(), std::declval<int>(), std::declval<float>())),
> +      decltype(bind_front(std::declval<F&>(), std::declval<int&>(), std::declval<float&>()))
> +      >);
> +  static_assert(std::is_same_v<
> +      decltype(bind_front(std::declval<F>(), std::declval<int>(), std::declval<float>())),
> +      decltype(bind_front(std::declval<const F&>(), std::declval<const int&>(), std::declval<const float&>()))
> +      >);
> +
>    // Reference wrappers should be handled:
>    static_assert(!std::is_same_v<
>        decltype(bind_front(std::declval<F>(), std::declval<int&>())),
> @@ -63,29 +72,58 @@ test01()
>        >);
>  }
>  
> +struct quals
> +{
> +  bool as_const;
> +  bool as_lvalue;
> +};
> +
> +template<typename... Args>
>  void
> -test02()
> +testTarget(Args... args)
>  {
> -  struct quals
> +  struct F
>    {
> -    bool as_const;
> -    bool as_lvalue;
> +    quals operator()(Args...) & { return { false, true }; }
> +    quals operator()(Args...) const & { return { true, true }; }
> +    quals operator()(Args...) && { return { false, false }; }
> +    quals operator()(Args...) const && { return { true, false }; }
>    };
>  
> +  F f;
> +  auto g = bind_front(f, args...);
> +  const auto& cg = g;
> +  quals q;
> +
> +  // constness and value category should be forwarded to the target object:
> +  q = g();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(g)();
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = cg();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(cg)();
> +  VERIFY( q.as_const && ! q.as_lvalue );
> +}
> +
> +template<typename... Args>
> +void
> +testBoundArgs(Args... args)
> +{
>    struct F
>    {
> -    quals operator()() & { return { false, true }; }
> -    quals operator()() const & { return { true, true }; }
> -    quals operator()() && { return { false, false }; }
> -    quals operator()() const && { return { true, false }; }
> +    quals operator()(Args..., int&) const { return { false, true }; }
> +    quals operator()(Args..., int const&) const { return { true, true }; }
> +    quals operator()(Args..., int&&) const { return { false, false }; }
> +    quals operator()(Args..., int const&&) const { return { true, false }; }
>    };
>  
>    F f;
> -  auto g = bind_front(f);
> +  auto g = bind_front(f, args..., 10);
>    const auto& cg = g;
>    quals q;
>  
> -  // constness and value category should be forwarded to the target object:
> +  // constness and value category should be forwarded to the bound objects:
>    q = g();
>    VERIFY( ! q.as_const && q.as_lvalue );
>    q = std::move(g)();
> @@ -94,6 +132,70 @@ test02()
>    VERIFY( q.as_const && q.as_lvalue );
>    q = std::move(cg)();
>    VERIFY( q.as_const && ! q.as_lvalue );
> +
> +  int i = 0;
> +  auto gr = bind_front(f, args..., std::ref(i));
> +  const auto& cgr = gr;
> +
> +  // bound object is reference wrapper, converts to same type of reference
> +  q = gr();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(gr)();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = cgr();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = std::move(cgr)();
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +
> +  auto gcr = bind_front(f, args..., std::cref(i));
> +  const auto& cgcr = gcr;
> +
> +  q = gcr();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(gcr)();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = cgcr();
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = std::move(cgcr)();
> +  VERIFY( q.as_const && q.as_lvalue );
> +}
> +
> +template<typename... Args>
> +void
> +testCallArgs(Args... args)
> +{
> +  struct F
> +  {
> +    quals operator()(Args..., int&) const { return { false, true }; }
> +    quals operator()(Args..., int const&) const { return { true, true }; }
> +    quals operator()(Args..., int&&) const { return { false, false }; }
> +    quals operator()(Args..., int const&&) const { return { true, false }; }
> +  };
> +
> +  F f;
> +  auto g = bind_front(f, args...);
> +  const auto& cg = g;
> +  quals q;
> +  int i = 10;
> +  const int ci = i;
> +
> +  q = g(i);
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = g(std::move(i));
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = g(ci);
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = g(std::move(ci));
> +  VERIFY( q.as_const && ! q.as_lvalue );
> +
> +  q = cg(i);
> +  VERIFY( ! q.as_const && q.as_lvalue );
> +  q = cg(std::move(i));
> +  VERIFY( ! q.as_const && ! q.as_lvalue );
> +  q = cg(ci);
> +  VERIFY( q.as_const && q.as_lvalue );
> +  q = cg(std::move(ci));
> +  VERIFY( q.as_const && ! q.as_lvalue );
>  }
>  
>  void
> @@ -167,11 +269,51 @@ test04()
>    VERIFY( bind_front(g2, 3)() == 6 );
>  }
>  
> +struct CountedArg
> +{
> +  CountedArg() = default;
> +  CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; }
> +  CountedArg& operator=(CountedArg&&) = delete;
> +
> +  int counter = 0;
> +};
> +CountedArg const c;
> +
> +void
> +testMaterialization()
> +{
> +  struct F
> +  {
> +    int operator()(int, CountedArg arg) const
> +    { return arg.counter; };
> +  };
> +
> +  // CountedArg is bound to rvalue-reference thus moved
> +  auto f0 = std::bind_front(F{});
> +  VERIFY( f0(10, CountedArg()) == 1 );
> +
> +  auto f1 = std::bind_front(F{}, 10);
> +  VERIFY( f1(CountedArg()) == 1 );
> +}
> +
>  int
>  main()
>  {
>    test01();
> -  test02();
>    test03();
>    test04();
> +
> +  testTarget();
> +  testTarget(10);
> +  testTarget(10, 20, 30);
> +
> +  testBoundArgs();
> +  testBoundArgs(10);
> +  testBoundArgs(10, 20, 30);
> +
> +  testCallArgs();
> +  testCallArgs(10);
> +  testCallArgs(10, 20, 30);
> +
> +  testMaterialization();
>  }
> diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> index 5fe0a83baec..6694322d67e 100644
> --- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> +++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/111327.cc
> @@ -37,6 +37,17 @@ int main() {
>    g1(); // { dg-error "deleted|no match" }
>    std::move(g1)(); // { dg-error "deleted|no match" }
>    std::move(std::as_const(g1))();
> +
> +  auto f2 = std::bind_front(F{}, 42, 10);
> +  f2(); // { dg-error "deleted|no match" }
> +  std::move(f2)();
> +  std::as_const(f2)();
> +  std::move(std::as_const(f2))();
> +
> +  auto g2 = std::bind_front(G{}, 42, 10);
> +  g2(); // { dg-error "deleted|no match" }
> +  std::move(g2)(); // { dg-error "deleted|no match" }
> +  std::move(std::as_const(g2))();
>  }
>  
>  // { dg-error "no type named 'type' in 'struct std::invoke_result" "" { target c++23 } 0 }
> -- 
> 2.50.1
> 
> 


More information about the Libstdc++ mailing list