C++ Logo

SG7

Advanced search

Subject: Re: Slicing packs vs slicing ranges
From: Andrew Sutton (asutton.list_at_[hidden])
Date: 2020-11-19 08:32:40


> Speaking of unpacking meta::info ranges, I personally see operator
> *|something|...* as different type of operator *|something|* which is to
> me same thing as having multiple operators per AST type.
>

That's not quite right. The ... is an operation applied to an expression,
template-argument, base-type-specifier, etc. It's not part of the splice
operator.

So if I know the *something* is a pack/range, it should reify into a
> pack/range. In the example we can go with library solution:
>
> enum E { A, B, C};
> constexpr auto enums = meta::enumerators_of(reflexpr(E));
> int vals[] {|meta::pack_from_range(enums)|...};
>
> And here the *...* is unpacking an ordinary pack created by the reify
> operator.
>

I agree, but there's a bit of a can of worms here. We're essentially
generalizing what can be expanded. These issues apply outside of splicing
too. P1858 runs afoul of these same problems, which is why P2237 leans so
heavily on it. Anyway, I think (I hope) these are solvable problems.

For simple, non-dependent examples, the behaviors are pretty obvious. In
the example above, the operand of |x| is a vector, so we know |x| can be
expanded. But we don't always know whether the operand is a pack or a
range, and we don't always know what should be expanded.

template<auto X>
void f() { eat(|X|...); }

This would normally be ill-formed at parse time (not instantiation time)
since |X| does not contain unexpanded parameter packs. But, we'd like to
expand over ranges of reflections too, so we'll need to say *something*
that lets us know |X| can be expanded.

I suggested this yesterday because I like the symmetry (see below, I'm
going to walk this back). This notation also appears in P1858.

template<auto X>
void f() { eat(...|X|...); }

For context: P1240 places ellipsis inside the splice operator:

template<auto X>
void f() { eat(idexpr(...X)); } // OK, but X had better be a Range when
instantiated

template<auto... Xs>
void f() { eat(idexpr(...Xs)); } // OK, Xs will expand in the usual way

So that it's always clear that an expansion is going to happen, even in the
dependent case. What I don't like about this is that it's a specialized
syntax for expanding over splices. What if somebody writes:

template<auto... Xs>
void f() { eat(idexpr(...Xs)...); }

Is it ill-formed? Do we just ignore the trailing expansion? Does it do
something different?

The notation also doesn't work naturally with fold expressions.

(idexpr(...Xs) && ...)

We could make that valid, but it visually puts the expansion in the wrong
place. I would be very unhappy if tried to make this just work. Also
contrast with P2237 splices:

(|Xs| && ...) // OK If Xs is a pack or range

That said, I wouldn't be unhappy if we always required an annotation on the
splice to indicate its operand is a range. For example:

template<enumeral E, typename... Xs> // E is an enum
void f() {
  eat(|Xs|...); // normal pack expansion
  eat(|...members_of(E)|...); // range expansion
}

Now that I think about it, I would probably want to use prefix ... to
indicate that it's operand is an unexpanded subexpression. (Based on my
incomplete understanding of how Clang and GCC implement pack expansion,
knowing which subexpressions can be expanded is kinda important.) Then you
could write more complex expansions of splices like this:

eat(|...members_of(E1)| + |...members_of(E2)| ...);

Which expands to a sequence of arguments, each of which is the sum of
corresponding enumerators in E1 and E2 assuming they have the same length
(ill-formed if not).

This also lets you mix range and pack expansion in a single operand:

eat(|...members_of(E)| + |Xs| ...);

And it's not too bad in fold expressions:

(|...vec| + ...)

Andrew

Hana
>
> On 19. 11. 2020, at 2:55, Barry Revzin via SG7 <sg7_at_[hidden]>
> wrote:
>
> Hey all,
>
> Andrew's Metaprogramming paper allows for the following syntax (pg 19 from
> D2237R1):
>
> enum E { A, B, C};
> constexpr auto enums = meta::enumerators_of(reflexpr(E));
> int vals[] {|enums|... };
>
> That is, you can directly expand a splice of enums, because it's a
> constexpr range. But this is the same exact syntax that would happen if we
> had a parameter pack:
>
> template <meta::info... enums>
> void f() {
> int vals[] { |enums| ... };
> }
>
> And I'm concerned about implicitly treating both ranges and packs the
> same. I think this could run into similar issues as we saw with expansion
> statements, and similar issues to the case I talk about in P1858 (the
> context here was directly expanding a tuple into its underlying parts,
> which is similar to expanding a range of meta::infos into its infos):
>
> template <typename T, typename... U>void call_f(T t, U... us){ // Is this intended to add 't' to each element in 'us' // or is intended to pairwise sum the tuple 't' and // the pack 'us'? f((t + us)...);}
>
> Here, it's easy for me to come up with examples of wanting either choice:
> * call_f(tuple(1, 2), 3, 4) could be intended to call f(4, 6), doing the
> pairwise sum.
> * call_f(point{1, 2}, point{0, 1}, point{3, 7}) could be intended to call
> f(point{1,3}, point{4,9}), adding 't' to each element.
>
> A similar thing could come up here - where we have a range of meta::info
> and a pack and may want to expand the range and may not want to:
>
> template <typename E, typename... Args>
> void foo(Args... args) {
> constexpr auto enums = meta::enumerators_of(reflexpr(E));
> g(|impl(enums, args)|...);
> }
> enum E { A, B, C };
> void bar() {
> foo<E>(1, 2, 3);
> }
>
> Are we calling impl(enums, 1) or are we calling impl(reflexpr(E::A), 1)?
>
> Barry
> --
> SG7 mailing list
> SG7_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg7
>
>
> --
> SG7 mailing list
> SG7_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg7
>



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

Older Archives on Google Groups