In function call, why doesn't nullptr match a pointer to a template object?

孤街浪徒 提交于 2019-12-22 04:31:08

问题


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:


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:


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)



回答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]




回答4:


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.




回答5:


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.


来源:https://stackoverflow.com/questions/20405569/in-function-call-why-doesnt-nullptr-match-a-pointer-to-a-template-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!