Thanks to the advice I realised it would be a lot easier to implement in C++17 so here is my solution:
namespace detail {
template <typename Fn, typename... Args>
struct is_partial_functor_nothrow_constructible {
enum : bool {
a = std::is_nothrow_constructible_v<Fn, Fn&&>,
b = (std::is_nothrow_constructible_v<Args, Args&&> && ...),
value = a && b
};
};
template <typename Fn, typename... Args>
constexpr bool is_partial_functor_nothrow_constructible_v =
is_partial_functor_nothrow_constructible<Fn, Args...>::value;
}
template <typename Fn, typename... Args>
class partial_functor {
private:
Fn fn_;
std::tuple<Args...> args_;
public:
constexpr partial_functor(Fn&& fn, Args&&... args)
noexcept(detail::is_partial_functor_nothrow_constructible_v<Fn, Args...>)
: fn_(std::forward<Fn>(fn))
, args_(std::forward_as_tuple(args...))
{}
template <typename... ExArgs>
constexpr decltype(auto) operator()(ExArgs&&... ex_args)
noexcept(std::is_nothrow_invocable_v<Fn, Args..., ExArgs...>)
{
return std::apply(fn_, std::tuple_cat(
args_, std::forward_as_tuple(ex_args...)));
}
};
template <typename Fn, typename... Args>
constexpr decltype(auto) partial(Fn&& fn, Args&&... args)
noexcept(detail::is_partial_functor_nothrow_constructible_v<Fn, Args...>)
{
return partial_functor<Fn, Args...>(
std::forward<Fn>(fn),
std::forward<Args>(args)...);
}