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@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg7

--
SG7 mailing list
SG7@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg7