Checked-dereference conditions (original) (raw)

I. Introduction

This is a proposal to add a new form of condition:

condition: expression attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause attribute-specifier-seqopt decl-specifier-seq declarator braced-init-list attribute-specifier-seqopt decl-specifier-seq declarator : expression

For example,if (T x : e) s translates toif (auto && __p = e) { T x = *__p; s } for some invisible name __p.

Translation for the new form of condition in a while or for loop is analogous.

The "next generation" version where the type defaults to auto && is also proposed.

II. Motivation

Range-based for-loops provide syntax that expands into the necessary bookkeeping and dereferencing needed to access the elements of a conventional begin()/end() traversable range.

There is a similar convention for checked access to pointees—the underlying objects of (smart) pointers and things like std::optional<>:

if (std::shared_ptr<T> sp = wp.lock())
{
    f(*sp);
}

Using the new form of condition, this can be written:

if (T & x : wp.lock())
{
    f(x);
}

Or, in next-gen form (where the type defaults to auto &&):

if (x : wp.lock())
{
    f(x);
}

Similar to how range-based for hides the iterators and just lets you name the underlying object, a checked-dereference condition hides the pointer-like thing and just lets you name the underlying object.

III. Use with while()

The new form of condition may also appear in a while loop:

while (T x : e) s

This translates to:

while (auto && __p = e) { T x = *__p; s }

This can be useful. For example, suppose we have:

std::optional<message> try_read(input &);

void process(message);

then instead of:

while (std::optional<message> m = try_read(i))
{
    process(*m);
}

we may write:

while (message m : try_read(i))
{
    process(m);
}

Or, in next-gen form:

while (m : try_read(i))
{
    process(m);
}

IV. Use with for()

Since a condition may also appear in a (traditional) for loop, a checked-dereference condition may be used there, too:

for (s1; T x : e1; e2) s2

translates to:

for (s1; auto && __p = e1; e2) { T x = *__p; s2 }

This form is not expected to be commonly used, but is easy to support consistently.

V. Use with switch()

Since a checked-dereference condition would not make much sense for a switch statement's condition, allowing this use is not proposed (so there would have to be a separate condition grammar production).

VI. Relation to monadic bind / pattern matching

The form if (T x : e) { ... } may remind one of something like

monad_bind(e, [](T x){ ... })

or even

functor_map(e, [](T x){ ... })

However, these are ways to make additional values "in the monad/functor".

By contrast, the checked-dereference condition is for getting underlying values "out" of the indirection, and so is more akin to pattern matching. That is,

if (x : e) s1; else s2;

is comparable to a pattern match like (here shown in Haskell):

case e of
    Just x -> s1
    Nothing -> s2

VII. Implementation

The author is planning a proof-of-concept implementation in Clang. Implementation experience for range-based and next-generation for suggests that the effort should be minimal.

VIII. Proposed Wording

None yet, but can be provided if there is interest.