Search code examples
c++templatesc++14variadic

How can a template parameter pack have other trailing arguments?


In the C++14 draft standard, [temp.param]/11 says:

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 you try compiling the following template, then the compiler will complain.

template< typename ...Args, void(*f)(Args...) > // ERROR
struct Bar
{};

But how does it work in this case?

template< typename F, F >
struct Bar;

template< typename ...Args, void(*f)(Args...) > // OK ???
struct Bar< void(*)(Args...), f >
{};

I can see that it has something to do with it being part of the specialization class template, but why?

The rule clearly states that it applies to a primary class template. Does this mean that the rules change for specializations?

I tried to search for this in the standard, but couldn't find anything. Can you please shine some light into this.


Solution

  • The rule clearly states that it applies to a primary class template. Does this mean that the rules change for specializations?

    Yes. Quite simply because a specialization is not a primary class template. So if the wording was intended to apply to all template declarations, it would say so. Instead, the rule is very much intended to only apply to the primary class template (... and alias templates, which cannot be specialized). There is no such restriction for specializations.

    This is fundamentally because it is not possible to provide any template arguments after a template parameter pack in the primary template, but it is definitely possible to do so in specializations. For instance, here's one way to concatenate two tuple specializations:

    template <typename T, typename U>
    struct tuple_concat;
    
    template <typename... Ts, typename... Us> // <== parameter pack *after* parameter pack
    struct tuple_concat<tuple<Ts...>, tuple<Us...>> {
        using type = tuple<Ts..., Us...>;
    };
    

    This is fine, it works, it's useful. But there's no benefit from being able to write stuff like this in a primary class/variable/alias template - so it's forbidden for simplicity.


    As with all things C++, there is of course a footnote. You could have been able to provide a trailing defaulted template parameter that is used to trigger a substitution failure. But there are other ways you can go about solving that problem, and then we'll have Concepts soon anyway.