В вызове функции почему nullptr не соответствует указателю на объект шаблона?

вот пример кода, который прекрасно работает:


#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 );
}

В приведенном ниже коде, однако, компилятор не может сопоставить std:: vector* с nullptr для второго параметра, даже будучи в состоянии вычесть типы шаблонов из первого параметра.


#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 );
}

сообщение об ошибке:


$ 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 );

почему это происходит?

5 ответов


из стандарта C++ (4.10 преобразования указателей [conv.ptr])

1 константа нулевого указателя является неотъемлемой константное выражение (5.19) prvalue типа integer это равно нулю или prvalue типа std:: nullptr_t. Константа нулевого указателя может быть преобразуется в тип указателя; результатом является значение указателя null этого типа и является отличается от любого другого значения указателя объекта или типа указателя функции. Такой преобразование называется null преобразование указателя.

в вашем первом exemple ваши два nullptr уже были преобразованы до вычета аргумента шаблона. Таким образом, нет никакой проблемы, у вас есть один и тот же тип дважды.

во втором, есть std::vector<int> и std::nullptr_t и это не соответствует. Вы должны сделать преобразование самостоятельно:static_cast<std::vector<int>*>(nullptr).


Это просто, как шаблон вычет работы: нет преобразования не происходит.

проблема не эндемична для nullptr либо, рассмотрим очень простой пример:

#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;
}

, который доходность:

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);
                 ^

таким образом, обращение nullptr to int* не происходит до для вывода аргумента шаблона либо. Как уже упоминалось, у вас есть два пути решения проблемы:

  • определение шаблона параметры (таким образом, вычет не происходит)
  • преобразование аргумента самостоятельно (дедукция происходит, но после явного преобразования)

компилятор не может вывести тип второго аргумента, потому что std::nullptr_t не является типом указателя.

1 литерал указателя является ключевым словом nullptr. Это prvalue типа std:: nullptr_t. [Примечание: std:: nullptr_t является отличным тип тип, ни тип указателя или указателя на член ; скорее, prvalue этого типа является константой нулевого указателя и может быть преобразуется в значение указателя null или null значение указателя. [§2.14.7]


шаблон вычет аргумента шаблона. Он не делает много преобразования аргументов, кроме преобразования в базу (ну, добавление const и ссылочные квалификаторы по типу и decay).

пока nullptr можно преобразовать в C< D, A >*, это не такой тип. И оба аргумента одинаково участвуют в дедукции.

вы можете заблокировать вычета второго аргумента, используя что-то вроде typename std::identity<C< D, A > >::type*, и за первые же аргумент. Если вы делаете это для обоих аргументов template типы не будут выведены.

другим подходом было бы взять два произвольных типа, а затем использовать SFINAE, чтобы гарантировать, что один тип указателя может быть преобразован в другой, а тот, который может быть преобразован из другого, может быть выведен как C<D,A> для некоторых template C и типов D и A. Это, вероятно, соответствует вашей внутренней ментальной модели того, что функция типа дедукции должны do. Однако результат будет очень, очень многословным.

еще лучшим подходом может быть вопрос "что вы ожидаете сделать с этими двумя аргументами" и выполнить тестирование типа утки на этом, а не выполнять сопоставление типов.


Это должно помешать вам создать шаблон, который имеет nullptr в качестве аргумента. Вы, скорее всего, не хотят. Вы хотите, чтобы шаблон использовал класс propper в качестве аргумента и принимал nullptr в качестве значения для этого аргумента.

вы можете

  • явным образом вызовите правильную версию шаблона
  • приведите nullptr к типу propper для шаблона.
  • создайте локальный var типа указателя вправо, дайте ему значение nullptr и сделайте позвоните, используя этот var.