C++ Logo

std-proposals

Advanced search

Middle ground between "return" and exceptions?

From: Dmitry Dmitry <dimanne_at_[hidden]>
Date: Mon, 14 Sep 2020 19:43:53 +0100
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
*Sent from gmail*

Received on 2020-09-14 13:44:23