Rust has a ?
operator for error propagation without exceptions. When a function returns a Result
or Option
type, one can write:
let a = get_a()?;
Which effectively means:
let _a = get_a();
let mut a = match _a {
Ok(value) => value,
Err(e) => return Err(e),
}
Or, in other words, if the returned value contains an error, the function will return and propagate the error. All of this happens in just one char, so the code is compact without too many if (err) return err
branches.
Same concept exists in C#: Null-conditional operators
In C++, the closest thing I was able to find is tl::expected and Boost.outcome. But those two require use of e. g. lambdas to achieve same thing and it's not as concise. From what I understand, affecting control flow like that would require some kind of language feature or extension.
I tried to find a proposal that would implement it or be related at least and couldn't. Is there a C++ proposal for implementing it?
There is no such proposal. Nor is there likely to be one in the immediate future. The C++ standard library does not even have value-or-error types at the moment, so having an operator whose sole purpose is to automatically unpack such types seems very cart-before-the-horse.
At present, the C++ committee seems more interested in finding a way to make exception throwing cheaper, and thus moving towards a Python-like environment where you just use exceptions for this sort of thing.
For the sake of completeness (and for no other reason), I will mention that co_await
exists. I bring this up because you can (ab)use co_await
to do something behaviorally equivalent to what you want.
When you co_await
on an object, the coroutine machinery transforms the given expression into its final form. And the coroutine machinery can suspend execution of the function, returning control to the caller. And this mechanism has the power to affect the return value of the function. Which looks kind of like a normal function return, if you squint hard enough.
With all this in mind, you can therefore (ab)use coroutine machinery for functions that return value-or-error types like expected
. You can co_await
on some value-or-error type. If the expression is a value, then the co_await
will not suspend the function, and it will unpack the value from the expression. If the expression is an error, then co_await
will "suspend" the function and propagate the error value into the return value for that function. But in "suspending" it, the coroutine machinery will never actually schedule execution of the coroutine to resume, so once control is given back to the caller, the coroutine is terminated.
That having been said, you should never do this. A non-comprehensive list of reasons why being:
It doesn't make sense, based on what co_await
is intended to do. A coroutine is supposed to be a function that suspends execution in order to resume it when some asynchronous process is complete. That is, the function "await"s on something. But you're not waiting on anything; you're (ab)using co_await
to create the effect of a transform-or-return. As such, a person reading the code won't necessarily know what's going on, since nothing is actually being "awaited" on.
Your function becomes a coroutine, and being a coroutine creates issues. That is, because coroutines are expected to be resumed, they have a certain amount of baggage. For example, co_return
does not allow for guaranteed elision (and probably cannot be changed to permit it in the future). Every coroutine has a promise object that acts as an intermediary between the internal function and its return value. Coroutines are dynamically allocated, and not dynamically allocating them is considered an optimization. So compilers may not optimize this case out. And so on.
You can't have a "normal" coroutine that also propagates errors in this way. A function either uses co_await
for errors or it uses co_await
for actual awaiting; it can't do both. So if you want a coroutine that can fail, you have to do things manually.