C++ Logo

std-proposals

Advanced search

Re: Initialisers in ternary operators

From: Jorg Brown <jorg.brown_at_[hidden]>
Date: Sat, 12 Sep 2020 20:28:01 -0700
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) , but it is not
implementable with lambdas, given a desire to support either assigning to
an existing variable or to new one, and given a desire to support:

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

Which is to say, ASSIGN_OR_RETURN can't expand to be a series of
statements, because then the "if" would only apply to the first of those
statements. And you can't put the statements inside braces, because then
assigning to a new variable would destruct the new variable when the scope
introduced by the braces ends.

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.

= - = - = - = - = - = - =

You forgot one thing, though.

(c) You have to address the nit-pickers who need to know how to handle the
existing problems with statement expressions.

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? Or are the
destructors delayed until the statement expression's result is dealt with?

For another, we'll have yet another case where parenthesizing an expression
changes its semantics.

For example, "max(1,2)" and "(max)(1,2)" do different things. Because if
max has been #defiine'd, putting parentheses around max will block macro
expansion.

Likewise, "max(x,2)" and "(max)(x,2)" do different things. Because ADL
lookup will examine x's namespace for declarations of max... but only if
max is *not* in parentheses.

And now, "{2, 3}" and "({2, 3})" will mean different things.

Note that the fact that parenthesization alters semantics is not a big
issue except in the context of the use of macros. However, as noted above,
the use of macros is one area where statement expressions have high value,
and macros tend to use extra parentheses to avoid syntactical surprises, so
introducing a new syntactical surprise is not particularly welcome.

On Sat, Sep 12, 2020 at 2:26 PM Jason McKesson via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> True, but there now needs to be a justification as to why having an
> expression issue a return (or other control logic) is an important
> thing to be able to do. Most of the reasons to have
> statement-expressions boil down to macro stuff, which we ought to be
> getting away from, not encouraging.


I totally agree with you, Jason. However, one thing I learned from being
in the EWG room in person is that the idea that we "ought to be getting
away from" macro stuff is not universally held. There are plenty of silent
supporters of macros who will not be impressed by a proposal whose primary
motivation is "this allows us not to have to use macros". (I think this is
one reason why "if constexpr" can't be used to surround class or function
definitions. Sadly.)

Received on 2020-09-12 22:28:16