{"id":13629925,"url":"https://github.com/FrancoisChabot/variadic_future","last_synced_at":"2025-04-17T13:30:51.720Z","repository":{"id":41530166,"uuid":"189498091","full_name":"FrancoisChabot/variadic_future","owner":"FrancoisChabot","description":"Variadic, completion-based futures for C++17","archived":false,"fork":false,"pushed_at":"2019-07-17T18:46:51.000Z","size":1160,"stargazers_count":42,"open_issues_count":7,"forks_count":8,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-01T22:44:50.469Z","etag":null,"topics":["cpp","futures","lockless","multithreading"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FrancoisChabot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-30T23:59:25.000Z","updated_at":"2024-06-01T06:45:27.000Z","dependencies_parsed_at":"2022-09-21T12:12:15.707Z","dependency_job_id":null,"html_url":"https://github.com/FrancoisChabot/variadic_future","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FrancoisChabot%2Fvariadic_future","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FrancoisChabot%2Fvariadic_future/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FrancoisChabot%2Fvariadic_future/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FrancoisChabot%2Fvariadic_future/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FrancoisChabot","download_url":"https://codeload.github.com/FrancoisChabot/variadic_future/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223757067,"owners_count":17197489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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","futures","lockless","multithreading"],"created_at":"2024-08-01T22:01:24.449Z","updated_at":"2024-11-08T21:30:28.118Z","avatar_url":"https://github.com/FrancoisChabot.png","language":"C++","readme":"[![CircleCI](https://circleci.com/gh/FrancoisChabot/variadic_future.svg?style=svg)](https://circleci.com/gh/FrancoisChabot/variadic_future)\r\n[![Build status](https://ci.appveyor.com/api/projects/status/b7ppx6xmmor89h4q/branch/master?svg=true)](https://ci.appveyor.com/project/FrancoisChabot/variadic-future/branch/master)\r\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/862b964980034316abf5d3d02c9ee63e)](https://www.codacy.com/app/FrancoisChabot/variadic_future?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=FrancoisChabot/variadic_future\u0026amp;utm_campaign=Badge_Grade)\r\n[![Total alerts](https://img.shields.io/lgtm/alerts/g/FrancoisChabot/variadic_future.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/FrancoisChabot/variadic_future/alerts/)\r\n[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/FrancoisChabot/variadic_future.svg?logo=lgtm\u0026logoWidth=18)](https://lgtm.com/projects/g/FrancoisChabot/variadic_future/context:cpp)\r\n[![Documentation](https://img.shields.io/badge/docs-doxygen-blue.svg)](https://francoischabot.github.io/variadic_future/annotated.html)\r\n# Variadic futures\r\n\r\nHigh-performance variadic completion-based futures for C++17.\r\n\r\n* No external dependency\r\n* Header-only\r\n* Lockless\r\n\r\n## Why?\r\n\r\nThis was needed to properly implement [Easy gRPC](https://github.com/FrancoisChabot/easy_grpc), and it was an interesting exercise.\r\n\r\n## What\r\n\r\nCompletion-based futures are a non-blocking, callback-based, synchronization mechanism that hides the callback logic from the asynchronous code, while properly handling error conditions. \r\n\r\nA fairly common pattern is to have some long operation perform a callback upon its completion. At first glance, this seems pretty straightforward:\r\n\r\n```cpp\r\nvoid do_something(int x, int y, std::function\u003cint\u003e on_complete);\r\n\r\nvoid foo() {\r\n  do_something(1, 12, [](int val) {\r\n    std::cout \u003c\u003c val \u003c\u003c \"\\n\";\r\n  });\r\n}\r\n```\r\n\r\nHowever, there's a few hidden complexities at play here. The code within `do_something()` has to make decisions about what to do with `on_complete`. Should `on_complete` be called inline or put in a work pool? Can we accept a default constructed `on_complete`? What should we do with error conditions? The path of least resistance led us to writing code with no error handling whatsoever...\r\n\r\nWith Futures, these decisions are delegated to the *caller* of `do_something()`, which prevents `do_something()` from having to know much about the context within which it is operating. Error handling is also not optional, so you will never have an error dropped on the floor.\r\n\r\n```cpp\r\nFuture\u003cint\u003e do_something(int x, int y);\r\n\r\nvoid foo() {\r\n  do_something(1, 12).finally([](expected\u003cint\u003e val) {\r\n    if(val.has_value()) {\r\n      std::cout \u003c\u003c val \u003c\u003c \"\\n\";\r\n    }\r\n  });\r\n```\r\n\r\nIt *looks* essentially the same, but now implementing `do_something()` is a lot more straightforward, less error-prone, and supports many more operation modes out of the box.\r\n\r\nOnce you start combining things, you can express some fairly complicated synchronization relationships in a clear and concise manner:\r\n\r\n```cpp\r\nFuture\u003cvoid\u003e foo() {\r\n  Future\u003cint\u003e fut_a = do_something_that_produces_an_int();\r\n  Future\u003cbool\u003e fut_b = do_something_that_produces_a_bool();\r\n \r\n  // Create a future that triggers once both fut_a and fut_b are ready\r\n  Future\u003cint, bool\u003e combined_fut = join(fut_a, fut_b);\r\n\r\n  // This callback will only be invoked if both fut_a and fut_b are successfully fullfilled. Otherwise,\r\n  // The failure gets automatically propagated to the resulting future.\r\n  Future\u003cvoid\u003e result = combined_fut.then([](int a, bool b) {\r\n    std::cout \u003c\u003c a \u003c\u003c \" - \" \u003c\u003c b;\r\n  });\r\n  \r\n\r\n  return result;\r\n}\r\n```\r\n\r\n## Documentation\r\n\r\nYou can find the auto-generated API reference [here](https://francoischabot.github.io/variadic_future/annotated.html).\r\n\r\n## Installation\r\n\r\n* Make the contents of the include directory available to your project.\r\n* Have a look at `var_future/config.h` and make changes as needed.\r\n* If you are from the future, you may want to use `std::expected` instead of `expected_lite`,\r\n\r\n## Usage\r\n### Prerequisites\r\n\r\nI am assuming you are already familiar with the [expected\u003c\u003e](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0323r7.html) concept/syntax. `aom::expected\u003cT\u003e` is simply a `std::expected\u003cT, std::exception_ptr\u003e`. \r\n\r\n### Consuming futures\r\n\r\nLet's say that you are using a function that happens to return a `Future\u003c...\u003e`, and you want to execute a callback when the values becomes available:\r\n\r\n```cpp\r\nFuture\u003cint, float\u003e get_value_eventually();\r\n```\r\n\r\nThe `Future\u003cint, float\u003e` will **eventually** be **fullfilled** with an `int` and a `float` or **failed** with one or more `std::exception_ptr`, up to one per field.\r\n\r\nThe simplest thing you can do is call `finally()` on it. This will register a callback that will be invoked when both  values are available or failed:\r\n\r\n```cpp\r\nauto f = get_value_eventually();\r\n\r\nf.finally([](expected\u003cint\u003e v, expected\u003cfloat\u003e f) { \r\n  if(v.has_value() \u0026\u0026 f.has_value()) {\r\n    std::cout \u003c\u003c \"values are \" \u003c\u003c *v \u003c\u003c \" and \" \u003c\u003c *f \u003c\u003c \"\\n\"; \r\n  }\r\n });\r\n```\r\n\r\nAlternatively, if you want to create a future that is **completed** once the callback has **completed**, you can use `then_expect()`.\r\n\r\nLike `finally()`, `then_expect()` invokes its callback when all values are either fullfilled or failed. However, this time, the return value of the callback is used to populate a `Future\u003cT\u003e` (even if the callback returns `void`). If the callback happens to throw an exception (like invoking `value()` on an `expected` containing an error), then that exception becomes the result's failure.\r\n\r\nRules:\r\n\r\n- if the callback returns a `Future\u003cT\u003e`, that produces a `Future\u003cT\u003e`.\r\n- if the callback returns a `expected\u003cT\u003e`, that produces a `Future\u003cT\u003e`.\r\n- if the callback returns `segmented(T, U)`, that produces a `Future\u003cT, U\u003e`.\r\n- Otherwise if the callback returns `T`, that produces a `Future\u003cT\u003e`\r\n  \r\n```cpp\r\nauto f = get_value_eventually();\r\n\r\nFuture\u003cfloat\u003e result = f.then_expect([](expected\u003cint\u003e v, expected\u003cfloat\u003e f) {\r\n  // Reminder: expected::value() throws an exception if it contains an error.\r\n  return f.value() * v.value(); \r\n });\r\n```\r\n\r\nFinally, this pattern of propagating a future's failure as the failure of its callback's result is so common that a third method does that all at once: `then()`.\r\n\r\nHere, if `f` contains one or more **failures**, then the callback is never invoked at all, and the first error is immediately propagated as the `result`'s failure.\r\n\r\nThe same return value rules as `then_expect()` apply.\r\n\r\n```cpp\r\nauto f = get_value_eventually();\r\n\r\nFuture\u003cfloat\u003e result = f.then([](int v, float f) {\r\n  return f * v; \r\n });\r\n```\r\n\r\nIn short:\r\n\r\n|                | error-handling          | error-propagating |\r\n|----------------|-------------------------|-------------------|\r\n| **chains**     | `then_expect()`         | `then()`          |\r\n| **terminates** | `finally()`             | N/A               |\r\n\r\n\r\n#### Void fields\r\n\r\nIf a callback attached to `then_expect()` or `then()` returns `void`, that produces a `Future\u003cvoid\u003e`.\r\n\r\n`Future\u003c\u003e::then()` has special handling of void fields: They are ommited entirely from the callback arguments:\r\n\r\n```cpp\r\nFuture\u003cvoid\u003e f_a;\r\nFuture\u003cvoid, int\u003e f_b;\r\nFuture\u003cfloat, void, int\u003e f_c;\r\n\r\nf_a.then([](){});\r\nf_b.then([](int v){});\r\nf_c.then([](float f, int v){});\r\n```\r\n\r\n#### The Executor\r\n\r\nThe callback can either\r\n\r\n1. Be executed directly wherever the future is fullfilled (**immediate**)\r\n2. Be posted to a work pool to be executed by some worker (**deffered**)\r\n\r\n**immediate** mode is used by default, just pass your callback to your chosen method and you are done.\r\n\r\nN.B. If the future is already fullfilled by the time a callback is attached in **immediate** mode, the callback will be invoked in the thread attaching the callback as the callback is being attached.\r\n\r\nFor **deferred** mode, you need to pass your queue (or an adapter) as the first parameter to the method. The queue only needs to be some type that implements `void push(T\u0026\u0026)` where `T` is a `Callable\u003cvoid()\u003e`.\r\n\r\n```cpp\r\n\r\nstruct Queue {\r\n  // In almost all cases, this needs to be thread-safe.\r\n  void push(std::function\u003cvoid()\u003e cb);\r\n};\r\n\r\nvoid foo(Queue\u0026 queue) {\r\n  get_value_eventually()\r\n    .then([](int v){ return v * v;})             \r\n    .finally(queue, [](expected\u003cint\u003e v) {\r\n      if(v.has_value()) {\r\n        std::cerr \u003c\u003c \"final value: \" \u003c\u003c *v \u003c\u003c \"\\n\";\r\n      }\r\n    });\r\n}\r\n```\r\n\r\n### Producing futures\r\n\r\nFutures can be created by `Future::then()` or `Future::then_expect()`, but the chain has to start somewhere.\r\n\r\n#### Promises\r\n\r\n`Promise\u003cTs...\u003e` is a lightweight interface you can use to create a future that will eventually be fullfilled (or failed).\r\n\r\n```cpp\r\nPromise\u003cint\u003e prom;\r\nFuture\u003cint\u003e fut = prom.get_future();\r\n\r\nstd::thread thread([p=std::move(prom)](){ \r\n  p.set_value(3); \r\n});\r\n```\r\n\r\n#### async\r\n\r\n`async()` will post the passed operation to the queue, and return a future to the value returned by that function.\r\n\r\n```cpp\r\naom::Future\u003cdouble\u003e fut = aom::async(queue, [](){return 12.0;})\r\n```\r\n\r\n#### Joining futures\r\n\r\nYou can wait on multiple futures at the same time using the `join()` function.\r\n\r\n```cpp\r\n\r\n#include \"var_future/future.h\"\r\n\r\nvoid foo() {\r\n  aom::Future\u003cint\u003e fut_a = ...;\r\n  aom::Future\u003cint\u003e fut_b = ...;\r\n\r\n  aom::Future\u003cint, int\u003e combined = join(fut_a, fut_b);\r\n\r\n  combined.finally([](aom::expected\u003cint\u003e a, aom::expected\u003cint\u003e b){\r\n    //Do something with a and/or b;\r\n  });\r\n}\r\n```\r\n\r\n#### Posting callbacks to an ASIO context.\r\n\r\nThis example shows how to use [ASIO](https://think-async.com/Asio/), but the same idea can be applied to other contexts easily.\r\n\r\n```cpp\r\n#include \"asio.hpp\"\r\n#include \"var_future/future.h\"\r\n\r\n// This can be any type that has a thread-safe push(Callable\u003cvoid()\u003e); method\r\nstruct Work_queue {\r\n  template\u003ctypename T\u003e\r\n  void push(T\u0026\u0026 cb) {\r\n    asio::post(ctx_, std::forward\u003cT\u003e(cb));\r\n  }\r\n\r\n  asio::io_context\u0026 ctx_;\r\n};\r\n\r\nint int_generating_operation();\r\n\r\nvoid foo() {\r\n  asio::io_context io_ctx;\r\n  Work_queue asio_adapter{io_ctx};\r\n\r\n  // Queue the operation in the asio context, and get a future to the result.\r\n  aom::Future\u003cint\u003e fut = aom::async(asio_adapter, int_generating_operation);\r\n\r\n  // push the execution of this callback in io_context when ready.\r\n  fut.finally(asio_adapter, [](aom::expected\u003cint\u003e v) {\r\n    //Do something with v;\r\n  });\r\n}\r\n```\r\n\r\n#### get_std_future()\r\n\r\n`Future\u003c\u003e` provides `get_std_future()`, as well as `get()`, which is the exact same as `get_std_future().get()` as a convenience for explicit synchronization. \r\n\r\nThis was added primarily to simplify writing unit tests, and using it extensively in other contexts is probably a bit of a code smell. If you find yourself that a lot, then perhaps you should just be using `std::future\u003c\u003e` directly instead.\r\n\r\n```cpp\r\nFuture\u003cint\u003e f1 = ...;\r\nstd::future\u003cint\u003e x_f = f1.get_std_future();\r\n\r\nFuture\u003cint\u003e f2 = ...;\r\nint x = f2.get();\r\n```\r\n\r\n### Future Streams\r\n\r\n**Warning:** The stream API and performance are not nearly as mature and tested as `Future\u003c\u003e`/`Promise\u003c\u003e`.\r\n\r\n#### Producing Future streams\r\n```cpp\r\naom::Stream_future\u003cint\u003e get_stream() {\r\n   aom::Stream_promise\u003cint\u003e prom;\r\n   auto result = prom.get_future();\r\n   \r\n   std::thread worker([p = std::move(prom)]() mutable {\r\n     p.push(1);\r\n     p.push(2);\r\n     p.push(3);\r\n     p.push(4);\r\n     \r\n     // If p is destroyed, the stream is implicitely failed.\r\n     p.complete();\r\n   });\r\n\r\n   worker.detach();\r\n\r\n   return result;\r\n}\r\n```\r\n\r\n#### Consuming Future streams\r\n```cpp\r\n auto all_done = get_stream().for_each([](int v) {\r\n   std::cout \u003c\u003c v \u003c\u003c \"\\n\";\r\n }).then([](){\r\n   std::cout \u003c\u003c \"all done!\\n\";\r\n });\r\n \r\n all_done.get();\r\n```\r\n\r\n## Performance notes\r\n\r\nThe library assumes that, more often than not, a callback is attached to the\r\nfuture before a value or error is produced, and is tuned this way. Everything\r\nwill still work if the value is produced before the callback arrives, but \r\nperhaps not as fast as possible.\r\n\r\nThe library also assumes that it is much more likely that a future will be \r\nfullfilled successfully rather than failed.\r\n\r\n## FAQs\r\n\r\n**Is there a std::shared_future\u003c\u003e equivalent?**\r\n\r\nNot yet. If someone would use it, it can be added to the library, we just don't want to add features that would not be used anywhere.\r\n\r\n**Why is there no terminating+error propagating method?**\r\n\r\nWe have to admit that it would be nice to just do `fut.finally([](int a, float b){ ... })`, but the problem with that is that errors would have nowhere to go. Having the path of least resistance leading to dropping errors on the ground by default is just a recipe for disaster in the long run.\r\n","funding_links":[],"categories":["C++"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFrancoisChabot%2Fvariadic_future","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFrancoisChabot%2Fvariadic_future","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFrancoisChabot%2Fvariadic_future/lists"}