Search code examples
c++gcc4

Unexpected const reference behavior


#include <iostream>

class A { 
  public:  
    A(){ cerr << "A Constructor" << endl; }  
    ~A(){ cerr << "A Destructor" << endl; }  
    A(const A &o){ cerr << "A Copy" << endl; } 
    A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
};


class B : public A { 
  public:  
    B() : A() { cerr << "B Constructor" << endl; }  
    ~B(){ cerr << "B Destructor" << endl; }
  private:
    B(const B &o) : A() { cerr << "B Copy" << endl; } 
    B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
};

int main() {  
  A a;  
  const A &b = B();  
  return 0; 
}

In GCC 4.2, I get this message:

In function 'int main()':
Line 16: error: 'B::B(const B&)' is private
compilation terminated due to -Wfatal-errors.

If I remove the "private" from B, I get the output I expect:

A Constructor
A Constructor
B Constructor
B Destructor
A Destructor
A Destructor

My question is: why does making a method which isn't called private change whether this code compiles? Is this standard-mandated? Is there a workaround?


Solution

  • The important verbiage in the current standard (C++03) seems to be in §8.5.3, which explains how references are initialized (In these quotes, T1 is the type of the reference being initialized and T2 is the type of the initializer expression).

    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 in one of the following ways (the choice is implementation-defined):

    -- The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

    -- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

    The constructor that would be used to make the copy shall be callable whether or not the copy is actually done.

    So, even if the implementation binds the reference directly to the temporary object, the copy constructor must be accessible.

    Note that this is changed in C++0x, per the resolution of CWG defect 391. The new language reads (N3092 §8.5.3):

    Otherwise, if T2 is a class type and

    -- the initializer expression is an rvalue and "cv1 T1" is reference-compatible with "cv2 T2,"

    -- T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type "cv3 T3" (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)),

    then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object).

    The first case applies and the reference is "bound directly" to the initializer expression.