cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[temp.variadic] p1 How many template arguments does a template parameter pack that is a pack expansion accept #462

Open xmh0511 opened 11 months ago

xmh0511 commented 11 months ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

[temp.param] p17 says:

If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack ... Similarly, a template parameter pack that is a type-parameter with a template-parameter-list containing one or more unexpanded packs is a pack expansion.

Consider this example:

#include <iostream>

template<class ...T>
struct A{
    template<template<T v> class...Tmpl>
    void fun(){}
};

template<int>
struct B{};

template<short>
struct C{};

int main(){
    A<int, short>{}.fun<B,C>();
}

GCC and Clang have a divergence to this example. [temp.variadic] p1 just says

A template parameter pack is a template parameter that accepts zero or more template arguments.

So, a template parameter pack can accept an arbitrary number of template arguments. However, in this example, the template parameter pack Tmpl is also a pack expansion that only has two elements, which intuitively should only accept two template-template arguments, I think.

Suggested Resolution

A parameter pack that is a pack expansion and that expands a pack P shall have the same number of elements as that of P.

languagelawyer commented 11 months ago

Looks similar to CWG2383

xmh0511 commented 11 months ago

This is another example where the implementations have divergences

#include <iostream>
#include <tuple>
template<class ...T>
struct A{
    template<template<T ...v> class...Tmpl>
    void fun(){
        using type = std::tuple<Tmpl<0>...>;
        std::cout<< typeid(type).name();
    }
};

template<int>
struct B{};

template<short>
struct C{};

int main(){
    A<int, int>{}.fun<B,C>();
}

Clang accepts it while GCC rejects it.

xmh0511 commented 11 months ago

Looks similar to CWG2383

Not that similar.

frederick-vs-ja commented 11 months ago

I failed to recognize the potential defect in the standard wording. It seems clear to me that GCC is obviously buggy.

In the first example,

In the second example, GCC is just unable to determine what Tmpl is, and the error message is definitely wrong.

xmh0511 commented 11 months ago

I failed to recognize the potential defect in the standard wording. It seems clear to me that GCC is obviously buggy.

In the first example,

* if we change to write `A<int, short>{}.fun<B>()`, then GCC raises ICE;

