28

I don't get the reason for which a parameter pack must be at the end of the parameter list if the latter is bound to a class, while the constraint is relaxed if the parameter list is part of a member method declaration.

In other terms, this one compiles:

class C {
    template<typename T, typename... Args, typename S>
    void fn() { }
};

The following one does not:

template<typename T, typename... Args, typename S>
class C { };

Why is the first case considered right and the second one is not?
I mean, if it's legal syntax, shouldn't it be in both the cases?

To be clear, the real problem is that I was defining a class similar to the following one:

template<typename T, typename... Args, typename Allocator>
class C { };

Having the allocator type as the last type would be appreciated, but I can work around it somehow (anyway, if you have a suggestion it's appreciated, maybe yours are far more elegant than mine!!).
That said, I got the error:

parameter pack 'Args' must be at the end of the template parameter list

So, I was just curious to fully understand why it's accepted in some cases, but it is not in some others.

Here is a similar question, but it simply explains how to solve the problem and that was quite clear to me.

Community
  • 1
  • 1
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • You're using a default for the last type parameter -- as far as I recall that changes things a lot. (But I forget the details). Perhaps you should include the defaults in both of your opening examples? – Aaron McDaid Jan 22 '16 at 07:17
  • Can you give an example of how you plan to call these templates? I'm not sure it's very relevant, but it might help – Aaron McDaid Jan 22 '16 at 07:18
  • Sorry, the default parameter was an error, no longer there. I plan to use that template as any other: `C>`. The issue is in the fact that that syntax is right for a member method, but it is not for a class definition and I don't understand why it is not!! – skypjack Jan 22 '16 at 07:34

2 Answers2

17

It is valid for function templates but only when argument deduction can help the compiler resolve the template parameters, as it stands your function template example is virtually useless because

template<typename T, typename... Args, typename S> void fn() { }
int main() { fn<int, int, int>(); }
test.cpp: In function 'int main()':
test.cpp:2:32: error: no matching function for call to 'fn()'
 int main() { fn<int, int, int>(); }
                                ^
test.cpp:1:57: note: candidate: template<class T, class ... Args, class S> void fn()
 template<typename T, typename... Args, typename S> void fn() { }
                                                         ^
test.cpp:1:57: note:   template argument deduction/substitution failed:
test.cpp:2:32: note:   couldn't deduce template parameter 'S'
 int main() { fn<int, int, int>(); }

the compiler has no way of determining which template parameters belong to the parameter pack, and which to S. In fact as @T.C. points out it should actually be a syntax error because a function template defined in this manner cannot ever be instantiated.

A more useful function template would be something like

template<typename T, typename... Args, typename S> void fn(S s) { }

as now the compiler is able to unambiguously match the function parameter s with the template type S, with the side effect that S will always be deduced - all explicit template parameters after the first will belong to Args.

None of this works for (primary) class templates, parameters aren't deduced and it's expressly forbidden:

From draft n4567

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4567.pdf

[temp.param] / 11

[...]If a template-parameter of a primary class template or alias template is a template parameter pack, it shall be the last template-parameter.[...]

(if they were deduced it would be ambiguous as in the function template example).

user657267
  • 20,568
  • 5
  • 58
  • 77
  • Uhm, OK, it seems that the example in the question has been reduced too much, but good point to point out how one can make it works or not. So it's legal syntax and it's all about deduction, no way to define a class like the one in the question, I've to find a reasonable and usable solution... Any idea? It would be appreciated, as mentioned in the question. :-) – skypjack Jan 22 '16 at 07:45
  • @skypjack I think you'd need to explain what exactly it is you're trying to do first, for instance why `Allocator` needs to be at the end. – user657267 Jan 22 '16 at 08:10
  • For it is there in almost all the classes of the library I've working on, but that's the first time I need to have a parameter pack in the class definition. Quite simple indeed, but if I can't find a solution still I can move it in another position, of course. – skypjack Jan 22 '16 at 08:13
  • 1
    @skypjack How about making the parameter pack its own class, like a `tuple` for instance? – user657267 Jan 22 '16 at 08:20
  • It makes sense, even though I guess it moves the problem a bit down because the first parameter and the parameters pack are used to define an inner function type `Ret(Params...)`. The prototype I was using when I found the problem was `template` and the class has a definition like `using some_type = Ret(Args...)`. Here I'd need to unroll the parameter pack somehow, if I use a *tuple* in the parameters list. Did I succeed in giving you an idea? – skypjack Jan 22 '16 at 08:24
  • 2
    @skypjack If the function type is meaningful to the end user, perhaps `template struct meow; template struct meow { };`? – T.C. Jan 22 '16 at 11:30
  • @T.C. You are right, far easier and more elegant than move it around!! I don't know why I didn't think to this solution (maybe because you are also far more skilled than me), thank you very much. Something like that should be fine. – skypjack Jan 22 '16 at 11:44
  • About "it has no way to determining...": couldn't the compiler take the template arguments `` consider `T` to be the first arg, `S` to be the last arg, and `Args` to be what remains? In theory, this looks feasible, at least when the argument list is explicit as in the example above. – chi Jan 22 '16 at 12:11
  • @chi I guess the problem is in the fact that the variadic part behaves greedily because of its position unless the type deduction helps the compiler to solve the ambiguity. – skypjack Jan 22 '16 at 14:23
  • @user657267 Can I invite you to integrate the solution proposed by T.C. into the answer? It's really a nice approach and I think it would be really useful to other users that have the same problem, wouldn't it? Thank you. – skypjack Jan 22 '16 at 17:45
  • 1
    @skypjack You should ask T.C. to add it to their answer, I don't want to take credit for someone else's idea :) – user657267 Jan 23 '16 at 01:30
  • I don't see what would be the ambiguity `template struct S{};`. When instantiated, first and last parameters match to the first and last non-variadic arguments (`A` and `Z` in this example). What is left is matched to the variadic `Args...`. – bolov Jan 23 '16 at 10:34
  • ...(cont). For instance: `S s1`. A is int, Args is char, Z is float. `S s2`. A is int, Z is float, `Args...` is empty. `S s3`. A is int, Z is float, Args... is `char, wchar`. – bolov Jan 23 '16 at 10:37
  • @bolov I suppose technically it's not an ambiguity because class template parameters aren't deduced. If they *were* deduced it would be ambiguous much like the function template example. In any case the standard mandates that class primary template parameter packs appear at the end of the template parameter list. – user657267 Jan 23 '16 at 11:02
  • @user657267 Would it be possible to put a link to the standard in the answer, other than a citation? For the sake of clarity, of course, you response is already a great one!! – skypjack Feb 01 '16 at 23:06
  • @skypjack The standard draft is updated fairly regularly so I'm not sure it makes much sense to link a specific version but I'll add one anyway. – user657267 Feb 02 '16 at 01:42
  • Thank you. I see, maybe you are right, but usually I put a link to the official documentation for I find it useful when I'm on the other side of the answer. :-) – skypjack Feb 02 '16 at 06:38
