C++ Logo

std-proposals

Advanced search

Re: Initial Idea of Indirect If Statement

From: Andrew Tomazos <andrewtomazos_at_[hidden]>
Date: Sun, 27 Oct 2019 19:18:49 +1000
On Sun, Oct 27, 2019 at 6:04 PM Jorg Brown <jorg.brown_at_[hidden]> wrote:

> You wrote that this one line:
>
> if (DECL : EXPR) X else Y;
>
> is equivalent to these 9 lines:
>
> {
> auto&& __c = EXPR;
> if (__c) {
> DECL = *__c;
> X;
> } else {
> Y; // noteworthy: DECL is not in scope here
> }
> }
>
> But with the advent of C++11, this is already much much simpler, since the
> __c assignment can be folded into the if.
>

> Also, it's a bit misleading to combine everything into one line, for
> comparison purposes. And the capitalization makes me think of macros.
>

The purpose of this statement of equivalence was purely to communicate the
proposed semantics, and not as a before v after comparison. DECL names a
sequence of tokens that matches a `for-range-declaration`, EXPR names some
sequence of tokens that matches a `expr-or-braced-init-list` and X and Y
names some sequence of tokens that both match `statement`.

>

>
> I would write it this way. This one line:
>
> if (type_goes_here decl : expr) X(decl) else Y();
>
> is equivalent to this line:
>
> if (type_goes_here decl = expr) X(*decl) else Y();
>
> with the exception that decl is not in scope for Y() clause, when this new
> form of if is used.
>
> I found your std::get_if use case compelling... but for fun I searched
> Google's internal C++ for uses and found 4 already. One use was:
>
> const PacketVariant packet = ...expr...;
> if (auto packet_ptr = std::get_if<PacketPtr>(&packet)) {
> ScheduleSendPacket(*packet_ptr);
> } else if (auto packet_ptr =
> std::get_if<InjectablePacketPtr>(&packet)) {
> InjectPacket(*packet_ptr);
> }
>
> As you can see, the fact that the first packet_ptr was still in scope,
> didn't prevent us from defining a second packet_ptr with a different type.
> And I think that allowing them both to have the same name would have been
> the primary benefit of the second packet_ptr not being in scope.
>

Yes I am familiar with that pattern, long before get_if people used it with
dynamic_cast to do a similar downcast if-else chain over some base class.
They are both poor-mans pattern matching.

I guess the motivation isn't as self-evident as I had hoped:

There are a number of types in the language that are optional-like. They
are explicitly convertible to bool and answer to unary operator*. If the
conversion to bool would result in false, it is UB to call operator* on
them. Such types include at least pointers, std::unique_ptr,
std::shared_ptr and std::optional. Beyond that there are a number of
functions that return objects of such types (such as dynamic_cast, get_if,
and many many others that use the false state to indicate failure). By
providing a way to fuse a branch on the bool conversion with the
dereference only in the safe (true) branch, we make the UB (from the false
state + dereference) statically unreachable. (or at least harder to
accidentally trigger)

In short: it's a safety issue. Ask yourself what the potential problems
are if the programmer made a typo in your PacketVariant example.

For the benefit of std-proposal folks: Unfortunately this proposal is a
duplicate of N4127
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4127.html which
was rejected pretty hard in 2014 - so unfortunately the current intention
is to withdraw this.

So, sorry, I don't find the extra implicit * worthy of a new language
> syntax.
>
> -- Jorg
>
>
>
> On Sat, Oct 26, 2019 at 9:15 AM Andrew Tomazos via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> We propose a new variant of the if statement that fuses a condition
>> declaration with an indirection in the true branch. We call it an
>> 'indirect if statement'.
>>
>> An indirect if statement of the form:
>>
>> if (DECL : EXPR) X else Y;
>>
>> is equivalent to:
>>
>> {
>> auto&& __c = EXPR;
>> if (__c) {
>> DECL = *__c;
>> X;
>> } else {
>> Y; // noteworthy: DECL is not in scope here
>> }
>> }
>>
>> So for example:
>>
>> int* get();
>>
>> int main() {
>> if (int x : get())
>> return x;
>> else
>> return EXIT_FAILURE;
>> }
>>
>> void f(std::variant<A,B,C> V) {
>> if (auto x : std::get_if<A>(V)) {
>> /*...*/
>> } else if (auto x : std::get_if<B>(V)) {
>> /*...*/
>> } else if (auto x : std::get_if<C>(V)) {
>> /*...*/
>> }
>> }
>>
>> template<typename D, typename B>
>> D* derived_if(B& b) {
>> return dynamic_cast<D*>(&b);
>> }
>>
>> void g(Animal& a) {
>> if (auto& dog : derived_if<Dog>(a)) {
>> /* ... */
>> } else if (auto& cat : derived_if<Cat>(a)) {
>> /* ... */
>> } else if (auto& pig : derived_if<Pig>(a)) {
>> /* ... */
>> }
>> }
>>
>> T* ptr;
>> std::optional<T> opt;
>> std::shared_ptr<T> sptr;
>> std::unique_ptr<T> uptr;
>>
>> int main() {
>> if (T x : ptr)
>> /*...*/;
>>
>> if (T x : opt)
>> /*...*/;
>>
>> if (T x : sptr)
>> /*...*/;
>>
>> if (T x : uptr)
>> /*...*/
>> }
>>
>> The motivation should be self-evident enough from the examples for this
>> first note.
>>
>> Initial thoughts appreciated.
>> -Andrew.
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2019-10-27 04:21:20