Skip to content

Latest commit

 

History

History
262 lines (196 loc) · 12.8 KB

stdcopyable_function.md

File metadata and controls

262 lines (196 loc) · 12.8 KB

std::copyable_function


Executive Abstract

std::copyable_function is a polymorphic, type-erased, copyable callable wrapper template in <functional> introduced in C++26 (via proposal P2548R6). Designed to resolve the long-standing architectural flaws of C++11's std::function (which lacked support for const, noexcept, and ref-qualified call signatures, and violated strict const-correctness), std::copyable_function provides a copyable, value-owning function container that supports full signature qualification specifiers (const, noexcept, &, &&). It is the primary abstraction for multi-subscriber event buses, broadcast logging pipelines, and GUI callback registries requiring copyable function handles with strict static type guarantees.


Motivation & Historical Context

Pre-C++26 Deficiencies: The Const-Correctness Flaw & Qualification Gap of std::function

C++11 introduced std::function<R(Args...)> as a copyable polymorphic callable container. However, as C++ evolved, std::function suffered from two severe structural defects:

1. The Const-Correctness Violation Bug

Calling a const instance of std::function allowed mutating the internal state of a non-const captured target lambda (e.g. mutable lambdas). The operator() const member function of std::function invoked a non-const operator() on its internal target object, violating the fundamental C++ invariant that const member methods must not mutate non-mutable object state!

// C++11/17 std::function Const-Correctness Flaw
int counter = 0;
auto mutable_lambda = [counter]() mutable { return ++counter; };

const std::function<int()> const_wrapper = mutable_lambda;

// C++17 DISASTER: Calling operator() const on const_wrapper MUTATES internal state!
const_wrapper(); // Compiles and mutates state despite const_wrapper being CONST!

2. The Qualification Void

std::function supported only unqualified signatures R(Args...). It could not declare that a callback was non-throwing (noexcept), or that a callback expected lvalue (&) vs. rvalue (&&) invocation.

3. The Asymmetry of C++23 std::move_only_function

C++23 introduced std::move_only_function, which added full const/noexcept/ref-qualifier signature support, but was strictly move-only (non-copyable). Multi-listener event dispatchers, GUI signal handlers, and broadcast channels requiring copyable function handles with noexcept or const guarantees had no standard solution.

Solution: ISO C++26 proposal P2548R6 introduced std::copyable_function, combining copyable value ownership with full signature qualifications (const, noexcept, &, &&) and strict const-correctness.


Technical Mechanics & Qualification Syntax

1. Extended Signature Qualification Grammar

$$\mathbf{\text{std::copyable_function< }} \text{ReturnType(Args...)} \quad [\mathbf{\text{const}}] \quad [\mathbf{\text{noexcept}}] \quad [\mathbf{\text{&}} \mid \mathbf{\text{&&}}] \quad \mathbf{>}$$

// 1. Const-qualified invocation: Requires stored target to be const-invocable!
std::copyable_function<void() const> f1;

// 2. Non-throwing invocation: Enforces that target's operator() is noexcept!
std::copyable_function<void() noexcept> f2;

// 3. Lvalue Ref-Qualified invocation: Callable only when wrapper is an lvalue
std::copyable_function<void() &> f3;

2. Comprehensive C++ Polymorphic Function Wrapper Hierarchy

                            Standard Function Wrappers
                                        │
     ┌──────────────────────────────────┼──────────────────────────────────┐
     ▼                                  ▼                                  ▼
1. std::function<R(Args...)>        2. std::move_only_function (C++23)  3. std::copyable_function (C++26)
• Copyable                          • Move-Only                         • Copyable
• NO const/noexcept qualifications  • Full const/noexcept/&/&& support  • Full const/noexcept/&/&& support
• Const-correctness violation bug!  • Small Buffer Optimization        • Fixes const-correctness bug!

Feature Matrix Comparison Table

Wrapper Class C++ Standard Copyable? Accepts Move-Only Targets? Supports const / noexcept / & / &&? Enforces Strict Const-Correctness?
std::function C++11 YES NO NO NO (Violates const)
std::move_only_function C++23 NO YES (std::unique_ptr OK) YES YES
std::copyable_function C++26 YES NO (Target must be copyable) YES YES
std::function_ref C++26 YES (Non-owning) YES (Borrowing view) YES YES

Evolution Across Standards (C++26 to C++29)

┌──────────────────────────────────────────────────────────────────┐
│ C++11 / C++14 / C++17 / C++20                                    │
├──────────────────────────────────────────────────────────────────┤
│ • std::function was the sole copyable function wrapper           │
│ • Lacked noexcept, const, and ref-qualifier signature types      │
│ • Violated const-correctness when invoking const wrappers        │
└──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ C++23                                                            │
├──────────────────────────────────────────────────────────────────┤
│ • Introduced std::move_only_function (P1651R2 / P0288R9)         │
│ • Supported qualifications, but was strictly NON-COPYABLE        │
└──────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼
┌──────────────────────────────────────────────────────────────────┐
│ C++26                                                            │
├──────────────────────────────────────────────────────────────────┤
│ • Introduced std::copyable_function in <functional> (P2548R6)    │
│ • Copyable ownership + full const/noexcept/ref-qualifier support │
│ • Fixed const-correctness invocation bugs                        │
└──────────────────────────────────────────────────────────────────┘

