Есть ли ошибка в GCC 4.7.2 реализации shared_ptr (templated) оператора присваивания?

Мой вопрос касается реализации шаблона оператора назначения shared_ptr в GCC 4.7.2, который, как я подозреваю, содержит ошибку.

ПОМЕЩЕНИЕ 1: С++ 11 СТАНДАРТ

Вот подпись шаблона оператора присваивания, о котором я говорю:

template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;

Из стандарта С++ 11 (20.7.2.2.3):

"Эквивалентно shared_ptr(r).swap(*this)."

Другими словами, шаблон оператора присваивания определяется в терминах шаблона конструктора. Подпись шаблона конструктора выглядит следующим образом:

template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;

Из стандарта С++ 11 (20.7.2.2.1):

"Требуется: конструктор [...] не должен участвовать в разрешении перегрузки, если Y * неявно конвертируется в T *."

ПОМЕЩЕНИЕ 2: GCC 4.7.2 ОСУЩЕСТВЛЕНИЕ:

Теперь реализация шаблона конструктора GCC 4.7.2 кажется мне верной (std::__shared_ptr - базовый класс std::shared_ptr):

template<typename _Tp1, typename = 
typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>
__shared_ptr(__shared_ptr<_Tp1, _Lp>&& __r) noexcept
    : 
    _M_ptr(__r._M_ptr), 
    _M_refcount()
{
    _M_refcount._M_swap(__r._M_refcount);
    __r._M_ptr = 0;
}

Однако реализация GCC 4.7.2 шаблона оператора присваивания такова:

template<typename _Tp1>
__shared_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept
{
    _M_ptr = __r._M_ptr;
    _M_refcount = __r._M_refcount; // __shared_count::op= doesn't throw
    return *this;
}

Что меня поражает, так это то, что эта операция не определена в терминах шаблона конструктора или swap(). В частности, простое присваивание _M_ptr = __r._M_ptr не дает такого же результата, как если типы _Tp1* и _Tp* явно проверяются на конвертируемость через std::is_convertible (который может быть специализированным).

ПОМЕЩЕНИЕ 3: РЕАЛИЗАЦИЯ VC10

Я заметил, что VC10 имеет более подходящую реализацию в этом отношении, которую я считаю правильным и ведет себя так, как я ожидал в своих тестовых случаях (в то время как GCC не делает):

template<class _Ty2>
_Myt& operator=(const shared_ptr<_Ty2>& _Right)
{
    // assign shared ownership of resource owned by _Right
    shared_ptr(_Right).swap(*this);
    return (*this);
}

ВОПРОС:

Есть ли ошибка в реализации GCC 4.7.2 shared_ptr? Я не мог найти отчет об ошибке для этой проблемы.

POST SCRIPTUM:

Если вы хотите спросить меня, что такое мои тестовые примеры, почему я забочусь об этой, казалось бы, несущественной детали, и почему я, кажется, подразумеваю, что мне нужно специализироваться на std::is_convertible, пожалуйста, сделайте это в чате. Это долгая история, и нет способа суммировать ее, не будучи неправильно понятой (со всеми ее неприятными последствиями). Заранее благодарю вас.

Ответы

Ответ 1

Что меня поражает, так это то, что эта операция не определена в терминах шаблона конструктора или swap().

Это не обязательно, он должен вести себя так, как если бы он был определен в этих терминах.

В частности, простое присваивание _M_ptr = __r._M_ptr не дает того же результата, что и в случае, когда типы _Tp1* и _Tp* явно проверяются на конвертируемость через std::is_convertible (который может быть специализированным).

Я не согласен: [meta.type.synop]/1 Поведение программы, которая добавляет специализации для любого из шаблонов классов, определенных в этом подпункте, это undefined, если не указано иное.

Таким образом, вы не можете изменить значение is_convertible<Y*, T*>, и если Y* можно конвертировать в T*, тогда назначение будет работать, и поскольку оба назначения (указателя и объекта refcount) равны noexcept, конец результат эквивалентен свопу. Если указатели не конвертируемы, то присваивание не будет скомпилировано, но это будет shared_ptr(r).swap(*this), поэтому оно все равно эквивалентно.

Если я ошибаюсь, напишите отчет об ошибке, и я исправлю его, но я не думаю, что соответствующая программа может обнаружить разницу между реализацией libstdС++ и требованиями стандарта. Тем не менее, у меня не было бы никаких возражений по поводу его изменения в рамках swap. Текущая реализация возникла прямо из shared_ptr в Boost 1.32, я не знаю, будет ли Boost по-прежнему делать то же самое или использовать shared_ptr(r).swap(*this) сейчас.

[Полное раскрытие, я являюсь сторонником libstdС++ и в основном отвечаю за код shared_ptr, который изначально был пожертвован авторами boost::shared_ptr, а затем был изменен мной с тех пор.]

Ответ 2

Реализация в GCC соответствует требованиям стандарта. Когда стандарт определяет, что поведение одной функции эквивалентно другому набору функций, это означает, что эффект первого эквивалентен эффекту последних функций, определенных в стандарте (а не как реализованному).

Стандарт не требует использования std::is_convertible для этого конструктора. Для конструктора требуется SFINAE, но для оператора присваивания не требуется SFINAE. Требование о том, чтобы типы конвертируемых были помещены в программу, а не на реализацию std::shared_ptr, и это ваша ответственность. Если типы, переданные внутри, не являются конвертируемыми, то это ошибка в вашей программе. Если они тогда, реализация должна принять код, даже если вы хотите отключить использование, специализируясь на шаблоне is_convertible.

Опять же, специализация is_convertible для ограничения преобразований указателей - это поведение undefined, поскольку вы изменяете семантику базового шаблона, который явно запрещен в стандарте.

Это приводит к исходному вопросу, на который вы не хотите отвечать: какой прецедент заставил вас задуматься над этим решением. Или сказал иначе: почему люди продолжают спрашивать о решениях, а не о реальных проблемах, которые они хотят решить?