{"id":21888479,"url":"https://github.com/kelbon/kelcoro","last_synced_at":"2025-04-05T16:05:12.185Z","repository":{"id":43275367,"uuid":"462725451","full_name":"kelbon/kelcoro","owner":"kelbon","description":"C++20 coroutine library","archived":false,"fork":false,"pushed_at":"2025-02-12T06:39:19.000Z","size":354,"stargazers_count":125,"open_issues_count":0,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-29T15:04:28.655Z","etag":null,"topics":["asynchronous-programming","coroutine-library","coroutines","cpp","cpp20","header-only"],"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/kelbon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":["kelbon"]}},"created_at":"2022-02-23T12:28:44.000Z","updated_at":"2025-03-13T21:07:26.000Z","dependencies_parsed_at":"2022-08-29T01:30:39.591Z","dependency_job_id":"dc7e5903-4bc3-41f9-aae8-44f5fdc72c5e","html_url":"https://github.com/kelbon/kelcoro","commit_stats":{"total_commits":63,"total_committers":3,"mean_commits":21.0,"dds":"0.031746031746031744","last_synced_commit":"d5cb73e45def3b7064b2b200e53ee4d737e22a76"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelbon%2Fkelcoro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelbon%2Fkelcoro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelbon%2Fkelcoro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kelbon%2Fkelcoro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kelbon","download_url":"https://codeload.github.com/kelbon/kelcoro/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361617,"owners_count":20926642,"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":["asynchronous-programming","coroutine-library","coroutines","cpp","cpp20","header-only"],"created_at":"2024-11-28T11:15:49.964Z","updated_at":"2025-04-05T16:05:12.145Z","avatar_url":"https://github.com/kelbon.png","language":"C++","funding_links":["https://github.com/sponsors/kelbon"],"categories":[],"sub_categories":[],"readme":"# kelcoro - C++20 coroutines library\n\nclang, gcc, msvc\n[![](\nhttps://github.com/kelbon/kelcoro/actions/workflows/build_and_test.yaml/badge.svg?branch=main)](\nhttps://github.com/kelbon/kelcoro/actions/workflows/build_and_test.yaml)\n\n* [`How to build?`](#build)\n  \n  * [`generator\u003cT\u003e`](#generatort)\n  * [`channel\u003cT\u003e`](#channelt)\n  * [`logical_thread`](#logical_thread)\n  * [`job`](#job)\n  * [`async_task\u003cT\u003e`](#async_taskt)\n  * [`task\u003cT\u003e`](#taskt)\n\n\u003cdetails\u003e\n\t\u003csummary\u003eHow to customize allocations?\u003c/summary\u003e\nLibrary uses memory resources(see concept `dd::memory_resource`)\n\nBy default all coroutines deduct memory resource from arguments\n\nAll coroutines have *_r version, for example `generator_r`, such alias will use selected memory resource for allocations (if required),\n\nfor example:\n\n```C++\ngenerator\u003cint\u003e g();                                // will use operator new if required\ngenerator\u003cint\u003e g(int, dd::with_resource\u003cResource\u003e) // will use 'Resource' if allocation required\n```\nAlso all coroutines have `dd::pmr::*` version which can use std::pmr::memory_resource using `dd::pass_resource` or default, if not passed (you can set default polymorphic memory resource globally)\n```C++\nstd::pmr::memory_resource\u0026 get_default_resource() noexcept\nstd::pmr::memory_resource\u0026 set_default_resource(std::pmr::memory_resource\u0026 r) noexcept;\n// next coroutine with 'dd::polymorphic_resource' on this thread will use 'm' for allocation\nvoid pass_resource(std::pmr::memory_resource\u0026 m) noexcept;\n```\n\u003c/details\u003e\n\t\n* Functions or seems like functions\n  * [`stop(Args...)`](#stop)\n  * [`jump_on(Executor)`](#jump_on)\n\n## `generator\u003cT\u003e`\ninterface:\n```C++\ntemplate \u003cyieldable Yield\u003e\nstruct generator {\n  // * if .empty(), then begin() == end()\n  // * produces next value(often first)\n  // iterator invalidated only when generator dies\n  iterator begin() \u0026 [[clang::lifetimebound]];\n  static std::default_sentinel_t end() noexcept;\n\n  // postcondition: empty(), 'for' loop produces 0 values\n  constexpr generator() noexcept = default;\n\n  // postconditions:\n  // * other.empty()\n  // * iterators to 'other' == end()\n  constexpr generator(generator\u0026\u0026 other) noexcept;\n  constexpr generator\u0026 operator=(generator\u0026\u0026 other) noexcept;\n\n  bool operator==(const generator\u0026 other) const noexcept;\n\n  bool empty() const noexcept;\n  explicit operator bool() const noexcept {\n    return !empty();\n  }\n\n  void reset(handle_type handle) noexcept;\n  // postcondition: .empty()\n  void clear() noexcept;\n  // postcondition: .empty()\n  // its caller responsibility to correctly destroy handle\n  handle_type release() noexcept;\n\n};\n```\n   * produces next/first value when .begin called\n   * recursive (see co_yield dd::elements_of(rng))\n * default constructed generator is an empty range\n\n   \n notes:\n  * tag `dd::elements_of(RNG)` accepts range(including other generator) and yields all elements of it more effectively, then for-loop\n  * tag `by_ref` allows to yield lvalue referencnes, so caller may get reference from iterator's operator* and change value, so generator will observe changes.\nThis allows usage generator as in-place output range, which may be very cose\n\n\nusage as output iterator example:\n```C++\ndd::generator\u003cint\u003e printer() {\n  int i;\n  while (true) {\n    co_yield dd::by_ref{i};\n    print(i);\n  }\n}\n\nvoid print_foo(std::vector\u003cint\u003e values) {\n  // prints all values\n  std::copy(begin(values), end(values), printer().begin().out());\n}\n```\n  * operator* of iterator returns rvalue reference to value, so `for(std::string s : generator())` is effective code(one move from generator)\n\nexample:\n```C++\ndd::generator\u003cint\u003e ints() {\n  for (int i = 0; i \u003c 100; ++i)\n      co_yield i;\n}\ndd::generator\u003cint\u003e intsints() {\n  co_yield dd::elements_of(ints());\n  co_yield dd::elements_of(ints());\n}\nvoid use() {\n  for (int i : intsints())\n      do_smth(i);\n}\n```\n\n## `logical_thread`\ninterface(similar to std::jthread):\n```C++\nstruct logical_thread {\n  logical_thread() noexcept = default;\n  logical_thread(logical_thread\u0026\u0026) noexcept;\n  bool joinable();\n  void join(); // block until coroutine is done\n  void detach();\n  bool stop_possible() const noexcept;\n  bool request_stop();\n  // co_await dd::this_coro::stop_token will return dd::stop_token for coroutine\n};\n```\nIt is cancellable coroutine, which behavior similar to https://en.cppreference.com/w/cpp/thread/jthread (can be .request_stop(),\nautomatically requested for stop and joined in destructor or when move assigned etc)\n\nExecution starts immediately when logical_thread created.\nIf unhandled exception happens in logical_thread std::terminate is called\n\nLifetime: If not .detach(), then coroutine frame dies with coroutine object, else (if.detach() called) frame dies after co_return (more precisely after final_suspend)\n\nexample :\n```C++\ndd::logical_thread Bar() {\n  // imagine that already C++47 and networking in the standard\n  auto socket = co_await async_connect(endpoint);\n  auto token = co_await dd::this_coro::stop_token; // cancellable coroutine can get stop token associated with it\n  while (!token.stop_requested()) {\n\tauto write_info = co_await socket.async_write(\"Hello world\");\n\tauto read_result = co_await socket.async_read();\n\tstd::cout \u003c\u003c read_result;\n  }\n}\n```\n## `job`\n behaves as always detached logical_thread, but more lightweight, has no methods, because always detached == caller have no guarantees about possibility to manipulate coro\n \nLifetime: same as detached dd::logical_thread\n## `async_task\u003cT\u003e`\n\ninterface:\n\n```C++\ntemplate \u003ctypename Result\u003e\nstruct async_task {\n  async_task() noexcept = default;\n  async_task(async_task\u0026\u0026 other) noexcept;\n\n  // postcondition: if !empty(), then coroutine suspended and value produced\n  void wait() const noexcept;\n\n  // returns true if 'get' is callable and will return immedially without wait\n  bool ready() const noexcept;\n\n  // postcondition: empty()\n  void detach() noexcept;\n\n  // precondition: !empty()\n  // must be invoked in one thread(one consumer)\n  std::add_rvalue_reference_t\u003cResult\u003e get() \u0026\u0026 noexcept;\n\n  // returns true if call to 'get' will produce UB\n  bool empty() const noexcept;\n};\n```\nIf unhandled exception happens in async_task std::terminate is called\nExecution starts immediately when async_task created.\n\nResult can be ignored, it is safe.\n\nexample:\n\n```C++\nasync_task\u003cint\u003e future_int() {\n  co_await dd::jump_on(my_executor);\n  auto x = co_await foo();\n  co_return bar(x);\n}\n```\n\n# Symmetric transfer between coroutines\nWhat is symmetric transfer? this is when the coroutine does not just suspended, but transfers control to another coroutine. Then, in the future, \nthe coroutine will be resumed. Thus, relative to the coroutine, this code looks absolutely synchronous, there is no synchronization and any overhead, \nbut in fact, the coroutine to which control was transferred can be suspended and not return control. This allows implementing easy-to-use channels (see examples)\n\n## `channel\u003cT\u003e`\ninterface:\nLiteraly same as dd::generator. But 'begin' and operator++ on iterator requires co_await.\nSo, there are macro co_foreach for easy usage.\n\nexample:\n```C++\nchannel\u003cint\u003e ints_creator() {\n  for (int i = 0; i \u003c 100; ++i) {\n    co_await jump_on(some_executor);\n    // any hard working for calculating i\n    for (int i = 0; i \u003c 10; ++i)\n      co_yield i; // control returns to caller ONLY after co_yield!\n    co_yield elements_of(std::vector{1, 2, 3, 4, 5}); // accepts range or other channel\n  }\n}\n\nasync_task\u003cvoid\u003e user() {\n  co_foreach(int i, ints_creator())\n    use(i);\n  }\n// if you want to not use macro, then:\n// auto c = ints_creator();\n// for (auto b = co_await c.begin(); b != c.end(); co_await ++b)\n//     use(*b);\n}\n```\n\n## `task\u003cT\u003e`\ninterface:\nonly operator co_await ! This means task may be used only in another coroutine.\nIt is just a channel between two coroutines for exactly one value(returned by co_return)\n\ntask is lazy (starts only when co_awaited)\n\nexample:\n\n```C++\ndd::task\u003cstd::string\u003e foo() {\n  co_await dd::jump_on(my_executor);\n  std::string result = co_await something();\n  co_return std::move(result);  // move here not useless!\n}\n\ndd::async_task\u003cstd::string\u003e foo_user() {\n  // task may be used only in other coroutine\n  co_return co_await foo();\n}\n\n```\n\n# Usefull\n\n## `jump_on`\nco_await jump_on(Executor) equals to suspending coroutine and resume it on Executor(with .attach(node)), for example it can be thread pool\nor dd::this_thread_executor(executes all on this thread) / dd::noop_executor etc\n## `stop`\nmore effective way to stop(request_stop + join) for many stopable arguments or range of such type.\n\n## `build`\n```CMake\n\ninclude(FetchContent)\nFetchContent_Declare(\n  kelcoro\n  GIT_REPOSITORY https://github.com/kelbon/kelcoro\n  GIT_TAG        origin/main\n)\nFetchContent_MakeAvailable(kelcoro)\ntarget_link_libraries(MyTargetName kelcorolib)\n\n```\n\u003cdetails\u003e\n  \u003csummary\u003eor use add_subdirectory\u003c/summary\u003e\n\n\n1. clone this repository into folder with your project\n   `git clone https://github.com/kelbon/kelcoro`\n3. add these lines to it's CMakeLists.txt\n\n```\nadd_subdirectory(kelcoro)\ntarget_link_libraries(MyTargetName PUBLIC kelcorolib)\n```\n\u003c/details\u003e\n\nBuilds tests/examples\n\n```\ngit clone https://github.com/kelbon/kelcoro\ncd kelcoro\ncmake . --preset debug_dev\n// also you can use --preset default / debug_dev / release_dev / your own\ncmake --build build\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelbon%2Fkelcoro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkelbon%2Fkelcoro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkelbon%2Fkelcoro/lists"}