The results Type
I/O operations in Capy report an expected failure, such as reaching the end of a stream, as a value.
Exceptions are kept for genuine errors, such as a failed frame allocation.
The value is a results<Ts…>, declared in:
#include <boost/capy/results.hpp>
results<Ts…> is an alias for std::tuple<std::error_code, Ts…>.
The error code always comes first. Zero or more payload values follow it.
Stream operations return results<std::size_t>: an error code, then a byte count.
Consuming results
Unpack the result with a structured binding, then test the error code before using the payload:
capy::task<std::size_t> read_once(capy::test::read_stream& rs)
{
char buf[64];
// read_some returns results<std::size_t>: an error code, then a byte count
auto [ec, n] = co_await rs.read_some(capy::make_buffer(buf));
if(ec) // always test the error code first
co_return 0;
co_return n; // n is meaningful because ec is clear
}
Compare the error code against a condition, not an exact error value.
For example, cond::eof matches the end-of-stream error that Capy’s streams and Corosio’s sockets report.
Payload Alongside an Error
Whether a payload is meaningful when the error code is set depends on the operation that produced it.
Many I/O operations report a meaningful partial result alongside the error.
read and write report the bytes transferred before the condition stopped them:
capy::task<> read_header(capy::test::read_stream& rs)
{
char buf[8];
// read keeps going until buf is full, or a condition stops it
auto [ec, n] = co_await capy::read(rs, capy::make_buffer(buf));
if(ec == capy::cond::eof)
{
// The stream ended early. n still counts the bytes
// placed in buf before it did.
std::string_view got(buf, n);
(void)got;
}
}
Given a stream that holds only "hi", this read stops with cond::eof and n == 2.
Other operations leave the payload unspecified on error, so check each operation’s reference page.
Reusing Variables with std::tie
Because results is a std::tuple, std::tie assigns into variables that already exist.
This suits a loop, where a structured binding would introduce new names on every pass:
capy::task<std::string> read_all(capy::test::read_stream& rs)
{
std::string body;
char buf[64];
std::error_code ec;
std::size_t n = 0;
for(;;)
{
// std::tie assigns into ec and n; no new bindings per iteration
std::tie(ec, n) = co_await rs.read_some(capy::make_buffer(buf));
if(ec)
break;
body.append(buf, n);
}
// ec == capy::cond::eof when the stream ended normally
co_return body;
}
The rest of the standard tuple API applies too: std::get, std::apply, std::tuple_cat, comparisons, and tuple assignment.
Producing results
A coroutine that reports errors this way returns task<results<Ts…>>:
// `results<Ts...>` holds an error code `ec` plus zero or more payload
// values. A task returning one reports failure without throwing.
capy::task<capy::results<>> ensure_ready(bool ready)
{
if(! ready)
co_return std::make_error_code(std::errc::not_connected); // ec converts
co_return {}; // success
}
capy::task<capy::results<std::size_t>> count_ready(bool ready)
{
using result = capy::results<std::size_t>;
if(! ready)
co_return result{std::make_error_code(std::errc::not_connected), 0};
co_return result{std::error_code(), 42}; // success, carrying a value
}
capy::task<> use_them()
{
// `results` models the tuple protocol: ec first, then the payloads.
auto [ec, n] = co_await count_ready(true);
if(ec)
co_return; // always check ec first
(void)n; // n is only meaningful when ec is falsy
}
For results<>, with no payload, a std::error_code converts implicitly, so co_return some_ec; compiles.
With payloads present you must supply the whole tuple, as count_ready shows.
Where results Appears
-
Streams —
read_some,write_some,readandwritereturnresults<std::size_t>. -
Concurrent Composition —
when_allflattens its children’s payloads into oneresults.when_anyreturns astd::variantwhose first alternative is the error code and whose others are the children’s payloads. -
Async Mutex —
async_mutex'sscoped_lock()returns an error code and a lock guard. -
Contingencies — What a contingency is, and how
ecsignals one.