Checked-dereference conditions

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 to  if (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.