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
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.
}
#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
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
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
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>
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>
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>
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>
#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>
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>
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>
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&>())),
>);
}
+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)();
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
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());
}
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 }
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&>())),
>);
}
+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)();
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
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();
}
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 }