-
Notifications
You must be signed in to change notification settings - Fork 126
Using ioresult and detecting errors
In v0.9 of sockpp, the library started moving toward more portable, thread-safe, error handling. The generic result<T>
template introduced a function return type that can definitively be a successful return value of type T
, or an error of type std::error_code
.
The standard error codes help bring uniformity across platforms, particularly in regard to Windows using different error codes than the rest of the world. The "portable error conditions can be found here, with POSIX equivalents listed.
The result<T>
type is loosely inspired by Rust's Result
, and is similar to the standard C++17 variant
or option
. The result can either be a success or an error, and once determined, the success result or error value can be extracted.
At a minimum, the value resolves to a bool, so can be used to determine the failure of an operation:
if (!sock.read_r(buf, BUF_SZ)) {
cerr << "Read failed" << endl;
}
But more likely, the success or error value would be wanted for subsequent operations or for reporting to the user:
auto res = sock.read_r(buf, BUF_SZ);
if (res) {
cout << "Read " << res << " bytes" << endl;
}
else {
cerr << "Error: " << res << endl;
}
The ostream inserter '<<' for result
detects whether the value is a success or error and prints it appropriately.
The success value type, T
can be any type that has a default constructor, copy semantics, and an ostream inserter, and anything that make sense to be returned from a function would be appropriate, except that is should not be a std::error_code
as that would make it difficult to distinguish between success and return value.
Internally, the result
contains both a success T
value and a std::error_code
. The error code is always valid, and determines if the result indicates a success or failure. A zero error code indicates the result was a success and the T
value contains the successful return value of the operation. On a failure, the success value will be equal to the default T
value,T{}
. This is all internal to the object and should be considered implementation details that may change in the future.
In the sockpp library, the result is typically used for I/O operations that return an int
value where an error is indicated by a value less-than zero, and a success by an amount greater-than zero. As such, a specialization of the result type ioresult
is used by the library, defined as:
using ioresult = result<int>;
For this, the low-level error integer "last error" is converted into a std::error_code
whereas a value greater than zero is saved as the success return.
Since failure values are definitive, the result can be compared directly against values of type std::error_code
or, more likely, the portable error conditions represented by std::errc
. For example we could do this:
while (true) {
auto ret = sock.read_r(buf, BUF_LEN);
if (ret == errc::interrupted) // If interrupted, try again
continue;
if (!ret) // Other errors, abort
return ret;
// Handle successful read
}
You should always check for a successful return before getting or using the success value()
. Although a non-default value is definitive of a successful return, there could be a valid value containing the default.
Therefore, it should be used something like:
auto ret = sock.read_r(buf, BUF_LEN);
if (ret) {
int n = ret.value(); // 'n' guaranteed to be >= 0
process_buffer(buf, n);
}