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?
From the C++ standard (4.10 Pointer conversions [conv.ptr])
1 A null pointer constant is an integral constant expression (5.19) prvalue of integer type
that evaluates to zero or a prvalue of type std::nullptr_t. A null pointer constant can be
converted to a pointer type; the result is the null pointer value of that type and is
distinguishable from every other value of object pointer or function pointer type. Such a
conversion is called a null pointer conversion.
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 a std::nullptr_t
and that does not match. You have to do the conversion yourself: static_cast<std::vector<int>*>(nullptr)
.
This is just how template deduction work: no conversion take place.
The problem is not endemic to nullptr
either, consider the extremely simple case:
#include <iostream>
struct Thing {
operator int() const { return 0; }
} thingy;
template <typename T>
void print(T l, T r) { std::cout << l << " " << r << "\n"; }
int main() {
int i = 0;
print(i, thingy);
return 0;
}
which yields:
prog.cpp: In function ‘int main()’:
prog.cpp:12:17: error: no matching function for call to ‘print(int&, Thing&)’
print(i, thingy);
^
prog.cpp:12:17: note: candidate is:
prog.cpp:8:6: note: template<class T> void print(T, T)
void print(T l, T r) { std::cout << l << " " << r << "\n"; }
^
prog.cpp:8:6: note: template argument deduction/substitution failed:
prog.cpp:12:17: note: deduced conflicting types for parameter ‘T’ (‘int’ and ‘Thing’)
print(i, thingy);
^
Thus, the conversion of nullptr
to int*
does not occur prior to template argument deduction either. As mentioned, you have two ways of solving the issue:
- specifying the template parameters (thus no deduction occur)
- converting the argument yourself (deduction occurs, but after your explicit conversion)
Compiler can not deduce the second argument type because std::nullptr_t
is not a pointer type.
1 The pointer literal is the keyword nullptr. It is a
prvalue of type std::nullptr_t. [Note: std::nullptr_t is a distinct
type that is neither a pointer type nor a pointer to member type;
rather, a prvalue of this type is a null pointer constant and can be
converted to a null pointer value or null member pointer value.
[§2.14.7]
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 and decay
).
While nullptr
can be converted into a C< 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, the template
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 some template
C
and types D
and A
. 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.
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
- explicitly call the right version of the template
- cast nullptr to a propper type for the template.
- create a local var of the right pointer type, give it value nullptr and make the call using that var.