If you want the same level of flexibility as with returns (and you usually want, because sometimes you need to be more specific and capture some context/explanation), then it would be:
Result<int, std::string> FindUsersCity() {
std::optional<ContactsServer> contacts = GetOrOpenContactsServerConnection();
std::optional<UserId> uid;
try {
uid = contacts.value().GetUserId(); // throw bad_optional_access on empty
} catch (const std::bad_optional_access&) {
return failure("failed to get GetOrOpenContactsServerConnection: details: " + ...)
}
std::optional<GeoServer> geo = GetOrOpenGeoServerConnection();
try {
std::optional<Location> uloc = geo.value().GetLocation(uid.value()); // throw bad_optional_access on empty
} catch (const std::bad_optional_access&) {
return failure("failed to get GetOrOpenGeoServerConnection: details: " + ...)
}
try {
return uloc.value().GetCityName();
} catch (const std::bad_optional_access&) {
return failure("failed to get GetLocation of: " + ...)
}
}