0

I'm observing strange behaviour with clang++ (3.5-1ubuntu1). If virtual inheritance is used, the copy constructor of the 'Base' class appears to be skipped. Please see the sample and results below.

The complicated inheritance hierarchy is needed, it comes from an application using std streams and streambufs.

Questions:

  • What should the value of foo.x be after foo's initialisation?
  • Why does the result change when normal instead of virtual inheritance is used? Notice that there's no multiple inheritance involved, so I'd expect no difference.
  • Am I invoking undefined behavior? If yes, where? If yes, could the compiler warn about it?

#include <iostream>

struct Base {
  int x;
  Base() : x(0) {}

  Base(Base const& other) : x(other.x) {
    std::cout << "Base::Base(Base const&)" << std::endl;
  }
};

// If virtual is removed, it works.
   struct Derived1 : virtual Base {
// struct Derived1 :         Base {
  Derived1(Derived1&& other) : Base(static_cast<const Base&>(other)) {
    std::cout << "MOVE constructor: other.x = " << other.x
              << ", this->x = " << x << std::endl;
  }

  Derived1() {
    x = 4711;
    std::cout << "DEFAULT constructor: x = " << x << std::endl;
  }
} ;

template< typename B >
struct Derived2 : B {
  // Deliberately invoke move constructor
  Derived2() : B( B() ) { }
} ;

int main() {
  Derived2<Derived1> foo ;

  // Expected: 4711, actual: 0
  std::cout << "foo.x = " << foo.x << std::endl;

  std::cout << "sizeof(Base) = " << sizeof(Base) << std::endl;
  std::cout << "sizeof(Derived1) = " << sizeof(Derived1) << std::endl;
  std::cout << "sizeof(foo) = " << sizeof(foo) << std::endl;

  // As expected, all addresses are equal, regardless whether virtual 
  // inheritance is used or not.
  std::cout << "&Derived2::x = " << &foo.x << std::endl;
  std::cout << "&Derived1::x = " << &static_cast<Derived1 const&>(foo).x << std::endl;
  std::cout << "&Base::x = " << &static_cast<Base const&>(foo).x << std::endl;
}

The result I'm getting:

DEFAULT constructor: x = 4711
MOVE constructor: other.x = 4711, this->x = 0
foo.x = 0
sizeof(Base) = 4
sizeof(Derived1) = 16
sizeof(foo) = 16
&Derived2::x = 0x7fff7a445338
&Derived1::x = 0x7fff7a445338
&Base::x = 0x7fff7a445338
Gerhard Wesp
  • 319
  • 3
  • 6
  • 6
    Virtual base objects are initialized by the most derived class. Your `Derived2` doesn't explicitly initialize the virtual base, so the default constructor is used. Note that the "copy" `B(B())` doesn't copy the virtual base subobject. – Kerrek SB Jan 07 '15 at 10:39
  • One never stops to learn C++ :) Thanks! However, then g++ (Ubuntu 4.8.2-19ubuntu1) gets it wrong---here I get foo.x = 4711. WTF? Is it UB after all? – Gerhard Wesp Jan 07 '15 at 11:08
  • Seems like GCC is wrong. The member initializer `Base(static_cast(other))` has to be ignored by 12.6.2/7, then `Base`'s default constructor is called (paragraph 13) and then `Derived1`'s move constructor. – Kerrek SB Jan 07 '15 at 21:21

0 Answers0