Skip to content

Examples

Coroutine context example

This is a simple example of using basic coroutine context below:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <iostream>

// include context header file
#include <libcopp/coroutine/coroutine_context_container.h>

// define a coroutine runner
int my_runner(void *) {
  copp::coroutine_context *addr = copp::this_coroutine::get_coroutine();

  std::cout << "cortoutine " << addr << " is running." << std::endl;

  addr->yield();

  std::cout << "cortoutine " << addr << " is resumed." << std::endl;

  return 1;
}

int main() {
  typedef copp::coroutine_context_default coroutine_type;

  // create a coroutine
  copp::coroutine_context_default::ptr_t co_obj = coroutine_type::create(my_runner);
  std::cout << "cortoutine " << co_obj << " is created." << std::endl;

  // start a coroutine
  co_obj->start();

  // yield from my_runner
  std::cout << "cortoutine " << co_obj << " is yield." << std::endl;
  co_obj->resume();

  std::cout << "cortoutine " << co_obj << " exit and return " << co_obj->get_ret_code() << "." << std::endl;
  return 0;
}

Also, you can use copp::coroutine_context_container<ALLOCATOR> instead of copp::coroutine_context_default to use a different stack allocator.

Coroutine task example

This is a simple example of using coroutine task with lambda expression:

#include <iostream>

// include task header file
#include <libcotask/task.h>

typedef cotask::task<> my_task_t;

int main() {
  // create a task using factory function [with lambda expression]
  my_task_t::ptr_t task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });

  std::cout << "task " << task->get_id() << " created" << std::endl;
  // start a task
  task->start();

  std::cout << "task " << task->get_id() << " yield" << std::endl;
  task->resume();
  std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;
  return 0;
}

Also, you can your stack allocator or id allocator by setting different parameters in template class cotask::task.

Using coroutine task manager

This is a simple example of using task manager:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>

// include context header file
#include <libcotask/task.h>
#include <libcotask/task_manager.h>

// create a task manager
typedef cotask::task<> my_task_t;
typedef my_task_t::ptr_t task_ptr_type;
typedef cotask::task_manager<my_task_t> mgr_t;
mgr_t::ptr_t task_mgr = mgr_t::create();

// If you task manager to manage timeout, it's important to call tick interval

void tick() {
  // the first parameter is second, and the second is nanosecond
  task_mgr->tick(time(nullptr), 0);
}

