Неявное преобразование из 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 to std::shared_ptr выполняется в вызове f(0), как это выглядит std::shared_ptr только явные конструкторы.

PS: вариант этого примера появляется в Scott Meyers'Эффективный Современный C++ пункт 8, как способ защиты таких вызовов с nullptr.

3 ответов


std:: shared_ptr имеет конструктор, который принимает std:: nullptr_t, буквальный 0 является константой нулевого указателя, которая преобразуется в std:: nullptr_t из раздела проект стандарта C++4.10 [усл.ptr] (акцент мой идет вперед):

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

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

как указывает T. C. формулировка была изменена на д-р 903 который требует целочисленного литерала со значением ноль в отличие от интегральное константное выражение который оценивает до нуля:

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


Per [conv.ptr] / 1 (цитата N4296 здесь):

A константа нулевого указателя является целочисленным литералом (2.13.2) со значением ноль или prvalue типа std::nullptr_t. ... Константа нулевого указателя интегрального типа может быть преобразована в prvalue типа std::nullptr_t.

shared_ptr имеет неявный конструктор, который принимает std::nullptr_t per [util.класса smartptr.общий.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);