Неявное преобразование из 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>'

Мои вопросы:

  1. Почему первый вызов успешен, а второй нет? Есть что-то, что я здесь скучаю?
  2. Я также не понимаю, как преобразование из 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); 
Другие вопросы по тегам