Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RTTI on a nested class, VS Bug?

The following code ouputs:

struct Property<int>::IValue

But I would expect it to output:

struct Property<int>::Value<int>

Code:

struct IProperty
{
    virtual ~IProperty() = 0;
};

IProperty::~IProperty(){}

template<typename T>
struct Property : IProperty
{
    struct IValue
    {
        virtual ~IValue() = 0;
    };

    template<typename Q>
    struct Value : IValue
    {
        Q thing;
    };

    Property() : m_pValue(new Value<T>()){}
    std::shared_ptr<IValue> m_pValue;
};

template<typename T>
Property<T>::IValue::~IValue(){}

int main()
{
    std::unique_ptr<IProperty> p(new Property<int>);

    std::cout << typeid(*static_cast<Property<int>&>(*p).m_pValue).name() << '\n';
}

If IValue and Value are moved outside of Property so that they are not nested classes anymore, I get the results I expect. Is this a VS Bug or expected behavior?

like image 888
David Avatar asked Jan 28 '13 16:01

David


1 Answers

Definitely this is Visual C++ compiler bug. For some strange reason compiler fails to determine that Property<T>::IValue dereferenced from shared_ptr is polymorphic base class and uses static type info for typeid expression instead of run-time type info.

Minimal code to reproduce the bug:

template <class T> struct Property
{
   struct IValue
   {
      void f() {}
      virtual ~IValue() = 0 {}
   };

   struct Value: IValue {};

   Property(): m_pValue(new Value())
   {
      // typeid inside class works as expected
      std::cout << "Inside class: " << typeid(*m_pValue).name() << std::endl;
   }

   // If changed to unique_ptr typeid will work as expected
   // But if changed to raw pointer the bug remains!
   std::shared_ptr<IValue> m_pValue;
};

int main()
{
   Property<int> p;
   // If next line uncommented typeid will work as expected
   //p.m_pValue->f();
   std::cout << "Outside class: " << typeid(*p.m_pValue).name() << std::endl;
}

I played a bit with this sample and discovered that typeid starts to work as expected and show run-time type name if some function from pointed object is called before typeid or if shared_ptr is replaced with unique_ptr. Funny, but replacing shared_ptr<IValue> with IValue* still reproduces the bug!

Actual output:

Inside class: struct Property<int>::Value
Outside class: struct Property<int>::IValue

Expected (correct) output:

Inside class: struct Property<int>::Value
Outside class: struct Property<int>::Value

From assembler listing it's clear that it's really compiler problem:

typeid called from Property ctor generates honest call to __RTtypeid:

; 225 : std::cout << typeid(*m_pValue).name() << std::endl;
  ... irrelevant code skipped ...
; __type_info_root_node
  push  OFFSET ?__type_info_root_node@@3U__type_info_node@@A
  mov ecx, DWORD PTR _this$[ebp]
; std::tr1::shared_ptr<Property<int>::IValue>::operator*
  call ??D?$shared_ptr@UIValue@?...<skipped>
  push eax
  call ___RTtypeid

typeid called externally generates static IValue type info:

; 237  :    std::cout << typeid(*p.m_pValue).name() << std::endl;
  ... irrelevant code skipped ...
; __type_info_root_node
  push  OFFSET ?__type_info_root_node@@3U__type_info_node@@A
  mov ecx, OFFSET ??_R0?AUIValue@?$Property@H@@@8

Note also that the same bug presents in VS2008, so it's old time behavior.

like image 62
Rost Avatar answered Sep 29 '22 09:09

Rost