Почему при вызове функции 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< int> * с 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 );

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

9

Решение

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

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

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

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

6

Другие решения

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

Проблема не является эндемичной для 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 в int* не происходит предшествующий для вывода аргумента шаблона либо. Как уже упоминалось, у вас есть два способа решения проблемы:

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

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

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

2

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

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

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

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

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

0

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

Вы также можете

  • явно назвать правильную версию шаблона
  • приведите nullptr к типу пропппера для шаблона.
  • создайте локальную переменную правильного типа указателя, присвойте ему значение nullptr и сделайте вызов, используя эту переменную.
-1
По вопросам рекламы [email protected]