Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is template parameter pack used in a function argument type as its template argument list not able to be explicit specified

I have the following piece of code:

template <typename, typename>
struct AAA{};

template<typename ...Args>
void f(AAA<Args...> *) {}

int main() {
    f<int, int>(nullptr);
}

This code results in a compile error. When compiling using g++ -std=c++1z the error shows as follows:

prog.cc: In function 'int main()':
prog.cc:8:24: error: no matching function for call to 'f<int, int>(std::nullptr_t)'
     f<int, int>(nullptr);
                        ^
prog.cc:5:6: note: candidate: template<class ... Args> void f(AAA<Args ...>*)
 void f(AAA<Args...> *) {}
      ^
prog.cc:5:6: note:   template argument deduction/substitution failed:
prog.cc:8:24: note:   mismatched types 'AAA<Args ...>*' and 'std::nullptr_t'
     f<int, int>(nullptr);

Using clang++ -std=c++1z the error is:

prog.cc:8:5: error: no matching function for call to 'f'
    f<int, int>(nullptr);
    ^~~~~~~~~~~
prog.cc:5:6: note: candidate template ignored: could not match 'AAA<int, int, Args...> *' against 'nullptr_t'
void f(AAA<Args...> *) {}
     ^
1 error generated.

I am running those above in a MSYS2 MinGW-w64 environment. My GCC version is GCC 7.1.0 and my Clang version is 4.0.0; the standard library I use both in GCC and in Clang is the libstdc++ bundled with my GCC compiler.

In my opinion, the call to function template foo has its template parameter explicitly specified thus the template parameter pack and the function argument type should already be specified. However, the error diagnostics shown above seem to suggest that the exact type of function parameter and the nullptr argument does not match, which seems to be a issue only possible when function argument deduction occurs. So my question is, why does such error occur? Is it just a compiler bug, or does the C++ standard have some rules that indicate the original code is just ill-formed?

like image 807
gnaggnoyil Avatar asked Jul 26 '17 05:07

gnaggnoyil


People also ask

Why do we use template template parameter?

Why we use :: template-template parameter? Explanation: It is used to adapt a policy into binary ones.

What is template argument list?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

Can we use non-type parameters as argument templates?

A non-type template argument provided within a template argument list is an expression whose value can be determined at compile time. Such arguments must be constant expressions, addresses of functions or objects with external linkage, or addresses of static class members.

Which is 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.


3 Answers

You may think the compiler should deduce the pack as int ,int, but the C++ standard explicitly requires the behavior you observed.

[temp.arg.explicit/9]

Template argument deduction can extend the sequence of template arguments corresponding to a template parameter pack, even when the sequence contains explicitly specified template arguments. [ Example:

template<class ... Types> void f(Types ... values);
void g() {
  f<int*, float*>(0, 0, 0);     // Types is deduced to the sequence int*, float*, int
}

— end example ]

The above means that even though some of the parameters were specified, deduction doesn't end. The parameter pack must always be expandable by argument deduction. It's as if the explicit arguments that were given are an instantiation of a template with a trailing parameter pack. When coupled with the following:

[temp.arg.explicit/3]

Trailing template arguments that can be deduced or obtained from default template-arguments may be omitted from the list of explicit template-arguments. A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. ...

The compiler must match the un-deduced arguments to an empty pack. But it has nothing to deduce it from.

As such, your attempt to plug Args... into AAA cannot possibly match. Because the type sequence is two types with a trailing list (that the compiler cannot deduce as empty from nullptr). While AAA expects just two types.

like image 59
StoryTeller - Unslander Monica Avatar answered Oct 07 '22 20:10

StoryTeller - Unslander Monica


For you are using typename ...Args, the compiler doesn't know if int, int are all the template parameters in use or more are available by deducing the function argument. Therefore the function isn't instantiated yet and the compiler goes on trying to deduce all the other possible parameters for the parameter pack from the function arguments.

In other terms, this works:

f<int>(new AAA<int, int>);

Because you are saying that the first parameter is int, but the compiler expects a parameters list and goes on trying to find more and more parameters greedily from the function argument, then it instantiates the function template.

More or less the same happens in your case, but the compiler cannot deduce anything from nullptr_t for function arguments doesn't match. It expects a pointer to an A<...>, that is not the case when you pass in nullptr.
This would work instead:

template <typename, typename>
struct AAA{};

template<typename A, typename B>
void f(AAA<A, B> *) {}

int main() {
    f<int, int>(nullptr);
}

Because the compiler knows template arguments are two and you are providing all of them, there is nothing to deduce and the function can be instantiated. It also makes much more sense, for AAA accepts only two template parameters, so a parameter pack for f seems useless here.

like image 42
skypjack Avatar answered Oct 07 '22 20:10

skypjack


Just to add an easy solution:

f<int, int>(nullptr); // doesn't work for the reasons explained by other answers
(*f<int, int>)(nullptr); // OK - does what you want

The latter forces the pack Args... to be {int, int}, and now the call itself isn't a call to a function template - it's just a call to a function pointer. We're calling a function that takes an AAA<int, int>*, and of course passing in nullptr is acceptable there.

For fun, you can also add arbitrarily many *s:

(*****f<int, int>)(nullptr); // still OK - does what you want

... but, you know... don't.

like image 12
Barry Avatar answered Oct 07 '22 20:10

Barry