C++ Logo

std-proposals

Advanced search

Re: Initialisers in ternary operators

From: Jorg Brown <jorg.brown_at_[hidden]>
Date: Sun, 13 Sep 2020 01:48:47 -0700
On Sat, Sep 12, 2020 at 8:50 PM Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> On Sat, Sep 12, 2020 at 11:28 PM Jorg Brown via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On Fri, Sep 11, 2020 at 6:29 AM Richard Hodges via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> On Fri, 11 Sep 2020 at 12:39, Garrett May via Std-Proposals <
>>> std-proposals_at_[hidden]> 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

Received on 2020-09-13 03:49:02