C++ Logo


Advanced search

Re: Initialisers in ternary operators

From: Jorg Brown <jorg.brown_at_[hidden]>
Date: Mon, 14 Sep 2020 02:59:49 -0700
Oh, I should add, there are two exemplary near-misses of what I want.

Exemplar 1:

#define ASSIGN_OR_RETURN(var, optional) \
  if (auto templine(__LINE__) = optional; !!templine(__LINE__)) \
    var = std::move(*templine(__LINE__)); \
  else \
    return nullopt

The trouble is, while this code works great for this:

  UserId uid;
  if (non_default) ASSIGN_OR_RETURN(uid, GetUserId());

It doesn't work for this:

  ASSIGN_OR_RETURN(Location uloc, uid.GetLocation());

Because in the second case, the definition of the "uloc" local variable
happens within the if. So when the if exits, the variable is gone.

You'd think you could get around this by flipping the if around, so that
the definition of the new variable is not inside the scope of the if:

#define ASSIGN_OR_RETURN(var, optional) \
  if (auto templine(__LINE__) = optional; !templine(__LINE__)) \
    return nullopt; \
  var = std::move(*templine(__LINE__))

But now you have the other problem: the "templine(__LINE__)" temporary
variable was created inside the if's conditional, so it goes out-of-scope
before we can move out of it. (Also, this is two statements, so
ASSIGN_OR_RETURN can no longer appear on the right side of an if statement,
without being enclosed in braces.)

In any case, these demonstrate the fundamental problem: when a boolean
expression is being tested, but the evaluation of that expression generates
additional rvalue data that is only valid if the boolean is "true", it's
surprisingly difficult to retrieve the extra data.

-- Jorg

On Sun, Sep 13, 2020 at 1:48 AM Jorg Brown <jorg.brown_at_[hidden]> wrote:

> 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-14 05:00:12