12

The first one is not right. The compiler is just buggy and failed to diagnose it. [temp.param]/11:

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list of the function template or has a default argument (14.8.2).


If the function type T(Args...) is meaningful to the end-user, one way to fix this would be to use a partial specialization instead:

template<class F, class Alloc> class C; //undefined
template<class T, class... Args, class Alloc>
class C<T(Args...), Alloc> {
    // implementation
};

Depending on the actual requirements, type-erasing the allocator might also be worth considering.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 2
    The compiler is GCC5, should I open a ticket on their bug tracking system if none exists for this problem? – skypjack Jan 22 '16 at 08:16
  • What do you mean with *type-erasing the allocator*? I know the type erasure as an idiom, but I failed to understand to what you are referring. – skypjack Jan 24 '16 at 08:42
  • 1
    @skypjack Basically, what `std::function`, `std::promise` and `std::packaged_task` do. You take an allocator in a constructor and type-erase it, so that your type doesn't depend on the allocator's type. – T.C. Jan 24 '16 at 08:45
  • Ok, got it, so you suggest to remove it from the parameter list of the template. Good hint, I'm not sure it's a viable solution for the real code I'm working on, but I'll consider it. Thank you. – skypjack Jan 24 '16 at 08:48
  • you should link #15 instead of #11. – user202729 May 12 '18 at 16:11
  • Never link to eel.is, the living standard. Right now the link is pointing to an unrelated article. – rustyx Apr 18 '20 at 08:10