C++ Logo

std-proposals

Advanced search

Re: Middle ground between "return" and exceptions?

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Mon, 14 Sep 2020 14:04:42 -0500
On Mon, Sep 14, 2020 at 1:44 PM Dmitry Dmitry via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> TL;DR: What do you think about creating a new flavour of control-flow
> statements (in particular "return") that could return from outer/parent
> scope?
>
> In the thread "*Initialisers in ternary operators*" we had this example:
> std::optional<std::string> FindUsersCity(bool non_default) {
> std::optional<ContactsServer> contacts =
> GetOrOpenContactsServerConnection();
> if(!contacts)
> return std::nullopt;
> std::optional<UserId> uid = contacts->GetUserId();
> if(!uid)
> return std::nullopt;
> std::optional<GeoServer> geo = GetOrOpenGeoServerConnection();
> if(!geo)
> return std::nullopt;
> std::optional<Location> uloc = geo->GetLocation(*uid);
> if(!uloc)
> return std::nullopt;
> return uloc->GetCityName();
> }
>
> It seems, that there are many situations with the following pattern:
>
> 1. there is a type
> 2. that defines some invalid state (in case of iterator the state is
> "being equal to end()", in case of Result/Outcome/Expected the state is
> "representing an error", in case of optional it is "nullopt", and so on...)
> 3. attempts of using which in the invalid state are supposed to
> "return" (from the scope where it is used). We can take a step back and
> consider other control-flow statements.
>
>
> Without going into implementation details, what do you think about the
> idea (on a conceptual level) of being able to describe this pattern
> somewhere somehow?
>
> If conceptually it is fine, then what do you think about implementing the
> idea by creating another flavour of return statements that can return not
> only from its own scope, but also from outer/parent scope?
> If you wish, this new flavour can be viewed as a middle ground between (1)
> regular return's (which often requires boilerplate for handling the pattern
> above) and (2) exceptions (which are too non-local and implicit and can
> "jump" at an arbitrary place).
>
> The code above would look like this:
> std::optional<std::string> FindUsersCity() {
> std::optional<ContactsServer> contacts =
> GetOrOpenContactsServerConnection();
> std::optional<UserId> uid = contacts.
> *GetOrReturnNullOpt()*->GetUserId();
> std::optional<GeoServer> geo = GetOrOpenGeoServerConnection();
> std::optional<Location> uloc = geo.*GetOrReturnNullOpt()*
> ->GetLocation(*uid);
> return uloc.*GetOrReturnNullOpt()*->GetCityName();
> }
> where *GetOrReturnNullOpt()* is a method that (1) either returns a value
> if std::optional is not empty, (2) or returns from FindUsersCity() if
> std::optional is empty.
>
>
> I think it could solve
>
> - Initial problem mentioned in "Initialisers in ternary operators": we
> could have something like this: return Cont.find(42).GetOrReturn(0).
> - The problem of using optional without boilerplate (see above).
> - The problem of Error-handling via Result<> without boilerplate,
> which has been a constant source of pain and complaints and holy wars
> between exceptions and return-values for error handling.
> - It also eliminates one more reason to exist for macros (that is how
> people are solving it right now: 1
> <https://stackoverflow.com/questions/51608396/how-to-exit-current-function-if-error-on-current-statement-in-c>,
> 2
> <https://stackoverflow.com/questions/6932401/elegant-error-checking/6933170>
> ).
>
>
> Any thoughts?
>
>
> --
> Dmitry
>

As I said in https://lists.isocpp.org/std-proposals/2020/09/1811.php, this
already exists in C++20 in the form of coroutines... you just need to opt
in support for optional<T> in the only sensible way and then you can write:

std::optional<std::string> FindUsersCity(bool non_default) {
    ConstactsSeriver cs = co_await GetOrOpenContatsServerConnection();
    UserId uid = co_await cs.GetUserId();
    GeoServer gs = co_await GetOrOpenGeoServerConnection();
    Location uloc = co_await gw.GetLocationId(uid);
    return uloc.GetCityName();
}

co_await here is effectively syntax sugar for and_then() (in the same way
in Haskell do-notation is syntax sugar for bind). It's just that the
continuation gets written as the next statement in the function rather than
the body of a lambda that gets passed as an argument.

Barry

Received on 2020-09-14 14:04:58