1

From [temp.variadic] (working draft) it seemed to me that a parameters pack can be expanded while defining an arguments list of another template class or function.

Consider the following class:

template<typename... T>
struct S {
    template<T... I>
    void m() {}
};

int main() {
    S<int, char> s;
    // ...
}

The intent is to capture the types used to specialize the template class S and use them to define an arguments list of non-type parameters for the member method m (T is limited to a few types, of course, but this isn't the argument of the question).

Is this legal code? Can I use a parameter pack the way I used it or am I misinterpreting the standard (pretty sure that's the case indeed)?


In order to add more details to the question, here are some results from a few experiments with the major compilers:

At least, compilers seem to be as confused as me.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Yes it's valid and it can even be empty: https://stackoverflow.com/questions/32540178/what-can-you-do-with-templates-with-zero-template-parameters – Johannes Schaub - litb Sep 25 '16 at 18:23
  • GCC is surprisingly not very smart when it comes to variadic templates. – DeiDei Sep 25 '16 at 18:25
  • @JohannesSchaub-litb Shouldn't `s.m<0>();` be rejected thus? Both compilers are in error in that case? – skypjack Sep 25 '16 at 18:40
  • other SO users told me that the semantics are that "T...I" means that T is expanded and "I" becomes a pack for an int and a char value. (i.e a heterogenous non-type template parameter pack). I fail to see why clang accepts fewer than the two arguments for "I" though. That said, I still don't think I completely understand the rules for this particular case after all these years. – Johannes Schaub - litb Sep 25 '16 at 18:41
  • @JohannesSchaub-litb I was just reading them actually. Thus also [this](http://stackoverflow.com/questions/39665300/gcc-vs-clang-variadic-template-and-pointers-to-member-methods) code is valid? I would accept your answer if you can spend a minute on it. I guess it comes from almost the same problem. – skypjack Sep 25 '16 at 18:44
  • What seems to happen is that clang just ignores the other parameter http://coliru.stacked-crooked.com/a/52a61a4a7eb7b240 . Since my agreement with the comments in my other question was significantly based on how clang accepts "I...", and now I see that clang's handling with it is deeply bugged, I may have to say that both answers in my linked question are just guesses and my question still needs a qualified answer, just as yours. – Johannes Schaub - litb Sep 25 '16 at 18:48
  • @JohannesSchaub-litb Nice. I'm creating bounties for them, but I'm not sure I'll get proper answers. You are one of the most skilled here on SO and if you have my same doubts.. well, I guess I have to find some workarounds for unlikely I'll be able to use those expressions!! :-) – skypjack Sep 25 '16 at 18:52
  • And as my comment says their view is inconsistent with "The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function's parameter-type-list", because that would mean that `template struct A { void f() { } void f(T... t) { } }; A<> a;` would be valid, if `T... t` *really* declares a parameter pack. But as it happens, it really just expands `T`, making two `void f()` functions. Both clang and gcc correctly reject that code. – Johannes Schaub - litb Sep 25 '16 at 18:57
  • @JohannesSchaub-litb Chapeau. Sometimes it seems to me that you know the standard by heart!! Is it worth it to open issues to both the compilers? – skypjack Sep 25 '16 at 19:02

1 Answers1

2

The definition of the template S, and the instantiation of S<int, char>, are valid.

See [temp.param]/15: "A template parameter pack that is a parameter-declaration whose type contains one or more unexpanded parameter packs is a pack expansion."

This means that template<T ...I> can mean one of two different things: if T is a non-pack type, then it declares a normal parameter pack, accepting any number of Ts. However, if T contains an unexpanded parameter pack, then the parameter declaration is instead expanded into a sequence of parameters when the outer template is instantiated.

Your first call to m is valid, but your second and third calls to m are ill-formed

The instantiation of S<int, char> looks like this:

template<>
struct S<int, char> {
  template<int I$0, char I$1>
  void m() {}
};

(where I$0 and I$1 are the first and second slices of the pack I).

Therefore (because neither I$0 nor I$1 can be deduced from a call to m), s.m<0,'c'>() is valid but s.m<0>() and s.m<>() are ill-formed.

Richard Smith
  • 13,696
  • 56
  • 78
  • I've already opened issues to both the compilers, giving almost the same details. Thank you. Does the same apply to [this](http://stackoverflow.com/questions/39665300/gcc-vs-clang-variadic-template-and-pointers-to-member-methods) question? – skypjack Sep 25 '16 at 20:22
  • For completeness, [here](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=77731) is the issue to GCC and [here](https://llvm.org/bugs/show_bug.cgi?id=30518) is the one to clang. Feel free to add details if needed. You are far more skilled than me on the argument!! – skypjack Sep 25 '16 at 20:28
  • Thanks for answering. So after the pack is expanded, I would think that the pack is not a parameter pack anymore. However, in the member function `f`, I can still say `t...`. Should that not be forbidden, given that `T` at that time was already expanded and the member function template specialization should look like `void f(int t)` instead of `void f( pack)`? I.e the instantiation/expansion of a function parameter pack yields a sequence of parameters, I thought. – Johannes Schaub - litb Sep 26 '16 at 08:58
  • This is the code I'm talking about: http://coliru.stacked-crooked.com/a/12f94d8c9ae763c0 . If it is still a pack even in the member function specialization and not just in the temploid definition, I can't understand why `template struct A { void f(); void f(T...t); };` is rejected. The presence of a function parameter pack is part of the parameter type list of a function. – Johannes Schaub - litb Sep 26 '16 at 09:01
  • SO: either it is always a pack and in the function template specialization it can be expanded like in the coliru testcase or in the perfect forwarding scenario but then `void f(); void f(T... t)` should be a valid overload, or it is not a pack anymore once the template was instantiated. Then I can't really understand how we can expand `t...`, because its size is only known *after* the containing template was instantiated. When at that time it's not a pack anymore, it shouldn't be able to be expanded anymore. – Johannes Schaub - litb Sep 26 '16 at 09:06
  • @JohannesSchaub-litb It looks like you are struggling with some corner cases of the language, but it's somehow intriguing. – skypjack Sep 26 '16 at 16:49
  • @skypjack In [this other question](http://stackoverflow.com/questions/39665300/gcc-vs-clang-variadic-template-and-pointers-to-member-methods), the code appears to be valid. – Richard Smith Sep 26 '16 at 20:35
  • @RichardSmith Did right closing it. They were almost the same question. Thank you. – skypjack Sep 26 '16 at 21:37