I noticed that the std::bind_front/std::bind_back/std::not_fn that yields the perfect forwarding call wrapper all require that the function argument and argument arguments passed in must be move-constructible.
Take the standard specification for std::bind_front as an example:
template<class F, class... Args> constexpr unspecified bind_front(F&& f, Args&&... args);[...]
Mandates:
is_constructible_v<FD, F> && is_move_constructible_v<FD> && (is_constructible_v<BoundArgs, Args> && ...) && (is_move_constructible_v<BoundArgs> && ...)is
true.
Where FD is the type decay_t<F>, and BoundArgs is a pack that denotes decay_t<Args>....
Also, the legacy std::bind has the Preconditions that FD and BoundArgs must meet the Cpp17MoveConstructible requirements in [func.bind.bind].
I can understand the is_constructible_v part since we must be able to forward arguments to the decayed copy inside the wrapper. But what confuses me is why we need to require these arguments to be move-constructible?
My initial guess was that this was to make the perfect forwarding call wrapper also move-constructible, because the standard requires that the call wrapper must meet the Cpp17MoveConstructible and Cpp17Destructible requirements according to [func.require].
However, this seems wrong, because a wrapper wrapping a non-movable object can still be move-constructible if the object has a valid copy constructor:
struct OnlyCopyable {
OnlyCopyable(const OnlyCopyable&) = default;
OnlyCopyable(OnlyCopyable&&) = delete;
};
struct Wrapper {
OnlyCopyable copy1;
std::tuple<OnlyCopyable> copy2;
};
static_assert(!std::move_constructible<OnlyCopyable>);
static_assert( std::move_constructible<Wrapper>); // ok
It turns out that such extra constraint makes the standard reject the following:
#include <functional>
struct OnlyCopyable {
OnlyCopyable() = default;
OnlyCopyable(const OnlyCopyable&) = default;
OnlyCopyable(OnlyCopyable&&) = delete;
};
struct OnlyCopyableFun {
OnlyCopyableFun() = default;
OnlyCopyableFun(const OnlyCopyableFun&) = default;
OnlyCopyableFun(OnlyCopyableFun&&) = delete;
int operator()(auto) const;
};
int main() {
OnlyCopyable arg;
auto fun1 = std::bind_front([](auto) { }, arg); // ill-formed
OnlyCopyableFun fun;
auto fun2 = std::bind_front(OnlyCopyableFun, 0); // ill-formed
}
which in my opinion, it shouldn't be as I don't see the benefits of rejecting the above.
So, why does the standard require that the argument types passed into the call wrapper factory must be move-constructible? What is the rationale behind this?