* if we change to write `A<int, short>{}.fun<>()`, then GCC incorrectly accepts it (which is similar to [CWG2383](https://cplusplus.github.io/CWG/issues/2383.html)).

In the second example, GCC is just unable to determine what Tmpl is, and the error message is definitely wrong.

No, the wording is not clear in this domain, I think.

A template parameter pack is a template parameter that accepts zero or more template arguments.

This permits A<int, short>{}.fun<B,B,B,B,B,B,B,...>();

In the second case, Clang also is wrong. the class template B cannot match template<intm int> class...Tmpl(after instantiation), I think.

frederick-vs-ja commented 11 months ago

[temp.variadic] p7 states:

[...] All of the packs expanded by a pack expansion shall have the same number of arguments specified. [...]

I think this sentence should apply to these examples.

xmh0511 commented 11 months ago

How do you think [temp.variadic] p7 is relevant here? There is no more than one pack that is expanded by the pattern of a pack expansion in my example.

[temp.variadic] p7 applies the case like tuple<T, U>... where T and U are packs and they have different numbers of elements.

frederick-vs-ja commented 11 months ago

How do you think [temp.variadic] p7 is relevant here?

The rationale mentioned in CWG2383 suggested that [temp.variadic] p7 should be relevant. Do you mean that it was wrong to consider these case to be subject to [temp.variadic] p7? (CWG2383 should be reopened if your reading is right.)

[temp.variadic] p7 applies the case like tuple<T, U>... where T and U are packs and they have different numbers of elements.

Not only in this case, see [temp.variadic] p5.3.

xmh0511 commented 11 months ago

You quoted

All of the packs expanded by a pack expansion shall have the same number of arguments specified.

However, in my example, template<T v> class...Tmpl is a pack expansion that only expands one pack T. I didn't see how you think your quoted rule should apply to my example, anyway.

Not only in this case, see [temp.variadic] p5.3.

Again, I just gave an example of how your quoted rule should work, I didn't say that's the only case.


The conclusion in that CWG issue seems to imply we lack wording like:

A pack that is expanded by another parameter pack declaration that is a pack expansion shall have the same number of elements.

frederick-vs-ja commented 11 months ago

However, in my example, template<T v> class...Tmpl is a pack expansion that only expands one pack T. I didn't see how you think your quoted rule should apply to my example, anyway.

Because the Rationale shown CWG2383 clearly indicated this.

The old example was:

template<class ...Types> struct Tuple_ { // _VARIADIC_TEMPLATE
  template<Types ...T> int f() {
    return sizeof...(Types);
  }
};

int main() {
  Tuple_<char,int> a;
  int b = a.f();
}

while the posted rationale was:

The example is ill-formed because the packs have different sizes: Types has 2, T has 0 (from the call).

And nothing other than that quoted sentence can indicate such kind of ill-formedness.

xmh0511 commented 11 months ago

The example is ill-formed because the packs have different sizes: Types has 2, T has 0 (from the call).

And nothing other than that quoted sentence can indicate such kind of ill-formedness.

However, your quoted sentence indeed does not apply to different packs in different pack expansions. The meaning of the quoted rule is just that simple: all packs that are expanded by the same pack expansion shall agree on their number of elements.

With that simplified example, class ...Types declares a parameter pack Types, and Types ...T also declares another parameter pack T, merely, the declaration is also a pack expansion. However, such two packs are not expanded by the same pack expansion, so your quote rule does not apply. Be carefully read your quoted rule

All of the packs expanded by a pack expansion shall have the same number of arguments specified.

Note that "a". I do think temp.variadic#example-5 is good to clarify what the rule intend to mean

template<class ... Args1> struct zip {
  template<class ... Args2> struct with {
    typedef Tuple<Pair<Args1, Args2> ... > type;
  };
};

typedef zip<short>::with<unsigned short, unsigned>::type T2;
    // error: different number of arguments specified for Args1 and Args2

Within this single pack expansion Pair<Args1, Args2> ..., Args1 and Args2 do not agree with the number of elements, which is just the intend of the rule.

frederick-vs-ja commented 11 months ago

With that simplified example, class ...Types declares a parameter pack Types, and Types ...T also declares another parameter pack T, merely, the declaration is also a pack expansion. However, such two packs are not expanded by the same pack expansion, so your quote rule does not apply.

So, do you mean that the rationale is not currently right, and additional wording is needed for justification?

When mentioning [temp.variadic] p5.3, I thought that "a template parameter pack that is a pack expansion is considered to expand itself", without which I didn't come up with a consistent reading.

This permits A<int, short>{}.fun<B,B,B,B,B,B,B,...>();

Considering this case you suggested... what's T if this is permitted?

xmh0511 commented 11 months ago

So, do you mean that the rationale is not currently right, and additional wording is needed for justification?

In terms of the status quo, we lack that wording.

When mentioning [temp.variadic] p5.3, I thought that "a template parameter pack that is a pack expansion is considered to expand itself", without which I didn't come up with a consistent reading.

a template parameter pack that is a pack expansion does not expand the pack that is being declared, instead, the template parameter pack expands another pack that has been declared by another parameter pack declaration, see [temp.param] p17. Moreover [temp.variadic] p7 says

A pack whose name appears within the pattern of a pack expansion is expanded by that pack expansion.

For the pack expansion Types ...T, it's the pack expansion of pack Types rather than T.

Considering this case you suggested... what's T if this is permitted?

This is the concerned issue here, template<T v> class ...Tmpl declares a pack Tmpl, then [temp.variadic] p1 says

A template parameter pack is a template parameter that accepts zero or more template arguments.

There is no wording to constrain how many arguments pack Tmpl can actually accept when it is declared by expanding another pack T.

t3nsor commented 11 months ago

I'm not understanding the confusion here. Here was the example in the original post:

template<class ...T>
struct A{
    template<template<T v> class...Tmpl>
    void fun(){}
};

The inner type-parameter is a pack expansion. When a specialization of A is instantiated, the instantiation of the declaration of fun results in the expansion of the inner pack. After expansion, it's no longer a pack; it's a list of 2 template parameters (assuming there are 2 elements in T). Which of these steps, in your opinion, is not specified by the current wording?

xmh0511 commented 11 months ago

Which of these steps, in your opinion, is not specified by the current wording?

When we say a template parameter is a pack, we are based on the grammar.

After expansion, it's no longer a pack; it's a list of 2 template parameters (assuming there are 2 elements in T).

No formal wording in the document says so. As you said, If Tmpl were no longer a pack, the Tmpl couldn't be expanded by other pack expansions anymore.

t3nsor commented 11 months ago

Which of these steps, in your opinion, is not specified by the current wording?

When we say a template parameter is a pack, we are based on the grammar.

After expansion, it's no longer a pack; it's a list of 2 template parameters (assuming there are 2 elements in T).

No formal wording in the document says so. As you said, If Tmpl were no longer a pack, the Tmpl couldn't be expanded by other pack expansions anymore.

Tmpl still denotes a pack after the declaration of Tmpl (i.e., in the template parameter list) has been expanded.

xmh0511 commented 11 months ago

Which of these steps, in your opinion, is not specified by the current wording?

When we say a template parameter is a pack, we are based on the grammar.

After expansion, it's no longer a pack; it's a list of 2 template parameters (assuming there are 2 elements in T).

No formal wording in the document says so. As you said, If Tmpl were no longer a pack, the Tmpl couldn't be expanded by other pack expansions anymore.

Tmpl still denotes a pack after the declaration of Tmpl (i.e., in the template parameter list) has been expanded.

If you agree that Tmpl is a template parameter pack, then [temp.variadic] p1 applies. If you don't agree that, then you should check [temp.param] p17

If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack.

t3nsor commented 11 months ago

Which of these steps, in your opinion, is not specified by the current wording?

When we say a template parameter is a pack, we are based on the grammar.

After expansion, it's no longer a pack; it's a list of 2 template parameters (assuming there are 2 elements in T).

No formal wording in the document says so. As you said, If Tmpl were no longer a pack, the Tmpl couldn't be expanded by other pack expansions anymore.

Tmpl still denotes a pack after the declaration of Tmpl (i.e., in the template parameter list) has been expanded.

If you agree that Tmpl is a template parameter pack, then [temp.variadic] p1 applies. If you don't agree that, then you should check [temp.param] p17

If a template-parameter is a type-parameter with an ellipsis prior to its optional identifier or is a parameter-declaration that declares a pack ([dcl.fct]), then the template-parameter is a template parameter pack.

No, again, you're conflating the declaration of Tmpl (which is expanded when the outer template is instantiated) with the meaning of the id-expression Tmpl should it appear within fun, which would be a pack (and thus itself would need to be expanded).

[temp.param]/17 implies that the declaration of Tmpl is a template parameter pack pre-expansion. It's because of this wording that that declaration ever gets expanded in the first place, i.e., the wording later in the same paragraph where it says that it's a pack expansion kicks in. After that has kicked in, the declaration of Tmpl, having been expanded, is effectively replaced by 2 type template parameters. But as long as that Tmpl is in scope, the id-expression Tmpl denotes the pack that comprises the sequence of template template parameters produced by expanding the declaration of Tmpl.

t3nsor commented 11 months ago

And I agree with @frederick-vs-ja that there is no real implementation divergence. GCC's behavior is not evidence that the other interpretation (i.e. that the inner pack accepts an arbitrary number of arguments, independently of how many arguments were provided to the outer pack) is plausible. If GCC were taking that interpretation, then GCC would actually accept an arbitrary number of arguments there (which it does not).

xmh0511 commented 11 months ago

It's because of this wording that that declaration ever gets expanded in the first place, i.e., the wording later in the same paragraph where it says that it's a pack expansion kicks in. After that has kicked in, the declaration of Tmpl, having been expanded, is effectively replaced by 2 type template parameters. But as long as that Tmpl is in scope, the id-expression Tmpl denotes the pack that comprises the sequence of template template parameters produced by expanding the declaration of Tmpl.

This is totally what you understand, not the meaning of the formal wording talks. Given the formal example and relevant comments

template <class... T>
  struct value_holder {
    template <T... Values> struct apply { };    // Values is a non-type template parameter pack
  };                                            // and a pack expansion

Values is a template parameter pack and simultaneously its declaration is a pack expansion. Since Values is a template parameter pack, then [temp.variadic] p1 applies. The rule is not in terms of whether the stuff it applies is the instantiated one or not.

xmh0511 commented 11 months ago

(i.e. that the inner pack accepts an arbitrary number of arguments, independently of how many arguments were provided to the outer pack) is plausible. If GCC were taking that interpretation, then GCC would actually accept an arbitrary number of arguments there (which it does not).

Note that GCC accepts zero arguments A<int, short>{}.fun<>(); https://godbolt.org/z/4Esq73naq

t3nsor commented 11 months ago

Your issue is based on this premise:

When we say a template parameter is a pack, we are based on the grammar.

It sounds like by this you're saying that a pack is always something that appears in the input to phase 7 (and because of that, at every point during semantic analysis, it's still a pack, because the input of phase 7 doesn't change; it just gets used to produce the output of phase 7). But this premise is itself not supported by the text. The issue boils down to the fact that the standard is sloppy at distinguishing them. The standard is sloppy, but there are probably hundreds of other places in the standard where that's also true. Most of the time, this doesn't result in any actual confusion about the intended meaning. Here, as well, there shouldn't be any such confusion about the intended meaning.

xmh0511 commented 11 months ago

IMO, the standard always defines the rules for grammar on the source code level. For example, [basic.def.odr] does not intend to apply to the instantiated declaration, such as https://github.com/cplusplus/CWG/issues/50#issuecomment-1165498636.

We lack a wording to constrain the number of elements in the declared pack P and the number of elements in the pack that is used in the parameter pack declaration of P

frederick-vs-ja commented 11 months ago

Note that GCC accepts zero arguments A<int, short>{}.fun<>(); https://godbolt.org/z/4Esq73naq

GCC only accepts zero argument, which doesn't conform to any viable reading (if the current specification is ambiguous).

We lack a wording to constrain the number of elements in the declared pack P and the number of elements in the pack that is used in the parameter pack declaration of P

The lacked constrains (if any) are not limited to those on numbers. Per your reading (A<int, short>{}.fun<B,B,B,B,B,B,B,...>() being allowed), it seems that A<short>{}.fun<B>() is still allowed even when the constrains on numbers are added.

If your reading makes sense, then either

In either case, there would be a larger issue than that on unequal numbers of elements.

xmh0511 commented 11 months ago

there can be inconsistent T's for a single specialization of A, or

I don't know how you can read out that meaning. Imposing the requirement on one aspect does not mean the other aspects are not necessary to be obeyed. By your logic, for this rule

All of the packs expanded by a pack expansion shall have the same number of arguments specified.

That would mean that there can be inconsistent arguments for a single specialization for the pack expansion, wouldn't it?

frederick-vs-ja commented 11 months ago

there can be inconsistent T's for a single specialization of A, or

I don't know how you can read out that meaning.

Maybe I was wrong for your reading. But I think correcting a wrong reading of a "wrong" (possibly right, but merely about a potential defect of the standard wording) reading makes little sense.

If this is not what you meant, it's sufficient to just clarify this.

Imposing the requirement on one aspect does not mean the other aspects are not necessary to be obeyed. By your logic, for this rule

All of the packs expanded by a pack expansion shall have the same number of arguments specified.

That would mean that there can be inconsistent arguments for a single specialization for the pack expansion, wouldn't it?

Totally not what I meant.

xmh0511 commented 10 months ago

Maybe I was wrong for your reading. But I think correcting a wrong reading of a "wrong" (possibly right, but merely about a potential defect of the standard wording) reading makes little sense.

As I said above, the status quo is we didn't impose the requirement between the number of elements in a template parameter pack P that is a pack expansion and the number of elements of another pack P2 to which the prior template parameter pack P is a pattern. For example:

template<class...T>
struct A{
   template<T... v>
   struct B{};
};

We should have a rule to impose the requirement to associate the number of elements in pack v with the number of elements in pack T

Your quoted rule does not apply to such a case at all.

frederick-vs-ja commented 10 months ago

We should have a rule to impose the requirement to associate the number of elements in pack v with the number of elements in pack T

Perhaps not only on the number. The requirement for number can be automatically added if proper requirements about matching are established (or is perhaps already added since the requirements are done).

Could you explain currently in this example what can v be for a certain T? E.g. is A<void*>::B<42> well-formed?

xmh0511 commented 10 months ago

Could you explain currently in this example what can v be for a certain T? E.g. is A<void*>::B<42> well-formed?

Whether according to the intuitive or the intent of the standard, this example should be ill-formed. If we have wording specifying that the template argument deduction occurs in these contexts, the issue will be easy to explain, for example, A<void*> makes the pack T be deduced to {void*}, then B<42> per [temp.deduct.type] p13

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value.

will make the pack T be deduced to {int}, then such two deduced results are conflicting, then according to [temp.deduct.type] p2

If type deduction cannot be done for any P/A pair, or if for any pair the deduction leads to more than one possible set of deduced values, or if different pairs yield different deduced values, or if any template argument remains neither deduced nor explicitly specified, template argument deduction fails.

Then, we can say the template argument deduction fails in this context is ill-formed. Moreover, if template argument deduction makes T have a different number of elements, the construct will be ill-formed too. The consistent deduction for the same parameter can provide "matching".

However, we do not have such wording to say that the template argument deduction occurs when we specify the template arguments for A<void*>::B.


IMO, T after the instantiation of the specialization A<void*> won't be considered as a dependent type, However, T...v in the declaration of template class B still be template parameter pack declaration, we do not have any wording says about what T...v is in the instantiated declaration(that is, it's not a lexical product), so, we should have wording to additional specify the association between pack v and the pack T, which should be "matching".