int main() {
  // create two coroutine task
  task_ptr_type co_task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });
  task_ptr_type co_another_task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;
    return 0;
  });

  int res = task_mgr->add_task(co_task, 5, 0);  // add task and setup 5s for timeout
  if (res < 0) {
    std::cerr << "some error: " << res << std::endl;
    return res;
  }

  res = task_mgr->add_task(co_another_task);  // add task without timeout
  if (res < 0) {
    std::cerr << "some error: " << res << std::endl;
    return res;
  }

  res = task_mgr->start(co_task->get_id());
  if (res < 0) {
    std::cerr << "start task " << co_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->start(co_another_task->get_id());
  if (res < 0) {
    std::cerr << "start task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->resume(co_task->get_id());
  if (res < 0) {
    std::cerr << "resume task " << co_task->get_id() << " failed, error code: " << res << std::endl;
  }

  res = task_mgr->kill(co_another_task->get_id());
  if (res < 0) {
    std::cerr << "kill task " << co_another_task->get_id() << " failed, error code: " << res << std::endl;
  } else {
    std::cout << "kill task " << co_another_task->get_id() << " finished." << std::endl;
  }

  return 0;
}

Using stack pool

This is a simple example of using stack pool for cotask:

#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>

// include context header file
#include <libcopp/stack/stack_pool.h>
#include <libcotask/task.h>

// define the stack pool type
typedef copp::stack_pool<copp::allocator::default_statck_allocator> stack_pool_t;

// define how to create coroutine context
struct sample_macro_coroutine {
  using stack_allocator_type = copp::allocator::stack_allocator_pool<stack_pool_t>;
  using coroutine_type = copp::coroutine_context_container<stack_allocator_type>;
  using value_type = int;
};

// create a stack pool
static stack_pool_t::ptr_t global_stack_pool = stack_pool_t::create();

typedef cotask::task<sample_macro_coroutine> sample_task_t;

int main() {
#if defined(LIBCOTASK_MACRO_ENABLED)

  global_stack_pool->set_min_stack_number(4);
  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
  // create two coroutine task
  {
    copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
    sample_task_t::ptr_t co_task = sample_task_t::create(
        []() {
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
          cotask::this_task::get_task()->yield();
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
          return 0;
        },
        alloc);

    if (!co_task) {
      std::cerr << "create coroutine task with stack pool failed" << std::endl;
      return 0;
    }

    std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
              << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
              << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
              << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;

    // ..., then do anything you want to do with these tasks
  }

  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;

  {
    copp::allocator::stack_allocator_pool<stack_pool_t> alloc(global_stack_pool);
    sample_task_t::ptr_t co_another_task = sample_task_t::create(
        []() {
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " started" << std::endl;
          cotask::this_task::get_task()->yield();
          std::cout << "task " << cotask::this_task::get<sample_task_t>()->get_id() << " resumed" << std::endl;
          return 0;
        },
        alloc);

    if (!co_another_task) {
      std::cerr << "create coroutine task with stack pool failed" << std::endl;
      return 0;
    }

    // ..., then do anything you want to do with these tasks
  }

  std::cout << "stack pool=> used stack number: " << global_stack_pool->get_limit().used_stack_number
            << ", used stack size: " << global_stack_pool->get_limit().used_stack_size
            << ", free stack number: " << global_stack_pool->get_limit().free_stack_number
            << ", free stack size: " << global_stack_pool->get_limit().free_stack_size << std::endl;
#else
  std::cerr << "this sample require cotask enabled." << std::endl;
#endif
  return 0;
}

Using then or await_task

This is a simple example of using then and await_task for cotask:

/*
 * sample_readme_5.cpp
 *
 *  Created on: 2014-05-19
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <vector>

#include <libcopp/utils/std/explicit_declare.h>

// include manager header file
#include <libcotask/task.h>

#if defined(LIBCOTASK_MACRO_ENABLED)

typedef cotask::task<> my_task_t;

int main() {
  int test_code = 128;

  // create a task using lambda expression
  my_task_t::ptr_t first_task = my_task_t::create([&]() {
    puts("|first task running and will be yield ...");
    cotask::this_task::get_task()->yield();
    puts("|first task resumed ...");
    printf("test code already reset => %d\n", ++test_code);
  });

  // add many then task using lambda expression
  first_task
      ->then([=]() {
        puts("|second task running...");
        printf("test code should be inited 128 => %d\n", test_code);
      })
      ->then([&]() {
        puts("|haha ... this is the third task.");
        printf("test code is the same => %d\n", ++test_code);
        return "return value will be ignored";
      })
      ->then(
          [&](LIBCOPP_EXPLICIT_UNUSED_ATTR void *priv_data) {
            puts("|it's boring");
            printf("test code is %d\n", ++test_code);
            assert(&test_code == priv_data);
            return 0;
          },
          &test_code);

  test_code = 0;
  // start a task
  first_task->start();
  first_task->resume();

  // these code below will failed.
  first_task->then([]() {
    puts("this will run immediately.");
    return 0;
  });

  my_task_t::ptr_t await_task = my_task_t::create([&]() {
    puts("await_task for first_task.");
    return 0;
  });
  await_task->await_task(first_task);

  printf("|task start twice will failed: %d\n", first_task->start());
  printf("|test_code end with %d\n", test_code);
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled");
  return 0;
}
#endif

Using c++20 coroutine

// Copyright 2026 owent
// Created by owent on 2022-05-27

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

static copp::callable_future<int> coroutine_callable_with_int_result() {
  // ... any code
  co_return 123;
}

static copp::callable_future<void> coroutine_callable_with_void_result() {
  // ... any code
  co_return;
}

static copp::callable_future<void> coroutine_simulator_task() {
  // suspend and wait custom waker
  (void)co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();
  // ... any code
  // We can get current status by co_yield yield_status()
  auto current_status = co_yield copp::callable_future<int>::yield_status();
  // The return value will be ignored when the future is already set by custom waker
  std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(current_status) << std::endl;

  co_await coroutine_callable_with_void_result();

  int result = co_await coroutine_callable_with_int_result();
  std::cout << "Coroutine int callable result: " << result << std::endl;
  co_return;
}

int main() {
  auto rpc_result = coroutine_simulator_task();

  // We should not explict call start and get_internal_handle().resume() in a real usage
  // It's only allowed to start and resume by co_wait the callable_future object
  rpc_result.get_internal_handle().resume();  // resume co_await LIBCOPP_MACRO_STD_COROUTINE_NAMESPACE suspend_always();

  std::cout << "Current coroutine callable status: " << static_cast<uint32_t>(rpc_result.get_status()) << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Using c++20 coroutine with custom generator

/*
 * sample_readme_7.cpp
 *
 *  Created on: 2020-05-22
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <assert.h>
#include <iostream>
#include <string>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

using my_generator = copp::generator_future<int>;
std::list<my_generator::context_pointer_type> g_sample_executor;

static void generator_callback(my_generator::context_pointer_type ctx) {
  g_sample_executor.emplace_back(std::move(ctx));
}

static copp::callable_future<void> coroutine_simulator_rpc() {
  my_generator generator_object{generator_callback};
  auto value1 = co_await generator_object;
  std::cout << "co_await named generator: " << value1 << std::endl;
  auto value2 = co_await my_generator{generator_callback};
  std::cout << "co_await temporary generator: " << value2 << std::endl;

  generator_object.get_context()->reset_value();
  auto value3 = co_await generator_object;
  std::cout << "reset and co_await named generator again: " << value3 << std::endl;
  co_return;
}

int main() {
  int result = 191;
  auto f = coroutine_simulator_rpc();

  while (!g_sample_executor.empty()) {
    auto ctx = g_sample_executor.front();
    g_sample_executor.pop_front();
    ctx->set_value(++result);
  }
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Custom error (timeout for example) for c++20 coroutine

By implementing std_coroutine_default_error_transform<CustomType>, we can transform error code of libcopp to our custom type.

/*
 * sample_readme_8.cpp
 *
 *  Created on: 2020-05-20
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

// ============================ types for task and generator ============================
class sample_message_t {
 public:
  int ret_code;

  sample_message_t() : ret_code(0) {}
  sample_message_t(int c) : ret_code(c) {}
  sample_message_t(const sample_message_t &) = default;
  sample_message_t &operator=(const sample_message_t &) = default;
  sample_message_t(sample_message_t &&) = default;
  sample_message_t &operator=(sample_message_t &&) = default;
  ~sample_message_t() {}
};

#  define SAMPLE_TIMEOUT_ERROR_CODE (-500)

LIBCOPP_COPP_NAMESPACE_BEGIN
template <>
struct std_coroutine_default_error_transform<sample_message_t> {
  using type = sample_message_t;
  type operator()(promise_status in) const {
    if (in == promise_status::kTimeout) {
      return sample_message_t{SAMPLE_TIMEOUT_ERROR_CODE};
    }
    return sample_message_t{static_cast<int>(in)};
  }
};
LIBCOPP_COPP_NAMESPACE_END

using int_generator = copp::generator_future<int>;
std::list<int_generator::context_pointer_type> g_int_executor;
using custom_generator = copp::generator_future<sample_message_t>;
std::list<custom_generator::context_pointer_type> g_sample_executor;

static void int_generator_callback(int_generator::context_pointer_type ctx) {
  g_int_executor.emplace_back(std::move(ctx));
}

static void custom_generator_callback(custom_generator::context_pointer_type ctx) {
  g_sample_executor.emplace_back(std::move(ctx));
}

static copp::callable_future<int> coroutine_simulator_rpc_integer_l2() {
  auto result = co_await int_generator{int_generator_callback};
  co_return result;
}

static copp::callable_future<void> coroutine_simulator_rpc_integer() {
  auto result = co_await coroutine_simulator_rpc_integer_l2();
  std::cout << "int generator is killed with code: " << result << std::endl;
  co_return;
}

static copp::callable_future<int> coroutine_simulator_rpc_custom_l2() {
  auto result = co_await custom_generator{custom_generator_callback};
  co_return result.ret_code;
}

static copp::callable_future<void> coroutine_simulator_rpc_custom() {
  auto result = co_await coroutine_simulator_rpc_custom_l2();
  std::cout << "custom generator is killed with code: " << result << std::endl;
  co_return;
}

int main() {
  // sample for await generator and timeout
  auto f1 = coroutine_simulator_rpc_integer();
  f1.kill(copp::promise_status::kCancle, true);
  std::cout << "int generator is killed" << std::endl;

  // sample for await task and timeout
  auto f2 = coroutine_simulator_rpc_custom();
  f2.kill(copp::promise_status::kTimeout, true);
  std::cout << "custom generator is killed" << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Let c++20 coroutine work with cotask::task

This is a simple example to let c++20 coroutine await cotask::task.

/*
 * sample_readme_9.cpp
 *
 *  Created on: 2020-05-20
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <iostream>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcotask/task.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

typedef cotask::task<> my_task_t;

static copp::callable_future<int> call_for_await_cotask(my_task_t::ptr_t t) {
  if (t) {
    auto ret = co_await t;
    co_return ret;
  }

  co_return 0;
}

static int cotask_action_callback(void*) {
  int ret = 234;
  void* ptr = nullptr;
  cotask::this_task::get_task()->yield(&ptr);
  if (ptr != nullptr) {
    ret = *reinterpret_cast<int*>(ptr);
  }
  return ret;
}

int main() {
  my_task_t::ptr_t co_task = my_task_t::create(cotask_action_callback);

  auto t = call_for_await_cotask(co_task);
  co_task->start();

  int res = 345;
  co_task->resume(reinterpret_cast<void*>(&res));

  std::cout << "co_await a cotask::task and get result: " << t.get_internal_promise().data() << std::endl;
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Using SetUnhandledExceptionFilter on Windows with cotask::task

Some applications will use SetUnhandledExceptionFilter to catch unhandled exception and analysis crash problem. But SetUnhandledExceptionFilter is only works with coroutine context of windows fiber. This is a sample of using windows fiber as coroutine context in cotask::task<MACRO>.

#include <iostream>

#include <libcopp/utils/config/libcopp_build_features.h>

#if (defined(LIBCOTASK_MACRO_ENABLED) && LIBCOTASK_MACRO_ENABLED) && defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && \
    LIBCOPP_MACRO_ENABLE_WIN_FIBER
// include task header file
#  include <libcotask/task.h>

struct my_task_macro_t {
  using stack_allocator_type = copp::coroutine_fiber_context_default::allocator_type;
  using coroutine_type = copp::coroutine_fiber_context_default;
  using value_type = int;
};

typedef cotask::task<my_task_macro_t> my_task_t;

#  ifdef _MSC_VER
#    pragma warning(push)
#    pragma warning(disable : 4091)

#    include <atlconv.h>
#    include <imagehlp.h>

#    pragma comment(lib, "dbghelp.lib")

#    ifdef UNICODE
#      define SAMPLE_VC_TEXT(x) A2W(x)
#    else
#      define SAMPLE_VC_TEXT(x) x
#    endif

LPTOP_LEVEL_EXCEPTION_FILTER g_msvc_debuger_old_handle = nullptr;
std::string g_msvc_debuger_pattern;

inline void CreateMiniDump(EXCEPTION_POINTERS *pep, LPCTSTR strFileName) {
  HANDLE hFile =
      CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

  if ((hFile != nullptr) && (hFile != INVALID_HANDLE_VALUE)) {
    MINIDUMP_EXCEPTION_INFORMATION mdei;
    mdei.ThreadId = GetCurrentThreadId();
    mdei.ExceptionPointers = pep;
    mdei.ClientPointers = FALSE;
    // MINIDUMP_CALLBACK_INFORMATION mci;
    // mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
    // mci.CallbackParam = 0;
    MINIDUMP_TYPE mdt =
        (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | MiniDumpWithDataSegs | MiniDumpWithHandleData |
                        MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules);
    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, nullptr);
    CloseHandle(hFile);
  }
}

LONG WINAPI GPTUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) {
  // 得到当前时间
  SYSTEMTIME st;
  ::GetLocalTime(&st);
  // 得到程序所在文件夹
  //  TCHAR exeFullPath[256]; // MAX_PATH
  //  GetModuleFileName(nullptr, exeFullPath, 256);//得到程序模块名称,全路径

  TCHAR szFileName[_MAX_FNAME] = {0};

  USES_CONVERSION;

  wsprintf(szFileName, TEXT("%s-%04d-%02d-%02d.%02d%02d%02d.%03d.dmp"), SAMPLE_VC_TEXT(g_msvc_debuger_pattern.c_str()),
           st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
  CreateMiniDump(pExceptionInfo, szFileName);

  if (nullptr == g_msvc_debuger_old_handle) {
    return EXCEPTION_EXECUTE_HANDLER;  // 下一个Handle, 一般是程序停止运行
  }

  return g_msvc_debuger_old_handle(pExceptionInfo);
}

void __cdecl sample_setup_msvc_mini_dump(const char *prefix) {
  g_msvc_debuger_pattern = prefix;
  g_msvc_debuger_old_handle = SetUnhandledExceptionFilter(GPTUnhandledExceptionFilter);
  if (g_msvc_debuger_old_handle == GPTUnhandledExceptionFilter) {
    g_msvc_debuger_old_handle = nullptr;
  }
}
#  endif

int main() {
#  ifdef _MSC_VER
  sample_setup_msvc_mini_dump("d:/libcopp-test-minidump");
#  endif
  // create a task using factory function [with lambda expression]
  my_task_t::ptr_t task = my_task_t::create([]() {
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
    cotask::this_task::get_task()->yield();
    std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed" << std::endl;

    // ! Make crash and it's will generate a mini dump into d:/libcopp-test-minidump-*.dmp
    // copp::this_coroutine::get_coroutine()->yield();
    return 0;
  });

  std::cout << "task " << task->get_id() << " created" << std::endl;
  // start a task
  task->start();

  std::cout << "task " << task->get_id() << " yield" << std::endl;
  task->resume();
  std::cout << "task " << task->get_id() << " stoped, ready to be destroyed." << std::endl;

  return 0;
}

#  ifdef _MSC_VER
#    pragma warning(pop)
#  endif

#else
int main() {
  std::cerr << "lambda not supported, or fiber is not supported, this sample is not available." << std::endl;
  return 0;
}
#endif

Using c++20 coroutine with channel receiver and sender

/*
 * sample_readme_11.cpp
 *
 *  Created on: 2025-03-04
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <assert.h>
#include <iostream>
#include <string>

// include manager header file
#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

using my_receiver = copp::generator_channel_receiver<int>;
using my_sender = copp::generator_channel_sender<int>;
std::list<my_sender> g_sample_executor;

static my_receiver generator_pick_receiver(std::pair<my_receiver, my_sender>&& receiver_and_sender) {
  g_sample_executor.emplace_back(std::move(receiver_and_sender.second));
  return receiver_and_sender.first;
}

static copp::callable_future<void> coroutine_simulator_rpc() {
  my_receiver my_generator = generator_pick_receiver(copp::make_channel<int>());

  auto value1 = co_await my_generator;
  std::cout << "co_await named channel receiver: " << value1 << std::endl;
  auto value2 = co_await generator_pick_receiver(copp::make_channel<int>());
  std::cout << "co_await temporary channel receiver: " << value2 << std::endl;

  co_return;
}

int main() {
  int result = 191;
  auto f = coroutine_simulator_rpc();

  while (!g_sample_executor.empty()) {
    auto ctx = g_sample_executor.front();
    g_sample_executor.pop_front();
    ctx->set_value(++result);
  }
  return 0;
}
#else
int main() {
  puts("this sample require cotask enabled and compiler support c++20 coroutine");
  return 0;
}
#endif

Using stackful coroutine task with channel receiver and sender

/*
 * sample_readme_12.cpp
 *
 *  Created on: 2025-03-05
 *      Author: owent
 *
 *  Released under the MIT license
 */

#include <iostream>

// include task header file
#include <libcopp/coroutine/stackful_channel.h>
#include <libcotask/task.h>

typedef cotask::task<> my_task_t;

int main() {
  // Returns <receiver, sender>
  auto channel = copp::make_stackful_channel<int>();
  // Create a task and use channel to receive data
  {
    my_task_t::ptr_t task = my_task_t::create([&channel]() {
      std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
      auto receiver = channel.first;
      int value = cotask::this_task::get<my_task_t>()->await_value(receiver);
      std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed, got value: " << value
                << std::endl;
      return 0;
    });

    task->start();
    channel.second.set_value(42);
  }

  // Use channel and custom transform function to receive error
  channel.first.reset_value();
  {
    my_task_t::ptr_t task = my_task_t::create([&channel]() {
      std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " started" << std::endl;
      auto receiver = channel.first;
      int value = cotask::this_task::get<my_task_t>()->await_value(receiver, [](copp::copp_error_code) { return -5; });
      std::cout << "task " << cotask::this_task::get<my_task_t>()->get_id() << " resumed, got value: " << value
                << std::endl;
      return 0;
    });

    task->start();
    task->kill();
  }

  return 0;
}

Using stackful coroutine with channel receiver and sender

This example demonstrates the channel/receiver pattern with stackful coroutines, including:

  • Basic producer/consumer communication
  • Error handling with custom transforms
  • Direct usage with copp::coroutine_context
// Copyright 2026 owent
// Sample code for stackful coroutine channel/receiver model

#include <libcopp/coroutine/coroutine_context_container.h>
#include <libcopp/coroutine/stackful_channel.h>
#include <libcotask/task.h>

#include <cstdio>
#include <iostream>
#include <memory>

// ===============================================
// Example 1: Basic Channel Usage with cotask::task
// ===============================================

class producer_action : public cotask::impl::task_action_impl {
 public:
  producer_action(copp::stackful_channel_sender<int> sender) : sender_(std::move(sender)), value_to_send_(0) {}

  int operator()(void*) override {
    std::cout << "[Producer] Starting production..." << std::endl;

    // Simulate producing values
    for (int i = 1; i <= 5; ++i) {
      value_to_send_ = i * 100;
      std::cout << "[Producer] Producing value: " << value_to_send_ << std::endl;

      // Send value through channel
      sender_.set_value(value_to_send_);

      std::cout << "[Producer] Value sent, yielding..." << std::endl;
    }

    std::cout << "[Producer] Production complete!" << std::endl;
    return 0;
  }

 private:
  copp::stackful_channel_sender<int> sender_;
  int value_to_send_;
};

class consumer_action : public cotask::impl::task_action_impl {
 public:
  consumer_action(copp::stackful_channel_receiver<int> receiver) : receiver_(std::move(receiver)) {}

  int operator()(void*) override {
    std::cout << "[Consumer] Starting consumption..." << std::endl;

    // Receive values from channel
    for (int i = 1; i <= 5; ++i) {
      std::cout << "[Consumer] Waiting for value " << i << "..." << std::endl;

      // await_value will suspend the task until a value is available
      int value = cotask::task<>::this_task()->await_value(receiver_);

      std::cout << "[Consumer] Received value: " << value << std::endl;

      // Reset channel for next value
      receiver_.reset_value();
    }

    std::cout << "[Consumer] Consumption complete!" << std::endl;
    return 0;
  }

 private:
  copp::stackful_channel_receiver<int> receiver_;
};

void example_basic_channel() {
  std::cout << "\n========== Example 1: Basic Channel Usage ==========\n" << std::endl;

  // Create a channel for int communication
  std::pair<copp::stackful_channel_receiver<int>, copp::stackful_channel_sender<int>> channel =
      copp::make_stackful_channel<int>();
  auto receiver = std::move(channel.first);
  auto sender = std::move(channel.second);

  // Create consumer and producer tasks
  auto consumer = cotask::task<>::create(consumer_action(std::move(receiver)));
  auto producer = cotask::task<>::create(producer_action(std::move(sender)));

  // Start consumer (it will suspend immediately waiting for data)
  std::cout << "Starting consumer task..." << std::endl;
  consumer->start();

  std::cout << "\nStarting producer task..." << std::endl;
  producer->start();

  std::cout << "\nBoth tasks completed!" << std::endl;
}

// ===============================================
// Example 2: Error Handling with Custom Transform
// ===============================================

class consumer_with_error_handling : public cotask::impl::task_action_impl {
 public:
  consumer_with_error_handling(copp::stackful_channel_receiver<int> receiver) : receiver_(std::move(receiver)) {}

  int operator()(void*) override {
    std::cout << "[Consumer] Starting with error handling..." << std::endl;

    // First receive with default error handling
    {
      int value = cotask::task<>::this_task()->await_value(receiver_);
      std::cout << "[Consumer] Received: " << value << std::endl;
      receiver_.reset_value();
    }

    // Second receive with custom error transform
    {
      auto error_transform = [](copp::copp_error_code err) {
        std::cout << "[Consumer] Error occurred: " << static_cast<int>(err) << ", returning default value -1"
                  << std::endl;
        return -1;  // Return default value on error
      };

      int value = cotask::task<>::this_task()->await_value(receiver_, error_transform);
      std::cout << "[Consumer] Received or defaulted: " << value << std::endl;
    }

    return 0;
  }

 private:
  copp::stackful_channel_receiver<int> receiver_;
};

void example_error_handling() {
  std::cout << "\n========== Example 2: Error Handling ==========\n" << std::endl;

  std::pair<copp::stackful_channel_receiver<int>, copp::stackful_channel_sender<int>> channel =
      copp::make_stackful_channel<int>();
  auto receiver = std::move(channel.first);
  auto sender = std::move(channel.second);

  auto consumer = cotask::task<>::create(consumer_with_error_handling(std::move(receiver)));

  consumer->start();

  // Send first value normally
  sender.set_value(42);

  // Kill the consumer task to trigger error handling
  std::cout << "\nKilling consumer task to demonstrate error handling..." << std::endl;
  consumer->kill();

  std::cout << "\nTask killed!" << std::endl;
}

// ===============================================
// Example 3: Using with copp::coroutine_context directly
// ===============================================

struct direct_context_example_data {
  copp::stackful_channel_receiver<int> receiver;
  int received_sum = 0;
};

int direct_consumer_runner(void* data_ptr) {
  auto* data = static_cast<direct_context_example_data*>(data_ptr);

  std::cout << "[Direct Consumer] Starting..." << std::endl;

  // Use inject_await with error transform for low-level control
  auto error_transform = [](copp::copp_error_code err) {
    std::cout << "[Direct Consumer] Error: " << static_cast<int>(err) << std::endl;
    return 0;
  };

  for (int i = 0; i < 3; ++i) {
    // Get current coroutine context
    auto* ctx = copp::this_coroutine::get<copp::coroutine_context>();

    std::cout << "[Direct Consumer] Awaiting value " << (i + 1) << "..." << std::endl;

    // Directly use inject_await on receiver
    int value = data->receiver.inject_await(ctx, error_transform);

    std::cout << "[Direct Consumer] Received: " << value << std::endl;
    data->received_sum += value;

    data->receiver.reset_value();
  }

  std::cout << "[Direct Consumer] Total sum: " << data->received_sum << std::endl;
  return 0;
}

void example_direct_coroutine_context() {
  std::cout << "\n========== Example 4: Direct Coroutine Context Usage ==========\n" << std::endl;

  std::pair<copp::stackful_channel_receiver<int>, copp::stackful_channel_sender<int>> channel =
      copp::make_stackful_channel<int>();
  auto receiver = std::move(channel.first);
  auto sender = std::move(channel.second);

  direct_context_example_data data;
  data.receiver = std::move(receiver);

  // Create coroutine context directly
  typedef copp::coroutine_context_container<copp::allocator::stack_allocator_malloc> coroutine_t;

  coroutine_t::ptr_t co = coroutine_t::create(direct_consumer_runner);

  std::cout << "Starting direct coroutine..." << std::endl;
  co->start(&data);

  // Send values
  for (int value : {10, 20, 30}) {
    std::cout << "\nSending value: " << value << std::endl;
    sender.set_value(value);
  }

  std::cout << "\nDirect coroutine completed with sum: " << data.received_sum << std::endl;
}

// ===============================================
// Main
// ===============================================

int main() {
#if defined(LIBCOTASK_MACRO_ENABLED)
  example_basic_channel();
  example_error_handling();
  example_direct_coroutine_context();

  std::cout << "\n========== All Examples Completed! ==========\n" << std::endl;
#else
  std::cerr << "libcotask is not enabled, sample is disabled" << std::endl;
#endif
  return 0;
}

For detailed documentation on channels, see the Channels Guide.

Using C++20 coroutine with channel receiver and sender

This example demonstrates the channel/receiver pattern with C++20 coroutines, including:

  • Basic channel usage with co_await
  • Producer/consumer pattern
  • Error handling with custom error transforms
  • Void channels for signaling
  • Comparison of generator vtable types
// Copyright 2026 owent
// Sample code for C++20 coroutine channel/receiver model

#include <libcopp/coroutine/callable_promise.h>
#include <libcopp/coroutine/generator_promise.h>

#include <cstdio>
#include <iostream>
#include <memory>
#include <vector>

#if defined(LIBCOPP_MACRO_ENABLE_STD_COROUTINE) && LIBCOPP_MACRO_ENABLE_STD_COROUTINE

template <class TReceiver>
static inline void reset_channel_value(TReceiver& receiver) {
  auto ctx = receiver.get_context();
  if (ctx) {
    ctx->reset_value();
  }
}

// ===============================================
// Example 1: Basic Channel Usage with C++20 Coroutine
// ===============================================

copp::callable_future<int> basic_consumer(copp::generator_channel_receiver<int> receiver) {
  std::cout << "[Consumer] Waiting for first value..." << std::endl;

  // Use co_await to receive value from channel
  int value1 = co_await receiver;
  std::cout << "[Consumer] Received first value: " << value1 << std::endl;

  // Must reset to receive next value
  reset_channel_value(receiver);

  std::cout << "[Consumer] Waiting for second value..." << std::endl;
  int value2 = co_await receiver;
  std::cout << "[Consumer] Received second value: " << value2 << std::endl;

  reset_channel_value(receiver);

  std::cout << "[Consumer] Waiting for third value..." << std::endl;
  int value3 = co_await receiver;
  std::cout << "[Consumer] Received third value: " << value3 << std::endl;

  co_return value1 + value2 + value3;
}

void example_basic_channel() {
  std::cout << "\n========== Example 1: Basic C++20 Channel Usage ==========\n" << std::endl;

  // Create a channel for int communication
  auto [receiver, sender] = copp::make_channel<int>();

  // Start consumer coroutine
  std::cout << "Starting consumer coroutine..." << std::endl;
  auto consumer_future = basic_consumer(std::move(receiver));

  std::cout << "\nConsumer is now waiting for values..." << std::endl;
  std::cout << "Is consumer ready? " << (consumer_future.is_ready() ? "Yes" : "No") << std::endl;

  // Send values through sender
  std::cout << "\nSending first value: 10" << std::endl;
  sender->set_value(10);

  std::cout << "\nSending second value: 20" << std::endl;
  sender->set_value(20);

  std::cout << "\nSending third value: 30" << std::endl;
  sender->set_value(30);

  std::cout << "\nConsumer completed with sum: " << consumer_future.get_internal_promise().data() << std::endl;
}

// ===============================================
// Example 2: Producer/Consumer Pattern
// ===============================================

copp::callable_future<void> producer_coroutine(copp::generator_channel_sender<int> sender, int count) {
  std::cout << "[Producer] Starting to produce " << count << " values..." << std::endl;

  for (int i = 1; i <= count; ++i) {
    int value = i * 100;
    std::cout << "[Producer] Producing value " << i << ": " << value << std::endl;

    // Send value to channel
    sender->set_value(value);

    // Yield to let consumer process
    auto current_status = co_yield copp::callable_future<void>::yield_status();
    (void)current_status;
  }

  std::cout << "[Producer] Production complete!" << std::endl;
  co_return;
}

copp::callable_future<int> consumer_coroutine(copp::generator_channel_receiver<int> receiver, int count) {
  std::cout << "[Consumer] Starting to consume " << count << " values..." << std::endl;

  int sum = 0;
  for (int i = 1; i <= count; ++i) {
    std::cout << "[Consumer] Waiting for value " << i << "..." << std::endl;

    int value = co_await receiver;
    std::cout << "[Consumer] Received value " << i << ": " << value << std::endl;

    sum += value;
    reset_channel_value(receiver);
  }

  std::cout << "[Consumer] Consumption complete! Total sum: " << sum << std::endl;
  co_return sum;
}

void example_producer_consumer() {
  std::cout << "\n========== Example 2: Producer/Consumer Pattern ==========\n" << std::endl;

  auto [receiver, sender] = copp::make_channel<int>();

  // Start both producer and consumer
  auto consumer = consumer_coroutine(std::move(receiver), 5);
  auto producer = producer_coroutine(sender, 5);

  std::cout << "\nBoth coroutines completed!" << std::endl;
  std::cout << "Final sum: " << consumer.get_internal_promise().data() << std::endl;
}

// ===============================================
// Example 3: Error Handling
// ===============================================

struct custom_error_transform {
  int operator()(copp::promise_status status) const {
    if (status == copp::promise_status::kKilled) {
      std::cout << "[Error Transform] Coroutine was killed, returning -999" << std::endl;
      return -999;
    } else if (status == copp::promise_status::kTimeout) {
      std::cout << "[Error Transform] Timeout occurred, returning -888" << std::endl;
      return -888;
    }
    std::cout << "[Error Transform] Other error: " << static_cast<int>(status) << ", returning -1" << std::endl;
    return -1;
  }
};

copp::callable_future<int> consumer_with_error_handling(
    copp::generator_channel_receiver<int, custom_error_transform> receiver) {
  std::cout << "[Consumer] Waiting for value (with custom error handling)..." << std::endl;

  // This will use custom_error_transform if an error occurs
  int value = co_await receiver;

  std::cout << "[Consumer] Received value or error default: " << value << std::endl;
  co_return value;
}

void example_error_handling() {
  std::cout << "\n========== Example 3: Error Handling with Custom Transform ==========\n" << std::endl;

  auto [receiver, sender] = copp::make_channel<int, custom_error_transform>();

  auto consumer = consumer_with_error_handling(std::move(receiver));

  std::cout << "\nKilling consumer to demonstrate error handling..." << std::endl;
  consumer.kill(copp::promise_status::kKilled, true);

  std::cout << "Consumer result after kill: " << consumer.get_internal_promise().data() << std::endl;
}

// ===============================================
// Example 4: Void Channel
// ===============================================

copp::callable_future<void> void_channel_consumer(copp::generator_channel_receiver<void> receiver) {
  std::cout << "[Consumer] Waiting for signal 1..." << std::endl;
  co_await receiver;
  std::cout << "[Consumer] Received signal 1!" << std::endl;

  reset_channel_value(receiver);

  std::cout << "[Consumer] Waiting for signal 2..." << std::endl;
  co_await receiver;
  std::cout << "[Consumer] Received signal 2!" << std::endl;

  reset_channel_value(receiver);

  std::cout << "[Consumer] Waiting for signal 3..." << std::endl;
  co_await receiver;
  std::cout << "[Consumer] Received signal 3!" << std::endl;

  co_return;
}

void example_void_channel() {
  std::cout << "\n========== Example 4: Void Channel (Signaling) ==========\n" << std::endl;

  auto [receiver, sender] = copp::make_channel<void>();

  auto consumer = void_channel_consumer(std::move(receiver));

  std::cout << "\nSending signal 1..." << std::endl;
  sender->set_value();

  std::cout << "\nSending signal 2..." << std::endl;
  sender->set_value();

  std::cout << "\nSending signal 3..." << std::endl;
  sender->set_value();

  std::cout << "\nConsumer completed!" << std::endl;
}

// ===============================================
// Example 5: Comparison of Generator Future Types
// ===============================================

void example_vtable_types() {
  std::cout << "\n========== Example 5: Generator VTable Types ==========\n" << std::endl;

  std::cout << "libcopp provides three generator vtable types:\n" << std::endl;

  std::cout << "1. generator_vtable_type::kDefault" << std::endl;
  std::cout << "   - Uses std::function for callbacks (most flexible)" << std::endl;
  std::cout << "   - Can capture complex state in lambdas" << std::endl;
  std::cout << "   - Type: copp::generator_future<T>" << std::endl;
  std::cout << "   - Best for: Complex scenarios with stateful callbacks\n" << std::endl;

  std::cout << "2. generator_vtable_type::kLightWeight" << std::endl;
  std::cout << "   - Uses function pointers instead of std::function" << std::endl;
  std::cout << "   - Lower overhead, faster performance" << std::endl;
  std::cout << "   - Type: copp::generator_lightweight_future<T>" << std::endl;
  std::cout << "   - Best for: Performance-critical code with simple callbacks\n" << std::endl;

  std::cout << "3. generator_vtable_type::kNone" << std::endl;
  std::cout << "   - No callbacks, designed for channel/receiver pattern" << std::endl;
  std::cout << "   - Minimal overhead, most efficient" << std::endl;
  std::cout << "   - Type: copp::generator_channel_future<T> (used by make_channel)" << std::endl;
  std::cout << "   - Best for: Channel-based communication between coroutines\n" << std::endl;

  std::cout << "Recommendation:" << std::endl;
  std::cout << "  - For channels: Use make_channel() (kNone vtable)" << std::endl;
  std::cout << "  - For simple futures: Use generator_lightweight_future (kLightWeight)" << std::endl;
  std::cout << "  - For complex futures: Use generator_future (kDefault)\n" << std::endl;
}

// ===============================================
// Main
// ===============================================

int main() {
  example_basic_channel();
  example_producer_consumer();
  example_error_handling();
  example_void_channel();
  example_vtable_types();

  std::cout << "\n========== All C++20 Channel Examples Completed! ==========\n" << std::endl;
  return 0;
}

#else

int main() {
  std::cerr << "C++20 coroutine is not enabled, sample is disabled" << std::endl;
  return 0;
}

#endif

For detailed documentation on channels, see the Channels Guide.