10

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?

1
  • Two things: 1) you want to remove the names in "class D, class A" -- these names conflict with D and A one scope up (in the template parameters of foo itself). 2) the error messages would be more readable in monotype. Commented Dec 5, 2013 at 16:47

5 Answers 5

9

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)
Sign up to request clarification or add additional context in comments.

Comments

7

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).

2 Comments

what is the reason it has to cast? this answer is same as other answers. doesn't explain why
@BЈовић 14.8.2.1 Deducing template arguments from a function call is worth a read. Basically the template matching is strict and does not look for conversion/promotion (except some very special one) whereas the function call (that occurs after template matching) allows conversion. That why this fails and this succeed.
3

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]

1 Comment

You say: "Compiler can not deduce the second argument type...", however we could argue that intuitively the types C, D and A could be deduced from the first argument (it is explicit) and then conversion could happen. As such, I think the issue is not at all linked to nullptr_t here, but rather to the mechanism of template deduction.
0

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.

Comments

-1

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.

5 Comments

std::nullptr is a prvalue, convertible to any pointer type. Compiler should be able to deduce the A and C, and convert nullptr into appropriate type... or am I wrong?
@BЈовић you are, because type C would be of the type nullptr. As long as the template uses a proper type (like vector<int>*) it will convert automatically. In this case it has to also infer the type from nullptr. Think what would be the result of make_pair(nullptr, nullptr). It will work if you do `make_pair<int*, int*>(nullptr, nullptr).
I didn't -1, but I think this and other answers are wrong. I think it fails for other reason. Besides you and other answerer just said the problem from the question - you didn't really say why
@BЈовић Look if it would work you would end up with templates like vector<nullptr_type>, and pair<nullptr_type, nullptry_type> that are useless because they would only be able to store nullptr. You want the template argument to be some class so that you can pass it as pair<A*, int*>. It would be a mistake in most cases. I'm not sure how it is achieved, or if the way it's done differs between compilers, but that was not the question.
sorry, that is not true. who would need such thing as vector<nullptr_t>? Anyway, here is correct and detailed answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.