C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Proposal to allow multiple template parameters packs of different types

From: Anoop Rana <ranaanoop986_at_[hidden]>
Date: Tue, 15 Nov 2022 23:12:05 +0530
>
> template<class... Ts, class... Us> void f();
> f<int,int>();
>
> is actually accepted today by both GCC and Clang (deducing Ts={int,int},
> Us={}).
> MSVC rejects the template declaration of `f` itself (even before it's ever
> called) with a hard error. I think MSVC's behavior is what the Standard
> strictly requires.
>

Yes, msvc is correct(as per current wording) in rejecting the program as
also answered on this stackoverflow answer
<https://stackoverflow.com/a/73279832/12002570>.

And ideally you'd be saying "Let's change the rules to *the thing that 2/3
> of vendors already do*" (i.e., "standardize existing practice"). I don't
> think that's actually what you're saying, *but, maybe it should be*?
>

Yes, that is exactly what point *1) *in my previous email offers. In
particular, we can make the program well-formed with `Ts={int, int}, Us={
}` which GCC and CLANG already do. This is perfectly reasonable and the
fact that 2/3 of the compilers already do this is also a plus(as you also
noted). This in turn means that the paper proposes two changes in the
standard. First is the change mentioned in temp.param#14(given in the
document) and second change is the addition of a separate clause saying
something like a template parameter pack consumes all the explicitly
template arguments. This is nothing but the *first option* that the paper
offers.

*Second option* that the paper offers is to make the program ill-formed due
to ambiguity in knowing which arguments match which parameter. Note that
even though the program is ill-formed according to the current wording but
it is ill-formed for a different reason currently. In particular, currently
it is ill-formed because U just can't be deduced or explicitly specified
which has nothing to do with ambiguity. This also means that even though
msvc currently rejects the program, it rejects it for a different reason
than the one which is proposed in this paper. If this second option is
choosen it will mean that we will need to make two changes. First is the
change mentioned in temp.param#14(given in the document). This first change
is common in both of the options. The second change that we will need to
make in this case is to add a separate clause saying something like if
there is an ambiguity in matching template arguments with template
parameter packs then the program is ill-formed.

Instead of spreading out your "before/after comparison" section vertically
> over three pages, I suggest you make a little table something like this:


I will revise the formatting(of before/after comparison) in my paper with
your given suggestion. My aim was to make it visually clean and
straightforward but after seeing your suggested table format, I think your
suggested table format can be more useful as it can contain more
information and in an organized way too.


> FWIW, I'd say "the same kind" rather than "the same underlying type," as
> the latter has a technical meaning already.
>

 I will replace the term "same underlying type" with "same kind" in my next
revised version of the document. I originally used the term to give more
emphasis on "type" vs "non-type" vs "template" but seeing that the term
"same underlying type" already has a different technical meaning, I will
change that term to just "same kind" .

As before, the paper lacks actual motivation for why a working programmer
> would want this change (or any change in this area). The proposal increases the
> number of paint splotches that are valid C++ programs
> <https://www.mcmillen.dev/sigbovik/2019.pdf>, but what is the *actual*
> benefit to a human C++ programmer?
>

I will add some use case(s) that I currently am aware of in my next
revision of the document.


A procedural note: You've labeled your mailing-list drafts PxxxxR0 and
> PxxxxR1. You should instead label them DxxxxR0 (draft revision 0) and
> DxxxxR0 (draft revision 1), and so on.
>

Okay, so the next revision of my draft will be named "*DxxxxR2*" as 2
versions(named PxxxxR0 and PxxxxR1) were already mailed here.

When (if) you submit the paper to an actual mailing, you'll number *that
> submission* as PxxxxR0, and then start working on new drafts as DxxxxR1
> (draft revision whatever).
>

I didn't understand what you meant by "actual mailing". Can you elaborate
on that. I mean is it different from this and how is it different(if it
is). I've read how to submit a c++ proposal
<https://isocpp.org/std/submit-a-proposal> but still have some doubts from
that page. Not sure if those should be asked in the std-discussion mailing
list.


Regards,
Anoop Rana







On Tue, 15 Nov 2022 at 21:13, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
wrote:

> On Mon, Nov 14, 2022 at 1:08 PM Anoop Rana <ranaanoop986_at_[hidden]> wrote:
>
>> > Ts=<int,int>, Us=<> , but as far as I could tell, Anoop rejects that
>> outcome.
>>
>> Note that I don't actually reject `Ts={int, int}`, `Us={ }`. What I am
>> objecting is that the program 1.1 is ill-formed *with `U` not being { } *as
>> per the current wording instead of being
>>
>> *1)* well-formed with `Ts={int, int}`, `Us={ }` assuming packs are
>> greedy(no ambiguity) or
>> *2) *ill-formed with `Ts={int, int}`, `Us={ }` assuming this is
>> ambiguous instead of being greedy or
>> *3) *ill-formed with `Ts={int, int}`, `Us={}` assuming packs are
>> greedy(no ambiguity). If this point 3 is what the standard currently
>> supports then it doesn't make sense because a parameter pack is allowed to
>> be empty so that Ts={int, int}, Us={ } is an expected output and the
>> program should be well-formed instead of ill-formed. Note again that
>> Ts={int, int}, Us={ } is an expected output in this point 3 because there
>> is no ambiguity interpretation used here in this point. These
>> interpretations are separate from each other as also noted below.
>>
>
> I think your classification into 1,2,3 doesn't make sense; but I've also
> just realized that I was wrong about the current state of affairs.
>
> template<class... Ts, class... Us> void f();
> f<int,int>();
>
> is actually accepted today by both GCC and Clang (deducing Ts={int,int},
> Us={}).
> MSVC rejects the template declaration of `f` itself (even before it's ever
> called) with a hard error. I think MSVC's behavior is what the Standard
> strictly requires.
> But this is highly relevant to your paper, because "Let's change the rules
> that everyone currently follows" is a harder sell than "Let's change the
> rules that 2/3 of vendors don't follow anyway." And ideally you'd be saying
> "Let's change the rules to *the thing that 2/3 of vendors already do*"
> (i.e., "standardize existing practice"). I don't think that's actually
> what you're saying, but, maybe it should be?
>
> As before, the paper lacks actual motivation for why a working programmer
> would want this change (or any change in this area). The proposal increases the
> number of paint splotches that are valid C++ programs
> <https://www.mcmillen.dev/sigbovik/2019.pdf>, but what is the *actual*
> benefit to a human C++ programmer?
>
> Instead of spreading out your "before/after comparison" section vertically
> over three pages, I suggest you make a little table something like this:
>
>
> |----------------------------------------------------------|-----------------|-----|-------|------|------|----------|
>
> | Template | Usage
> | GCC | Clang | MSVC | EDG | Standard |
>
>
> |----------------------------------------------------------|-----------------|-----|-------|------|------|----------|
>
> | template<class... Ts, class... Us> int f1(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, class... Us> int f1(); | f1<int, int>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int... Us> int f2(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int... Us> int f2(); | f2<int, 1>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int U> int f3(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int U> int f3(); | f3<int, 1>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, template<class> class U> int f4(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, template<class> class U> int f4(); | f4<int, C>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int U, class... Vs> int f5(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int U, class... Vs> int f5(); | f5<int, 1>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int U, class... Vs> int f5(); | f5<1, int>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int U, class... Vs> int f5(); | f5<int, 1,
> int> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int... Us, class... Vs> int f6(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int... Us, class... Vs> int f6(); | f6<int>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int... Us, class... Vs> int f6(); | f6<int, int>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<class... Ts, int... Us, class... Vs> int f6(); | f6<int, 1>
> | Err | Err | Err | Err | Err |
>
> | template<class... Ts, int... Us, class... Vs> int f6(); | f6<int, 1,
> int> | Err | Err | Err | Err | Err |
>
> | template<auto... Ts, int... Us> int f7(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<auto... Ts, int... Us> int f7(); | f7<1>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<auto... Ts, int... Us> int f7(); | f7<&i, 1>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<int... Ts, auto... Us> int f8(); |
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<int... Ts, auto... Us> int f8(); | f7<1>
> | ✓ | ✓ | Err | Warn | Err |
>
> | template<int... Ts, auto... Us> int f8(); | f7<&i>
> | Err | Err | Err | Err | Err |
>
> | template<same_as<char> auto... Ts, same_as<int> auto... Us> int f9(); |
> | ✓ | ✓ | Err | Unsupp | Err |
>
> | template<same_as<char> auto... Ts, same_as<int> auto... Us> int f9(); |
> f9<1> | Err | Err | Err | Unsupp | Err |
>
> | template<same_as<char> auto... Ts, same_as<int> auto... Us> int f9(); |
> f9<'x', 1> | Err | Err | Err | Unsupp | Err |
>
> and then add another column on the end, "Proposed," with what you propose
> for each case.
>
> *NOTE:*
>> Just to sum up some things, according to the paper `template<typename...
>> T, typename... U> f(){}` is ill-formed because `U` and `T` have the same
>> underlying types(meaning they both are "type" parameter packs)
>>
>
> FWIW, I'd say "the same kind" rather than "the same underlying type," as
> the latter has a technical meaning already.
> I'd like to see an example where the parser's kind-disambiguation rules
> come into play, like
> constexpr double PI = 3.14; // red herring
> template<char... Ts, class... Us> int f10(); // currently ill-formed,
> proposed OK
> f10<char{}>(); // proposed OK with Ts={0} Us={}
> f10<char()>(); // proposed OK with Ts={} Us={char()}
> f10<char(int(3.14))>(); // proposed OK with Ts={3} Us={}
> f10<char(int(PI))>(); // proposed OK with Ts={} Us={char(int)}
>
> A procedural note: You've labeled your mailing-list drafts PxxxxR0 and
> PxxxxR1. You should instead label them DxxxxR0 (draft revision 0) and
> DxxxxR0 (draft revision 1), and so on. When (if) you submit the paper to an
> actual mailing, you'll number *that submission* as PxxxxR0, and then
> start working on new drafts as DxxxxR1 (draft revision whatever).
>
> HTH,
> Arthur
>
>>

Received on 2022-11-15 17:42:18