bexec is a small C++20 sender/receiver concurrency library inspired by P2300. It is intended as a temporary, practical bridge while standardized C++26 execution support becomes widely available in mainstream stable toolchains.
This is not a full P2300 implementation. The current code is a C++26-aligned subset with a member-customization API, environment-aware P2300-style completion signatures, small schedulers, in-place stop tokens, simple environment queries, sender factories/adaptors, and focused tests.
- A header-only C++20 library with an umbrella header at
include/bexec/bexec.hppand feature headers underinclude/bexec/. - Role-oriented public headers for operation states, receivers, senders, schedulers, queries, and completion signatures.
- A minimal sender/receiver vocabulary:
startconnectscheduleset_valueset_errorset_stopped
- Member-based customization. For example,
connect(sender, receiver)callssender.connect(receiver). - Basic sender factories and adaptors:
justjust_errorjust_stoppedthenupon_errorupon_stoppedlet_valuelet_errorlet_stoppedinto_variant- pipe syntax:
sender | then(fn)
- A stack-owned
run_loopscheduler forthis_thread::sync_wait, tests, and small local scheduling scenarios. - Minimal
inplace_stop_source,inplace_stop_token, andinplace_stop_callback. - Minimal environment/query support with
get_env,query,get_stop_token,get_allocator,get_scheduler, andget_delegation_schedulertags. repeat_untilfor sequential repetition using a sender factory.when_allfor structured startup, value aggregation, raw error delivery, first-terminal stop propagation, and downstream cancellation propagation.when_all_with_variantfor child senders with multiple value alternatives.starts_on(scheduler, sender)andon(scheduler, sender).- Standard-style
simple_counting_scope,counting_scope, publicassociate, detachedspawn, andspawn_futurefor scope-tracked eager work. bexec::this_thread::sync_wait(sender)andbexec::this_thread::sync_wait_with_variant(sender).- Coroutine integration:
- lazy, awaitable
task<T>/task<void>; - sender awaiting through
as_awaitableandwith_awaitable_senders<Promise>; - synchronous single-pass
generator<T>.
- lazy, awaitable
- It is not a complete implementation of P2300.
- It does not use
tag_invoke. - It does not depend on
stdexec. - It does not provide domains, bulk execution,
let_async_scope, advanced scheduler properties, or ABI-stable boundaries. - It does not provide the nonstandard
bexec::sync_waitalias oron(sender, scheduler)overload.
cmake -S . -B build
cmake --build build
ctest --test-dir build --output-on-failureTests are built as feature targets rather than one monolithic executable.
CTest registers basic, integration, and stress runs inside every feature:
the corresponding sources live under tests/basic/, tests/integration/,
and tests/stress/. The test build uses an installed GoogleTest package when
available and otherwise downloads a pinned release. GoogleTest is neither
located nor downloaded when BEXEC_BUILD_TESTS=OFF.
# Build all feature tests and independent-header compile checks.
cmake --build build --target bexec_tests
# Run all cases for one feature.
ctest --test-dir build -L run_loop --output-on-failure
# Run the bounded stress cases for every feature.
ctest --test-dir build -L stress --output-on-failure
# Increase stress iterations by a factor from 1 through 100.
BEXEC_STRESS_MULTIPLIER=10 \
ctest --test-dir build -L stress --output-on-failureThe project builds tests and examples by default. Disable them with:
cmake -S . -B build -DBEXEC_BUILD_TESTS=OFF -DBEXEC_BUILD_EXAMPLES=OFFThe exported target is always bexec::bexec. To use an installed copy with
CMake:
find_package(bexec CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE bexec::bexec)The installed bexec.pc also supports pkg-config, either directly or through
CMake:
find_package(PkgConfig REQUIRED)
pkg_check_modules(BEXEC REQUIRED IMPORTED_TARGET bexec)
target_link_libraries(your_target PRIVATE PkgConfig::BEXEC)Source-based inclusion remains supported. Tests and examples default to off when bexec is a subproject:
include(FetchContent)
FetchContent_Declare(
bexec
GIT_REPOSITORY https://github.com/haomingbai/bexec.git
GIT_TAG v0.0.1
)
FetchContent_MakeAvailable(bexec)
target_link_libraries(your_target PRIVATE bexec::bexec)For a local checkout, add_subdirectory(path/to/bexec) provides the same
target. Maintainers can generate both native packages with:
cmake -S . -B build/package \
-DBEXEC_BUILD_TESTS=OFF -DBEXEC_BUILD_EXAMPLES=OFF
cpack --config build/package/CPackConfig.cmake -G DEB
cpack --config build/package/CPackConfig.cmake -G RPMscripts/format.shThe formatter auto-detects existing source directories and applies Google C++
style with clang-format.
Naming intentionally follows the C++ standard library rather than Google C++
names: use snake_case throughout, with trailing underscores for private data
members.
#include <bexec/bexec.hpp>
struct receiver {
void set_value(int) noexcept {}
void set_error(std::exception_ptr) noexcept {}
void set_stopped() noexcept {}
};
auto s = bexec::just(1) | bexec::then([](int x) { return x + 1; });
auto op = bexec::connect(std::move(s), receiver{});
bexec::start(op);auto result = bexec::this_thread::sync_wait(
bexec::when_all(bexec::just(1), bexec::just(std::string{"ok"})));
if (result) {
auto& [number, text] = *result;
}bexec::run_loop loop;
auto sched = loop.get_scheduler();
auto s = bexec::schedule(sched) | bexec::then([] {
// Runs when loop.run() drains queued work.
});
auto op = bexec::connect(std::move(s), receiver{});
bexec::start(op);
loop.finish();
loop.run();- Receiver customization is member-only and terminal receiver members must be
noexcept:r.set_value(...),r.set_error(error), andr.set_stopped(). - Sender customization is member-only:
sender.connect(receiver). - Scheduler customization is member-only:
scheduler.schedule(). repeat_until(factory, predicate)uses a sender-producing factory. Reusing the same operation state across iterations is intentionally not supported.repeat_untilstores the most recent child value and forwards it when the predicate returns true.when_all()andwhen_all_with_variant()require at least one child sender.- Plain
when_allrequires each child to have at most one value completion alternative. Usewhen_all_with_variantwhen a child has multiple possible value shapes. associate(sender, token)is a pipeable sender adaptor that adds a possibleset_stopped()completion when association is rejected.spawn(sender, token, env)andspawn_future(sender, token, env)are independent scope consumers: each connects its wrapped child before trying to associate it, but starts that child only after association succeeds.spawnaccepts detached senders that complete only withset_value()and/orset_stopped().spawn_futurereturns a move-only sender that consumes the stored result. Destroying that returned sender before the child completes requests stop for the child. Join receivers must exposeget_schedulerthrough their environment.- If exceptions are disabled,
thencannot translate thrown exceptions tostd::exception_ptr; the same limitation applies to other adaptors that report callable/connect failures asstd::exception_ptr.
See docs/usage/, docs/design.md, and docs/maintenance.md for details.