12

Consider the following code, where B is a virtual base class inherited by D through B1 and B2:

#include <iostream>

class B
{
protected:
    int x;

protected:

    B(int x) : x{x}{std::cout << x << std::endl;}
};

class B1 : virtual public B
{
protected:

    B1() : B(0){}
};

class B2 : virtual public B
{
protected:

    B2() : B(10){}
};

class D : public B1, public B2
{
public:

    D() : B(99), B1(), B2() {}
    void print() {std::cout << "Final: " << x << std::endl;}
};

int main() {
    D d;
    d.print();
    return 0;
}

See working example here. I use outputs in B's constructor and after D has been completely constructed to keep track of what's going on. Everything works fine, when I compile the above example with g++-4.8.1. It prints

99
Final: 99

because Bs constructor is called once from the most derived class (D) and that also determines the final value of x.

Now comes the strange part: If I change the line

D() : B(99), B1(), B2() {}

to the new uniform initialization syntax, i.e.

D() : B{99}, B1{}, B2{} {}

strange things happen. For one, it doesn't compile anymore, with the error

prog.cpp: In constructor ‘D::D()’:
prog.cpp:17:5: error: ‘B1::B1()’ is protected
     B1() : B(0){}
     ^
prog.cpp:31:27: error: within this context
     D() : B{99}, B1{}, B2{} {}

(and the same for B2, see here) which doesn't make sense because I am using it in a derived class, so protected should be fine. If I correct for that and make the constructors of B1 and B2 public instead of protected, everything gets totally messed up (see here), as the output becomes

99
0
10
Final: 10

So, in fact, the parts of B1s and B2s constructors that initialize B are still executed and even change the value of x. This should not be the case for virtual inheritance. And remember, the only things I have changed are

  • public instead of protected constructors in B1 and B2
  • use classname{} syntax in member initialization list of D instead of classname().

I cannot believe such a basic thing goes wrong in gcc. But I tested it with clang on my local machine and there, all three cases compile and run as intended (i.e. like the first example above). If it's not a bug, can someone please point me to what I am missing?

EDIT: My first search somehow didn't bring it up, but now I found this other question, showing at least the protected/public error. However, this was gcc-4.7, so I would have expected it to be dealt with in gcc-4.8. So, should I conclude initializer lists are just fundamentally messed up in gcc!?

Community
  • 1
  • 1
Oguk
  • 1,127
  • 7
  • 13
  • 2
    This works with clang 3.5, but fails with gcc 4.9.0. So it's probably someone's compiler bug. – Bill Lynch Nov 13 '14 at 17:06
  • Yeah, that's pretty much what I said in my question, except that it seems to persist even in gcc-4.9.0! What frightens me is that this can silently lead to multiple constructor calls for the same object (if all the constructors are public), violating one of the most fundamental OOP principles... – Oguk Nov 13 '14 at 17:09
  • 1
    I get exactly the same errors in `GCC 5.1.1`. – Galik Aug 16 '15 at 06:17
  • @Galik and Oguk: I have this and many other problems in the latest GCC 5 with `{braced init}` in constructors. Worse is that some of them are highly complex (read: fickle), making minimal replication difficult. Even then, they don't usually rush to fix these, as I presume they think we'll just figure out - and _be able_ - to use `(parentheses)` instead... rather than chasing other red herrings for hours and wasting our time, which has happened to me several times now thanks to these bugs. It's a shame. – underscore_d Mar 22 '16 at 08:03

2 Answers2

1

I don't know if it's too late to answer this, but your code compiles fine in GCC 4.9.2!

~$g++ -std=c++11 test.cpp 
~$./a.out 
99
Final: 99

~$gcc --version
gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Constantinos Glynos
  • 2,952
  • 2
  • 14
  • 32
1

Regarding multiple calls of virtual base class constructor: I could reproduce the issue with the following code (with GCC 5.1.0).

#include <iostream>

struct V {
    V(){std::cout << "V()\n";}
};

struct A : virtual V {
    A() : V{} {std::cout << "A()\n";}
};

struct B : A {
    B(): V{}, A{} {std::cout << "B()\n";}
};

int main(int argc, char **argv) {
    B b{};
}

This results in the following output:

V()
V()
A()
B()

I don't think this is correct accoring to the C++ standard:

[class.base.init]/7

... The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.

When the call of the A constructor is changed to use parenthesis instead of braces the resulting executable works as expected and only calls V() once.

I've created a bug report for GCC regarding this issue: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818

Edit: I missed that there already has been a bug report about this: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922

Kredon
  • 31
  • 4