On Sat, Sep 12, 2020 at 8:50 PM Arthur O'Dwyer <arthur.j.odwyer@gmail.com> wrote:
On Sat, Sep 12, 2020 at 11:28 PM Jorg Brown via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Fri, Sep 11, 2020 at 6:29 AM Richard Hodges via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Fri, 11 Sep 2020 at 12:39, Garrett May via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Hm .. who said,(in the context of C++ talk): "don't be clever unless you really have to"?  
I'm not really sure what that means?

Either way, my personal preference would be in support of Richard Hodges's suggestion. I've found GCC's expression statements to be useful in the past, and I think standardising it would be a perfect solution for this. It avoids the weirdness of having an initializer only for ternary operators that Ville Voutilainen mentioned (due to the syntax of ternaries) whilst also providing a way for scope to yield a value.

All that remains now is for someone to write a paper, in which is laid out the motivating use case, a demonstration that existing language features are limiting productivity and an impact assessment.

There is already a compiler supporting the feature, so that's in the paper's favour.

I suspect the biggest problems would be:
a) convincing everyone that the existing lambda syntax is not sufficient

Regarding statement expressions, I dislike deviations from the standard in Google's code, so I tried to get rid of statement expressions when adopted c++11 and got lambdas.  And indeed, lambdas solve much of the need.

But there was one strong outlier, and it looks like this:

std::optional<std::string> oldFindUsersCity(bool non_default) {
  std::optional<UserId> uid = UserId{};
  if (non_default) {
    uid = GetUserId();
    if (!uid) return nullopt;
  }
  std::optional<Location> uloc = uid->GetLocation();
  if (!uloc) return nullopt;
  return uloc->GetCityName();
}

Those if/return pairs make the code really ugly really fast, so people made macros:

std::optional<string> FindUsersCity(bool non_default) {
  UserId uid;
  if (non_default) ASSIGN_OR_RETURN(uid, GetUserId());
  ASSIGN_OR_RETURN(Location uloc, uid.GetLocation());
  return uloc.GetCityName();
}

And therein lies the problem.  These macros aren't that hard to implement with statement expressions (https://godbolt.org/z/G7Wsj3)

Or even just with plain old macros, right? as long as you give up the idea of making a single macro that can both declare a new local variable and have non-local control flow.
https://godbolt.org/z/esz6G1  (notice the sneaky declaration of `Location uloc;` instead of doing it inside the macro)

If you were going to use this as a motivating example in a paper, you'd have to do something to "fix it up" so that the simple solution didn't work.

Fair enough.  The variables involved often do not have default constructors.  So fixing up my example produces https://godbolt.org/z/naxdTv .  Ball's heading over to your side of the court again, sir.

[...]
b) convincing other compiler vendors to bother to implement the feature.

Already done. gcc and clang and icc and Zapcc already implement it.  MSVC was the only one I tested, that didn't support it.  This is because there's a Unix header file that uses the feature. 

However, vendors may have radically different ideas about what it means to e.g. `goto` into a statement-expression, or `goto` out of one, or nest statement-expressions inside each other.  My impression has always been that vendors treat statement-expressions as a novelty for "compatibility with someone else," never as a flagship feature in their own right. The goal is just to keep them from segfaulting and paper over any bugs as they're filed.

Yup.  And honestly, that's the strongest argument in favor of standardizing them.  At least from my perspective as a tooling guy at Google, having literally hundreds of thousands of uses of statement expressions, without a rigorously defined set of expectations on how they work... What this means is that we are depending entirely on luck, that they do what we expect them to be doing.  It potentially blocks us from upgrading to a new compiler, changing to a different compiler, moving to a new CPU architecture, etc.

For one thing, there's the issue of destructors.  C++03 tightened the rules on when destructors were run, which was a big help.  But what happens when a temporary is used inside of a statement expression?  Is it destructed at the end of whatever statement (inside the statement expression) used it?  Is there an exception for the last statement in the expression?

Not to mention, the syntax for statement-expressions is like
({ foo(); })
which looks a heck of a lot like you're discarding the result of foo(). Should it warn if foo() is [[nodiscard]]? Why or why not? (Rhetorical question to be discussed in a paper, if at all.)

Anyway, "structured programming for the win." If you can write your algorithm completely with local control structures (if/while/for/function calls) and abstain from non-local control flow entirely, you'll be a lot happier — and you won't need statement-expressions.

OK, so if I only use local control structures, and eschew goto and return, then how do I write the typical multiple-error-return flow, without ending up with code that is increasingly indented, right up until the end?  What's the right way, according to your model, of writing:

std::optional<std::string> FindUsersCity(bool non_default) {
     std::optional<std::string> city = std::nullopt;
     std::optional<ContactsServer> contacts = GetOrOpenContactsServerConnection();
     if (contacts) {
          std::optional<UserId> uid = contacts->GetUserId();
          if (uid) {
               std::optional<GeoServer> geo = GetOrOpenGeoServerConnection();
               if (geo) {
                    std::optional<Location> uloc = geo->GetLocation(*uid);
                    if (uloc) {
                         city = uloc->GetCityName();
                    }
               }
          }
     }
     return city;
}

 -- Jorg