2

I'm getting unexpected behavior from the following code:

struct Base
{
    Base() {}
    virtual ~Base() {}

    virtual void foo() const = 0;

protected:
    Base(const Base &) {}
};

struct Derived : public Base
{
    Derived() {}
    Derived(const Derived &other) : Base(other) {}

    virtual void foo() const {}
};

struct NewDerived
{
    operator const Derived() { return Derived(); }
};

void func(const Base &b)
{
    b.foo();
}

int main()
{
    func(NewDerived());
    return 0;
}

With MSVC2008, I get this compilation error in main():

error C2248: 'Base::Base' : cannot access protected member declared in class 'Base'

Why is it trying to access the copy constructor of Base?

If I make Base's copy constructor public, the code compiles and slices the return value at runtime and the call to foo() inside func() triggers a pure virtual function called error.

Can someone please shed a bit of light?

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
kede
  • 244
  • 1
  • 8

1 Answers1

6

The relevant quote from the standard is in 8.5.3p5 (C++11):

has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be implicitly converted to an xvalue, class prvalue, or function lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3”, then the reference is bound to the value of the initializer expression in the first case and to the result of the conversion in the second case (or, in either case, to an appropriate base class subobject).

Example:

struct A { };
struct B : A { } b;
extern B f();
const A& rca2 = f(); // bound to the A subobject of the B rvalue.
A&& rra = f(); // same as above
struct X {
operator B();
operator int&();
} x;
const A& r = x; // bound to the A subobject of the result of the conversion

In your case, T1 is Base, T2 is NewDerived, and T3 is Derived. From the above quote, the copy constructor should not be called and the lvalue reference should bind to the Base subobject.

However, note that in C++03, this was not the case. In C++03, the following quotes were pertinent:

If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound to the object represented by the rvalue (see 3.10 [basic.lval]) or to a sub-object within that object.

...

Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5 [dcl.init]). The reference is then bound to the temporary.

The first quoted paragraph does not apply, because Base is not reference compatible with NewDerived, so only the last paragraph applies, which means a temporary Base object must be created. Therefore, MSVC2008 and gcc are conforming to C++03 rules.

Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • 1
    As a side note, why does the following minor change to the code allows it to compile, at least in VS: `int main() { const Base& b(NewDerived()); }` Any ideas ?? – AlexK May 23 '13 at 21:31
  • 1
    @AlexK:You've been struck by [MVP](http://en.wikipedia.org/wiki/Most_vexing_parse). Try `const Base& b((NewDerived()));` (Note the extra parentheses). – Jesse Good May 23 '13 at 21:35
  • @Jesse Got it. Great catch ! – AlexK May 23 '13 at 21:39
  • Thanks a bunch guys. That's helpful. I added a Bonus Question if you have any creative ideas. – kede May 23 '13 at 22:12
  • @kede: It's hard to say with just a small snippet of code. I couldn't see why the conversion function is not returning a `File`? Perhaps a separate question with the full code woud help. – Jesse Good May 24 '13 at 03:40
  • Good point. I removed it. I may ask it as a separate question later. – kede May 24 '13 at 10:23