Unexpected Constant 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 "private" from B, I get the expected result:

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

My question is: why does a method that is not called private change make this code compile? Is this the standard? Is there any workaround?

+8
c ++ gcc4
Jul 14 '10 at 18:17
source share
3 answers

An important wording in the current standard (C ++ 03) seems to be given in Section 8.5.5, which explains how links are initialized (in these quotes, T1 is the type of link initialization and T2 is the type of initializer expression).

If the initializer expression is an rvalue, with T2 type of the class, and " cv1 T1 " is link-compatible with " cv2 T2 ", the link is bound in one of the following ways (choice from implementation):

- The link is attached to the object represented by rvalue (see 3.10), or to a sub-object inside this object.

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

The constructor that will be used to create the copy must be called regardless of whether the copy is actually executed.

Thus, even if the implementation binds the link directly to a temporary object, the copy constructor must be available.

Note that this has changed in C ++ 0x to allow CWG defect 391 . The new language reads (N3092 §8.5.3):

Otherwise, if T2 is a class type and

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

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

then the link is bound to the rvalue initializer value in the first case and to the object that is the result of the conversion in the second case (or, in any case, to the corresponding subobject of the object’s base class).

The first case applies, and the link is "directly linked" to the initializer expression.

+4
Jul 14 '10 at 19:03
source share

So you use "copy-initialization":

8.5 / 11 Initializers

The initialization form (using parentheses or =) is usually insignificant, but it matters when the object being initialized has a class type; See below....

The initialization that occurs in the transfer of arguments, the return of the function, the exception (15.1), the processing of the exception (15.3) and the list of initializers enclosed in parentheses (8.5.1) are called copy-initialization and are equivalent to the form

 T x = a; 

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), the type conversion function for notation types (5.2.3), and basic and element initializers (12.6.2) is called direct initialization and is equivalent to the form

 T x(a); 

In 13.3.1.3 Constructor Initialization, overloads for the selected constructor:

When objects of a class type are initialized with direct initialization (8.5) or copied from an expression of the same or derived class type (8.5), the constructor selects the overload resolution. For direct initialization, candidate functions are all the constructors of the class of the initialized object. To initialize a copy, the candidate functions are all the conversion constructors (12.3.1) of this class.

So, for copy-initialization, the copy constructor should be available. However, the compiler is allowed to “optimize” the copy:

12.2 / 1 Temporary objects

Even with the exception of creating a temporary object (12.8), all semantic restrictions must be respected as if a temporary object had been created. [Example: even if the copy constructor is not called, all semantic restrictions, such as accessibility (clause 11), must be met. ]

You can get the desired effect by avoiding copy initialization and using direct initialization:

  const A &b(B()); 



Note:

Since the newer versions of GCC seem to have a different behavior, I thought I would post this note that could take into account the difference (both of which still comply with the standards):

8.5.3 / 5 Links say:

If the initializer expression is an rvalue, and T2 is the class type, and "cv1 T1" is link-compatible with "cv2 T2", the link is bound in one of the following ways (the choice is determined by the implementation)

  • The link is bound to an object represented by rvalue (see 3.10), or to a sub-object within this object.

  • The temporary type "cv1 T2" [sic] is created and the constructor is called to copy the entire rvalue object to the temporary one. The link is attached to a temporary or to a sub-object within the temporary.

The constructor that will be used to create the copy must be called regardless of whether the copy is actually executed.

I initially read the last sentence ("the constructor that will be used ...") to apply to both parameters, but perhaps it should be read as soon as it applies to the seconds option - or at least it’s possible that like GCC accompanying read it.

I'm not sure if this happens between the distinguishable behavior of GCC versions (comments are welcome). We are definitely reaching the limits of my language skills ...

+3
Jul 14 '10 at 18:48
source share

I think this is really a compiler error, gcc seems to be copy initialization. Use direct initialization instead:

 const A& b(B()); 

The call to the copy constructor during initialization of the copy is always optimized (instance of the copy instance), and then should not be available.

+1
Jul 14 '10 at 18:47
source share