Comprehensive Code Examples

Example 1: Multi-Subscriber Event Dispatcher with std::copyable_function

Demonstrates storing multiple copies of a callback handle inside a broadcast event registry.

#include <iostream>
#include <vector>

// C++26 <functional> Header
#if defined(__cpp_lib_copyable_function) && __cpp_lib_copyable_function >= 202306L
#include <functional>

class EventBroadcaster {
private:
    // C++26 std::copyable_function handles can be COPIED into multiple subscriber slots!
    std::vector<std::copyable_function<void(int) const>> m_listeners;

public:
    void subscribe(std::copyable_function<void(int) const> listener) {
        m_listeners.push_back(listener);        // Copy 1 into vector
        m_listeners.push_back(std::move(listener)); // Copy 2 into vector
    }

    void dispatch(int event_id) const {
        for (const auto& listener : m_listeners) {
            listener(event_id); // Invokes const-qualified copyable function
        }
    }
};

int main() {
    EventBroadcaster bus;

    auto logger = [](int id) {
        std::cout << "  [Subscriber Log] Event " << id << " received.\n";
    };

    bus.subscribe(logger);
    bus.dispatch(1001);

    return 0;
}
#else
int main() {
    std::cout << "C++26 std::copyable_function demonstrated.\n";
    return 0;
}
#endif

Example 2: Enforcing noexcept Exception Specifications

Demonstrates how std::copyable_function<void() noexcept> enforces non-throwing exception contracts at compile time.

#include <iostream>

#if defined(__cpp_lib_copyable_function) && __cpp_lib_copyable_function >= 202306L
#include <functional>

void safe_telemetry_job() noexcept {
    std::cout << "Executing strictly non-throwing telemetry callback.\n";
}

void throwing_job() {
    throw std::runtime_error("Error!");
}

int main() {
    // 1. Valid: safe_telemetry_job is declared noexcept!
    std::copyable_function<void() noexcept> safe_task = safe_telemetry_job;
    safe_task();

    // 2. UNCOMMENTING BELOW FAILS AT COMPILE TIME (Target is NOT noexcept!):
    // std::copyable_function<void() noexcept> bad_task = throwing_job;
    // COMPILER ERROR: Target callable does not satisfy 'noexcept' specification!

    return 0;
}
#else
int main() { return 0; }
#endif

Example 3: Enforcing Strict const-Correctness

Demonstrates std::copyable_function rejecting non-const mutable closures when qualified as const.

#include <iostream>

#if defined(__cpp_lib_copyable_function) && __cpp_lib_copyable_function >= 202306L
#include <functional>

int main() {
    int state = 0;
    auto mutable_closure = [state]() mutable {
        return ++state; // Mutates local copy inside closure!
    };

    // C++26 std::copyable_function<int() const> REQUIRES the callable to be CONST-INVOCABLE!
    // UNCOMMENTING BELOW FAILS AT COMPILE TIME (Fixes C++11 std::function const bug!):
    // std::copyable_function<int() const> const_fn = mutable_closure;
    // COMPILER ERROR: Cannot bind mutable closure to const-qualified copyable_function!

    // Correct Usage: Use non-const signature for mutable closures
    std::copyable_function<int()> non_const_fn = mutable_closure;
    std::cout << "Mutable Closure Step 1: " << non_const_fn() << " (1)\n";

    return 0;
}
#else
int main() { return 0; }
#endif

Engineering Best Practices & Common Pitfalls

  1. Use std::copyable_function for Multi-Subscriber Event Registries:

    • Standardize on std::copyable_function when building broadcast event dispatchers, GUI action listeners, or multi-threaded worker pools where callbacks must be copied across multiple handler slots while enforcing noexcept or const guarantees.
  2. std::copyable_function Requires CopyConstructible Targets:

    • std::copyable_function cannot store move-only callables (such as lambdas capturing std::unique_ptr).
    • Rule: Use std::move_only_function (C++23) for move-only task queues, and std::copyable_function (C++26) when callbacks must be copyable.
  3. Check Validity (operator bool()) Before Calling:

    • Calling an unassigned std::copyable_function throws std::bad_copyable_function_call (or causes UB if qualified as noexcept). Always verify if (fn) before invocation.

Relationships

  • Related to:
  • Superseded by: None.
  • Supersedes: std::function for modern C++26 codebases due to strict const-correctness and noexcept qualification support.

Back to C++26 section.