Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When are template parameter packs deduced as empty?

Tags:

Consider the following examples (Coliru link):

template <class... T> struct S { using type = int; };

template <class... T>
void f(typename S<T...>::type) {
    static_assert(sizeof...(T) == 0);
}

template <class... T>
void g(typename S<T...>::type, S<T...>*) {}

int main() {
    f(42);
    g(42, nullptr);
}

GCC and Clang are both happy with the call to f, but not the call to g.

In the call to f, although T... appears in a non-deduced context, it ends up being deduced as empty. This seems to be due to [temp.arg.explicit]/4:

... A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. ...

In the call to g, however, the fact that T... additionally appears in a deduced context, which causes deduction to be attempted and failed, seems to cause g to become non-viable. There seems to be no "fallback" to T... being empty once deduction has been attempted and failed.

  • Is this behaviour intended? If so, why?
  • If so, was it intended that the "not otherwise deduced" wording specifies this behaviour? (i.e., it implies that the empty fallback only occurs if the pack appears in no deduced contexts)
  • If so, is this wording clear enough? It seems a plausible alternative reading of "not otherwise deduced" is "either deduction was not done, or deduction was attempted and failed".
like image 721
Brian Bi Avatar asked Jul 24 '19 02:07

Brian Bi


People also ask

What is the purpose of template parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

What is a parameter pack in C++?

Parameter packs (C++11) A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.

What is Variadic template in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

Which is a correct example of template parameters?

For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.


1 Answers

... A trailing template parameter pack ([temp.variadic]) not otherwise deduced will be deduced as an empty sequence of template arguments. ...

It could be enough to say that the not otherwise deduced is not a clause that should somehow or automagically relax some other rules or actually it is has nothing to do with why it's malformed (contrary to what I think you imply).

This other rule is perhaps best demonstrated by another very simple example:

template<class T>
void f(T, T){};

int main() {
    f(int{42},short{42});
}

The above fails to compile. Why? Because even while short converts to int seamlessly (promotion), it is not the same type.

Additionally since nullptr just has the somewhat plain type of std::nullptr_t - it is very ill-suited to participate in template argument deduction at all.

So let's forget about non-deduced context for a moment, and try with deduced one:

template <class... T>
void g(S<T...>*, S<T...>* ) {}

int main() {
    S<> s1;
    g(&s1, nullptr);
}

or if you prefer, just

int main() {
    S<> s1;
    g(&s1, 0);
}

and both fail for the same reason.

Now, if you would like to allow conversion - then use an identity template - and this even works for the non-deduced context!

For your case, the example could look like (c++2a):

template <class... T>
void g(typename S<T...>::type, std::type_identity_t<S<T...> >*) {}

int main() {
    f(42);
    g(42, nullptr);
} 

Which is valid. (note if you don't have have c++20 just write the identity template yourself)

As stated in a comment, turning the question around could perhaps lead to a more interesting question ?

What's the reasoning for allowing empty template argument deduction in non-deduced contexts?

like image 143
darune Avatar answered Oct 20 '22 18:10

darune