39
votes

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?

5

5 Answers

27
votes

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.

16
votes

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.

12
votes

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.

6
votes

I want to add another solution that invokes the {} notion

template <typename, typename>
struct AAA{};

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

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

When the argument is {}, deduction for the parameter is disabled (nondeduced context), so there will be no mismatch and the parameter initialization actually produces a null pointer aswell.

4
votes

Great answer by @skypjack.

You need to help the compiler in deducing the function argument:

AAA<int, int> *a = nullptr;
f<int, int>(a); //works

f<int, int>( (AAA<int, int> *)nullptr );  //even this will work.

Fundamentally, nullptr represents a "no object" which can be assigned to any pointer type.