15

Right now, I am learning the features of Inheritance in C++ and wanted to test out the recently learnt concept of Virtual Base classes. I tried the following simple code:

#include <iostream>

using namespace std;

class A
{
private:
    int m_value;
    string m_caller;
public:
    A(int p_value, string p_caller) : m_value{p_value}, m_caller{p_caller}
    {
        cout<<"Instantiating A via "<<m_caller<<endl;
    }
};

class B : virtual public A
{
private:
    int m_value;
public:
    B(int p_value1,int p_value2) : A{p_value1,"B"}, m_value{p_value2}
    {
        cout<<"Instantiating B."<<endl;
    }
};

class C : public B
{
public:
    C(int p_value1,int p_value2) : A{p_value1,"C"}, B(p_value1, p_value2)
    {
        cout<<"Instantiating C."<<endl;
    }
};

int main()
{
    C c1(1,2);
    return 0;
}

Please note the B(p_value1, p_value2) in the constructor of class C. This gave me the desired output:

Instantiating A via C
Instantiating B.
Instantiating C.

But, the moment I changed it to B{p_value1, p_value2}, I got the following output:

Instantiating A via C
Instantiating A via B
Instantiating B.
Instantiating C.

I tried looking for the answer, but all the answers I got quoted some C++ standards. Being a beginner in OOPs, I am looking for a simpler explanation for this behaviour. Thanks a lot!

P.S. I am using C::B in Windows with compiler g++ 4.8.1.

SimplyOm
  • 179
  • 8
  • 3
    Fyi, clang++ 3.7 does not exhibit the behavior you're describing. Both versions emit the same output (the first). – WhozCraig Jun 27 '16 at 19:36
  • You're calling the copy constructor in the second case. – lorro Jun 27 '16 at 19:37
  • 3
    @UpAndAdam The most derived class is responsible for initializing virtual base classes, the code wouldn't compile if the OP didn't call `A`'s constructor directly. And what is this about *passing in converted string literals as though they are ints*? – Praetorian Jun 27 '16 at 19:42
  • nm, read part of his code wrong, redacting.. – UpAndAdam Jun 27 '16 at 19:43
  • More fyi, g++ v6.1 exhibits the behavior you describe as well ([**see here**](http://coliru.stacked-crooked.com/a/ba3f1b30ad557d7c)), but clang++ 3.8 does not ([**see here**](http://coliru.stacked-crooked.com/a/69d2d24974c4fb26)). – WhozCraig Jun 27 '16 at 19:46
  • @WhozCraig Thanks for pointing it out! Guess this and a few other reasons I found on Google are good enough for me to should shift to using clang :) – SimplyOm Jun 27 '16 at 19:47
  • 1
    @lorro I couldn't get how the copy constructor is called. As far as I understand a copy constructor is a member function which initializes an object using another object of the same class. I clearly haven't done that – SimplyOm Jun 27 '16 at 19:53
  • 2
    I think you may be experiencing a similar/same issue [to **this question**](http://stackoverflow.com/questions/26914076/initialization-list-bug-in-gcc), which has a variety of assessments (including a claim this works in gcc 4.9.2). Take that for what it's worth. It also mentioned [a **bug**](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922) already filed against gcc about this. – WhozCraig Jun 27 '16 at 19:54
  • @WhozCraig Yes, I suppose that's it then! Thanks again :) – SimplyOm Jun 27 '16 at 20:01

1 Answers1

8

This is a compiler bug in g++.

In C++14 (N4140) section [dcl.init.list], the definition of list initialization is (edited for conciseness):

List-initialization of an object or reference of type T is defined as follows:

  • If T is an aggregate, aggregate initialization is performed
  • Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
  • Otherwise, if T is a specialization of std::initializer_list, [...]
  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution. If a narrowing conversion is required to convert any of the arguments, the program is ill-formed.
  • [...]

The first 3 points don't apply: B is not an aggregate (aggregate cannot have base classes), the initializer list does have elements, B is not a specialization of std::initializer_list.

The fourth point does apply because overload resolution matches B{p_value1, p_value2} to the constructor B(int, int) according to [over.match.list]/1.2:

If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

It follows from the last quote that B(whatever) and B{whatever} should behave identically.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365