C++ Logo

SG7

Advanced search

Subject: Re: P2320: "The Syntax of Static Reflection" feedback request
From: David Rector (davrec_at_[hidden])
Date: 2021-02-16 09:31:46


> On Feb 16, 2021, at 8:49 AM, Andrew Sutton <asutton.list_at_[hidden]> wrote:
>
> 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.
>

$ can also be seen as an S for "splice", or still further as an S superimposed over a dividing line, for an even more vivid suggestion of "splicing".

While I wouldn’t say I have incredibly strong feelings about this, I think these subtle matters are worthy of serious consideration: I find that `&somepointer` is effortlessly read as "address of somepointer" largely due to the A-looking "&" (or "ampersand") immediately calling to mind "address" — the brain doesn’t need to halt and do mental gymnastics while parsing.

>
> 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.
>

I would probably write that `x.($m)(a)`, which would track with how you would write `x.(*m)(a)` if m were not a reflected method but a method pointer.

>
> 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.
>

This alias issue was an aside and can be ignored. I trust the alias can itself be reflected by some mechanism.

Re: "required reading", I’ll pass, I don’t think a request for feedback on syntax should require any reading; users who haven’t kept up to date can offer useful feedback, indeed I would argue the most important feedback, since they have some separation from the process, a defense against groupthink.

>
> 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.

That bit leads to still more confusion. So the idea, were we to drop the trailing ellipsis, is that reflected pack expansions will be expanded with a leading ellipsis? I.e. the exact reverse of every other situation?

Surely you can see how this warrants further justification, such potential for confusion can’t just be accepted under the rationale you’ve given.

So, walk me through why certain compiler can’t handle this, or identify where I’m misunderstanding:

```
template<typename... Args>
auto f(Args... args) {
  auto newArgsRefl = somemetafunc(^Args);
  return g(static_cast<[:newArgsRefl:]>(args)...);
}
```



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

Older Archives on Google Groups