Non-defaulted template params after a defaulted param: class vs function

91 views Asked by At

From this page on cppreference about default template arguments:

If the default is specified for a template parameter of a primary class template, [...] each subsequent template parameter must have a default argument, except the very last one may be a template parameter pack (since C++11). In a function template, there are no restrictions on the parameters that follow a default [...].

This means the following code compiles:

template <typename U = int, typename T>
U foo(T) { /*... */ }

while this doesn't:

template <typename U = int, typename T>
struct S {
    S(T) { /* ... */ }
};

What's the reasoning behind this?

This kind of makes sense to me until C++17, as you had to specify T (hence U) when constructing an instance of S:

S<int, double> s(1.2);

However, C++17 introduced CTAD: couldn't the standard allow non defaulted template parameters after the last defaulted one as long as they're deducible from the initializer?

Note: I've already had a look at question "Non-last default template arguments for function templates" but it doesn't answer mine.

1

There are 1 answers

0
user3188445 On

There's an important restriction with CTAD that you must deduce all of the template arguments. If you specify any template arguments explicitly, then you will not get CTAD. This means that default template arguments and CTAD aren't really helpful to one another--at any given place you are may be using one or the other but not both.

Hence, the introduction of CTAD doesn't change anything about the reason why you can't have parameters without default arguments following parameters with default arguments in a template class: there's no way to specify the later parameters if you are taking advantage of the default arguments, and since you have to specify those parameters to use the template, the default arguments are useless.

In your concrete example, suppose the language allowed:

template <typename U = int, typename T>
struct S {
    S(T) { /* ... */ }
};

There would be no way to use this template without specifying either both U and T, or (if you have an explicit deduction guide) neither. So there's no example where you could ever make use of the default int argument for U, which means specifying it would likely be a bug, so it's better just to make it illegal.

Note how this restriction doesn't apply to function templates, where you might be able to deduce later template parameters from the arguments passed to the function.