OSC++.036: std::move_only_function — Move-Only Callbacks and Resource Ownership in C++23

Woman software engineer working on modern C++ code at a desk

std::move_only_function is a C++23 type-erased callable wrapper designed for objects that can be moved but do not need to be copied.

It extends the ideas behind std::function to callbacks that own unique resources, including lambdas capturing std::unique_ptr by move.

This chapter, OSC++.036, follows OSC++.035 on std::bind_back.

Why copyability matters for callbacks

A std::function<void()> wrapper requires a copy-constructible target.

A lambda that captures a std::unique_ptr by value through move is not copyable, because its captured unique pointer cannot be copied.

This is a common issue when callbacks own sockets, file handles, task state or other exclusive resources.

std::move_only_function<void()> can hold such a lambda and transfer ownership of the wrapper to another scope.

Compile with a compiler and standard library implementing C++23 std::move_only_function, such as a sufficiently recent GCC or MSVC toolchain.

Check the library feature-test macro __cpp_lib_move_only_function (value 202110L) rather than assuming all C++23 modes include the feature.

How move-only ownership differs from std::function

A std::function object can be copied and therefore requires a copyable stored target.

A std::move_only_function object can be moved but not copied.

Both erase the concrete callable type behind a specified function signature.

Neither is automatically thread-safe; moving a callback between threads does not synchronize access to resources referenced or owned by the callback.

The following CppCon presentation surveys practical C++23 library additions, including move-only function wrappers.

It gives broader context for the language and library evolution.

Function signatures and qualifiers

Unlike std::function, std::move_only_function supports function-type signatures qualified with const, ref-qualifiers and noexcept.

These qualifiers can constrain how the stored callable may be invoked.

For example, std::move_only_function<int() const> represents a wrapper callable on a const object, while an unqualified int() signature does not impose that same const-invocation requirement.

Transferring a callback into a task queue

Move-only callbacks are particularly useful when a task queue takes ownership of work.

A queue can store std::move_only_function<void()> objects and move them out for execution.

The queue still needs suitable synchronization for concurrent producers and consumers, and the callback must not be invoked when empty.

Unlike std::function, invoking an empty std::move_only_function has undefined behavior; check its boolean state before calling.

Move semantics and the lifetime of captured resources

A move operation transfers the wrapper’s callable state without requiring that state to be copyable.

After moving from a wrapper, the source remains valid but its state is unspecified; do not rely on it being empty.

A moved-to wrapper can be tested with operator bool before invocation.

Captured pointers or references still require their own lifetime discipline.

Capturing a raw pointer to a local variable does not extend that variable’s lifetime.

For a deeper explanation of value categories, moving objects and resource ownership, the following university lecture discusses copy elision, implicit moves and exception behavior.

Its focus is broader than the C++23 wrapper but supplies the underlying model.

Selecting the right callable abstraction

Use a template parameter when the caller can supply a concrete callable type and generic code is appropriate.

Use std::function when copyable type-erased callbacks are required.

Use std::move_only_function when the owning wrapper must support noncopyable targets.

Use a function pointer when a plain function address suffices, and consider non-owning callable references only when their lifetime can be guaranteed.

The preceding chapters on std::invoke, std::bind_front and std::bind_back explain complementary callable tools.

Common mistakes and diagnostics

If a compiler reports that std::move_only_function is not a member of std, check the selected language standard, library version and header <functional>.

If copying a wrapper fails, move it instead when ownership transfer is intended.

If a callback references a destroyed object, redesign the capture or ownership strategy.

If a callback is empty, do not invoke it.

The wrapper’s presence does not guarantee the underlying operation will succeed.

Knowledge check

Why can a move-only lambda be stored here but not in std::function? Because this wrapper does not require its stored target to be copyable.

Does std::move_only_function make callback execution thread-safe? No.

What happens when an empty move-only function is invoked? The behavior is undefined; test the wrapper before invocation.

Further reading

Read the cppreference reference for std::move_only_function, the WG21 P0288R9 proposal, and std::function documentation.

Continue with related OSC++.032 and OSC++.033.

BitcoinVersus.Tech Editor’s Note: We volunteer daily to ensure the credibility of the information on this platform is Verifiably True.

If you would like to support our work and help further secure the integrity of our research initiatives, please scan the QR code below to donate 5,000 sats (best results on a laptop or larger screen).

BitcoinVersus.Tech Bitcoin donation QR

BitcoinVersus.tech is not a financial advisor.

This media platform reports on financial subjects purely for informational purposes.

Leave a Reply