Подтвердить что ты не робот

Что случилось с копированием-конструированием shared_ptr из неявного литья?

Рассмотрим этот минимальный пример:

#include <memory>

struct B {
  typedef std::shared_ptr<B> Ptr;
};

struct A {
  operator B::Ptr() { // type conversion operator                  <----+
    return std::make_shared<B>();  //                                   |
  }                                //                                   |
};                                 //                                   |
                                   //                                   |
int main() {                       //                                   |
  A* a = new A;                    //                                   |
  B::Ptr{*a}; // copy construction from a implicit cast to B::Ptr ----+ 
}

Это невинное построение копии shared_ptr<B> неудачно выполняется на g++ 4.6.3 x86_64-linux-gnu, но, похоже, работает для g++ 4.5 (обратите внимание, что новая версия ломается, а старшая работает!). Из того, что я могу сказать по ошибке (см. Ниже), g++ 4.6, похоже, передает A по значению, а не по ссылке (r или l).

Итак, вопрос в том, что правильно и что нарушается? Предполагается ли, что это поведение потерпит неудачу? Если да, то почему?
Насколько я понимаю правила преобразования, в этот момент следует попытаться использовать неявный бросок в B::Ptr, верно?


Примечание. Я привел этот пример к общей технической проблеме, и этот код не имеет смысла для любой производственной системы в ее нынешнем виде.

Вот точная ошибка:

shp.cpp: In function ‘int main()’:
shp.cpp:17:12: error: no matching function for call to ‘std::shared_ptr<B>::shared_ptr(<brace-enclosed initializer list>)’
shp.cpp:17:12: note: candidates are:
/usr/include/c++/4.6/bits/shared_ptr.h:315:2: note: template<class _Alloc, class ... _Args> std::shared_ptr::shared_ptr(std::_Sp_make_shared_tag, const _Alloc&, _Args&& ...)
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr(std::nullptr_t) [with _Tp = B, std::nullptr_t = std::nullptr_t]
/usr/include/c++/4.6/bits/shared_ptr.h:266:17: note:   no known conversion for argument 1 from ‘A’ to ‘std::nullptr_t’
/usr/include/c++/4.6/bits/shared_ptr.h:258:2: note: template<class _Tp1, class _Del> std::shared_ptr::shared_ptr(std::unique_ptr<_Up, _Ep>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:253:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(std::auto_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:248:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::weak_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:236:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(std::shared_ptr<_Tp1>&&)
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note: std::shared_ptr<_Tp>::shared_ptr(std::shared_ptr<_Tp>&&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:226:7: note:   no known conversion for argument 1 from ‘A’ to ‘std::shared_ptr<B>&&’
/usr/include/c++/4.6/bits/shared_ptr.h:218:2: note: template<class _Tp1, class> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&)
/usr/include/c++/4.6/bits/shared_ptr.h:206:2: note: template<class _Tp1> std::shared_ptr::shared_ptr(const std::shared_ptr<_Tp1>&, _Tp*)
/usr/include/c++/4.6/bits/shared_ptr.h:184:2: note: template<class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:165:2: note: template<class _Tp1, class _Deleter, class _Alloc> std::shared_ptr::shared_ptr(_Tp1*, _Deleter, _Alloc)
/usr/include/c++/4.6/bits/shared_ptr.h:146:2: note: template<class _Deleter> std::shared_ptr::shared_ptr(std::nullptr_t, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:129:2: note: template<class _Tp1, class _Deleter> std::shared_ptr::shared_ptr(_Tp1*, _Deleter)
/usr/include/c++/4.6/bits/shared_ptr.h:112:11: note: template<class _Tp1> std::shared_ptr::shared_ptr(_Tp1*)
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note: std::shared_ptr<_Tp>::shared_ptr(const std::shared_ptr<_Tp>&) [with _Tp = B, std::shared_ptr<_Tp> = std::shared_ptr<B>]
/usr/include/c++/4.6/bits/shared_ptr.h:103:7: note:   no known conversion for argument 1 from ‘A’ to ‘const std::shared_ptr<B>&’
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note: constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = B]
/usr/include/c++/4.6/bits/shared_ptr.h:100:17: note:   candidate expects 0 arguments, 1 provided
4b9b3361

Ответ 1

Код не соответствует текущей версии стандарта (я смотрю пост-стандартную черновик n3376).

Правила для инициализации списка указывают:

13.3.1.7 Инициализация с помощью инициализации списка [over.match.list]

1 - Когда объекты типа неагрегатного класса T инициализируются по списку [...]:

  • Если не существует жизнеспособного конструктора-списка инициализаторов, разрешение перегрузки выполняется снова, где кандидатные функции являются всеми конструкторами класса T, а список аргументов состоит из элементов списка инициализаторов.

Однако, когда разрешение на перегрузку применяется к конструктору копирования B::Ptr с единственным параметром const std::shared_ptr<B> &, список аргументов (*a), состоящий из одного элемента типа lvalue A; разрешение перегрузки не разрешено рассматривать функцию преобразования A::operator B::Ptr:

13.3.3.1 Неявные последовательности преобразования [over.best.ics]

4 - Однако при рассмотрении аргумента конструктора или пользовательской функции преобразования, которая является кандидатом [...] на 13.3.1.7 [...], когда список инициализаторов имеет ровно один элемент и преобразование в некоторый класс X или ссылка на (возможно, cv-qualified) X рассматривается для первого параметра конструктора X [...], рассматриваются только стандартные последовательности преобразования и последовательности преобразования многоточия.

Так что g++ - 4.6 правильно отклонить этот код; g++ - 4.7.2, к сожалению, принимает его, что неверно.

Правильный способ написать это будет использовать прямую инициализацию (B::Ptr(*a)) или static_cast<B::Ptr>.

Ограничение допустимых преобразований можно проследить на бумаге n2672, хотя в этой статье применяется только пункт 13.3.3.1p4 к аргументу пользовательской функции преобразования. Дополнительное ограничение на конструкторы было добавлено в дефект 978:

978. Неправильная спецификация для инициализации копирования

13.3.3.1 [over.best.ics] в пункте 4 говорится:
[...]
Это не совсем правильно, поскольку это относится к аргументам конструктора, а не только к аргументам пользовательских функций преобразования.

Текущую формулировку 13.3.3.1p4 можно проследить до семенного дефекта 84, в котором введено правило общего права, которое только для выполнения неявного преобразования будет вызываться одно определяемое пользователем преобразование.

Я немного обеспокоен этим ответом; Я спросил Можно ли вызвать пользовательскую функцию преобразования через инициализацию списка?, чтобы узнать, может ли кто-нибудь разъяснить намерение стандарта здесь.