Why auto_ptr seems to breach private inheritance on Visual C++?
Asked Answered
F

1

14

Background information: This was detected on Visual Studio 2008, and confirmed again on Visual Studio 2013. G++ screamed at the code, while Visual accepted the private inheritance breach silently.

So, on Visual C++, we have the following code:

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
}

Why would the first (temporary) auto_ptr compile? I went inside it in debug, it did exactly what is was supposed to do with a public inheritance (call the right constructor, etc.)

Wondering if perhaps the issue was with the auto_ptr implementation (we never know...), I reduced the issue on this standalone code:

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
}

Again, I expected the code to NOT compile, as Derived inherits privately from Base.

But when we create a temporary, it works.

And we can't blame it on std::auto_ptr.

Is there something in the standard (either 98 or 11 or 14) I missed, or is this a bug?

Footman answered 19/6, 2014 at 9:54 Comment(6)
My guess is that Ptr<Base>(new Derived); does not do what it looks like it does. It looks like it creates a temporary of type Ptr<Base> constructed with a new Derived, but it actually declares a function or some such nonsense.Pigeonhearted
@nwp: this would be easy to verify, just printing something from within Derived constructor and destructor allows to check whether there is actual code executed.Hopeh
This bug is a similar compiler error, but with static_cast: connect.microsoft.com/VisualStudio/feedback/details/540343/…Burnley
FWIW, you don't even need a template to produce the bug. class Ptr with hard-coded Base inside yields the same behaviour.Tamper
Or for an even more reduced example: class Base {}; class Derived : Base {}; struct S { S(Base *) {} }; int main() { S(new Derived); }Tamper
@MatthieuM : I've thought about that. So, in my test case, my constructor does a std::cout, and I see it when I execute the code, so the object is definitively constructed.Footman
C
3

The Derived*-to-Base* conversion, even if the inheritance is private, is permitted in C-style and functional casts. And no, it doesn't mean reinterpret_cast in that case.

This isn't allowed by the standard, but it very nearly looks as if it is allowed, so it's a subtle bug.

5.2.3 Explicit type conversion (functional notation) [expr.type.conv]

1 [...] If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). [...]

5.4 Explicit type conversion (cast notation) [expr.cast]

4 The conversions performed by

  • a const_cast (5.2.11),
  • a static_cast (5.2.9),
  • a static_cast followed by a const_cast,
  • a reinterpret_cast (5.2.10), or
  • a reinterpret_cast followed by a const_cast,

can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible:

  • a pointer to an object of derived class type or an lvalue or rvalue of derived class type may be explicitly converted to a pointer or reference to an unambiguous base class type, respectively;
  • [...]

In the situation you've got, the compiler interprets it as a static_cast from Derived* to auto_ptr<Base>, and in that static_cast, a pointer to an object of derived class type is converted to a pointer of an unambiguous base class type. So it looks like the standard allows it.

However, the conversion from Derived* to Base* is implicit, it merely happens to be performed as part of an explicit different conversion. So in the end, no, the standard really doesn't allow it.

You may want to report this as a bug. From Csq's comment, we learn that there is a related report, in which an explicit static_cast also allows this conversion, but it's not exactly the same thing. In that case, the conversion from Derived* to Base* is explicit, but it is implicit here, and Visual C++ does usually reject that in implicit conversions.

Note that in functional casts that use multiple expressions, this misinterpretation isn't possible: the compiler correctly rejects the following:

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
}
Correct answered 19/6, 2014 at 17:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.