6

This is a question I encountered while reading this section on learncpp.com. I used the code listed here, then made slight alterations for testing.

Background

Virtual inheritance creates a common reference to the base class, which has two effects.

First, it removes ambiguity because only once copy of the base's members are created (e.g. adding a print() function to PoweredDevice and calling it in main() would otherwise cause a compiler error).

Second, the most derived class becomes responsible for invoking the base constructor. If one of the intermediate classes attempts to invoke the base constructor in an initialization list, the call should be ignored.

The Problem

When I compile and run the code, it returns:

PoweredDevice: 3
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2

It should return:

PoweredDevice: 3
Scanner: 1
Printer: 2

When I follow the execution using GDB (7.11.1), it shows that the intermediate functions are also calling PoweredDevice through the initialization list--but these should be ignored. This multiple initialization of PoweredDevice does not lead to ambiguity of any members, but does trouble me because code is executing multiple times when it should only happen once. For a more complicated problem I would not be comfortable using virtual inheritance.

Why are these intermediate classes still initializing the base? Is it a quirk with my compiler (gcc 5.4.0) or am I misunderstanding how virtual inheritance works?

Edit: Code

#include <iostream>
using namespace std;

class PoweredDevice
{
public:
    int m_nPower;
public:
    PoweredDevice(int nPower)
        :m_nPower {nPower}
    {
        cout << "PoweredDevice: "<<nPower<<endl;
    }
    void print() { cout<<"Print m_nPower: "<<m_nPower<<endl; }
};

class Scanner : public virtual PoweredDevice
{
public:
    Scanner(int nScanner, int nPower)
        : PoweredDevice(nPower)
    {
        cout<<"Scanner: "<<nScanner<<endl;
    }
};

class Printer : public virtual PoweredDevice
{
public:
    Printer(int nPrinter, int nPower)
        : PoweredDevice(nPower)
    {
        cout<<"Printer: "<<nPrinter<<endl;
    }
};

class Copier : public Scanner, public Printer
{
public:
    Copier(int nScanner, int nPrinter, int nPower)
        :Scanner {nScanner, nPower}, Printer {nPrinter, nPower}, PoweredDevice {nPower}
    { }
};

int main()
{
    Copier cCopier {1,2,3};
    cCopier.print();
    cout<<cCopier.m_nPower<<'\n';
    return 0;
}
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Ryan B
  • 83
  • 1
  • 7
  • 3
    what "slight alterations" did you make? Anyhow you should post the code here – 463035818_is_not_an_ai Jul 19 '16 at 19:24
  • 2
    Welcome to Stack Overflow! Please **[edit]** your question with a [mcve] or [SSCCE (Short, Self Contained, Correct Example)](http://sscce.org) – NathanOliver Jul 19 '16 at 19:25
  • Added the code in an edit. Variations I checked included member values and functions in the base class, including different access specifiers. I also tried changing virtual inheritance access levels. – Ryan B Jul 19 '16 at 19:41
  • The bug appears to be contingent on the virtual base constructor vs the intermediate constructors that call it having different numbers/types of arguments. Now this is really dredging up the depths of my memory. I recall filing a bug where protected access didn't work properly when using braces and different ctor signatures. I thought they fixed that, but I guess not enough... :C – underscore_d May 21 '17 at 20:01
  • I opened a bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80849. There is also this old pre-existing bug, which may or may not be related: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922 – underscore_d May 21 '17 at 20:33

1 Answers1

11

This appears to be a GCC bug, triggered when uniform initialisation is used with virtual inheritance.


If we change:

Copier(int nScanner, int nPrinter, int nPower)
    :Scanner {nScanner, nPower}, Printer {nPrinter, nPower}, PoweredDevice {nPower}
{ }

to:

Copier(int nScanner, int nPrinter, int nPower)
    :Scanner (nScanner, nPower), Printer (nPrinter, nPower), PoweredDevice (nPower)
{ }

The error disappears, and it behaves as expected:

PoweredDevice: 3
Scanner: 1
Printer: 2
Print m_nPower: 3
3

Both Clang and Visual Studio are able to compile the original code properly, and give the expected output.

  • 3
    @RyanB You're welcome. If you want to check whether something is a bug in a specific compiler, or a language issue, you can usually use online environments for compilers you don't have installed. I like [Rextester](http://www.rextester.com/) for this, it has Clang, GCC, and VC++ available from the drop-down menu. Each one is on a separate page, though, so you should copy your code before switching to a different one. – Justin Time - Reinstate Monica Jul 19 '16 at 20:15
  • Did anyone ever file a bug for this? Otherwise we'll just have to keep suffering it. – underscore_d May 21 '17 at 19:42
  • 2
    I opened a bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80849. There is also this old pre-existing bug, which may or may not be related: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922 – underscore_d May 21 '17 at 20:33