Info
-
Did you know that C++23 added
bind_back
to simplify writing higher order functions?
Example
int main() {
std::cout << std::bind_front(std::divides{}, 2.)(1.); // prints 2
std::cout << std::bind_back (std::divides{}, 2.)(1.); // prints 0.5
}
Puzzle
- Can you implement a simplified version of
bind_back
to illustrate its usage with higher order functions?
[[nodiscard]] constexpr auto bind_back(auto&&...); // TODO
int main() {
using namespace boost::ut;
"bind_back"_test = [] {
expect(3._d == bind_back(std::plus{}, 1.)(2.));
expect(3._d == bind_back(std::plus{}, 2.)(1.));
expect(3._d == bind_back(std::plus{}, 1., 2.)());
expect(3._d == bind_back(std::plus{})(1., 2.));
expect(2._d == bind_back(std::divides{}, 1.)(2.));
expect(.5_d == bind_back(std::divides{}, 2.)(1.));
expect(1._d == bind_back(std::minus{}, 1.)(2.));
expect(-1._d == bind_back(std::minus{}, 2.)(1.));
};
}
Solutions
#define FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
[[nodiscard]] constexpr auto bind_back(const auto& func, auto... bound_args) {
return [=] [[nodiscard]] (auto&&... unbound_args) {
return func(FWD(unbound_args)..., bound_args...);
};
}
[[nodiscard]] constexpr auto bind_back(auto&& fn, auto&&...bound_args) {
return [=](auto&&...args){
return fn(args..., bound_args...);
};
};
[[nodiscard]] constexpr auto bind_back(auto&& fn, auto&&... back_args) {
return [fn = std::forward<decltype(fn)>(fn),
...back_args = std::forward<decltype(back_args)>(back_args)] (auto&&... front_args) {
return std::invoke(fn, front_args..., back_args...);
};
}
// From https://wg21.link/p2445
namespace proposed_std {
template <typename T, typename U>
using __override_ref_t = std::conditional_t<std::is_rvalue_reference_v<T>,
std::remove_reference_t<U> &&, U &>;
template <typename T, typename U>
using __copy_const_t = std::conditional_t<std::is_const_v<std::remove_reference_t<T>>,
U const, U>;
template <typename T, typename U>
using __forward_like_t = __override_ref_t<T &&, __copy_const_t<T, std::remove_reference_t<U>>>;
template <typename T>
[[nodiscard]] constexpr
auto forward_like(auto&& x) noexcept -> __forward_like_t<T, decltype(x)> {
return static_cast<__forward_like_t<T, decltype(x)>>(x);
}
}
template <typename F, typename... BackArgs>
[[nodiscard]] constexpr auto bind_back(F&& fn, BackArgs&&... back_args) {
return [fn = std::forward<F>(fn),
...back_args = std::forward<BackArgs>(back_args)]
<typename Self, typename... FrontArgs> (
this Self&& self, FrontArgs&&... front_args) -> decltype(auto) {
return std::invoke(
proposed_std::forward_like<Self>(fn),
std::forward<FrontArgs>(front_args)...,
proposed_std::forward_like<Self>(back_args)...);
};
}
template <typename F, typename ... BArgs>
auto bind_back_helper (F const & f, BArgs ... bound_args)
{
return [f,... bound_args = std::forward<BArgs>(bound_args)](auto... call_args){
return std::invoke_r<double>(f,call_args...,bound_args...);
};
}
template <typename ... Args>
[[nodiscard]] constexpr auto bind_back(auto &&... args)
{
return bind_back_helper<Args...>(args...);
};
[[nodiscard]] constexpr auto bind_back(auto&&... args)
{
if constexpr (sizeof...(args) == 1) {
auto [fun] = std::tuple{args...};
return [fun=fun](auto arg1, auto arg2) { return fun(arg1, arg2); };
} else if constexpr(sizeof ...(args) == 2) {
auto [fun, arg] = std::tuple{args...};
return [fun=fun, arg1=arg](auto arg2) { return fun(arg2, arg1); };
} else {
auto [fun, arg1, arg2] = std::tuple{args...};
return [fun=fun, arg1=arg1, arg2=arg2]() { return fun(arg1, arg2);};
}
}
template <typename F, typename ... Args1>
[[nodiscard]] constexpr auto bind_back( F && f, Args1 && ... args1 ){
return [&]<typename ... Args2>( Args2 && ... args2 ){
return std::invoke(f, std::forward<Args2>(args2)..., std::forward<Args1>(args1)...);
};
}
// Heavily inspired by https://stackoverflow.com/questions/64712892/mimic-stdbind-front-in-c17-for-calling-member-functions
// https://godbolt.org/z/cqPjTY
[[nodiscard]] constexpr auto bind_back(auto &&f, auto&&... args){
return [f=std::forward<decltype(f)>(f),
frontArgs = std::make_tuple(std::forward<decltype(args)>(args)...)]
(auto&&...backArgs) {
return std::apply(
f,
std::tuple_cat(
std::forward_as_tuple(backArgs...),
frontArgs
));
};
}