В вызове функции почему 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.