MSVC: Covariant Return Types And Virtual Inheritance

529 Views Asked by At

I'm unsure whether this is a bug in the visual-c++ compiler or undefined behaviour.

Setup

struct DummyBase { virtual ~DummyBase() = default; };
struct DummyDerived : virtual public DummyBase {};

Just a class and a derived class using virtual inheritance

DummyDerived derived;
DummyBase* base = &derived;

std::cout << "Derived : " << &derived << std::endl;
std::cout << "Base    : " << base << std::endl;

When casting DummyDerived* to DummyBase* the pointer is offset. This seems to be caused by virtual inheritance:

Derived : 00000000002CF838
Base    : 00000000002CF840

Even though the pointer values are different a comparison will return true:

std::cout << "IsSame  : " << (base == &derived) << std::endl << std::endl;

Output:

IsSame  : 1

So far so good.

Problem

The problem arises in the following setup:

 struct IBaseReturner
 {
   virtual DummyBase* Get() = 0;
 };

 struct IDerivedReturner : public virtual IBaseReturner
 {
   virtual DummyDerived* Get() = 0;
 };

 struct BaseReturner : public virtual IBaseReturner
 {
 };

 struct DerivedReturner : public BaseReturner, public virtual IDerivedReturner
 {
   DummyDerived* Ptr;
   virtual DummyDerived* Get() override { return Ptr; }
 };

Here we have interfaces and implementations of classes with methods that return either DummyBase or DummyDerived overwritten via covariant return types. Again with virtual inheritance.

// Setup
DerivedReturner returner;
returner.Ptr = &derived;
IBaseReturner* baseReturner = &returner;

Now return DummyDerived* from DerivedReturner and DummyBase* from the same returner cast to IBaseReturner:

DummyDerived* derivedOriginal = returner.Get();
DummyBase* baseFromInterface = baseReturner->Get();

Compare Just like obove:

std::cout << "Derived Original    : " << derivedOriginal << std::endl;
std::cout << "Base from Interface : " << baseFromInterface << std::endl;

Output

Derived Original    : 00000000002CF838
Base from Interface : 00000000002CF838

Unlike above the pointers have the SAME value. Now compare them:

std::cout << "IsSame  : " << (baseFromInterface == derivedOriginal) << std::endl;

Output:

IsSame  : 0

The comparison returns false even tough the adresses are the same. This is expected since the pointer to DummyBase should have a different value. Also when trying to dynamic_cast:

std::cout << dynamic_cast<DummyDerived*>(baseFromInterface);

And expception is thrown:

unknown file: error: C++ exception with description "Access violation - no RTTI data!" thrown in the test body.

Obviously since the pointer was not correctly offset.

Conclusion

It seems as though when calling IBaseReturner::Get the visual-c++ compiler fails to do the necessary pointer arithmetic to cast DummyDerived* to DummyBase*. This happens in vs2013 and vs2015 (didn't try any other version). Also when compiled with gcc it works fine.

Question

While the setup might be a bit complex the question it rather simple:

Is this a msvc bug or am I causing undefined behavoiur?

Added an online example : http://rextester.com/KHZXGQ27304

1

There are 1 best solutions below

0
Fedor On

Your code does not have an undefined behavior (both GCC and Clang compile and execute it fine, demo: https://gcc.godbolt.org/z/q91vbhjTh), and its run fails due to a bug in MSVC, which is still present in Visual Studio 2019 and 2022.

Reported MSVC bug: https://developercommunity.visualstudio.com/t/Invalid-code-generated-for-virtual-inher/1598214

From their response:

This is related to some issues in the virtual function table layout in the compiler, when virtual inheritance and covariant return types are involved. If you are blocked, one possible workaround is to swap the order of the base classes:

struct DerivedReturner : public virtual IDerivedReturner, public BaseReturner