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

Почему так случилось?


person user1574855    schedule 05.12.2013    source источник
comment
Две вещи: 1) вы хотите удалить имена в классе D, классе A - эти имена конфликтуют с D и A на одну область видимости (в параметрах шаблона самого foo). 2) сообщения об ошибках были бы более читабельны в монотипии.   -  person mornfall    schedule 05.12.2013


Ответы (5)


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

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

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

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

person Johan    schedule 05.12.2013
comment
по какой причине он должен бросать? этот ответ такой же, как и другие ответы. не объясняет почему - person BЈовић; 06.12.2013
comment
@ BЈовић 14.8.2.1 Deducing template arguments from a function call стоит прочитать. В основном сопоставление шаблонов строгое и не ищет конверсии / продвижения (за исключением некоторых очень специальных), тогда как вызов функции (который происходит после сопоставления с шаблоном) разрешает преобразование. Вот почему это не удается и успешно. - person Johan; 06.12.2013

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

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

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

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

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

person masoud    schedule 05.12.2013
comment
Вы говорите: «Компилятор не может вывести второй тип аргумента ...», однако мы можем утверждать, что интуитивно типы C, D и A могут быть выведены из первого аргумента (это явно), и тогда может произойти преобразование. Таким образом, я думаю, что проблема здесь вовсе не связана с nullptr_t, а скорее с механизмом вывода из шаблона. - person Matthieu M.; 05.12.2013

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

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

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

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

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

person Yakk - Adam Nevraumont    schedule 05.12.2013

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

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

  • явно вызвать правильную версию шаблона
  • приведите nullptr к типу propper для шаблона.
  • создайте локальную переменную правильного типа указателя, присвойте ей значение nullptr и выполните вызов, используя эту переменную.
person Sorin    schedule 05.12.2013
comment
std::nullptr - это prvalue, конвертируемое в любой тип указателя. Компилятор должен уметь выводить A и C и преобразовывать nullptr в соответствующий тип ... или я ошибаюсь? - person BЈовић; 05.12.2013
comment
@ BЈовић да, потому что тип C будет типа nullptr. Пока в шаблоне используется правильный тип (например, вектор ‹int› *), он будет преобразован автоматически. В этом случае он также должен вывести тип из nullptr. Подумайте, что будет в результате make_pair(nullptr, nullptr). Он будет работать, если вы выполните `make_pair‹ int *, int * ›(nullptr, nullptr). - person Sorin; 05.12.2013
comment
Я не сделал -1, но я думаю, что этот и другие ответы неверны. Я думаю, что это не удается по другой причине. Кроме того, вы и другой респондент только что сказали, что проблема из вопроса - вы действительно не сказали, почему - person BЈовић; 05.12.2013
comment
@ BЈовић Послушайте, если бы это сработало, вы бы получили такие шаблоны, как vector<nullptr_type> и pair<nullptr_type, nullptry_type>, которые бесполезны, потому что они могут хранить только nullptr. Вы хотите, чтобы аргумент шаблона был неким классом, чтобы вы могли передать его как pair<A*, int*>. В большинстве случаев это было бы ошибкой. Я не уверен, как это достигается, или различается ли то, как это делается в разных компиляторах, но вопрос был не в этом. - person Sorin; 06.12.2013
comment
извините, это неправда. кому может понадобиться такая вещь, как vector<nullptr_t>? В любом случае, здесь правильный и подробный ответ. - person BЈовић; 06.12.2013