Почему auto_ptr, кажется, нарушает частное наследование на Visual С++?

Фоновая информация: это было обнаружено в Visual Studio 2008 и снова подтверждено на Visual Studio 2013. g++ закричала над кодом, в то время как Visual молчаливо нарушала конфиденциальное наследование.

Итак, на Visual С++ у нас есть следующий код:

class Base {};
class Derived : Base {};      // inherits privately. Adding explicitly the
                              //    keyword private changes nothing

int main()
{
   std::auto_ptr<Base>(new Derived) ;   // compiles, which is NOT EXPECTED
   std::auto_ptr<Base> p(new Derived) ; // Does not compile, which is expected
}

Почему первый (временный) auto_ptr компилируется? Я вошел в него в отладке, он сделал именно то, что должно было делать с публичным наследованием (вызвать правый конструктор и т.д.)

Удивительно, что, возможно, проблема с реализацией auto_ptr (мы никогда не знаем...), я уменьшил проблему на этом автономном коде:

class Base {};
class Derived : Base {};

template <typename T>
class Ptr
{
   T * m_p;

   public :
      Ptr(T * p_p)
         : m_p(p_p)
      {
      }
} ;

int main()
{
   Ptr<Base>(new Derived) ;   // compiles, which is NOT EXPECTED
   Ptr<Base> p(new Derived) ; // Does not compile, which is expected
}

Опять же, я ожидал, что код НЕ компилируется, поскольку Derived наследует конфиденциально от Base.

Но когда мы создаем временное, оно работает.

И мы не можем обвинять его в std:: auto_ptr.

Есть ли что-то в стандарте (98 или 11 или 14), которое я пропустил, или это ошибка?

Ответы

Ответ 1

Преобразование Derived* -to- Base*, даже если наследование является приватным, разрешено в C-стиле и функциональных переводах. И нет, это не означает reinterpret_cast в этом случае.

Это не допускается стандартом, но он почти выглядит так, как будто это разрешено, поэтому это тонкая ошибка.

5.2.3 Явное преобразование типа (функциональная нотация) [expr.type.conv]

1 [...] Если список выражений является единственным выражением, выражение преобразования типа эквивалентно (в определении и, если определено по смыслу), соответствующему выражению (5.4). [...]

5.4 Явное преобразование типов (литая нотация) [expr.cast]

4 Конверсии, выполняемые

  • a const_cast (5.2.11),
  • a static_cast (5.2.9),
  • a static_cast, за которым следует const_cast,
  • a reinterpret_cast (5.2.10) или
  • a reinterpret_cast, за которым следует const_cast,

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

  • указатель на объект типа производного класса или lvalue или rvalue типа производного класса может быть явно преобразуется в указатель или ссылается на однозначный тип базового класса, соответственно;
  • [...]

В сложившейся ситуации компилятор интерпретирует его как static_cast от Derived* до auto_ptr<Base>, а в этом static_cast указатель на объект производного типа класса преобразуется в указатель однозначного типа базового класса. Таким образом, похоже, что стандарт позволяет это.

Однако преобразование из Derived* в Base* неявно, оно просто выполняется как часть явного другого преобразования. Поэтому, в конце концов, нет, стандарт действительно этого не позволяет.

Вы можете сообщить об этом как об ошибке. Из комментария Csq мы узнаем, что существует связанный отчет, в котором явный static_cast также позволяет это преобразование, но это не совсем то же самое. В этом случае преобразование из Derived* в Base* является явным, но оно здесь неявно, и Visual С++ обычно отклоняет это в неявных преобразованиях.

Обратите внимание, что в функциональных методах, которые используют несколько выражений, эта неверная интерпретация невозможна: компилятор правильно отклоняет следующее:

class Base { };
class Derived : Base { };

template <typename T>
class Ptr {
public:
  Ptr(T *a, T *b) { }
};

int main() {
  Ptr<Base>(new Derived, new Derived);
  // error C2243: 'type cast' : conversion from 'Derived *' to 'Base *' exists, but is inaccessible
}