Неявное преобразование из int в shared_ptr
Рассмотрим код ниже:
#include <iostream>
#include <memory>
void f(std::shared_ptr<int> sp) {}
template <typename FuncType, typename PtrType>
auto call_f(FuncType f, PtrType p) -> decltype(f(p))
{
return f(p);
}
int main()
{
f(0); // doesn't work for any other int != 0, thanks @Rupesh
// call_f(f, 0); // error, cannot convert int to shared_ptr
}
В первой строке main()
целое число 0
превращается в std::shared_ptr<int>
и вызов f(0)
успешно без проблем. Однако использование шаблона для вызова функции делает все по-другому. Вторая строка больше не будет компилироваться, ошибка
error: could not convert 'p' from 'int' to 'std::shared_ptr<int>'
Мои вопросы:
- Почему первый вызов успешен, а второй нет? Есть что-то, что я здесь скучаю?
- Я также не понимаю, как преобразование из
int
вstd::shared_ptr
выполняется в вызовеf(0)
как это выглядитstd::shared_ptr
имеет только явные конструкторы.
PS: вариант этого примера представлен в статье 8 " Эффективного современного C++" Скотта Мейерса как способ защиты таких вызовов с помощью nullptr
,
3 ответа
std::shared_ptr имеет конструктор, который принимает std::nullptr_t, литерал 0
константа нулевого указателя, которая конвертируется в std::nullptr_t из черновика стандартного раздела C++ 4.10
[conv.ptr] (выделение мое будет впереди):
Константа нулевого указателя - это целочисленное константное выражение (5.19) prvalue целочисленного типа с нулевым значением или prvalue типа std::nullptr_t. Константа нулевого указателя может быть преобразована в тип указателя; результат является нулевым значением указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя функции. Такое преобразование называется преобразованием нулевого указателя. Два значения нулевого указателя одного типа должны сравниваться одинаково. Преобразование константы с нулевым указателем в указатель на cv-квалифицированный тип является одиночным преобразованием, а не последовательностью преобразования указателя, за которым следует преобразование квалификации (4.4). Константа нулевого указателя целочисленного типа может быть преобразована в значение типа std::nullptr_t. [Примечание: результирующее значение prvalue не является нулевым значением указателя. —Конечная записка]
во втором случае p
выводится как тип int, который, хотя имеет значение ноль, больше не является константой нулевого указателя и поэтому не соответствует тому же случаю.
Как указывает TC, формулировка была изменена с помощью DR 903, для которого требуется целочисленный литерал со значением ноль, а не целочисленное константное выражение, которое оценивается как ноль:
Константа нулевого указателя является целочисленным литералом (2.14.2) со значением ноль или значением типа std::nullptr_t. Константа нулевого указателя может быть преобразована в тип указателя; результат является нулевым значением указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя функции.
Согласно [conv.ptr]/1 (цитируя N4296 здесь):
Константа нулевого указателя является целочисленным литералом (2.13.2) со значением ноль или значением типа
std::nullptr_t
,... Константа нулевого указателя целочисленного типа может быть преобразована в тип значенияstd::nullptr_t
,
shared_ptr
имеет неявный конструктор, который принимает std::nullptr_t
per [util.smartptr.shared.const]/1:
constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }
который создает пустой, не владеющий shared_ptr
,
Когда вы звоните f(0)
непосредственно, 0
является константой нулевого указателя, которая неявно преобразуется в shared_ptr<int>
вышеуказанным конструктором. Когда вы вместо этого позвоните call_f(f, 0)
тип литерала 0 выводится int
и конечно int
не может быть преобразован в shared_ptr<int>
,
Первый вызов f(0) компилируется как f(nullptr), что хорошо для компилятора (но, на мой взгляд, этого не должно быть). Второй вызов создаст объявление для функции, которая будет работать с любым типом int, что недопустимо.
Самое смешное, что даже этот код работает:
f(3-3);
f(3*0);