Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialization list bug in gcc?

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!?

like image 474
Oguk Avatar asked Nov 13 '14 16:11

Oguk


2 Answers

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.
like image 167
Constantinos Glynos Avatar answered Nov 15 '22 20:11

Constantinos Glynos


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

like image 43
Kredon Avatar answered Nov 15 '22 19:11

Kredon