{"id":51604894,"url":"https://github.com/shpegun60/delegate","last_synced_at":"2026-07-12T01:01:43.217Z","repository":{"id":348118785,"uuid":"1196571862","full_name":"shpegun60/delegate","owner":"shpegun60","description":"С++ simple delegate library","archived":false,"fork":false,"pushed_at":"2026-03-30T20:54:58.000Z","size":179,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-30T22:24:41.173Z","etag":null,"topics":["cpp","delegate","delegates"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shpegun60.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-30T20:36:24.000Z","updated_at":"2026-03-30T20:55:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shpegun60/delegate","commit_stats":null,"previous_names":["shpegun60/delegate"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/shpegun60/delegate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shpegun60%2Fdelegate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shpegun60%2Fdelegate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shpegun60%2Fdelegate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shpegun60%2Fdelegate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shpegun60","download_url":"https://codeload.github.com/shpegun60/delegate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shpegun60%2Fdelegate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35378723,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cpp","delegate","delegates"],"created_at":"2026-07-12T01:01:39.458Z","updated_at":"2026-07-12T01:01:43.158Z","avatar_url":"https://github.com/shpegun60.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tiny_delegate\n\n`tiny_delegate.hpp` is a compact C++17/C++20 callback library with three related delegate types:\n\n- `tiny::delegate_ref\u003cSig\u003e`: non-owning, cheap to copy and rebind\n- `tiny::delegate_sbo\u003cSig, InlineBytes, InlineAlign\u003e`: owning, move-only, SBO-first\n- `tiny::delegate\u003cSig, InlineBytes, InlineAlign\u003e`: hybrid, move-only, owns by default but can switch into explicit ref mode\n\nIt is aimed at projects that want a small, explicit, predictable callback abstraction without immediately defaulting to `std::function`.\n\n## Screenshot\n\n![tiny_delegate Qt demon test UI](img/qt.png)\n\n## Features\n\n- C++17/C++20 compatible\n- No exceptions required by design\n- No built-in assert policy forced on the user\n- Move-only owning delegates\n- Non-owning ref delegate\n- Small-buffer optimization\n- Optional heap fallback\n- Explicit `borrow` and `bind` APIs\n- Compile-time fit checks for size and alignment\n- Works with move-only callables\n\n## Why tiny_delegate?\n\n`tiny_delegate` is strong not because it tries to beat every callback library on every axis, but because it hits a very practical balance:\n\n- explicit lifetime semantics\n- predictable ownership\n- low-level control over size and alignment\n- simple API surface\n- good fit for embedded and systems code\n\n### What is especially good about it\n\n- It gives you three clear tools instead of one overloaded abstraction:\n  - `delegate_ref` for non-owning callback views\n  - `delegate_sbo` for owning-only callbacks\n  - `delegate` for hybrid \"automatic chooser\" behavior\n- `tiny::delegate` is unusually practical because it can switch between:\n  - function pointer path\n  - owning functor/lambda path\n  - explicit `borrow(...)` ref path\n  - explicit `bind\u003c\u0026T::method\u003e(obj)` ref path\n- `borrow` and `bind` are explicit, which makes lifetime intent visible in code instead of hidden in implicit conversions.\n- Size and alignment are first-class configuration knobs:\n  - `InlineBytes`\n  - `InlineAlign`\n  - `fits_inline`\n  - `required_inline_bytes`\n  - `required_inline_align`\n  - `static_assert_fits_inline`\n- Heap fallback can be disabled completely, which is a very strong property for embedded code.\n- Oversized or over-aligned owning callables can fail at compile time instead of surprising you at runtime.\n- Introspection is built in:\n  - `owning()`\n  - `non_owning()`\n  - `uses_inline()`\n  - `uses_heap()`\n\n### Why that matters in practice\n\nFor ordinary application code, many callback wrappers are \"good enough\".\n\nFor low-level code, firmware, hot paths, or callback-heavy infrastructure, the usual questions are:\n\n- who owns the callable?\n- is this a borrowed reference or an owned object?\n- can this allocate?\n- will this fit inline?\n- what happens if alignment is larger than expected?\n\n`tiny_delegate` answers those questions directly in the API.\n\n### Compared with common alternatives\n\n#### Compared with `llvm::function_ref`\n\n`llvm::function_ref` is a non-owning callable reference and is mainly intended for short-lived parameter use.\n\n`tiny_delegate` gives you that style through `delegate_ref`, but also adds:\n\n- owning delegates\n- SBO\n- optional heap fallback\n- explicit member binding\n- one hybrid type that can do both owning and ref modes\n\nIf you only want a tiny non-owning parameter wrapper, `llvm::function_ref` is simpler.\nIf you want one small library that covers both stored and non-stored callbacks, `tiny_delegate` is broader.\n\n#### Compared with `absl::AnyInvocable`\n\n`absl::AnyInvocable` is a move-only owning callable wrapper.\n\n`tiny_delegate` is stronger when you want:\n\n- an explicit non-owning companion type\n- explicit `borrow(...)`\n- explicit method binding\n- size and alignment surfaced as part of the public API\n- embedded-friendly \"no heap fallback\" configuration\n\nIf you only need a production-grade move-only owning wrapper inside the Abseil ecosystem, `AnyInvocable` is a strong choice.\nIf you want a unified owning + non-owning delegate toolbox, `tiny_delegate` gives you more structure.\n\n#### Compared with `function2`\n\n`function2` is a very feature-rich library. It supports copyable wrappers, move-only wrappers, non-owning views, richer qualifier handling and multi-signature overload support.\n\n`tiny_delegate` is attractive when you want something smaller and easier to reason about:\n\n- simpler mental model\n- explicit ownership split\n- direct embedded-oriented size/alignment controls\n- direct `borrow` / `bind` APIs\n\nIf you need maximum wrapper expressiveness, `function2` may be stronger.\nIf you want a smaller, more focused delegate library with very explicit semantics, `tiny_delegate` is often the nicer fit.\n\n#### Compared with ETL-style embedded delegates\n\nETL-style delegates are often very good for embedded work, especially when you want non-owning callback references.\n\n`tiny_delegate` adds a useful extra layer:\n\n- a dedicated non-owning type\n- a dedicated owning type\n- a hybrid type that can choose automatically\n- compile-time size/alignment fit tooling for owned callables\n\nThat makes it attractive when your project mixes classic embedded callback references with modern lambdas and move-only functors.\n\n### Honest limitations\n\n`tiny_delegate` is not trying to be the best at everything.\n\nOther libraries may be better if you need:\n\n- copyable owning delegates as the main API\n- allocator-aware wrappers\n- multiple overload signatures in one wrapper type\n- full cv/ref/noexcept-qualified wrapper signatures\n- a very large existing ecosystem around the callback type\n\nIts strength is not \"maximum feature count\".\nIts strength is clarity, predictability, and a very good ownership model for real systems code.\n\n## Header\n\n```cpp\n#include \"tiny_delegate.hpp\"\n```\n\n## The Three Main Types\n\n### `tiny::delegate_ref\u003cSig\u003e`\n\nNon-owning callback view.\n\n- Stores a free-function pointer, or\n- stores a pointer to an external callable/object\n\nProperties:\n\n- copyable\n- does not manage lifetime\n- very cheap to copy and reassign\n\nUse it when:\n\n- the target object definitely outlives the callback\n- you want maximum cheapness\n- you do not want ownership at all\n\n### `tiny::delegate_sbo\u003cSig, InlineBytes, InlineAlign\u003e`\n\nOwning callback.\n\nProperties:\n\n- move-only\n- owns the callable\n- stores inline when it fits\n- can optionally fall back to heap if enabled\n- has no borrow/ref mode\n\nUse it when:\n\n- you want ownership only\n- you want the type itself to communicate \"this callback owns its target\"\n- you want SBO behavior without the hybrid ref path\n\n### `tiny::delegate\u003cSig, InlineBytes, InlineAlign\u003e`\n\nHybrid callback.\n\nProperties:\n\n- move-only\n- function pointer path for free functions and captureless non-generic lambdas\n- owning path for normal lambdas/functors\n- explicit ref path via `tiny::borrow(x)`\n- explicit ref path via `tiny::bind\u003c\u0026T::method\u003e(obj)`\n\nUse it when:\n\n- you want one type for most callback use cases\n- sometimes you want ownership, sometimes borrow/bind\n\n## Which Type Should I Use?\n\n| Need | Recommended type |\n|---|---|\n| Pure non-owning callback view | `tiny::delegate_ref\u003cSig\u003e` |\n| Always own the callable | `tiny::delegate_sbo\u003cSig, ...\u003e` |\n| One general-purpose callback type | `tiny::delegate\u003cSig, ...\u003e` |\n| Embedded code with strict lifetime discipline | `tiny::delegate_ref\u003cSig\u003e` or `tiny::delegate\u003cSig, ...\u003e` with heap fallback off |\n\n## Capability Matrix\n\n| Source / behavior | `delegate_ref` | `delegate_sbo` | `delegate` |\n|---|---|---|---|\n| Free function pointer | yes | yes | yes |\n| Captureless non-generic lambda | yes, through function-pointer conversion | yes | yes |\n| Stateful lambda | yes, only via `tiny::borrow(lvalue)` | yes, as owned callable | yes, as owned callable |\n| Move-only lambda | no | yes | yes |\n| Functor object | yes, only via `tiny::borrow(lvalue)` | yes | yes |\n| Direct `tiny::borrow(...)` API | yes | no | yes |\n| Direct method-bind API | yes | no | yes |\n| Copyable type | yes | no | no |\n| Can use heap fallback | no | yes | yes |\n| Can be in explicit ref mode | yes | no | yes |\n\n## What Each Type Can Be Constructed From\n\n### `delegate_ref`\n\nAccepted forms:\n\n- free-function pointer\n- captureless non-generic lambda, via conversion to function pointer\n- `tiny::borrow(functor_lvalue)`\n- `tiny::borrow(generic_lambda_lvalue)`\n- `tiny::delegate_ref\u003cSig\u003e::bind\u003c\u0026T::method\u003e(obj)`\n\nExamples:\n\n```cpp\nint plus_one(int x) { return x + 1; }\n\nstruct Functor {\n    int operator()(int x) { return x + 10; }\n};\n\nstruct Device {\n    int read(int x) { return x + 100; }\n};\n\nFunctor fn;\nDevice dev;\n\ntiny::delegate_ref\u003cint(int)\u003e a = \u0026plus_one;\ntiny::delegate_ref\u003cint(int)\u003e b = tiny::borrow(fn);\ntiny::delegate_ref\u003cint(int)\u003e c =\n    tiny::delegate_ref\u003cint(int)\u003e::bind\u003c\u0026Device::read\u003e(dev);\n```\n\nNot supported:\n\n- owning a temporary lambda/functor\n- move-only callable ownership\n- heap fallback\n\n### `delegate_sbo`\n\nAccepted forms:\n\n- free-function pointer\n- captureless non-generic lambda\n- generic lambda, as owned callable\n- stateful lambda\n- move-only lambda\n- functor object\n\nExamples:\n\n```cpp\ntiny::delegate_sbo\u003cint(int), 64\u003e a = \u0026plus_one;\ntiny::delegate_sbo\u003cint(int), 64\u003e b = [](int x) { return x + 2; };\ntiny::delegate_sbo\u003cint(int), 64\u003e c = [sum = 0](int x) mutable { return sum += x; };\ntiny::delegate_sbo\u003cint(int), 64\u003e d = Functor{};\n```\n\nNot supported:\n\n- `tiny::borrow(...)`\n- `tiny::bind\u003c\u0026T::method\u003e(obj)`\n- ref mode of any kind\n\nIf you really need to call an external object through `delegate_sbo`, you can still wrap it manually:\n\n```cpp\nstruct Device {\n    int read(int x) { return x + 1; }\n};\n\nDevice dev;\n\ntiny::delegate_sbo\u003cint(int), 64\u003e cb = [\u0026dev](int x) {\n    return dev.read(x);\n};\n```\n\nBut note what this means:\n\n- `delegate_sbo` owns the lambda wrapper\n- it does not own `dev`\n- `dev` must still outlive callback use\n- this is not the same as library-level `borrow` / `bind`\n\n### `delegate`\n\nAccepted forms:\n\n- free-function pointer\n- captureless non-generic lambda\n- generic lambda, as owned callable\n- stateful lambda\n- move-only lambda\n- functor object\n- `tiny::borrow(functor_lvalue)`\n- `tiny::bind\u003c\u0026T::method\u003e(obj)`\n\nExamples:\n\n```cpp\ntiny::delegate\u003cint(int)\u003e a = \u0026plus_one;\ntiny::delegate\u003cint(int)\u003e b = [](int x) { return x + 2; };\ntiny::delegate\u003cint(int)\u003e c = [sum = 0](int x) mutable { return sum += x; };\ntiny::delegate\u003cint(int)\u003e d = Functor{};\ntiny::delegate\u003cint(int)\u003e e = tiny::borrow(fn);\ntiny::delegate\u003cint(int)\u003e f = tiny::bind\u003c\u0026Device::read\u003e(dev);\n```\n\nThis is the \"automatic chooser\" type:\n\n- if the input behaves like a function pointer, it uses the function-pointer path\n- if the input is a normal callable object, it owns it\n- if the input is `borrow(...)`, it becomes non-owning\n- if the input is `bind(...)`, it becomes non-owning method binding\n\n## Configuration Macros\n\nDefine these before including the header.\n\n```cpp\n#define TINY_DELEGATE_DEFAULT_BYTES 64\n#define TINY_DELEGATE_DEFAULT_ALIGN alignof(std::max_align_t)\n#define TINY_DELEGATE_ENABLE_HEAP_FALLBACK 0\n#define TINY_DELEGATE_ASSERT(expr, msg) ((void)0)\n#include \"tiny_delegate.hpp\"\n```\n\n### `TINY_DELEGATE_DEFAULT_BYTES`\n\nDefault inline byte capacity used by:\n\n- `tiny::delegate\u003cSig\u003e`\n- free `tiny::bind\u003c\u0026T::method\u003e(obj)` helper\n\nDefault:\n\n```cpp\n64\n```\n\n### `TINY_DELEGATE_DEFAULT_ALIGN`\n\nDefault inline alignment used by `tiny::delegate\u003cSig\u003e`.\n\nDefault:\n\n```cpp\nalignof(std::max_align_t)\n```\n\n### `TINY_DELEGATE_ENABLE_HEAP_FALLBACK`\n\nControls behavior when an owning callable does not fit inline.\n\n- `0`: compile-time failure\n- `1`: allocate on heap\n\nDefault:\n\n```cpp\n0\n```\n\n### `TINY_DELEGATE_ASSERT(expr, msg)`\n\nUser hook for runtime assertions.\n\nDefault:\n\n```cpp\n#define TINY_DELEGATE_ASSERT(expr, msg) ((void)0)\n```\n\nExample:\n\n```cpp\n#include \u003ccassert\u003e\n#define TINY_DELEGATE_ASSERT(expr, msg) assert((expr) \u0026\u0026 (msg))\n#include \"tiny_delegate.hpp\"\n```\n\n## Quick Start\n\n```cpp\n#include \"tiny_delegate.hpp\"\n\nint plus_one(int x) { return x + 1; }\n\nstruct Worker {\n    int base = 10;\n    int add(int x) { return base + x; }\n};\n\nint main() {\n    tiny::delegate\u003cint(int)\u003e a = \u0026plus_one;\n\n    Worker w{20};\n    tiny::delegate\u003cint(int)\u003e b = tiny::bind\u003c\u0026Worker::add\u003e(w);\n\n    auto counter = [sum = 0](int x) mutable {\n        sum += x;\n        return sum;\n    };\n    tiny::delegate\u003cint(int)\u003e c = counter;\n\n    return a(1) + b(2) + c(3);\n}\n```\n\n## About `auto`\n\nYes, you can use `auto` for local variables when the initializer already creates a delegate object.\n\nExamples:\n\n```cpp\nauto counter = [sum = 0](int x) mutable { return sum += x; };\nWorker w{20};\n\nauto a = tiny::delegate\u003cint(int)\u003e{\u0026plus_one};\nauto b = tiny::delegate_sbo\u003cint(int), 64\u003e{[](int x) { return x + 2; }};\nauto c = tiny::delegate_ref\u003cint(int)\u003e{tiny::borrow(counter)};\nauto d = tiny::bind\u003c\u0026Worker::add\u003e(w);\n```\n\nImportant:\n\n```cpp\nauto cb = \u0026plus_one;\n```\n\nThis is only a raw function pointer:\n\n```cpp\nint (*)(int)\n```\n\nIt is not a `tiny::delegate`.\n\nSo these are different:\n\n```cpp\nauto a = \u0026plus_one;                         // raw function pointer\nauto b = tiny::delegate\u003cint(int)\u003e{\u0026plus_one}; // delegate object\n```\n\nPractical rule:\n\n- for API surface, class members, typedefs and examples that teach the library, prefer the explicit delegate type\n- for local variables, `auto` is fine if the initializer already makes the delegate type obvious\n\n## Basic Semantics\n\n### Empty delegates\n\n```cpp\ntiny::delegate\u003cvoid()\u003e cb;\n\nif (!cb) {\n    // empty\n}\n\ncb = nullptr;\n```\n\nThe same idea works for:\n\n- `tiny::delegate_ref\u003cSig\u003e`\n- `tiny::delegate_sbo\u003cSig, ...\u003e`\n- `tiny::delegate\u003cSig, ...\u003e`\n\nCalling an empty delegate is only guarded by `TINY_DELEGATE_ASSERT`, so if your project wants hard failure on misuse, override that macro.\n\n### Copy and move rules\n\n- `tiny::delegate_ref\u003cSig\u003e` is copyable\n- `tiny::delegate_sbo\u003cSig, ...\u003e` is move-only\n- `tiny::delegate\u003cSig, ...\u003e` is move-only\n\nOwning delegates are move-only so they can hold move-only callables.\n\n## Usage Examples\n\n### 1. Free function\n\n```cpp\nint on_value(int x) { return x + 5; }\n\ntiny::delegate\u003cint(int)\u003e cb = \u0026on_value;\nint y = cb(7); // 12\n```\n\nThis uses the function-pointer path.\n\n### 2. Captureless non-generic lambda\n\n```cpp\ntiny::delegate\u003cint(int)\u003e cb = [](int x) { return x * 2; };\nint y = cb(9); // 18\n```\n\nBecause the lambda is captureless and non-generic, it converts to a function pointer.\n\n### 3. Stateful lambda with owned copy\n\n```cpp\nauto stateful = [sum = 0](int x) mutable {\n    sum += x;\n    return sum;\n};\n\ntiny::delegate\u003cint(int)\u003e cb = stateful;\n\nint a = cb(1);      // 1\nint b = cb(2);      // 3\nint c = stateful(5); // 5, separate state\n```\n\nImportant:\n\n- `stateful` keeps its own state\n- `cb` stores its own copied state\n\nThis is expected behavior.\n\n### 4. Move-only lambda\n\n```cpp\n#include \u003cmemory\u003e\n\ntiny::delegate\u003cint(int)\u003e cb = [ptr = std::make_unique\u003cint\u003e(40)](int x) {\n    return *ptr + x;\n};\n\nint y = cb(2); // 42\n```\n\nAlso works with `delegate_sbo`:\n\n```cpp\ntiny::delegate_sbo\u003cint(int), 64\u003e cb = [ptr = std::make_unique\u003cint\u003e(7)](int x) {\n    return *ptr + x;\n};\n```\n\n### 5. Pure non-owning callable with `delegate_ref`\n\n```cpp\nstruct Accumulator {\n    int total = 0;\n\n    int operator()(int x) {\n        total += x;\n        return total;\n    }\n};\n\nAccumulator acc;\ntiny::delegate_ref\u003cint(int)\u003e ref = tiny::borrow(acc);\n\nint a = ref(3); // 3\nint b = ref(4); // 7\n```\n\n`ref` does not own `acc`.\n\n### 6. Non-owning callable with `delegate`\n\n```cpp\nAccumulator acc;\ntiny::delegate\u003cint(int)\u003e cb = tiny::borrow(acc);\n\nint a = cb(3); // 3\nint b = cb(4); // 7\n\nbool is_ref = cb.non_owning(); // true\nbool own = cb.owning();        // false\nbool inl = cb.uses_inline();   // false\nbool heap = cb.uses_heap();    // false\n```\n\nThis is the hybrid delegate in explicit ref mode.\n\n### 7. Borrowing a const functor\n\n```cpp\nstruct Adder {\n    int bias = 10;\n    int operator()(int x) const { return bias + x; }\n};\n\nconst Adder add{};\ntiny::delegate\u003cint(int)\u003e cb = tiny::borrow(add);\n\nint y = cb(5); // 15\n```\n\nThis works because the callable is invocable as `const`.\n\n### 8. Binding a non-const member function\n\n```cpp\nstruct Device {\n    int base = 100;\n    int read(int x) { return base + x; }\n};\n\nDevice dev{100};\ntiny::delegate\u003cint(int)\u003e cb = tiny::bind\u003c\u0026Device::read\u003e(dev);\n\nint y = cb(23); // 123\n```\n\n### 9. Binding a const member function\n\n```cpp\nstruct Sensor {\n    int base = 50;\n    int sample(int x) const { return base + x; }\n};\n\nconst Sensor s{50};\ntiny::delegate\u003cint(int)\u003e cb = tiny::bind\u003c\u0026Sensor::sample\u003e(s);\n\nint y = cb(2); // 52\n```\n\n### 10. `delegate_ref::bind`\n\nIf you want a pure ref delegate for a method:\n\n```cpp\nstruct Driver {\n    void tick() {}\n};\n\nDriver d;\ntiny::delegate_ref\u003cvoid()\u003e ref =\n    tiny::delegate_ref\u003cvoid()\u003e::bind\u003c\u0026Driver::tick\u003e(d);\n```\n\n### 11. Owning-only delegate with `delegate_sbo`\n\n```cpp\nusing Task = tiny::delegate_sbo\u003cvoid(), 32\u003e;\n\nTask t = [] {\n    // owned callable\n};\n```\n\nThis type never enters borrow/bind ref mode.\n\n### 12. Custom-sized owning delegate\n\n```cpp\nusing SmallCb = tiny::delegate\u003cint(int), 16\u003e;\n\nSmallCb cb = [](int x) { return x + 4; };\nint y = cb(3); // 7\n```\n\nIf the callable does not fit and heap fallback is off, compilation fails.\n\n### 13. Over-aligned callable\n\nSometimes a callable fits by size but not by alignment.\n\n```cpp\nstruct alignas(32) BigAlign {\n    int operator()(int x) const { return x + 1; }\n};\n```\n\nOn many platforms the default inline alignment is `16`, so this does not fit inline by default.\n\nUse a larger alignment if you want inline storage:\n\n```cpp\nusing AlignedCb = tiny::delegate\u003cint(int), 64, 32\u003e;\n\nAlignedCb cb = BigAlign{};\nint y = cb(5); // 6\n```\n\nThe same idea applies to `tiny::delegate_sbo`.\n\n### 14. Compile-time fit checks\n\n```cpp\nstruct MyFunctor {\n    int operator()(int) const { return 0; }\n};\n\nusing D = tiny::delegate\u003cint(int), 64\u003e;\n\nstatic_assert(D::fits_inline\u003cMyFunctor\u003e());\nstatic_assert(D::required_inline_bytes\u003cMyFunctor\u003e() == sizeof(MyFunctor));\nstatic_assert(D::required_inline_align\u003cMyFunctor\u003e() == alignof(MyFunctor));\n```\n\nIf you want a clearer compile-time failure:\n\n```cpp\nusing D = tiny::delegate\u003cint(int), 64, 16\u003e;\nD::static_assert_fits_inline\u003cMyFunctor\u003e();\n```\n\nAlso available on `tiny::delegate_sbo`.\n\n### 15. Oversized callable with heap fallback on\n\n```cpp\n#define TINY_DELEGATE_ENABLE_HEAP_FALLBACK 1\n#include \"tiny_delegate.hpp\"\n\nstruct Large {\n    char payload[256]{};\n    int operator()(int x) const { return x + 1; }\n};\n\ntiny::delegate\u003cint(int), 64\u003e cb = Large{};\n\nbool heap = cb.uses_heap();    // true\nbool inl = cb.uses_inline();   // false\n```\n\nThe same pattern works for `tiny::delegate_sbo`.\n\n### 16. Oversized callable with heap fallback off\n\nWith the default policy:\n\n```cpp\n#define TINY_DELEGATE_ENABLE_HEAP_FALLBACK 0\n#include \"tiny_delegate.hpp\"\n```\n\nan oversized or over-aligned owning callable becomes a compile-time error.\n\nThis is often exactly what embedded code wants.\n\n### 17. Resetting with `nullptr`\n\n```cpp\ntiny::delegate\u003cvoid()\u003e cb = [] {};\ncb = nullptr;\n\nif (!cb) {\n    // empty again\n}\n```\n\nThe same works for:\n\n- `tiny::delegate_ref\u003cSig\u003e`\n- `tiny::delegate_sbo\u003cSig, ...\u003e`\n\n### 18. Rebinding\n\n```cpp\nint plus_one(int x) { return x + 1; }\nint plus_two(int x) { return x + 2; }\n\ntiny::delegate\u003cint(int)\u003e cb = \u0026plus_one;\ncb = \u0026plus_two;\ncb = nullptr;\ncb = \u0026plus_one;\n```\n\nRebinding an owning delegate destroys the previous owned target and installs the new one.\n\n### 19. `sig_of_t` for signature deduction\n\n```cpp\nint plus_one(int x) { return x + 1; }\n\nstruct Worker {\n    int run(int x) const { return x; }\n};\n\nusing Sig1 = tiny::sig_of_t\u003cdecltype(\u0026plus_one)\u003e;     // int(int)\nusing Sig2 = tiny::sig_of_t\u003cdecltype(\u0026Worker::run)\u003e;  // int(int)\n```\n\nThe free `tiny::bind\u003c\u0026T::method\u003e(obj)` helper uses this same idea internally.\n\n### 20. Free `bind` vs typed `bind`\n\nFree helper:\n\n```cpp\nauto cb = tiny::bind\u003c\u0026Device::read\u003e(dev);\n```\n\nReturn type:\n\n```cpp\ntiny::delegate\u003ctiny::sig_of_t\u003cdecltype(\u0026Device::read)\u003e\u003e\n```\n\nThat means the free helper uses the global defaults:\n\n- `TINY_DELEGATE_DEFAULT_BYTES`\n- `TINY_DELEGATE_DEFAULT_ALIGN`\n\nIf you need custom inline capacity or alignment, use the typed form:\n\n```cpp\nusing FastCb = tiny::delegate\u003cint(int), 32, 32\u003e;\nFastCb cb = FastCb::bind\u003c\u0026Device::read\u003e(dev);\n```\n\n### 21. Generic lambda\n\nGeneric lambdas are not function pointers, but they still work as callable objects if the requested signature is valid.\n\nOwned case:\n\n```cpp\nauto generic = [](auto x) { return x + 1; };\n\ntiny::delegate\u003cint(int)\u003e a = generic;\ntiny::delegate_sbo\u003cint(int), 64\u003e b = generic;\n```\n\nBorrowed case:\n\n```cpp\ntiny::delegate_ref\u003cint(int)\u003e ref = tiny::borrow(generic);\ntiny::delegate\u003cint(int)\u003e borrowed = tiny::borrow(generic);\n```\n\nThe important distinction is:\n\n- captureless non-generic lambda can use the function-pointer path\n- generic lambda is treated like a normal functor object\n\n## Example Cookbook\n\nThis section is intentionally repetitive. It is here so the README can be used as a quick reference when you are trying to remember \"can I do X with this delegate type?\".\n\n### 22. Void callback\n\n```cpp\ntiny::delegate\u003cvoid()\u003e cb = [] {\n    // do something\n};\n\nif (cb) {\n    cb();\n}\n```\n\n### 23. Callback with multiple arguments\n\n```cpp\ntiny::delegate\u003cint(int, int)\u003e add = [](int a, int b) {\n    return a + b;\n};\n\nint sum = add(2, 3); // 5\n```\n\n### 24. Store a delegate as a class member\n\n```cpp\nclass JobQueue {\npublic:\n    using DoneCb = tiny::delegate\u003cvoid(int), 32\u003e;\n\n    void setDoneCallback(DoneCb cb) {\n        done_ = std::move(cb);\n    }\n\n    void finish(int code) {\n        if (done_) done_(code);\n    }\n\nprivate:\n    DoneCb done_;\n};\n```\n\n### 25. Pass a delegate into a function\n\n```cpp\nusing Callback = tiny::delegate\u003cvoid(int), 32\u003e;\n\nvoid subscribe(Callback cb) {\n    if (cb) cb(42);\n}\n\nsubscribe([](int value) {\n    // value == 42\n});\n```\n\n### 26. Return a delegate from a factory\n\n```cpp\ntiny::delegate\u003cint(int)\u003e make_scaler(int base) {\n    return [base](int x) {\n        return base * x;\n    };\n}\n\nauto cb = make_scaler(3);\nint y = cb(4); // 12\n```\n\n### 27. Return a bound method\n\n```cpp\nstruct Device {\n    int base = 7;\n    int read(int x) const { return base + x; }\n};\n\ntiny::delegate\u003cint(int)\u003e make_reader(const Device\u0026 dev) {\n    return tiny::bind\u003c\u0026Device::read\u003e(dev);\n}\n```\n\nThe object must still outlive the returned delegate.\n\n### 28. `delegate_ref` copied to multiple views\n\n```cpp\nstruct Counter {\n    int total = 0;\n\n    int operator()(int x) {\n        total += x;\n        return total;\n    }\n};\n\nCounter counter;\n\ntiny::delegate_ref\u003cint(int)\u003e a = tiny::borrow(counter);\ntiny::delegate_ref\u003cint(int)\u003e b = a;\n\nint x = a(2); // 2\nint y = b(3); // 5\n```\n\nBoth refs target the same external object.\n\n### 29. Switch one `delegate` between owning and ref modes\n\n```cpp\nstruct Device {\n    int base = 100;\n    int operator()(int x) { return base + x; }\n    int read(int x) { return base + x; }\n};\n\nDevice dev;\ntiny::delegate\u003cint(int)\u003e cb = [](int x) { return x + 1; }; // owning\n\ncb = tiny::borrow(dev); // ref mode through operator()\ncb = tiny::bind\u003c\u0026Device::read\u003e(dev); // ref mode by method binding\ncb = [value = 5](int x) { return value + x; }; // back to owning\n```\n\n`tiny::delegate` is the only one of the three types that can switch like this.\n\n### 30. Capture by reference\n\n```cpp\nint external = 10;\n\ntiny::delegate\u003cint()\u003e cb = [\u0026external] {\n    return external;\n};\n\nexternal = 20;\nint y = cb(); // 20\n```\n\nThe delegate owns the lambda object, but the lambda object still refers to `external`.\n\n### 31. Mutable lambda with internal state\n\n```cpp\ntiny::delegate\u003cint(int)\u003e cb = [count = 0](int x) mutable {\n    count += x;\n    return count;\n};\n\nint a = cb(1); // 1\nint b = cb(1); // 2\nint c = cb(5); // 7\n```\n\n### 32. Method callback for a scheduler\n\n```cpp\nstruct Task {\n    void run() {\n        // do work\n    }\n};\n\nclass Scheduler {\npublic:\n    using Callback = tiny::delegate\u003cvoid(), 32\u003e;\n\n    void set(Callback cb) {\n        cb_ = std::move(cb);\n    }\n\n    void tick() {\n        if (cb_) cb_();\n    }\n\nprivate:\n    Callback cb_;\n};\n\nTask task;\nScheduler sch;\nsch.set(tiny::bind\u003c\u0026Task::run\u003e(task));\n```\n\n### 33. Ref-only callback for a scheduler\n\n```cpp\nstruct Task {\n    void run() {}\n};\n\nclass Scheduler {\npublic:\n    using Callback = tiny::delegate_ref\u003cvoid()\u003e;\n\n    void set(Callback cb) { cb_ = cb; }\n    void tick() { if (cb_) cb_(); }\n\nprivate:\n    Callback cb_;\n};\n\nTask task;\nScheduler sch;\nsch.set(tiny::delegate_ref\u003cvoid()\u003e::bind\u003c\u0026Task::run\u003e(task));\n```\n\n### 34. Use `delegate_sbo` when ownership must be explicit\n\n```cpp\nclass WorkerPool {\npublic:\n    using Job = tiny::delegate_sbo\u003cvoid(), 48\u003e;\n\n    void setJob(Job job) {\n        job_ = std::move(job);\n    }\n\n    void execute() {\n        if (job_) job_();\n    }\n\nprivate:\n    Job job_;\n};\n```\n\n### 35. Check whether heap fallback was used\n\n```cpp\n#define TINY_DELEGATE_ENABLE_HEAP_FALLBACK 1\n#include \"tiny_delegate.hpp\"\n\nstruct Large {\n    char payload[256]{};\n    void operator()() const {}\n};\n\ntiny::delegate\u003cvoid(), 64\u003e cb = Large{};\n\nif (cb.uses_heap()) {\n    // oversized callable went to heap\n}\n```\n\nOnly meaningful when heap fallback is enabled.\n\n### 36. Check whether `delegate_sbo` stayed inline\n\n```cpp\ntiny::delegate_sbo\u003cvoid(), 64\u003e cb = [] {\n    // small callable\n};\n\nbool inl = cb.uses_inline();\nbool heap = cb.uses_heap();\n```\n\n### 37. Use the convenience aliases\n\n```cpp\ntiny::delegate64\u003cvoid()\u003e a = [] {};\ntiny::delegate32\u003cint(int)\u003e b = [](int x) { return x + 1; };\n\ntiny::delegate_sbo64\u003cvoid()\u003e c = [] {};\ntiny::delegate_sbo32\u003cint(int)\u003e d = [](int x) { return x + 2; };\n```\n\nThese are only shorthand for common byte sizes.\n\n### 38. Explicit typed `bind` when you need custom alignment\n\n```cpp\nstruct Device {\n    int read(int x) const { return x + 1; }\n};\n\nusing Callback = tiny::delegate\u003cint(int), 64, 32\u003e;\n\nDevice dev;\nCallback cb = Callback::bind\u003c\u0026Device::read\u003e(dev);\n```\n\n### 39. Temporary objects are intentionally rejected for `borrow`\n\n```cpp\nauto ok_lambda = [count = 0](int x) mutable { return count += x; };\nauto ok = tiny::borrow(ok_lambda); // ok\n\n// auto bad = tiny::borrow([count = 0](int x) mutable { return count += x; });\n// error: cannot borrow a temporary\n```\n\n### 40. Temporary objects are intentionally rejected for `bind`\n\n```cpp\nstruct Device {\n    int read(int x) const { return x + 1; }\n};\n\nDevice dev;\nauto ok = tiny::bind\u003c\u0026Device::read\u003e(dev); // ok\n\n// auto bad = tiny::bind\u003c\u0026Device::read\u003e(Device{});\n// error: cannot bind a temporary\n```\n\n### 41. Generic lambda with explicit borrow\n\n```cpp\nauto generic = [](auto x) { return x + 10; };\n\ntiny::delegate_ref\u003cint(int)\u003e ref = tiny::borrow(generic);\ntiny::delegate\u003cint(int)\u003e cb = tiny::borrow(generic);\n```\n\nThis stays non-owning.\n\n### 42. Generic lambda as owned callback\n\n```cpp\nauto generic = [](auto x) { return x * 2; };\n\ntiny::delegate\u003cint(int)\u003e cb = generic;\ntiny::delegate_sbo\u003cint(int), 64\u003e box = generic;\n```\n\nThis stores the generic lambda as a normal callable object.\n\n### 43. Reconfigure the same API between `delegate_ref` and `delegate`\n\n```cpp\nstruct Device {\n    int read(int x) { return x + 1; }\n};\n\nclass FastApi {\npublic:\n    using Callback = tiny::delegate_ref\u003cint(int)\u003e;\n    void set(Callback cb) { cb_ = cb; }\nprivate:\n    Callback cb_;\n};\n\nclass FlexibleApi {\npublic:\n    using Callback = tiny::delegate\u003cint(int), 32\u003e;\n    void set(Callback cb) { cb_ = std::move(cb); }\nprivate:\n    Callback cb_;\n};\n```\n\nSame overall pattern, different ownership model.\n\n### 44. Factory with `auto` and explicit delegate construction\n\n```cpp\nint plus_one(int x) { return x + 1; }\n\nauto make_callback() {\n    return tiny::delegate\u003cint(int)\u003e{\u0026plus_one};\n}\n```\n\nThis is a good use of `auto`, because the returned object is already a delegate.\n\n### 45. Local `auto` from free `bind`\n\n```cpp\nstruct Device {\n    int read(int x) const { return x + 5; }\n};\n\nDevice dev;\nauto cb = tiny::bind\u003c\u0026Device::read\u003e(dev);\n```\n\nHere `auto` is fine because `tiny::bind` already returns a delegate object.\n\n### 46. Local `auto` that is not a delegate\n\n```cpp\nint plus_one(int x) { return x + 1; }\n\nauto raw = \u0026plus_one; // raw function pointer, not tiny::delegate\n```\n\nThis is the main `auto` pitfall to remember.\n\n## Introspection APIs\n\n### `tiny::delegate`\n\n```cpp\ntiny::delegate\u003cint(int)\u003e cb;\n\nbool empty = !cb;\nbool ref = cb.non_owning();\nbool own = cb.owning();\nbool inl = cb.uses_inline();\nbool heap = cb.uses_heap();\n```\n\nMeaning:\n\n- `non_owning()`: callback is in borrow/bind ref mode\n- `owning()`: callback owns something\n- `uses_inline()`: callback owns inline storage\n- `uses_heap()`: callback owns heap storage\n\nImportant:\n\nIf `cb` is in ref mode, then:\n\n```cpp\ncb.non_owning(); // true\ncb.owning();     // false\ncb.uses_inline();// false\ncb.uses_heap();  // false\n```\n\n`uses_inline()` is intentionally about owned inline storage, not \"anything that is not heap\".\n\n### `tiny::delegate_sbo`\n\n```cpp\ntiny::delegate_sbo\u003cint(int), 64\u003e cb = [](int x) { return x + 1; };\n\nbool inl = cb.uses_inline();\nbool heap = cb.uses_heap();\n```\n\n## Practical Patterns\n\n### Event subscription\n\n```cpp\nclass Button {\npublic:\n    using Callback = tiny::delegate\u003cvoid(), 32\u003e;\n\n    void setOnClick(Callback cb) {\n        onClick_ = std::move(cb);\n    }\n\n    void click() {\n        if (onClick_) onClick_();\n    }\n\nprivate:\n    Callback onClick_;\n};\n```\n\nUsage:\n\n```cpp\nButton b;\nb.setOnClick([] {\n    // ...\n});\n```\n\n### Ref-only hot path\n\n```cpp\nclass Dispatcher {\npublic:\n    using RxCb = tiny::delegate_ref\u003cvoid(const std::uint8_t*, std::size_t)\u003e;\n\n    void setHandler(RxCb cb) { handler_ = cb; }\n\n    void receive(const std::uint8_t* data, std::size_t size) {\n        if (handler_) handler_(data, size);\n    }\n\nprivate:\n    RxCb handler_;\n};\n```\n\nThis is a good fit when:\n\n- lifetime is guaranteed externally\n- rebinding should be cheap\n- you want no ownership overhead\n\n### Embedded / STM32-friendly policy\n\n```cpp\n#define TINY_DELEGATE_ENABLE_HEAP_FALLBACK 0\n#define TINY_DELEGATE_DEFAULT_BYTES 64\n#define TINY_DELEGATE_DEFAULT_ALIGN alignof(std::max_align_t)\n#include \"tiny_delegate.hpp\"\n```\n\nEffect:\n\n- small callables fit inline\n- oversized callables fail at compile time\n- no hidden heap allocation\n\nFor extremely hot paths with stable lifetime, `tiny::delegate_ref` is often the cleanest choice.\n\n## Lifetime Rules\n\nThis section is the most important one.\n\n### `tiny::borrow(x)` requires a long-lived lvalue\n\nCorrect:\n\n```cpp\nauto counter = [sum = 0](int x) mutable { return sum += x; };\ntiny::delegate\u003cint(int)\u003e cb = tiny::borrow(counter);\n```\n\nWrong:\n\n```cpp\ntiny::delegate\u003cint(int)\u003e cb =\n    tiny::borrow([sum = 0](int x) mutable { return sum += x; }); // error\n```\n\nBorrowing a temporary is forbidden.\n\n### `tiny::bind\u003c\u0026T::method\u003e(obj)` requires a long-lived lvalue object\n\nCorrect:\n\n```cpp\nDevice dev;\nauto cb = tiny::bind\u003c\u0026Device::read\u003e(dev);\n```\n\nWrong:\n\n```cpp\nauto cb = tiny::bind\u003c\u0026Device::read\u003e(Device{}); // error\n```\n\nBinding a temporary is forbidden.\n\n### Owning a lambda is not the same as owning the external data it references\n\n```cpp\nint external = 10;\ntiny::delegate\u003cint()\u003e cb = [\u0026external] {\n    return external;\n};\n```\n\nThe delegate owns the lambda object.\n\nBut the lambda object still refers to `external`, so `external` must outlive callback use.\n\n## Alignment and Size Rules\n\nInline fit depends on both:\n\n- `sizeof(callable) \u003c= InlineBytes`\n- `alignof(callable) \u003c= InlineAlign`\n\nThat means:\n\n- enough bytes is not enough\n- over-aligned callables may require custom `InlineAlign`\n\nThis is expected behavior, not a bug.\n\n## Convenience Aliases\n\n```cpp\ntemplate \u003cclass Sig\u003e using delegate64 = delegate\u003cSig, 64\u003e;\ntemplate \u003cclass Sig\u003e using delegate32 = delegate\u003cSig, 32\u003e;\n\ntemplate \u003cclass Sig\u003e using delegate_sbo64 = delegate_sbo\u003cSig, 64\u003e;\ntemplate \u003cclass Sig\u003e using delegate_sbo32 = delegate_sbo\u003cSig, 32\u003e;\n```\n\nThese are only convenience aliases.\n\n## What This Library Does Not Do\n\n- It does not manage lifetime of borrowed/bound targets\n- It does not emulate `std::bind` full argument binding\n- It does not copy owning delegates\n- It does not silently heap-allocate unless you enabled heap fallback\n\n## Common Pitfalls\n\n### \"Why is my original lambda state different from the delegate state?\"\n\nBecause:\n\n```cpp\nauto lambda = [count = 0](int x) mutable { return count += x; };\ntiny::delegate\u003cint(int)\u003e cb = lambda;\n```\n\nstores a copy of `lambda`.\n\n### \"Why does my over-aligned callable not fit?\"\n\nBecause alignment is checked independently from size.\n\nUse a larger alignment:\n\n```cpp\nusing D = tiny::delegate\u003cint(int), 64, 32\u003e;\n```\n\n### \"Why did free `bind` use the default inline size?\"\n\nBecause free `tiny::bind\u003c\u0026T::method\u003e(obj)` always returns `tiny::delegate\u003csig\u003e` with the global defaults.\n\nIf you need a custom configuration, use:\n\n```cpp\nusing D = tiny::delegate\u003cint(int), 32, 32\u003e;\nD cb = D::bind\u003c\u0026T::method\u003e(obj);\n```\n\n## Summary\n\nUse:\n\n- `tiny::delegate_ref\u003cSig\u003e` for non-owning callback views\n- `tiny::delegate_sbo\u003cSig, ...\u003e` for explicit owning SBO delegates\n- `tiny::delegate\u003cSig, ...\u003e` for the general case\n\nUse:\n\n- `tiny::borrow(x)` when lifetime is guaranteed externally\n- `tiny::bind\u003c\u0026T::method\u003e(obj)` when you want to bind only the object\n- `fits_inline`, `required_inline_bytes`, `required_inline_align`, and `static_assert_fits_inline` when you want compile-time confidence\n\nFor embedded systems, keeping heap fallback off is usually the most honest and predictable policy.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshpegun60%2Fdelegate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshpegun60%2Fdelegate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshpegun60%2Fdelegate/lists"}