C++26: More Function Wrappers

ibobev1 pts0 comments

C++26: More function wrappers | Sandor Dargo's Blog<br>Sandor Dargo's Blog<br>On C++, software development and books

HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE

Blog 2026 05 20 C++26: More function wrappers Post<br>Cancel

C++26: More function wrappers<br>Sandor Dargo May 20 2026-05-20T00:00:00+02:00<br>3 min

C++26 continues to fill the gaps in our type-erased callable wrapper story. We already had std::function since C++11 and std::move_only_function since C++23, but there were still missing pieces. Now we’re getting two new additions: std::copyable_function and std::function_ref.<br>What’s wrong with std::function?<br>std::function has served us well, but it has two well-known issues. First, it can add significantly to binary size. Second, and more fundamentally, it has a const-correctness defect . Its operator() is declared const, but it can invoke a non-const operator() on the stored callable:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>// https://godbolt.org/z/9YxcW5eqK

#include<br>#include

struct Counter {<br>int counter = 0;<br>void operator()() {<br>++counter;<br>std::cout "modifying state: " counter '\n';<br>};

int main() {<br>const std::functionvoid()> f = Counter{};<br>f(); // OK (!)

This defect is baked into the original design and cannot be fixed without breaking the ABI.<br>C++23 introduced std::move_only_function (P0288R9) which fixed the const-correctness issue and added support for cv/ref/noexcept qualifiers. But as its name suggests, you can’t copy it. We still needed a wrapper that is both copyable and const-correct.<br>std::copyable_function<br>P2548R6 fills exactly that gap. Its design follows std::move_only_function closely, adding a copy constructor and copy assignment operator, and requiring that stored callables are copy-constructible.<br>The key difference from std::function is that the qualifier in the signature directly controls how operator() is declared:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>// https://godbolt.org/z/orxPK9Gn9

#include

struct Counter {<br>int count = 0;<br>int operator()() { return ++count; } // non-const<br>};

int main() {<br>// copyable_function means operator() is non-const<br>std::copyable_functionint()> f = Counter{};<br>f(); // OK, non-const call

// If you want const, you say so explicitly:<br>// std::copyable_function g = Counter{}; // ERROR!<br>// Counter::operator() is not const-qualified

std::copyable_functionint() const> h = []{ return 42; }; // OK

Just like move_only_function, it supports the full range of qualifier combinations – const, noexcept, lvalue/rvalue ref-qualifiers, and any combination thereof. This is a significant improvement over std::function, which supports none of these.<br>A copyable_function can be implicitly converted to a move_only_function, but not the other way around. Like move_only_function, it omits the rarely useful target() and target_type() member functions.<br>std::function_ref<br>P0792R14 adds a different kind of wrapper: a non-owning, type-erased callable reference . Think of it as std::string_view for callables.<br>// With function pointer -- too restrictive, no lambdas with captures:<br>payload retry(size_t times, payload(*action)());

// With template -- works but bloats binary, must be in header:<br>templateclass F><br>payload retry(size_t times, F&& action);

// With function_ref -- lightweight, non-owning, no allocation:<br>payload retry(size_t times, std::function_refpayload()> action);

It has reference semantics : there is no default constructor, no operator bool, and no nullptr comparison. A function_ref always refers to a valid callable. Every specialization is trivially copyable, meaning it can be passed in registers.<br>Assignment from non-function types is deleted to prevent dangling references - since function_ref doesn’t own the callable, assigning a temporary would be a bug.<br>It supports const and noexcept qualifiers but not ref-qualifiers, since ref-qualifiers on a reference type wouldn’t make sense.<br>A follow-up paper, P3961R1, fixes double indirection when constructing a function_ref from another function_ref, and allows assigning a noexcept-qualified function_ref to a non-noexcept one - just as you’d expect from plain function pointers.<br>Choosing the right wrapper<br>With four callable wrappers now available, here’s a quick guide:<br>function_ref : For callback parameters. Non-owning, zero overhead.move_only_function : For storing callables when you don’t need to copy them. Task queues, deferred execution.copyable_function : For storing callables when you need copies. The modern std::function replacement.std::function : Legacy. Avoid in new code.Conclusion<br>C++26 completes the type-erased callable wrapper picture. std::copyable_function gives us what std::function should have been from the start: a copyable wrapper with correct const semantics. std::function_ref fills the non-owning niche, offering a lightweight, zero-allocation alternative for callback parameters. Together with std::move_only_function, there’s no longer a reason to reach for std::function in new code.<br>Connect deeper<br>If you liked...

function const function_ref operator counter move_only_function

Related Articles