P0288R9: move_only_function
P0288R9 is a proposal for C++23 that introduces std::move_only_function
, a conservative, move-only equivalent of std::function
. It doesn't have the const-correctness bug of std::function
, provides support for const
/&
/&&
/noexcept
qualified function types, doesn't have the target_type()
and target()
accessors, and imposes a strong precondition on invocation.
Motivation
std::function
is a function wrapper introduced in C++11. std::function
can store, copy, and invoke copy-constructible callable objects, which excludes move-only callable objects. std::move_only_function
addresses this issue as it doesn't provide a copy-constructor and a copy-assignment operator.
auto invocation_count = std::make_unique<std::uint64_t>(0);
// error: std::function target must be copy-constructible
std::function fn = [invocation_count = std::move(invocation_count)]() {
std::println("{}", *invocation_count);
*invocation_count += 1;
};
while (true) {
fn();
}
std::function
has a const-correctness bug. Its operator()
is const
-qualified even though the function object's operator()
might be not, so it's possible to invoke a function object's non-const
operator through a const
instance. Because std::function
owns the callable object, this bug will allow code to mutate its content through a const
-reference. std::move_only_function
addresses this issue.
// the `operator()` of the lambda expression's function object is not `const`-qualified
auto lambda = [&] mutable {};
{
std::function<void(void)> fn = lambda;
fn();
const auto &fn_ref = fn;
// the `operator()` of `lambda` is not `const`-qualified, but the invocation compiles
fn_ref();
}
{
std::move_only_function<void(void)> fn = lambda;
fn();
const auto &fn_ref = fn;
// the `operator()` of `fn_ref` is not `const`-qualified, so the invocation is invalid
fn_ref();
}
{
// the `operator()` of `lambda` is not `const`-qualified, so the constraints can't be satisfied
std::move_only_function<void(void) const> fn = lambda;
}
Design
template <typename R, typename... ArgTypes> class move_only_function;
template <typename R, typename... ArgTypes>
class move_only_function<
R(ArgTypes...) MOVE_ONLY_FUNCTION_CV
MOVE_ONLY_FUNCTION_REF noexcept(MOVE_ONLY_FUNCTION_NOEXCEPT)>;
The <functional>
header provides a partial specialization of move_only_function
for each combination of the qualifiers on the function type:
MOVE_ONLY_FUNCTION_CV
is either empty orconst
.MOVE_ONLY_FUNCTION_REF
is either empty,&
, or&&
.MOVE_ONLY_FUNCTION_NOEXCEPT
is eithertrue
orfalse
.
std::move_only_function
class template provides polymorphic wrappers that generalize the notion of a callable object. These wrappers can store, move, and call callable objects, given a call signature, allowing functions to be first-class objects. The constructor that accepts a callable object is constrained with std::is_invocable_r
or std::is_nothrow_invocable_r
. The qualifiers present in the class template parameter will be forwarded to its operator()
. The operator()
won't check if the callable object is present, so it won't throw the std::bad_function_call
exception.