Here is an example of a code that works perfectly:
#include<iostream>
#include<vector>
template< class D, template< class D, class A > class C, class A = std::allocator< D > >
void foo( C< D, A > *bar, C< D, A > *bas ) {
std::cout << "Ok!" << std::endl;
}
int main( ) {
std::vector< int > *sample1 = nullptr;
std::vector< int > *sample2 = nullptr;
foo( sample1, sample2 );
return( 0 );
}
In the code below, however, the compiler is unable to match std::vector< int >* with nullptr for the second parameter, even being able to deduct the template types from the first parameter.
#include<iostream>
#include<vector>
template< class D, template< class D, class A > class C, class A = std::allocator< D > >
void foo( C< D, A > *bar, C< D, A > *bas ) {
std::cout << "Ok!" << std::endl;
}
int main( ) {
std::vector< int > *sample = nullptr;
foo( sample, nullptr );
return( 0 );
}
The error message is:
$ g++ -std=c++11 nullptr.cpp -o nullptr
nullptr.cpp: In function ‘int main()’:
nullptr.cpp:11:24: error: no matching function for call to ‘foo(std::vector<int>*&, std::nullptr_t)’
foo( sample, nullptr );
nullptr.cpp:11:24: note: candidate is:
nullptr.cpp:5:6: note: template<class D, template<class D, class A> class C, class A> void foo(C<D, A>*, C<D, A>*)
void foo( C< D, A > *bar, C< D, A > *bas ) {
nullptr.cpp:5:6: note: template argument deduction/substitution failed:
nullptr.cpp:11:24: note: mismatched types ‘C<D, A>*’ and ‘std::nullptr_t’
foo( sample, nullptr );
Why does that happen?
template argument deduction is pattern matching. It does not do much conversion of the arguments other than conversion-to-base (well, adding
const
and reference qualifiers on the type anddecay
).While
nullptr
can be converted into aC< D, A >*
, it is not such a type. And both arguments participate equally in the deduction.You can block deduction of the second argument by using something like
typename std::identity<C< D, A > >::type*
, and the same for the first argument. If you do it for both arguments, thetemplate
types will not be deduced.Another approach would be to take two arbitrary types, then use SFINAE to ensure that one type of pointer can be converted to the other, and the one that can be can be converted to from the other can be deduced to be a
C<D,A>
for sometemplate
C
and typesD
andA
. This probably matches what your internal mental model of what the function type deduction should do. However, the result will be really, really verbose.An even better approach might be asking "what do you expect to do with these two arguments", and do duck-type testing on that, rather than doing type matching.
This is just how template deduction work: no conversion take place.
The problem is not endemic to
nullptr
either, consider the extremely simple case:which yields:
Thus, the conversion of
nullptr
toint*
does not occur prior to template argument deduction either. As mentioned, you have two ways of solving the issue:Compiler can not deduce the second argument type because
std::nullptr_t
is not a pointer type.That's to prevent you from creating a template that has nullptr as a argument. You most likely don't want that. You want the template to use a propper class as an argument and take nullptr as a value for that argument.
You can either
From the C++ standard (4.10 Pointer conversions [conv.ptr])
In your first exemple your two nullptr have already been converted before template argument deduction. So there is no problem you have the same type twice.
In the second one, there is a
std::vector<int>
and astd::nullptr_t
and that does not match. You have to do the conversion yourself:static_cast<std::vector<int>*>(nullptr)
.