C++ Logo

SG7

Advanced search

Subject: Re: P2320: "The Syntax of Static Reflection" feedback request
From: Andrew Sutton (asutton.list_at_[hidden])
Date: 2021-02-16 07:49:47


> 2. I would probably prefer the inverse “splice” operation to then also be
> a unary operator (an alternative which is discussed at the bottom of the
> paper). Not only does having them both be unary ops make their inverse
> relationship clearer, but the “[: :]” is a bit too clumsy for me, have to
> press and release shift too many times, and so many dots in there it
> clutters things up a bit visually.
>

On my keyboard, there is 1 more shifted character in [:expr:] than $expr
and 1 less than $(expr).

In particular I would suggest $ as the inverse of ^; that visually says to
> me it is "cashing in” a higher-order/meta object, bringing the ^ back down
> to earth.
>

That metaphor may not translate well to countries that don't use $ as a
currency symbol. We did consider unary / for reflection and \ for splicing,
but at the end of the day, decided that a bracket was slightly better.

> Importantly, you could also allow usage of parens with both operators for
> clarity/visually “enclosing” the expressions for users that want or need
> it; e.g.
>
> ```
> namespace detail {
> auto srefl = ^S;
> auto innerRefl = ^(S::Inner);
> }
> using InnerAlias = typename $(detail::srefl)::Inner;
> ```
>

Depending on how you specify the operand, a unary splice runs afoul of
serious parsing problems. If the operand is a postfix expression:

new typename $T(args) // can parse as:
new typename $[T(args)]
new typename [$T](args)

The workaround is to limit the operand to id-expressions or (E). And now,
every time you want to splice something more than complex than a variable
name you're typing the entire sequence $(E), and now we're effectively back
to a bracket operator. And somebody will eventually suggest, "require the
parens".

Also, unary-expressions whose operands are postfix expressions don't work
well for splicing members.

x.$m(a) // parses as this:
x.[$m(a)]

But if you wanted to x.$m with (a), you'd have to write x.$(m)(a), which is
pretty meh. Contrast to x.[:m:](a), where the splice is plainly obvious.

> By the way — what does reflecting an alias do? Does it fetch the
> properties of the canonical type, or the properties of the alias itself?
> If you wanted to support both, perhaps we could use the parens after ^ to
> distinguish:
> ```
> auto innerAliasRefl = ^(InnerAlias); // reflects the canonical type
> auto innerRefl = ^InnerAlias; //reflects the alias
> static_assert(std::is_same_v<$innerRefl,
> $(meta::desugar_alias(innerAliasRefl))>);
> ```
>

The semantics of reflection are described in P1240 and again in P2237, both
of which should be considered required reading for this discussion. We have
some small changes planned, but the semantics are relatively stable.

> 3. All the dots in the pack expansion stuff are pretty hard to read; I
> think more consideration/justification/explanation of that syntax is
> warranted. In particular I don’t understand the need to “nominate” a pack
> for expansion; I see the footnote about some compilers needing to be
> alerted beforehand about whether or not to keep the tokens that follow, but
> I don’t understand that, nor have much sympathy for those compilers :) —
> better the compilers deal with confusion than the user.
>

Even for compilers that are "better", you need a way of designating a
subexpression of, say an initializer-clause as being something that can be
expanded. Packs are declared. Other terms that might be expanded are not.
Our starting point was just adding ... before that subexpression, just like
we do with pack declarations (points for consistency).

We've gone through several iterations of this idea (along with Barry
Revzin, who's written a bunch of pack-related proposals). Eventually, we
realized that trying to specify exactly which subexpresion was a pack was
kind of meaningless and that we should just move the "pack operator" all
the way to the left of the initializer clause. It makes the entire
expression a pattern for expansion, so we get:

f(... /*expression containing things that may be packs */ ...);

Which, despite having a lot of dots, is actually a really general,
consistent and IMO elegant solution to all of the problems we've run into
trying to figure out how this should work.

And if you actually read the paper, there's discussion about dropping the
trailing ellipsis when using a leading ellipsis because you can't have an
unexpanded pattern.

Andrew



SG7 list run by sg7-owner@lists.isocpp.org

Older Archives on Google Groups